Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions test/ui-e2e/.auth/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -58,10 +58,10 @@ 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
//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.');
Expand Down
4 changes: 2 additions & 2 deletions test/ui-e2e/global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
13 changes: 8 additions & 5 deletions test/ui-e2e/run-ui-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/ui-e2e/src/pages/LoginPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
}
131 changes: 131 additions & 0 deletions test/ui-e2e/tests/ha-login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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 ({}, 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.');
} catch (error) {
console.error('[setup] Failed to enable HA. Aborting tests.', error);
throw error;
}
});

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 });

//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
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) {
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 });
});

});
2 changes: 1 addition & 1 deletion test/ui-e2e/tests/resource-tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading