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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ kuttl-test.json
# ignore vendor
vendor/
.vscode/
.DS_Store
65 changes: 65 additions & 0 deletions test/ui-e2e/.auth/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { test as setup } from '@playwright/test';

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

setup('authenticate to OpenShift Cluster', async ({ page, baseURL }) => {
// Navigate to the OpenShift Console
const targetUrl = baseURL || process.env.CONSOLE_URL || process.env.BASE_URL;

if (!targetUrl) {
throw new Error("No Console URL provided! Ensure your bash script exports BASE_URL or CONSOLE_URL.");
}

console.log(`Navigating to OpenShift Console: ${targetUrl}`);
await page.goto(targetUrl);

//set locators
const idpScreenText = page.getByText(/Log in with/i);
const usernameInput = page.getByLabel(/Username/i)
.or(page.locator('input[name="username"]'))
.or(page.getByPlaceholder(/Username/i));

//wait for the IDP screen OR the Username field to appear
try {
await Promise.race([
idpScreenText.waitFor({ state: 'visible', timeout: 15000 }),
usernameInput.waitFor({ state: 'visible', timeout: 15000 })
]);
} catch (e) {
console.log("Timed out waiting for OpenShift login page to render.");
}

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

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

// look for the specific IDP
const idpLink = page.getByRole('link', { name: new RegExp(idpName, 'i') });

await idpLink.waitFor({ state: 'visible', timeout: 5000 });
await idpLink.click();
} else {
console.log("No IDP screen detected (or already selected), proceeding to credentials...");
}

// fill in the Credentials
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
await usernameInput.fill(user);

const passwordInput = page.getByLabel(/Password/i)
.or(page.locator('input[name="password"]'))
.or(page.getByPlaceholder(/Password/i));

if (!process.env.CLUSTER_PASSWORD) {
throw new Error("CLUSTER_PASSWORD is not set in the environment!");
}

await passwordInput.fill(process.env.CLUSTER_PASSWORD);
await page.getByRole('button', { name: /Log in/i }).click();

//save the auth state
await page.waitForLoadState('networkidle');
await page.context().storageState({ path: authFile });
});
10 changes: 10 additions & 0 deletions test/ui-e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
.auth/storageState.json
.env
65 changes: 65 additions & 0 deletions test/ui-e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# GitOps Operator - UI End-to-End Tests

This suite validates the OpenShift GitOps Operator UI, focusing on Argo CD and SSO integration.

## Prerequisites
1. **Node.js** (v18+)
2. **OpenShift CLI (oc)**: Installed and in your PATH.
3. **Install Dependencies:** Navigate to this directory and install required packages:
```bash
cd test/ui-e2e
npm install
npx playwright install chromium
```

## Environment Variables
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:

```text
# .env file example
CLUSTER_PASSWORD=your_openshift_admin_password
OC_API_URL=[https://api.cluster.com:6443](https://api.cluster.com:6443)
CLUSTER_USER=kubeadmin # (Optional) Defaults to kubeadmin
IDP=kube:admin # (Optional) Defaults to kube:admin
```

## Execution Commands

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.**

**Run All Tests (Headless):**
```bash
./run-ui-tests.sh --project=chromium
```

**Run All Tests (Headed + Trace):**
```bash
./run-ui-tests.sh --project=chromium --headed --reporter=list --trace on
```

**Run Single Test (Headed + Trace):**
```bash
./run-ui-tests.sh tests/login.spec.ts --project=chromium --headed --trace on
```

**View Trace Results:**
```bash
npx playwright show-trace test-results/**/*/trace.zip
```

** Helpful Flags Explained**
* `--headed`: Runs tests in a visible browser. Without this, tests run in "headless" mode (invisible background).
* `--reporter=list`: Changes console output to a clean, line-by-line list so you can see exactly which test is running in real-time.
* `--trace on`: Captures a full "recording" (DOM snapshots, network, actions) of the test for debugging.

## Architecture

**Global Setup:**
`.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.

**Spec Isolation:**
`login.spec.ts` explicitly clears session cookies to force a full SSO UI validation from a fresh state.

## Troubleshooting

* **"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 changes: 111 additions & 0 deletions test/ui-e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions test/ui-e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "ui-e2e",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@playwright/test": "^1.59.1",
"@types/node": "^25.6.0",
"dotenv": "^17.4.2"
}
}
77 changes: 77 additions & 0 deletions test/ui-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/

// top of playwright.config.ts
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['list'],
['html', { open: process.env.CI ? 'never' : 'on-failure' }]
],

/* GLOBAL FOUNDATION: These apply to everything */
use: {
baseURL: process.env.ARGOCD_URL,
ignoreHTTPSErrors: true,
trace: 'on-first-retry',
},

/* Configure for major browsers */
projects: [
{
name: 'setup',
testDir: './',
testMatch: '**/.auth/setup.ts',
/* Only changes the URL for this specific project */
use: {
baseURL: process.env.CONSOLE_URL, },
},

// Update chromium project
{
name: 'chromium',
dependencies: ['setup'],
use: {
...devices['Desktop Chrome'],
storageState: '.auth/storageState.json',
// project still has ignoreHTTPSErrors: true from above
},
},

{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
// storageState and dependencies here later if we want to run Firefox tests but for now just focus on Chromium
},
},
// ... webkit etc ...
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});
Loading
Loading