From 5c25f1435bd88b4ca027b894dfe048c629e40e0d Mon Sep 17 00:00:00 2001 From: Dominik Vagner Date: Tue, 7 Apr 2026 11:55:58 +0200 Subject: [PATCH 1/3] test: fix auth describe and change project name This fixes an issue with a wrong describe used in `auth.setup.ts`, changes the project name of API tests to something appropriate and quiets unnecessary logs. --- _playwright-tests/tests/auth.setup.ts | 3 +-- playwright.config.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/_playwright-tests/tests/auth.setup.ts b/_playwright-tests/tests/auth.setup.ts index e52979e78..bc0ded367 100644 --- a/_playwright-tests/tests/auth.setup.ts +++ b/_playwright-tests/tests/auth.setup.ts @@ -1,11 +1,10 @@ import { expect, test as setup } from '@playwright/test'; import { setAuthorizationHeader, throwIfMissingEnvVariables } from './helpers/loginHelpers'; -import { describe } from 'node:test'; const DefaultOrg = 99999; const DefaultUser = 'BananaMan'; -describe('Setup', async () => { +setup.describe('Setup', async () => { setup('Ensure needed ENV variables exist', async () => { expect(() => throwIfMissingEnvVariables()).not.toThrow(); }); diff --git a/playwright.config.ts b/playwright.config.ts index 02a861b9a..d02673931 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,7 +3,7 @@ import path from 'path'; import { config } from 'dotenv'; -config({ path: path.join(__dirname, './.env') }); +config({ path: path.join(__dirname, './.env'), quiet: true }); // test-utils storageState paths; respect PLAYWRIGHT_AUTH_DIR when set (CI or local). if (!process.env.PLAYWRIGHT_AUTH_DIR) { @@ -48,7 +48,7 @@ export default defineConfig({ projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/, expect: { timeout: 20000 } }, { - name: 'Google Chrome', + name: 'API', use: { ...devices['Desktop Chrome'], }, From 371fddf51b50eee724265bb8fa550284fff7b1e6 Mon Sep 17 00:00:00 2001 From: Dominik Vagner Date: Tue, 7 Apr 2026 11:59:55 +0200 Subject: [PATCH 2/3] test: fix polling helper This fixes incorrect docs and makes the interval longer as 10 ms was way too spammy. --- _playwright-tests/test-utils/src/helpers/poll.ts | 4 ++-- _playwright-tests/tests/API/Repositories.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_playwright-tests/test-utils/src/helpers/poll.ts b/_playwright-tests/test-utils/src/helpers/poll.ts index 708f0fd4c..8b8903bff 100644 --- a/_playwright-tests/test-utils/src/helpers/poll.ts +++ b/_playwright-tests/test-utils/src/helpers/poll.ts @@ -29,12 +29,12 @@ export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); * Waits for a repository introspection to complete by polling the repository status. * @param client - Configuration object for API client * @param repoUuid - UUID of the repository to check - * @param interval - Polling interval in seconds (default: 10) + * @param interval - Polling interval in miliseconds (default: 200) */ export const waitWhileRepositoryIsPending = async ( client: Configuration, repoUuid: string, - interval: number = 10, + interval: number = 200, ): Promise => { const getRepository = () => new RepositoriesApi(client).getRepository({ diff --git a/_playwright-tests/tests/API/Repositories.spec.ts b/_playwright-tests/tests/API/Repositories.spec.ts index 2dabd2ad6..d2d9b39da 100644 --- a/_playwright-tests/tests/API/Repositories.spec.ts +++ b/_playwright-tests/tests/API/Repositories.spec.ts @@ -44,7 +44,7 @@ test.describe('Repositories', () => { }); await test.step('Wait for introspection to be completed', async () => { - const resp = await waitWhileRepositoryIsPending(client, repo.uuid!.toString()); + const resp = await waitWhileRepositoryIsPending(client, repo.uuid!.toString(), 50); expect(resp.status).toBe('Valid'); expect(resp.lastIntrospectionTime).toBeDefined(); From 1efeeed0102b219b67b6a35cc6e23450d140fd51 Mon Sep 17 00:00:00 2001 From: Dominik Vagner Date: Tue, 7 Apr 2026 12:07:36 +0200 Subject: [PATCH 3/3] test: make generated client use the PW requests This changes the way how the generated typescript client, we use for testing, sends the requests. Previously we used the default or undici for this, but that meant we couldn't see those requests in the PW traces, making it hard to debug. We can utilize the built-in Playwright request fixture and API context to do this, by providing a custom "FetchAPI" to the generated client. Also makes the client dynamically pick-up fresh credentials per request by default. --- .../test-utils/src/fixtures/client.ts | 83 ++++++++++++++----- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/_playwright-tests/test-utils/src/fixtures/client.ts b/_playwright-tests/test-utils/src/fixtures/client.ts index c3054e245..61105b148 100644 --- a/_playwright-tests/test-utils/src/fixtures/client.ts +++ b/_playwright-tests/test-utils/src/fixtures/client.ts @@ -1,38 +1,77 @@ // Define a fixture to hold the API client -import { test as oldTest, expect } from '@playwright/test'; -import { Configuration, ResponseContext, ResponseError } from '../client'; -import { setGlobalDispatcher, ProxyAgent, Agent } from 'undici'; +import { test as oldTest, expect, APIRequestContext, APIResponse } from '@playwright/test'; +import { Configuration, ResponseError, FetchAPI } from '../client'; +import { fileNameToEnvVar, getFileNameFromAuthPath } from 'test-utils/helpers'; type WithApiConfig = { client: Configuration; }; -// Default error handling doesn't print the error, so print it here -const responseReader = { - post: async function (context: ResponseContext): Promise { - if (context.response != undefined && context.response.status > 300) { - const bodyText = await context.response.text(); - console.log('Response errored with ' + context.response.status + ': ' + bodyText); +function isString(value: unknown): value is string { + return typeof value === 'string'; +} + +function constructHeaders(headers?: HeadersInit, storageState?: string, extraHTTPHeaders?: {[key: string]: string}) { + const out: Record = {}; + + if (storageState) { + const tokenName = fileNameToEnvVar(getFileNameFromAuthPath(storageState)); + const token = process.env[tokenName]; + if (!token) { + throw new Error( + `Token environment variable "${tokenName}" is not set. Check authentication setup.`, + ); } - }, -}; + out['authorization'] = token; + } else if (extraHTTPHeaders) { + new Headers(extraHTTPHeaders).forEach((value, key) => { out[key] = value; }); + } + + if (headers) { + new Headers(headers).forEach((value, key) => { out[key] = value; }); + }; + + return Object.keys(out).length ? out : undefined; +} + +async function toFetchResponseBody( + response: APIResponse, +): Promise { + const nullBodyResponses = new Set([101, 103, 204, 205, 304]); + if (nullBodyResponses.has(response.status())) return undefined; + + return response.body().then((b) => b.toString()); +} export const clientTest = oldTest.extend({ client: - // eslint-disable-next-line no-empty-pattern - async ({ extraHTTPHeaders }, use, r) => { - if (r.project?.use?.proxy?.server) { - const dispatcher = new ProxyAgent({ uri: new URL(r.project.use.proxy.server).toString() }); - setGlobalDispatcher(dispatcher); - } else if (r.project.use.baseURL?.match('^https:\/\/\\w+\.foo\.redhat\.com:\\d+$')?.length) { - const dispatcher = new Agent({ connect: { rejectUnauthorized: false } }); - setGlobalDispatcher(dispatcher); - } + async ({ extraHTTPHeaders, request, storageState }, use, r) => { + const pwFetch = (api: APIRequestContext): FetchAPI => async (url, init) => { + const storage = storageState ?? r.project.use.storageState; + const extraHeaders = extraHTTPHeaders ?? r.project.use.extraHTTPHeaders; + const response = await api.fetch(String(url), { + failOnStatusCode: false, + ignoreHTTPSErrors: true, + method: init?.method, + headers: constructHeaders(init?.headers, isString(storage) ? storage : undefined, extraHeaders), + data: init?.body, + }); + + try { + return new Response(await toFetchResponseBody(response), { + status: response.status(), + statusText: response.statusText(), + headers: response.headers(), + }); + } finally { + await response.dispose(); + } + }; const client = new Configuration({ + fetchApi: pwFetch(request), basePath: r.project.use.baseURL + '/api/content-sources/v1', - headers: extraHTTPHeaders ?? r.project.use.extraHTTPHeaders, - middleware: [responseReader], + middleware: [], }); await use(client);