From 569422251f6fb33a7484bca040beae48d7ab1510 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Tue, 23 Jun 2026 16:59:06 +0100 Subject: [PATCH 01/11] test: add UI E2E tests for Argo CD Resource Tree and Pod logs Signed-off-by: Triona Doyle --- test/ui-e2e/README.md | 2 +- test/ui-e2e/run-ui-tests.sh | 13 ++++++++----- test/ui-e2e/tests/resource-tree.spec.ts | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/test/ui-e2e/README.md b/test/ui-e2e/README.md index fae9ca41057..9ecd4afb805 100644 --- a/test/ui-e2e/README.md +++ b/test/ui-e2e/README.md @@ -68,7 +68,7 @@ All executions are driven via the ./run-ui-tests.sh wrapper script. This wrapper | `--headed` | Launches the visible Chromium browser UI. Excellent for local debugging. | | `--trace on` | Records a granular execution trace (DOM snapshots, network calls, actions) for visual triage. | | `--reporter=list` | Switches stdout to a clean line-by-line format, ideal for monitoring real-time execution steps. | -| --env= | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | +| `--env=` | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | ### Visual Debugging (Trace Viewer) diff --git a/test/ui-e2e/run-ui-tests.sh b/test/ui-e2e/run-ui-tests.sh index 87b684f40d2..d4dfa9b7ea7 100755 --- a/test/ui-e2e/run-ui-tests.sh +++ b/test/ui-e2e/run-ui-tests.sh @@ -73,8 +73,8 @@ if [ -z "$GITOPS_VERSION" ]; then GITOPS_VERSION="Unknown" fi -#get Argo CD version -ARGO_API_VERSION=$(curl -s -k "$ARGOCD_URL/api/version" | grep -o '"Version":"[^"]*"' | cut -d'"' -f4) +#get Argo CD version (with CodeRabbit timeout fix) +ARGO_API_VERSION=$(curl -s -k --max-time 10 "$ARGOCD_URL/api/version" | grep -o '"Version":"[^"]*"' | cut -d'"' -f4) if [ -z "$ARGO_API_VERSION" ]; then ARGO_API_VERSION="Unknown" fi @@ -86,11 +86,14 @@ echo " " # 2. Execute based on the environment if [ "$ENV" = "ci" ] || [ "$ENV" = "pipeline" ]; then echo "Running headlessly in automation ($ENV)..." - npm ci + + # CodeRabbit hard-fails + npm ci || { echo "Error: npm ci failed."; exit 1; } + if [ "$(uname -s)" = "Darwin" ]; then - npx playwright install chromium + npx playwright install chromium || { echo "Error: Playwright browser install failed."; exit 1; } else - npx playwright install chromium --with-deps + npx playwright install chromium --with-deps || { echo "Error: Playwright browser install failed."; exit 1; } fi #headed from args diff --git a/test/ui-e2e/tests/resource-tree.spec.ts b/test/ui-e2e/tests/resource-tree.spec.ts index e262fa9b221..4123e396f0b 100644 --- a/test/ui-e2e/tests/resource-tree.spec.ts +++ b/test/ui-e2e/tests/resource-tree.spec.ts @@ -6,7 +6,7 @@ test.describe('Argo CD Resource Tree and Pod Logs', () => { test.use({ storageState: '.auth/storageState.json' }); - test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp, argoVersion }) => { + test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp }) => { test.setTimeout(120000); const appsPage = new ApplicationsPage(page); From 5dc675facde984c9c71d945db99372d46e16a1b0 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Wed, 24 Jun 2026 13:52:51 +0100 Subject: [PATCH 02/11] test updates and address coderabbit feedback Signed-off-by: Triona Doyle --- test/ui-e2e/.auth/setup.ts | 6 +++--- test/ui-e2e/global.setup.ts | 4 ++-- test/ui-e2e/src/fixtures.ts | 28 ---------------------------- 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/test/ui-e2e/.auth/setup.ts b/test/ui-e2e/.auth/setup.ts index 03fad54e779..2a5ba878808 100644 --- a/test/ui-e2e/.auth/setup.ts +++ b/test/ui-e2e/.auth/setup.ts @@ -34,8 +34,8 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => { if (await idpScreenText.isVisible()) { console.log(`IDP selection screen detected. Selecting provider: "${idpName}"`); - //look for the specific IDP - const idpLink = page.getByRole('link', { name: idpName, exact: true }); + //look for the specific idp link without exact matching + const idpLink = page.getByRole('link', { name: idpName }); await idpLink.waitFor({ state: 'visible', timeout: TIMEOUTS.short }); await idpLink.click(); @@ -58,7 +58,7 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => { await passwordInput.fill(process.env.CLUSTER_PASSWORD); await page.getByRole('button', { name: /Log in/i }).click(); -//handle the OpenShift 4.x Welcome Tour modal if it appears + //handle the openshift welcome tour modal if it appears try { const skipTourButton = page.getByRole('button', { name: /skip tour/i }); //wait up to 5 seconds for the modal to pop up diff --git a/test/ui-e2e/global.setup.ts b/test/ui-e2e/global.setup.ts index abad09b10e0..466a9e675bb 100644 --- a/test/ui-e2e/global.setup.ts +++ b/test/ui-e2e/global.setup.ts @@ -13,10 +13,10 @@ async function globalSetup() { execSync('oc delete all -l app=spring-petclinic -n openshift-gitops --wait=false', { stdio: 'ignore' }); console.log('* Cluster sanitized. Starting test suite.'); - } catch (error) { + } catch (error) { console.error('Pre-flight cleanup failed. Check your cluster connection.', error); throw error; - } + } } export default globalSetup; \ No newline at end of file diff --git a/test/ui-e2e/src/fixtures.ts b/test/ui-e2e/src/fixtures.ts index f64787d22a1..29cea47f0f6 100644 --- a/test/ui-e2e/src/fixtures.ts +++ b/test/ui-e2e/src/fixtures.ts @@ -5,7 +5,6 @@ import { ApplicationsPage } from './pages/ApplicationsPage'; //define custom fixture types type MyFixtures = { managedApp: string; - argoVersion: string; }; export const test = base.extend({ @@ -31,33 +30,6 @@ export const test = base.extend({ await use(page); }, - //get target argocd version - argoVersion: async ({ page }, use) => { - try { - //get version - const response = await page.request.get('/api/version'); - - if (!response.ok()) { - throw new Error(`API returned status: ${response.status()}`); - } - - const data = await response.json(); - const fullVersion = data.Version || 'Unknown'; - - //extract the major.minor version (e.g., "v2.10.1" -> "2.10") - const match = fullVersion.match(/v(\d+\.\d+)/); - const version = match ? match[1] : '3.0'; - - //for debugging/CI logs - console.log(`TARGETING ARGO CD VERSION: ${fullVersion}`); - - await use(version); - } catch (error) { - console.warn(`\n[warn] Failed to fetch Argo CD version from API. Defaulting to 3.0. Reason: ${error instanceof Error ? error.message : 'Unknown'}\n`); - await use('3.0'); // Default to 3.0 - } - }, - managedApp: [ async ({ page }, use) => { const appName = `e2e-app-${Date.now()}`; const appsPage = new ApplicationsPage(page); From 824fd29121c0d7a3a4563c1da5bf417541923dd2 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Wed, 24 Jun 2026 19:19:07 +0100 Subject: [PATCH 03/11] address yet more coderabbit feedback .. Signed-off-by: Triona Doyle --- test/ui-e2e/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ui-e2e/README.md b/test/ui-e2e/README.md index 9ecd4afb805..fae9ca41057 100644 --- a/test/ui-e2e/README.md +++ b/test/ui-e2e/README.md @@ -68,7 +68,7 @@ All executions are driven via the ./run-ui-tests.sh wrapper script. This wrapper | `--headed` | Launches the visible Chromium browser UI. Excellent for local debugging. | | `--trace on` | Records a granular execution trace (DOM snapshots, network calls, actions) for visual triage. | | `--reporter=list` | Switches stdout to a clean line-by-line format, ideal for monitoring real-time execution steps. | -| `--env=` | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | +| --env= | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | ### Visual Debugging (Trace Viewer) From dc0a0fee060506bdf75a7f354064ef962278fec6 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Fri, 26 Jun 2026 11:13:58 +0100 Subject: [PATCH 04/11] add Argocd version check and harden app health locators Signed-off-by: Triona Doyle --- test/ui-e2e/src/fixtures.ts | 28 +++++++++++++++++++++++++ test/ui-e2e/tests/resource-tree.spec.ts | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/ui-e2e/src/fixtures.ts b/test/ui-e2e/src/fixtures.ts index 29cea47f0f6..f64787d22a1 100644 --- a/test/ui-e2e/src/fixtures.ts +++ b/test/ui-e2e/src/fixtures.ts @@ -5,6 +5,7 @@ import { ApplicationsPage } from './pages/ApplicationsPage'; //define custom fixture types type MyFixtures = { managedApp: string; + argoVersion: string; }; export const test = base.extend({ @@ -30,6 +31,33 @@ export const test = base.extend({ await use(page); }, + //get target argocd version + argoVersion: async ({ page }, use) => { + try { + //get version + const response = await page.request.get('/api/version'); + + if (!response.ok()) { + throw new Error(`API returned status: ${response.status()}`); + } + + const data = await response.json(); + const fullVersion = data.Version || 'Unknown'; + + //extract the major.minor version (e.g., "v2.10.1" -> "2.10") + const match = fullVersion.match(/v(\d+\.\d+)/); + const version = match ? match[1] : '3.0'; + + //for debugging/CI logs + console.log(`TARGETING ARGO CD VERSION: ${fullVersion}`); + + await use(version); + } catch (error) { + console.warn(`\n[warn] Failed to fetch Argo CD version from API. Defaulting to 3.0. Reason: ${error instanceof Error ? error.message : 'Unknown'}\n`); + await use('3.0'); // Default to 3.0 + } + }, + managedApp: [ async ({ page }, use) => { const appName = `e2e-app-${Date.now()}`; const appsPage = new ApplicationsPage(page); diff --git a/test/ui-e2e/tests/resource-tree.spec.ts b/test/ui-e2e/tests/resource-tree.spec.ts index 4123e396f0b..e262fa9b221 100644 --- a/test/ui-e2e/tests/resource-tree.spec.ts +++ b/test/ui-e2e/tests/resource-tree.spec.ts @@ -6,7 +6,7 @@ test.describe('Argo CD Resource Tree and Pod Logs', () => { test.use({ storageState: '.auth/storageState.json' }); - test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp }) => { + test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp, argoVersion }) => { test.setTimeout(120000); const appsPage = new ApplicationsPage(page); From 721e127b933b658dbcaba8a3f36e83ca091132dd Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Thu, 9 Jul 2026 10:50:46 +0100 Subject: [PATCH 05/11] Add test Login with HA enabled Signed-off-by: Triona Doyle --- test/ui-e2e/.auth/setup.ts | 2 +- test/ui-e2e/run-ui-tests.sh | 2 +- test/ui-e2e/src/pages/LoginPage.ts | 2 +- test/ui-e2e/tests/ha-login.spec.ts | 122 ++++++++++++++++++++++++ test/ui-e2e/tests/resource-tree.spec.ts | 2 +- 5 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 test/ui-e2e/tests/ha-login.spec.ts diff --git a/test/ui-e2e/.auth/setup.ts b/test/ui-e2e/.auth/setup.ts index 2a5ba878808..8cf9728ee04 100644 --- a/test/ui-e2e/.auth/setup.ts +++ b/test/ui-e2e/.auth/setup.ts @@ -61,7 +61,7 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => { //handle the openshift welcome tour modal if it appears try { const skipTourButton = page.getByRole('button', { name: /skip tour/i }); - //wait up to 5 seconds for the modal to pop up + //wait briefly for the modal to pop up await skipTourButton.waitFor({ state: 'visible', timeout: TIMEOUTS.short }); await skipTourButton.click(); console.log('Dismissed the OpenShift Welcome Tour modal.'); diff --git a/test/ui-e2e/run-ui-tests.sh b/test/ui-e2e/run-ui-tests.sh index d4dfa9b7ea7..0e090a3d3e0 100755 --- a/test/ui-e2e/run-ui-tests.sh +++ b/test/ui-e2e/run-ui-tests.sh @@ -87,7 +87,7 @@ echo " " if [ "$ENV" = "ci" ] || [ "$ENV" = "pipeline" ]; then echo "Running headlessly in automation ($ENV)..." - # CodeRabbit hard-fails + #coderabbit hard-fails npm ci || { echo "Error: npm ci failed."; exit 1; } if [ "$(uname -s)" = "Darwin" ]; then diff --git a/test/ui-e2e/src/pages/LoginPage.ts b/test/ui-e2e/src/pages/LoginPage.ts index a1b107c0ff1..8665c2d52a6 100644 --- a/test/ui-e2e/src/pages/LoginPage.ts +++ b/test/ui-e2e/src/pages/LoginPage.ts @@ -63,6 +63,6 @@ export class LoginPage { } //Success Checking make we land on the applications dashboard - await this.page.waitForURL('**/applications**', { timeout: 20000 }); + await this.page.waitForURL('**/applications**', { timeout: 30000 }); } } \ No newline at end of file diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts new file mode 100644 index 00000000000..fa4804e92e2 --- /dev/null +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -0,0 +1,122 @@ +import { test, expect } from '@playwright/test'; +import { execSync } from 'child_process'; +import { LoginPage } from '../src/pages/LoginPage'; + +//run tests together (same ha state setup) +test.describe.configure({ mode: 'serial' }); + +test.describe('HA Login Verification', () => { + + //force fresh login + test.use({ storageState: { cookies: [], origins: [] } }); + + test.beforeAll(async () => { + test.setTimeout(360000); //6 mins for ha rollout + + console.log('\n[setup] Enabling High Availability (HA) for Argo CD...'); + try { + //patch cr + execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":true}}}\'', { stdio: 'inherit' }); + + console.log('[setup] Polling cluster for new HA deployment (this may take a few minutes)...'); + let retries = 15; + let podsReady = false; + + while (retries > 0 && !podsReady) { + try { + execSync('oc wait --for=condition=Available deployment/openshift-gitops-redis-ha-haproxy -n openshift-gitops --timeout=30s', { stdio: 'pipe' }); + podsReady = true; + } catch (e) { + console.log(`[setup] HA proxy not provisioned yet. Retrying in 10s... (${retries} attempts left)`); + await new Promise(resolve => setTimeout(resolve, 10000)); + retries--; + } + } + + if (!podsReady) { + throw new Error('HA proxy deployment never appeared or became available after polling.'); + } + + console.log('[setup] Waiting for Operator to roll out HA-aware components...'); + + //wait for rollouts + execSync('oc rollout status statefulset/openshift-gitops-redis-ha-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + + console.log('[setup] Rollouts complete. Giving cluster time to stabilize network routes...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + + console.log('[setup] HA successfully enabled and stabilized.'); + } catch (error) { + console.error('[setup] Failed to enable HA. Aborting tests.', error); + throw error; + } + }); + + test.afterAll(async () => { + test.setTimeout(300000); //5 mins for teardown + + console.log('\n[teardown] Disabling High Availability (HA) to restore cluster state...'); + try { + //disable ha + execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":false}}}\'', { stdio: 'inherit' }); + + //wait for rollbacks + execSync('oc wait --for=condition=Available deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + + //wait for ha components to delete + try { + execSync('oc wait --for=delete statefulset/openshift-gitops-redis-ha-server -n openshift-gitops --timeout=300s', { stdio: 'ignore' }); + execSync('oc wait --for=delete deployment/openshift-gitops-redis-ha-haproxy -n openshift-gitops --timeout=300s', { stdio: 'ignore' }); + } catch (e) { + //ignore if already deleted + } + + console.log('[teardown] Cluster successfully restored to non-HA state.'); + } catch (error) { + console.error('[teardown] Failed to disable HA during cleanup! Cluster may be in a dirty state.', error); + throw error; + } + }); + + test('Local Admin Login under HA', async ({ page }) => { + test.setTimeout(120000); + + let rawOutput = execSync('oc extract secret/openshift-gitops-cluster -n openshift-gitops --keys=admin.password --to=-', { timeout: 30000 }).toString(); + const adminPassword = rawOutput.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'))[0]; + + if (!adminPassword) { + throw new Error('failed to extract admin password from cluster secret'); + } + + await page.goto('/login?dex=none', { waitUntil: 'load' }); + + const userField = page.getByLabel(/username/i); + await userField.waitFor({ state: 'visible', timeout: 30000 }); + + //fill form + await userField.fill('admin'); + await page.locator('input[type="password"]').fill(adminPassword); + await page.getByRole('button', { name: /sign in/i }).click(); + + await expect(page.getByText('Applications', { exact: true }).first()).toBeVisible({ timeout: 30000 }); + }); + + test('OpenShift SSO Login under HA', async ({ page }) => { + test.setTimeout(120000); + + const loginPage = new LoginPage(page); + await loginPage.goto(); + + await loginPage.loginViaOpenShift( + process.env.CLUSTER_USER!, + process.env.CLUSTER_PASSWORD!, + process.env.IDP || 'kube:admin' + ); + + await expect(page.getByText('Applications', { exact: true }).first()).toBeVisible({ timeout: 30000 }); + }); + +}); \ No newline at end of file diff --git a/test/ui-e2e/tests/resource-tree.spec.ts b/test/ui-e2e/tests/resource-tree.spec.ts index e262fa9b221..4123e396f0b 100644 --- a/test/ui-e2e/tests/resource-tree.spec.ts +++ b/test/ui-e2e/tests/resource-tree.spec.ts @@ -6,7 +6,7 @@ test.describe('Argo CD Resource Tree and Pod Logs', () => { test.use({ storageState: '.auth/storageState.json' }); - test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp, argoVersion }) => { + test('Navigate to app details, open a Pod, and verify logs stream', async ({ page, managedApp }) => { test.setTimeout(120000); const appsPage = new ApplicationsPage(page); From 02db72de41272550d3b29dbbaf2ca0a791ff98f9 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Tue, 21 Jul 2026 12:22:45 +0100 Subject: [PATCH 06/11] address coderabbit feedback for timeouts Signed-off-by: Triona Doyle --- test/ui-e2e/tests/ha-login.spec.ts | 35 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index fa4804e92e2..091067f5e7b 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -11,15 +11,15 @@ test.describe('HA Login Verification', () => { test.use({ storageState: { cookies: [], origins: [] } }); test.beforeAll(async () => { - test.setTimeout(360000); //6 mins for ha rollout + test.setTimeout(600000); //10 mins for ha rollout console.log('\n[setup] Enabling High Availability (HA) for Argo CD...'); try { - //patch cr - execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":true}}}\'', { stdio: 'inherit' }); + //patch cr with strict timeout + execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":true}}}\'', { stdio: 'inherit', timeout: 30000 }); console.log('[setup] Polling cluster for new HA deployment (this may take a few minutes)...'); - let retries = 15; + let retries = 30; let podsReady = false; while (retries > 0 && !podsReady) { @@ -59,20 +59,29 @@ test.describe('HA Login Verification', () => { console.log('\n[teardown] Disabling High Availability (HA) to restore cluster state...'); try { - //disable ha - execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":false}}}\'', { stdio: 'inherit' }); + //disable ha with strict timeout + execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":false}}}\'', { stdio: 'inherit', timeout: 30000 }); //wait for rollbacks execSync('oc wait --for=condition=Available deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - //wait for ha components to delete - try { - execSync('oc wait --for=delete statefulset/openshift-gitops-redis-ha-server -n openshift-gitops --timeout=300s', { stdio: 'ignore' }); - execSync('oc wait --for=delete deployment/openshift-gitops-redis-ha-haproxy -n openshift-gitops --timeout=300s', { stdio: 'ignore' }); - } catch (e) { - //ignore if already deleted - } + //helper to independently wait for deletions and only ignore NotFound errors + const waitForDelete = (resource: string) => { + try { + execSync(`oc wait --for=delete ${resource} -n openshift-gitops --timeout=300s`, { stdio: 'pipe' }); + } catch (e: any) { + const stderr = e.stderr ? e.stderr.toString() : ''; + const message = e.message || ''; + if (!stderr.includes('NotFound') && !message.includes('NotFound')) { + throw e; //rethrow timeouts or api failures + } + } + }; + + //wait for ha components to delete independently + waitForDelete('statefulset/openshift-gitops-redis-ha-server'); + waitForDelete('deployment/openshift-gitops-redis-ha-haproxy'); console.log('[teardown] Cluster successfully restored to non-HA state.'); } catch (error) { From 54c988f0fccab18401488f79c65242ba400e4ea8 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Tue, 21 Jul 2026 12:51:34 +0100 Subject: [PATCH 07/11] use testInfo.setTimeout in hook fixtures per coderabbit feedback Signed-off-by: Triona Doyle --- test/ui-e2e/tests/ha-login.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index 091067f5e7b..81634abb8a2 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -10,8 +10,8 @@ test.describe('HA Login Verification', () => { //force fresh login test.use({ storageState: { cookies: [], origins: [] } }); - test.beforeAll(async () => { - test.setTimeout(600000); //10 mins for ha rollout + test.beforeAll(async ({}, testInfo) => { + testInfo.setTimeout(600000); //10 mins for ha rollout console.log('\n[setup] Enabling High Availability (HA) for Argo CD...'); try { @@ -54,8 +54,8 @@ test.describe('HA Login Verification', () => { } }); - test.afterAll(async () => { - test.setTimeout(300000); //5 mins for teardown + test.afterAll(async ({}, testInfo) => { + testInfo.setTimeout(300000); //5 mins for teardown console.log('\n[teardown] Disabling High Availability (HA) to restore cluster state...'); try { From d7d2aab4ae41b9f2495cf3872295f01f761a1009 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Mon, 27 Jul 2026 09:57:12 +0100 Subject: [PATCH 08/11] Update HA test cleanup and Update readme for full test suite execution Signed-off-by: Triona Doyle --- test/ui-e2e/README.md | 32 ++++++++++++++---------------- test/ui-e2e/run-ui-tests.sh | 4 ++-- test/ui-e2e/tests/ha-login.spec.ts | 12 ++++++++++- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/test/ui-e2e/README.md b/test/ui-e2e/README.md index fae9ca41057..d9e1b9092a1 100644 --- a/test/ui-e2e/README.md +++ b/test/ui-e2e/README.md @@ -1,4 +1,3 @@ - # OpenShift GitOps Operator - UI End-to-End Test Suite This directory contains the Playwright-based UI End-to-End (E2E) automation suite for the OpenShift GitOps Operator. It validates core frontend workflows, console integration, Red Hat Single Sign-On (RHSSO) loops, and multi-version Argo CD compatibility across OpenShift clusters. @@ -21,7 +20,6 @@ Navigate to this directory and install the Node modules along with the required cd test/ui-e2e npm install npx playwright install chromium - ``` --- @@ -35,40 +33,43 @@ The test suite requires cluster administrative credentials to discover routes an Generate a local `.env` file in the root of this directory using the following block: ```bash -cat < .env +cat < .env export CLUSTER_USER="kubeadmin" export CLUSTER_PASSWORD="" export OC_API_URL="" export IDP="kube:admin" # (Optional) Defaults to kube:admin EOF - ``` -> **Security Warning:** The `.env` file is explicitly ignored by Git. Please don't commit credentials to the repository. +> **Security Warning:** The `.env` file is explicitly ignored by Git. Please don't commit credentials to the repository. --- ## Execution Commands -All executions are driven via the ./run-ui-tests.sh wrapper script. This wrapper automatically syncs your local oc CLI context to match your .env configuration, performs route discovery for the Console/Argo CD components, and initializes the Playwright runner. +All executions are driven via the `./run-ui-tests.sh` wrapper script. This wrapper automatically syncs your local oc CLI context to match your `.env` configuration, performs route discovery for the Console/Argo CD components, and initializes the Playwright runner. + +> **IMPORTANT: Sequential Execution (`--workers=1`)** +> By default, Playwright attempts to run tests in parallel. Because our suite creates and destroys resources in the shared `openshift-gitops` namespace, parallel execution can cause race conditions and false test failures. **Always append `--workers=1` when running the entire test suite** to force sequential execution. ### Standard Test Execution | Target | Command | | --- | --- | -| **Run All Tests (Local Headless)** | `./run-ui-tests.sh --project=chromium` | -| **Run All Tests (Local Headed + Trace)** | `./run-ui-tests.sh --project=chromium --headed --trace on` | -| **Run All Tests (Simulate CI)** | `./run-ui-tests.sh --env=ci --project=chromium` | +| **Run All Tests (Local Headless)** | `./run-ui-tests.sh --project=chromium --workers=1` | +| **Run All Tests (Local Headed + Trace)** | `./run-ui-tests.sh --project=chromium --workers=1 --headed --trace on` | +| **Run All Tests (Simulate CI)** | `./run-ui-tests.sh --env=ci --project=chromium --workers=1` | | **Run a Specific Spec File** | `./run-ui-tests.sh tests/resource-tree.spec.ts --project=chromium --headed` | ### Playwright Flags Reference | Flag | Purpose | | --- | --- | +| `--workers=1` | Forces tests to run sequentially (one at a time). Essential for preventing cluster resource race conditions when running multiple files. | | `--headed` | Launches the visible Chromium browser UI. Excellent for local debugging. | | `--trace on` | Records a granular execution trace (DOM snapshots, network calls, actions) for visual triage. | | `--reporter=list` | Switches stdout to a clean line-by-line format, ideal for monitoring real-time execution steps. | -| --env= | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | +| `--env=` | Overrides the local setup to simulate automation. It forces headless execution, performs a clean `npm ci`, and installs required browser binaries dynamically. | ### Visual Debugging (Trace Viewer) @@ -79,7 +80,6 @@ When a test fails, the terminal output will provide an exact command to view the ```bash # Example: npx playwright show-trace test-results/create-application-chromium/trace.zip - ``` --- @@ -98,13 +98,12 @@ npx playwright show-trace test-results/create-application-chromium/trace.zip │ └── resource-tree.spec.ts ├── .env # Local runtime environment overrides (Git ignored) └── run-ui-tests.sh # Context-aware orchestrator & URL discovery engine - ``` ### Core Architecture Patterns -* **Global Authentication Reusability:** The .auth/setup.ts module runs first to execute the login sequence against the OpenShift cluster identity provider. It drops an authenticated session state cookie into storageState.json, allowing subsequent test specs to skip login actions entirely and save execution time. -* **Isolated SSO Specs:** Explicit UI authentication testing (such as login.spec.ts) bypasses global storage state configurations and clears active browser contexts intentionally to validate raw login screens and provider selections. +* **Global Authentication Reusability:** The `.auth/setup.ts` module runs first to execute the login sequence against the OpenShift cluster identity provider. It drops an authenticated session state cookie into `storageState.json`, allowing subsequent test specs to skip login actions entirely and save execution time. +* **Isolated SSO Specs:** Explicit UI authentication testing (such as `login.spec.ts`) bypasses global storage state configurations and clears active browser contexts intentionally to validate raw login screens and provider selections. * **Cross-Version UI Abstraction:** Selectors inside the Page Object Models are written to withstand UI layout drift between consecutive OpenShift versions by prioritizing user-facing roles and text-based assertions over brittle CSS class trees. --- @@ -113,6 +112,5 @@ npx playwright show-trace test-results/create-application-chromium/trace.zip ### Symptom: Playwright targets the wrong cluster version -* **Cause:** The wrapper script handles cross-cluster contexts dynamically. If your terminal environment variables don't match your local ~/.kube/config cache, your terminal may fall back to cached sessions. -* **Resolution:** Ensure you either run `source .env` inside your terminal window to reset active shell contexts, or verify that the variables declared within your .env file match your active target system configuration. - +* **Cause:** The wrapper script handles cross-cluster contexts dynamically. If your terminal environment variables don't match your local `~/.kube/config` cache, your terminal may fall back to cached sessions. +* **Resolution:** Ensure you either run `source .env` inside your terminal window to reset active shell contexts, or verify that the variables declared within your `.env` file match your active target system configuration. \ No newline at end of file diff --git a/test/ui-e2e/run-ui-tests.sh b/test/ui-e2e/run-ui-tests.sh index 0e090a3d3e0..1b59649b0a2 100755 --- a/test/ui-e2e/run-ui-tests.sh +++ b/test/ui-e2e/run-ui-tests.sh @@ -73,7 +73,7 @@ if [ -z "$GITOPS_VERSION" ]; then GITOPS_VERSION="Unknown" fi -#get Argo CD version (with CodeRabbit timeout fix) +#get Argo CD version ARGO_API_VERSION=$(curl -s -k --max-time 10 "$ARGOCD_URL/api/version" | grep -o '"Version":"[^"]*"' | cut -d'"' -f4) if [ -z "$ARGO_API_VERSION" ]; then ARGO_API_VERSION="Unknown" @@ -87,7 +87,7 @@ echo " " if [ "$ENV" = "ci" ] || [ "$ENV" = "pipeline" ]; then echo "Running headlessly in automation ($ENV)..." - #coderabbit hard-fails + #hard-fails npm ci || { echo "Error: npm ci failed."; exit 1; } if [ "$(uname -s)" = "Darwin" ]; then diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index 81634abb8a2..e1eb05d3129 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -62,11 +62,18 @@ test.describe('HA Login Verification', () => { //disable ha with strict timeout execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":false}}}\'', { stdio: 'inherit', timeout: 30000 }); + //give operator time to reconcile the cr patch checking rollout status + console.log('[teardown] Waiting for Operator to begin reconciliation...'); + await new Promise(resolve => setTimeout(resolve, 15000)); + //wait for rollbacks execSync('oc wait --for=condition=Available deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - //helper to independently wait for deletions and only ignore NotFound errors + //wait for standard redis to roll out and become ready again + execSync('oc rollout status deployment/openshift-gitops-redis -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + + //helper to independently wait for deletions and only ignore notfound errors const waitForDelete = (resource: string) => { try { execSync(`oc wait --for=delete ${resource} -n openshift-gitops --timeout=300s`, { stdio: 'pipe' }); @@ -83,6 +90,9 @@ test.describe('HA Login Verification', () => { waitForDelete('statefulset/openshift-gitops-redis-ha-server'); waitForDelete('deployment/openshift-gitops-redis-ha-haproxy'); + //give the kubernetes service endpoints 5 seconds to stabilize routing + await new Promise(resolve => setTimeout(resolve, 5000)); + console.log('[teardown] Cluster successfully restored to non-HA state.'); } catch (error) { console.error('[teardown] Failed to disable HA during cleanup! Cluster may be in a dirty state.', error); From 59fc4fda0142692f101f9745d4a2a39607270805 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Mon, 27 Jul 2026 15:55:52 +0100 Subject: [PATCH 09/11] extract HA setup to a reusable utility and dynamically reduce resources on constrained clusters Signed-off-by: Triona Doyle --- test/ui-e2e/src/utils/ha-manager.ts | 87 +++++++++++++++++++++++++++++ test/ui-e2e/tests/ha-login.spec.ts | 81 ++------------------------- 2 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 test/ui-e2e/src/utils/ha-manager.ts diff --git a/test/ui-e2e/src/utils/ha-manager.ts b/test/ui-e2e/src/utils/ha-manager.ts new file mode 100644 index 00000000000..456d667b39a --- /dev/null +++ b/test/ui-e2e/src/utils/ha-manager.ts @@ -0,0 +1,87 @@ +import { execSync } from 'child_process'; + +//private helper test file doesn't need to see it +function isClusterResourceConstrained(): boolean { + if (process.env.REDUCE_HA_RESOURCES === 'true') return true; + try { + const raw = execSync('oc get nodes -l node-role.kubernetes.io/worker -o json', { stdio: 'pipe' }).toString(); + const workers = JSON.parse(raw).items || []; + if (workers.length < 3) return true; + for (const node of workers) { + const memKi = parseInt(node.status?.allocatable?.memory || '0', 10); + if (memKi > 0 && memKi < 12 * 1024 * 1024) return true; + } + } catch {} + return false; +} + +export async function enableHA() { + console.log('\n[setup] Enabling High Availability (HA) for Argo CD...'); + + const reduceResources = isClusterResourceConstrained(); + const lowRes = { requests: { cpu: '50m', memory: '64Mi' }, limits: { cpu: '250m', memory: '128Mi' } }; + const patchObj = reduceResources + ? { spec: { ha: { enabled: true, resources: lowRes }, redis: { resources: lowRes } } } + : { spec: { ha: { enabled: true } } }; + + if (reduceResources) { + console.log('[setup] Resource-constrained cluster detected. Applying reduced specs for HA patch...'); + } + + execSync(`oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p '${JSON.stringify(patchObj)}'`, { stdio: 'inherit', timeout: 30000 }); + + console.log('[setup] Polling cluster for new HA deployment (this may take a few minutes)...'); + let retries = 30; + let podsReady = false; + + while (retries > 0 && !podsReady) { + try { + execSync('oc wait --for=condition=Available deployment/openshift-gitops-redis-ha-haproxy -n openshift-gitops --timeout=30s', { stdio: 'pipe' }); + podsReady = true; + } catch (e) { + console.log(`[setup] HA proxy not provisioned yet. Retrying in 10s... (${retries} attempts left)`); + await new Promise(resolve => setTimeout(resolve, 10000)); + retries--; + } + } + + if (!podsReady) throw new Error('HA proxy deployment never appeared or became available after polling.'); + + console.log('[setup] Waiting for Operator to roll out HA-aware components...'); + execSync('oc rollout status statefulset/openshift-gitops-redis-ha-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + + console.log('[setup] Rollouts complete. Giving cluster time to stabilize network routes...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + console.log('[setup] HA successfully enabled and stabilized.'); +} + +export async function disableHA() { + console.log('\n[teardown] Disabling High Availability (HA) to restore cluster state...'); + + //setting resources to get rid of any overrides from the setup + const disablePatch = { spec: { ha: { enabled: false, resources: null }, redis: { resources: null } } }; + execSync(`oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p '${JSON.stringify(disablePatch)}'`, { stdio: 'inherit', timeout: 30000 }); + + console.log('[teardown] Waiting for Operator to begin reconciliation...'); + await new Promise(resolve => setTimeout(resolve, 15000)); + + execSync('oc wait --for=condition=Available deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + execSync('oc rollout status deployment/openshift-gitops-redis -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); + + const waitForDelete = (resource: string) => { + try { + execSync(`oc wait --for=delete ${resource} -n openshift-gitops --timeout=300s`, { stdio: 'pipe' }); + } catch (e: any) { + if (!e.message?.includes('NotFound') && !e.stderr?.toString().includes('NotFound')) throw e; + } + }; + + waitForDelete('statefulset/openshift-gitops-redis-ha-server'); + waitForDelete('deployment/openshift-gitops-redis-ha-haproxy'); + + await new Promise(resolve => setTimeout(resolve, 5000)); + console.log('[teardown] Cluster successfully restored to non-HA state.'); +} \ No newline at end of file diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index e1eb05d3129..22df77957ef 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -1,53 +1,18 @@ import { test, expect } from '@playwright/test'; import { execSync } from 'child_process'; import { LoginPage } from '../src/pages/LoginPage'; +import { enableHA, disableHA } from '../src/utils/ha-manager'; -//run tests together (same ha state setup) test.describe.configure({ mode: 'serial' }); test.describe('HA Login Verification', () => { - //force fresh login test.use({ storageState: { cookies: [], origins: [] } }); test.beforeAll(async ({}, testInfo) => { testInfo.setTimeout(600000); //10 mins for ha rollout - - console.log('\n[setup] Enabling High Availability (HA) for Argo CD...'); try { - //patch cr with strict timeout - execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":true}}}\'', { stdio: 'inherit', timeout: 30000 }); - - console.log('[setup] Polling cluster for new HA deployment (this may take a few minutes)...'); - let retries = 30; - let podsReady = false; - - while (retries > 0 && !podsReady) { - try { - execSync('oc wait --for=condition=Available deployment/openshift-gitops-redis-ha-haproxy -n openshift-gitops --timeout=30s', { stdio: 'pipe' }); - podsReady = true; - } catch (e) { - console.log(`[setup] HA proxy not provisioned yet. Retrying in 10s... (${retries} attempts left)`); - await new Promise(resolve => setTimeout(resolve, 10000)); - retries--; - } - } - - if (!podsReady) { - throw new Error('HA proxy deployment never appeared or became available after polling.'); - } - - console.log('[setup] Waiting for Operator to roll out HA-aware components...'); - - //wait for rollouts - execSync('oc rollout status statefulset/openshift-gitops-redis-ha-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - execSync('oc rollout status deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - - console.log('[setup] Rollouts complete. Giving cluster time to stabilize network routes...'); - await new Promise(resolve => setTimeout(resolve, 10000)); - - console.log('[setup] HA successfully enabled and stabilized.'); + await enableHA(); } catch (error) { console.error('[setup] Failed to enable HA. Aborting tests.', error); throw error; @@ -56,46 +21,10 @@ test.describe('HA Login Verification', () => { test.afterAll(async ({}, testInfo) => { testInfo.setTimeout(300000); //5 mins for teardown - - console.log('\n[teardown] Disabling High Availability (HA) to restore cluster state...'); try { - //disable ha with strict timeout - execSync('oc patch argocd openshift-gitops -n openshift-gitops --type=merge -p \'{"spec":{"ha":{"enabled":false}}}\'', { stdio: 'inherit', timeout: 30000 }); - - //give operator time to reconcile the cr patch checking rollout status - console.log('[teardown] Waiting for Operator to begin reconciliation...'); - await new Promise(resolve => setTimeout(resolve, 15000)); - - //wait for rollbacks - execSync('oc wait --for=condition=Available deployment/openshift-gitops-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - execSync('oc rollout status deployment/openshift-gitops-dex-server -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - - //wait for standard redis to roll out and become ready again - execSync('oc rollout status deployment/openshift-gitops-redis -n openshift-gitops --timeout=300s', { stdio: 'inherit' }); - - //helper to independently wait for deletions and only ignore notfound errors - const waitForDelete = (resource: string) => { - try { - execSync(`oc wait --for=delete ${resource} -n openshift-gitops --timeout=300s`, { stdio: 'pipe' }); - } catch (e: any) { - const stderr = e.stderr ? e.stderr.toString() : ''; - const message = e.message || ''; - if (!stderr.includes('NotFound') && !message.includes('NotFound')) { - throw e; //rethrow timeouts or api failures - } - } - }; - - //wait for ha components to delete independently - waitForDelete('statefulset/openshift-gitops-redis-ha-server'); - waitForDelete('deployment/openshift-gitops-redis-ha-haproxy'); - - //give the kubernetes service endpoints 5 seconds to stabilize routing - await new Promise(resolve => setTimeout(resolve, 5000)); - - console.log('[teardown] Cluster successfully restored to non-HA state.'); + await disableHA(); } catch (error) { - console.error('[teardown] Failed to disable HA during cleanup! Cluster may be in a dirty state.', error); + console.error('[teardown] Failed to disable HA. Cluster may be in a dirty state.', error); throw error; } }); @@ -111,11 +40,9 @@ test.describe('HA Login Verification', () => { } await page.goto('/login?dex=none', { waitUntil: 'load' }); - const userField = page.getByLabel(/username/i); await userField.waitFor({ state: 'visible', timeout: 30000 }); - //fill form await userField.fill('admin'); await page.locator('input[type="password"]').fill(adminPassword); await page.getByRole('button', { name: /sign in/i }).click(); From a847497d175048f1000bd7d073eda1ebf1897b33 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Mon, 27 Jul 2026 19:35:39 +0100 Subject: [PATCH 10/11] increase logs tab wait timeout to 30s to resolve flakiness on FIPS clusters Signed-off-by: Triona Doyle --- .../src/pages/ApplicationDetailsPage.ts | 4 ++-- test/ui-e2e/tests/ha-login.spec.ts | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/test/ui-e2e/src/pages/ApplicationDetailsPage.ts b/test/ui-e2e/src/pages/ApplicationDetailsPage.ts index 0bd6405d5bf..ff9a75b4d05 100644 --- a/test/ui-e2e/src/pages/ApplicationDetailsPage.ts +++ b/test/ui-e2e/src/pages/ApplicationDetailsPage.ts @@ -51,8 +51,8 @@ export class ApplicationDetailsPage { } async verifyPodLogs(expectedLogText?: string) { - //click Logs - await this.logsTab.waitFor({ state: 'visible', timeout: 5000 }); + //click Logs timeout 30s + await this.logsTab.waitFor({ state: 'visible', timeout: 30000 }); await this.logsTab.click(); const logFilterInput = this.slideOutPanel.getByPlaceholder('containing'); diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index 22df77957ef..2b6382f4321 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, request } from '@playwright/test'; import { execSync } from 'child_process'; import { LoginPage } from '../src/pages/LoginPage'; import { enableHA, disableHA } from '../src/utils/ha-manager'; @@ -23,6 +23,23 @@ test.describe('HA Login Verification', () => { testInfo.setTimeout(300000); //5 mins for teardown try { await disableHA(); + + console.log('[teardown] Polling OpenShift Route via Playwright until it returns 200 OK...'); + + //get route url + const routeHost = execSync(`oc get route openshift-gitops-server -n openshift-gitops -o jsonpath='{.spec.host}'`).toString().trim(); + + //create api context ignoring self-signed certs + const apiContext = await request.newContext({ ignoreHTTPSErrors: true }); + + //poll until 200 ok to prevent 503 errors on next test + await expect(async () => { + const response = await apiContext.get(`https://${routeHost}`); + expect(response.status()).toBe(200); + }).toPass({ timeout: 60000 }); + + console.log('[teardown] Routing stabilized. Ready for the next test suite.'); + } catch (error) { console.error('[teardown] Failed to disable HA. Cluster may be in a dirty state.', error); throw error; From 4630103506203e0494e780f8217e8a0caf951cf1 Mon Sep 17 00:00:00 2001 From: Triona Doyle Date: Tue, 28 Jul 2026 12:49:05 +0100 Subject: [PATCH 11/11] update teardown for HA clean to ensure clean state for next test Signed-off-by: Triona Doyle --- test/ui-e2e/tests/ha-login.spec.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/ui-e2e/tests/ha-login.spec.ts b/test/ui-e2e/tests/ha-login.spec.ts index 2b6382f4321..28066c352bf 100644 --- a/test/ui-e2e/tests/ha-login.spec.ts +++ b/test/ui-e2e/tests/ha-login.spec.ts @@ -24,6 +24,9 @@ test.describe('HA Login Verification', () => { try { await disableHA(); + console.log('[teardown] Rollout complete. Waiting 15s for router to drop terminating HA pods...'); + await new Promise(resolve => setTimeout(resolve, 15000)); + console.log('[teardown] Polling OpenShift Route via Playwright until it returns 200 OK...'); //get route url @@ -32,10 +35,22 @@ test.describe('HA Login Verification', () => { //create api context ignoring self-signed certs const apiContext = await request.newContext({ ignoreHTTPSErrors: true }); - //poll until 200 ok to prevent 503 errors on next test + //poll ui, auth, api, and callback routes with cache-busting to prevent 503s await expect(async () => { - const response = await apiContext.get(`https://${routeHost}`); - expect(response.status()).toBe(200); + const cb = Date.now(); //cache buster to force fresh network connections + + const uiResponse = await apiContext.get(`https://${routeHost}/?cb=${cb}`); + const authResponse = await apiContext.get(`https://${routeHost}/auth/login?cb=${cb}`); + const apiResponse = await apiContext.get(`https://${routeHost}/api/version?cb=${cb}`); + const callbackResponse = await apiContext.get(`https://${routeHost}/api/dex/callback?cb=${cb}`); + + //check for 2xx status codes + expect(uiResponse.ok()).toBeTruthy(); + expect(authResponse.ok()).toBeTruthy(); + expect(apiResponse.ok()).toBeTruthy(); + + //callback will return 400 without a token, just ensure it isn't 503 + expect(callbackResponse.status()).not.toBe(503); }).toPass({ timeout: 60000 }); console.log('[teardown] Routing stabilized. Ready for the next test suite.');