Skip to content

Commit efde789

Browse files
author
Triona Doyle
committed
Set up Playwright E2E foundation and automate the Argo CD SSO authentication
Signed-off-by: Triona Doyle <bot@example.com>
1 parent 02400d0 commit efde789

11 files changed

Lines changed: 489 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ kuttl-test.json
3131
# ignore vendor
3232
vendor/
3333
.vscode/
34+
.DS_Store

test/ui-e2e/.auth/setup.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { test as setup } from '@playwright/test';
2+
3+
const authFile = '.auth/storageState.json';
4+
5+
setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => {
6+
// 1. Navigate to the OpenShift Console
7+
// It checks Playwright's config (baseURL) first, then falls back to environment variables
8+
const targetUrl = baseURL || process.env.CONSOLE_URL || process.env.BASE_URL;
9+
10+
if (!targetUrl) {
11+
throw new Error("No Console URL provided! Ensure your bash script exports BASE_URL or CONSOLE_URL.");
12+
}
13+
14+
console.log(`Navigating to OpenShift Console: ${targetUrl}`);
15+
await page.goto(targetUrl); // <-- THIS WAS THE MISSING LINK!
16+
17+
// 2. Define our locators flexibly
18+
const idpScreenText = page.getByText(/Log in with/i);
19+
const usernameInput = page.getByLabel(/Username/i)
20+
.or(page.locator('input[name="username"]'))
21+
.or(page.getByPlaceholder(/Username/i));
22+
23+
// 3. Wait for EITHER the IDP screen OR the Username field to appear
24+
try {
25+
await Promise.race([
26+
idpScreenText.waitFor({ state: 'visible', timeout: 15000 }),
27+
usernameInput.waitFor({ state: 'visible', timeout: 15000 })
28+
]);
29+
} catch (e) {
30+
console.log("Timed out waiting for OpenShift login page to render.");
31+
}
32+
33+
// Set a default user to prevent undefined errors if you forget to export it
34+
const user = process.env.CLUSTER_USER || 'kubeadmin';
35+
36+
// 4. Handle the IDP Screen if it exists
37+
if (await idpScreenText.isVisible()) {
38+
console.log("IDP selection screen detected. Selecting provider...");
39+
40+
// Decide which IDP to click based on the user
41+
const idpRegex = (user === 'kubeadmin') ? /kube:admin/i : /htpasswd/i;
42+
43+
// OpenShift IDPs are usually links styled as buttons
44+
await page.getByRole('link', { name: idpRegex }).click();
45+
} else {
46+
console.log("No IDP screen detected, proceeding directly to credentials...");
47+
}
48+
49+
// 5. Fill in Cluster Credentials
50+
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
51+
await usernameInput.fill(user); // Using the fallback variable defined above
52+
53+
const passwordInput = page.getByLabel(/Password/i)
54+
.or(page.locator('input[name="password"]'))
55+
.or(page.getByPlaceholder(/Password/i));
56+
57+
// Assert that password exists so we don't accidentally type 'undefined'
58+
if (!process.env.CLUSTER_PASSWORD) {
59+
throw new Error("CLUSTER_PASSWORD is not set in the environment!");
60+
}
61+
62+
await passwordInput.fill(process.env.CLUSTER_PASSWORD);
63+
await page.getByRole('button', { name: /Log in/i }).click();
64+
65+
// 6. Save this pure OpenShift auth state
66+
// Added a brief wait to ensure login completes before saving state
67+
await page.waitForLoadState('networkidle');
68+
await page.context().storageState({ path: authFile });
69+
});

test/ui-e2e/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
# Playwright
3+
node_modules/
4+
/test-results/
5+
/playwright-report/
6+
/blob-report/
7+
/playwright/.cache/
8+
/playwright/.auth/
9+
.auth/storageState.json
10+
.env

test/ui-e2e/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# GitOps Operator - UI End-to-End Tests
2+
3+
This suite validates the OpenShift GitOps Operator UI, focusing on Argo CD and SSO integration.
4+
5+
## Prerequisites
6+
1. **Node.js** (v18+)
7+
2. **OpenShift CLI (oc)**: Installed and in your PATH.
8+
3. **Install Dependencies:** Navigate to this directory and install required packages:
9+
```bash
10+
cd test/ui-e2e
11+
npm install
12+
npx playwright install chromium
13+
```
14+
15+
## Environment Variables
16+
You must provide cluster credentials before running tests. You can either `export` these in your terminal (or pipeline), or create a `.env` file in the `test/ui-e2e` directory:
17+
18+
```text
19+
# .env file example
20+
CLUSTER_PASSWORD=your_openshift_admin_password
21+
OC_API_URL=[https://api.cluster.com:6443](https://api.cluster.com:6443)
22+
CLUSTER_USER=kubeadmin # (Optional) Defaults to kubeadmin
23+
IDP=kube:admin # (Optional) Defaults to kube:admin
24+
```
25+
26+
## Execution Commands
27+
28+
All commands use the `./run-ui-tests.sh` wrapper which handles auth, OpenShift token generation, and URL discovery. **Ensure you are in the `test/ui-e2e` directory.**
29+
30+
**Run All Tests (Headless):**
31+
```bash
32+
./run-ui-tests.sh --project=chromium
33+
```
34+
35+
**Run All Tests (Headed + Trace):**
36+
```bash
37+
./run-ui-tests.sh --project=chromium --headed --reporter=list --trace on
38+
```
39+
40+
**Run Single Test (Headed + Trace):**
41+
```bash
42+
./run-ui-tests.sh tests/login.spec.ts --project=chromium --headed --trace on
43+
```
44+
45+
**View Trace Results:**
46+
```bash
47+
npx playwright show-trace test-results/**/*/trace.zip
48+
```
49+
50+
** Helpful Flags Explained**
51+
* `--headed`: Runs tests in a visible browser. Without this, tests run in "headless" mode (invisible background).
52+
* `--reporter=list`: Changes console output to a clean, line-by-line list so you can see exactly which test is running in real-time.
53+
* `--trace on`: Captures a full "recording" (DOM snapshots, network, actions) of the test for debugging.
54+
55+
## Architecture
56+
57+
**Global Setup:**
58+
`.auth/setup.ts` logs into the OCP console to generate a reusable session (`storageState.json`). This prevents having to log in repeatedly for every test file.
59+
60+
**Spec Isolation:**
61+
`login.spec.ts` explicitly clears session cookies to force a full SSO UI validation from a fresh state.
62+
63+
## Troubleshooting
64+
65+
* **"Invalid login or password" during automated login:** If you are testing against multiple clusters sequentially, your terminal's `oc` CLI might be holding onto a sticky session from an older cluster. Run `oc logout` before running the bash script to force a clean authentication.

test/ui-e2e/package-lock.json

Lines changed: 111 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/ui-e2e/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "ui-e2e",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {},
6+
"keywords": [],
7+
"author": "",
8+
"license": "ISC",
9+
"description": "",
10+
"devDependencies": {
11+
"@playwright/test": "^1.59.1",
12+
"@types/node": "^25.6.0",
13+
"dotenv": "^17.4.2"
14+
}
15+
}

test/ui-e2e/playwright.config.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
/**
4+
* Read environment variables from file.
5+
* https://github.com/motdotla/dotenv
6+
*/
7+
8+
// top of playwright.config.ts
9+
import dotenv from 'dotenv';
10+
import path from 'path';
11+
dotenv.config({ path: path.resolve(__dirname, '.env') });
12+
13+
/**
14+
* See https://playwright.dev/docs/test-configuration.
15+
*/
16+
export default defineConfig({
17+
testDir: './tests',
18+
/* Run tests in files in parallel */
19+
fullyParallel: true,
20+
/* Fail the build on CI if you accidentally left test.only in the source code. */
21+
forbidOnly: !!process.env.CI,
22+
/* Retry on CI only */
23+
retries: process.env.CI ? 2 : 0,
24+
/* Opt out of parallel tests on CI. */
25+
workers: process.env.CI ? 1 : undefined,
26+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
27+
reporter: [
28+
['list'],
29+
['html', { open: process.env.CI ? 'never' : 'on-failure' }]
30+
],
31+
32+
/* GLOBAL FOUNDATION: These apply to everything */
33+
use: {
34+
baseURL: process.env.ARGOCD_URL,
35+
ignoreHTTPSErrors: true,
36+
trace: 'on-first-retry',
37+
},
38+
39+
/* Configure for major browsers */
40+
projects: [
41+
{
42+
name: 'setup',
43+
testDir: './',
44+
testMatch: '**/.auth/setup.ts',
45+
/* Only changes the URL for this specific project */
46+
use: {
47+
baseURL: process.env.CONSOLE_URL, },
48+
},
49+
50+
// Update chromium project
51+
{
52+
name: 'chromium',
53+
dependencies: ['setup'],
54+
use: {
55+
...devices['Desktop Chrome'],
56+
storageState: '.auth/storageState.json',
57+
// project still has ignoreHTTPSErrors: true from above
58+
},
59+
},
60+
61+
{
62+
name: 'firefox',
63+
use: {
64+
...devices['Desktop Firefox'],
65+
// storageState and dependencies here later if we want to run Firefox tests but for now just focus on Chromium
66+
},
67+
},
68+
// ... webkit etc ...
69+
],
70+
71+
/* Run your local dev server before starting the tests */
72+
// webServer: {
73+
// command: 'npm run start',
74+
// url: 'http://localhost:3000',
75+
// reuseExistingServer: !process.env.CI,
76+
// },
77+
});

0 commit comments

Comments
 (0)