Skip to content

Commit ca8a012

Browse files
trdoyle81Triona Doyle
andauthored
GITOPS-9682 UI E2E Create App test (#1162)
* GITOPS-9682 UI E2E Create App test Signed-off-by: Triona Doyle <tekton@example.com> * address Coderabbit PR review feedback Signed-off-by: Triona Doyle <tekton@example.com> * address Coderabbit 'nitpick' feedback Signed-off-by: Triona Doyle <tekton@example.com> * address further coderabbit feedback ... Signed-off-by: Triona Doyle <tekton@example.com> * update applications page locators for 1.21.0 compatibility Signed-off-by: Triona Doyle <tekton@example.com> * address the coderabbit feedback Signed-off-by: Triona Doyle <tekton@example.com> * bump Playwright to v1.61.0 to resolve Node.js v22 compatibility errors Signed-off-by: Triona Doyle <tekton@example.com> --------- Signed-off-by: Triona Doyle <tekton@example.com> Co-authored-by: Triona Doyle <tekton@example.com>
1 parent 8fa22b8 commit ca8a012

11 files changed

Lines changed: 397 additions & 85 deletions

File tree

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test as setup } from '@playwright/test';
1+
import { test as setup, expect } from '@playwright/test';
22

33
const authFile = '.auth/storageState.json';
44

@@ -13,29 +13,25 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => {
1313
console.log(`Navigating to OpenShift Console: ${targetUrl}`);
1414
await page.goto(targetUrl);
1515

16-
//set locators
16+
// Set locators
1717
const idpScreenText = page.getByText(/Log in with/i);
1818
const usernameInput = page.getByLabel(/Username/i)
1919
.or(page.locator('input[name="username"]'))
2020
.or(page.getByPlaceholder(/Username/i));
2121

22-
//wait for the IDP screen OR the Username field to appear
23-
try {
24-
await Promise.race([
25-
idpScreenText.waitFor({ state: 'visible', timeout: 15000 }),
26-
usernameInput.waitFor({ state: 'visible', timeout: 15000 })
27-
]);
28-
} catch (e) {
29-
console.log("Timed out waiting for OpenShift login page to render.");
30-
}
22+
// Fail loudly if the page is dead so we don't get weird errors later
23+
await expect(
24+
idpScreenText.or(usernameInput).first(),
25+
"OpenShift login page failed to load. Check cluster health and URL."
26+
).toBeVisible({ timeout: 20000 });
3127

3228
const idpName = process.env.IDP || 'kube:admin';
3329
const user = process.env.CLUSTER_USER || 'kubeadmin';
3430

3531
if (await idpScreenText.isVisible()) {
3632
console.log(`IDP selection screen detected. Selecting provider: "${idpName}"`);
3733

38-
// look for the specific IDP
34+
// Look for the specific IDP
3935
const idpLink = page.getByRole('link', { name: new RegExp(idpName, 'i') });
4036

4137
await idpLink.waitFor({ state: 'visible', timeout: 5000 });
@@ -44,7 +40,7 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => {
4440
console.log("No IDP screen detected (or already selected), proceeding to credentials...");
4541
}
4642

47-
// fill in the Credentials
43+
// Fill in the credentials
4844
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
4945
await usernameInput.fill(user);
5046

@@ -59,7 +55,9 @@ setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => {
5955
await passwordInput.fill(process.env.CLUSTER_PASSWORD);
6056
await page.getByRole('button', { name: /Log in/i }).click();
6157

62-
//save the auth state
63-
await page.waitForLoadState('networkidle');
58+
// Save the auth state
59+
await expect(page.getByRole('navigation').first()).toBeVisible({ timeout: 15000 });
60+
await expect(page).toHaveURL(/(console|k8s|overview|dashboards)/i, { timeout: 15000 });
6461
await page.context().storageState({ path: authFile });
62+
6563
});

test/ui-e2e/README.md

Lines changed: 92 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,115 @@
1-
# GitOps Operator - UI End-to-End Tests
21

3-
This suite validates the OpenShift GitOps Operator UI, focusing on Argo CD and SSO integration.
2+
# OpenShift GitOps Operator - UI End-to-End Test Suite
43

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-
```
4+
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.
145

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:
6+
---
177

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-
```
8+
## Prerequisites
259

26-
## Execution Commands
10+
Before running the suite locally, ensure your machine has the following tools installed:
2711

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.**
12+
1. **Node.js** (v18 or higher)
13+
2. **OpenShift CLI (oc)**: Must be configured in your system PATH.
14+
3. **Browser Binaries**: Playwright requires its own specific browser engines to run tests reproducibly. These are installed automatically when you run the `npx playwright install` setup command.
2915

30-
**Run All Tests (Headless):**
31-
```bash
32-
./run-ui-tests.sh --project=chromium
33-
```
16+
### Installation
17+
18+
Navigate to this directory and install the Node modules along with the required Playwright browser binaries:
3419

35-
**Run All Tests (Headed + Trace):**
3620
```bash
37-
./run-ui-tests.sh --project=chromium --headed --reporter=list --trace on
21+
cd test/ui-e2e
22+
npm install
23+
npx playwright install chromium
24+
3825
```
3926

40-
**Run Single Test (Headed + Trace):**
27+
---
28+
29+
## Environment Configuration
30+
31+
The test suite requires cluster administrative credentials to discover routes and handle authentication loops. You can configure these either via a local `.env` file or by exporting them directly into your terminal/CI environment pipeline.
32+
33+
### Quick Setup (Local Development)
34+
35+
Generate a local `.env` file in the root of this directory using the following block:
36+
4137
```bash
42-
./run-ui-tests.sh tests/login.spec.ts --project=chromium --headed --trace on
38+
cat <<EOF > .env
39+
export CLUSTER_USER="kubeadmin"
40+
export CLUSTER_PASSWORD="<your_cluster_password>"
41+
export OC_API_URL="<your_cluster_server_url>"
42+
export IDP="kube:admin" # (Optional) Defaults to kube:admin
43+
EOF
44+
4345
```
4446

45-
**View Trace Results:**
47+
> **Security Warning:** The `.env` file is explicitly ignored by Git. Please don't commit credentials to the repository.
48+
49+
---
50+
51+
## Execution Commands
52+
53+
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.
54+
55+
### Standard Test Execution
56+
57+
| Target | Command |
58+
| --- | --- |
59+
| **Run All Tests (Headless/CI Mode)** | `./run-ui-tests.sh --project=chromium` |
60+
| **Run All Tests (Headed + Visual Tracing)** | `./run-ui-tests.sh --project=chromium --headed --trace on` |
61+
| **Run a Specific Spec File** | `./run-ui-tests.sh tests/create-application.spec.ts --project=chromium --headed --trace on` |
62+
63+
### Playwright Flags Reference
64+
65+
| Flag | Purpose |
66+
| --- | --- |
67+
| `--headed` | Launches the visible Chromium browser UI. Excellent for local debugging. |
68+
| `--trace on` | Records a granular execution trace (DOM snapshots, network calls, actions) for visual triage. |
69+
| `--reporter=list` | Switches stdout to a clean line-by-line format, ideal for monitoring real-time execution steps. |
70+
71+
### Visual Debugging (Trace Viewer)
72+
73+
If a test fails during execution, Playwright records a full interactive timeline (DOM snapshots, network calls, console logs).
74+
75+
When a test fails, the terminal output will provide an exact command to view the trace. Copy and paste that specific command:
76+
4677
```bash
47-
npx playwright show-trace test-results/**/*/trace.zip
78+
# Example:
79+
npx playwright show-trace test-results/create-application-chromium/trace.zip
80+
4881
```
4982

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.
83+
---
5484

55-
## Architecture
85+
## Suite Architecture
5686

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.
87+
```text
88+
├── .auth/
89+
│ └── setup.ts # Orchestrates global OCP authentication & saves storageState.json
90+
├── src/
91+
│ └── pages/ # Page Object Models (POM) isolating UI selectors from spec logic
92+
│ └── ApplicationsPage.ts
93+
├── tests/ # Test specs organized by feature epic
94+
│ ├── login.spec.ts
95+
│ └── create-application.spec.ts
96+
├── .env # Local runtime environment overrides (Git ignored)
97+
└── run-ui-tests.sh # Context-aware orchestrator & URL discovery engine
98+
99+
```
100+
101+
### Core Architecture Patterns
59102

60-
**Spec Isolation:**
61-
`login.spec.ts` explicitly clears session cookies to force a full SSO UI validation from a fresh state.
103+
* **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.
104+
* **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.
105+
* **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.
106+
107+
---
62108

63109
## Troubleshooting
64110

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.
111+
### Symptom: Playwright targets the wrong cluster version
112+
113+
* **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.
114+
* **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.
115+

test/ui-e2e/package-lock.json

Lines changed: 12 additions & 12 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"license": "ISC",
99
"description": "",
1010
"devDependencies": {
11-
"@playwright/test": "^1.59.1",
11+
"@playwright/test": "^1.61.0",
1212
"@types/node": "^25.6.0",
1313
"dotenv": "^17.4.2"
1414
}

test/ui-e2e/run-ui-tests.sh

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@ export CLUSTER_USER=${CLUSTER_USER:-"kubeadmin"}
1515
export IDP=${IDP:-"kube:admin"}
1616

1717
#check auth state first
18-
echo "Checking cluster authentication..."
19-
if ! oc whoami > /dev/null 2>&1; then
20-
if [ -n "$OC_API_URL" ] && [ -n "$CLUSTER_PASSWORD" ]; then
21-
echo "Attempting automated login..."
22-
oc login "$OC_API_URL" -u "$CLUSTER_USER" -p "$CLUSTER_PASSWORD" --insecure-skip-tls-verify=true
23-
else
24-
echo "Error: Not logged in. Missing OC_API_URL or CLUSTER_PASSWORD."
18+
echo "Syncing CLI context..."
19+
if [ -n "$OC_API_URL" ] && [ -n "$CLUSTER_PASSWORD" ]; then
20+
# If variables exist, FORCE the CLI to match them so there is no cross-cluster confusion
21+
echo "Logging into $OC_API_URL..."
22+
oc login "$OC_API_URL" -u "$CLUSTER_USER" -p "$CLUSTER_PASSWORD" --insecure-skip-tls-verify=true > /dev/null 2>&1
23+
24+
if [ $? -ne 0 ]; then
25+
echo "Error: Failed to log into the cluster. Please check the credentials in your .env file."
2526
exit 1
2627
fi
28+
elif ! oc whoami > /dev/null 2>&1; then
29+
# If variables don't exist AND we aren't logged in, fail out
30+
echo "Error: Not logged in. Missing OC_API_URL or CLUSTER_PASSWORD."
31+
exit 1
32+
else
33+
# If variables don't exist but we ARE logged in locally, just use the current session
34+
echo "No .env credentials found. Using existing oc CLI session..."
2735
fi
2836

2937
#find the URLs for console and argocd

test/ui-e2e/src/fixtures.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { test as base, expect } from '@playwright/test';
2+
import { LoginPage } from './pages/LoginPage';
3+
import { ApplicationsPage } from './pages/ApplicationsPage';
4+
5+
//define custom fixture types
6+
type MyFixtures = {
7+
managedApp: string;
8+
};
9+
10+
export const test = base.extend<MyFixtures>({
11+
12+
//login override
13+
page: async ({ page }, use) => {
14+
const loginPage = new LoginPage(page);
15+
await loginPage.goto();
16+
17+
// 1. Grab variables from the environment
18+
const user = process.env.CLUSTER_USER || 'kubeadmin';
19+
const pass = process.env.CLUSTER_PASSWORD;
20+
const idp = process.env.IDP || 'kube:admin';
21+
22+
// 2. Fail loudly if the password is missing
23+
if (!pass) {
24+
throw new Error('CLUSTER_PASSWORD environment variable is missing. Cannot authenticate.');
25+
}
26+
27+
// 3. Pass them into the login method
28+
await loginPage.loginViaOpenShift(user, pass, idp);
29+
30+
await use(page);
31+
},
32+
33+
//app setup/teardown
34+
managedApp: [ async ({ page }, use) => {
35+
const appName = `e2e-app-${Date.now()}`;
36+
const appsPage = new ApplicationsPage(page);
37+
38+
console.log(`[setup] creating and syncing application: ${appName}`);
39+
await appsPage.navigate();
40+
await appsPage.createApp(
41+
appName,
42+
'https://github.com/redhat-developer/openshift-gitops-getting-started.git',
43+
'app'
44+
);
45+
await appsPage.syncApplication(appName);
46+
await appsPage.verifyStatus(appName);
47+
48+
//pass the name to the test
49+
await use(appName);
50+
51+
//teardown
52+
console.log(`[teardown] deleting ${appName} via api`);
53+
const response = await page.request.delete(`/api/v1/applications/${appName}?cascade=true`, {
54+
headers: { 'Content-Type': 'application/json' }
55+
});
56+
57+
// 4. Update the teardown to only ignore 404s, treating 403s as failures
58+
if (response.status() === 404) {
59+
return;
60+
} else {
61+
expect(response.status()).toBeLessThan(400);
62+
}
63+
}, { timeout: 120000 } ],
64+
});
65+
66+
//export it so spec files can use it
67+
export { expect };

0 commit comments

Comments
 (0)