diff --git a/.gitignore b/.gitignore index df7c0013..c43049f4 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ yarn-error.log* cc-test-reporter coverage playwright-report +playwright/.auth test-results # Storybook artifacts diff --git a/.tekton/sources-ui-pull-request.yaml b/.tekton/sources-ui-pull-request.yaml index 88c30424..c449ee5d 100644 --- a/.tekton/sources-ui-pull-request.yaml +++ b/.tekton/sources-ui-pull-request.yaml @@ -40,11 +40,17 @@ spec: npm install npm run lint npm test -- --runInBand --no-cache + # Workspace setup script (runs before E2E tests) + - name: workspace-setup-script + value: | + #!/bin/bash + set -ex + npm ci # E2E testing configuration - name: e2e-app-port value: "8000" - name: e2e-playwright-image - value: "mcr.microsoft.com/playwright:v1.58.2-jammy" + value: "mcr.microsoft.com/playwright:v1.60.0-jammy" # Application startup script (Caddy serves app assets) - name: run-app-script value: | @@ -63,7 +69,6 @@ spec: timeout 120s bash -c 'until curl -k -s -m 5 https://stage.foo.redhat.com:1337 > /dev/null 2>&1; do sleep 5; done' echo "Proxy ready!" - npm run playwright install --with-deps chromium npm run playwright -- test - name: e2e-credentials-secret value: sources-ui-credentials-secret diff --git a/package-lock.json b/package-lock.json index 306761d9..1e2cede7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,8 +44,9 @@ "@babel/preset-env": "7.28.6", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.60.0", "@redhat-cloud-services/frontend-components-config": "^6.8.3", + "@redhat-cloud-services/playwright-test-auth": "^0.0.2", "@redhat-cloud-services/tsc-transform-imports": "^1.0.37", "@storybook/addon-docs": "^9.1.19", "@storybook/addon-webpack5-compiler-swc": "^3.0.0", @@ -69,6 +70,7 @@ "msw": "^2.12.10", "msw-storybook-addon": "^2.0.6", "npm-run-all2": "^8.0.4", + "playwright": "^1.60.0", "prettier": "^3.8.0", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.5", @@ -79,8 +81,8 @@ "whatwg-fetch": "^3.6.20" }, "engines": { - "node": ">=15.14.0", - "npm": ">=7.24.2" + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/@adobe/css-tools": { @@ -3581,13 +3583,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", - "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.59.1" + "playwright": "1.60.0" }, "bin": { "playwright": "cli.js" @@ -3801,6 +3803,19 @@ "tslib": "^2.6.2" } }, + "node_modules/@redhat-cloud-services/playwright-test-auth": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/playwright-test-auth/-/playwright-test-auth-0.0.2.tgz", + "integrity": "sha512-6B12razgM3Tyof1C63Dp6tKeWLOE+DdnrbezQ+kgRDJT76Wn8XdnkFAn5eZGT/eEgU27eGBkmnKTZIOoiUVBXQ==", + "dev": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@playwright/test": "^1.40.0", + "playwright": "^1.40.0" + } + }, "node_modules/@redhat-cloud-services/rbac-client": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/rbac-client/-/rbac-client-4.2.5.tgz", @@ -15168,13 +15183,13 @@ } }, "node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.59.1" + "playwright-core": "1.60.0" }, "bin": { "playwright": "cli.js" @@ -15187,9 +15202,9 @@ } }, "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 0d011dc8..658bccbd 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,9 @@ "@babel/preset-env": "7.28.6", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.60.0", "@redhat-cloud-services/frontend-components-config": "^6.8.3", + "@redhat-cloud-services/playwright-test-auth": "^0.0.2", "@redhat-cloud-services/tsc-transform-imports": "^1.0.37", "@storybook/addon-docs": "^9.1.19", "@storybook/addon-webpack5-compiler-swc": "^3.0.0", @@ -74,6 +75,7 @@ "msw": "^2.12.10", "msw-storybook-addon": "^2.0.6", "npm-run-all2": "^8.0.4", + "playwright": "^1.60.0", "prettier": "^3.8.0", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.5", diff --git a/playwright.config.js b/playwright.config.js index 0bbc240b..563b1a0b 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -5,12 +5,24 @@ import { defineConfig, devices } from '@playwright/test'; */ export default defineConfig({ testDir: './playwright', - fullyParallel: true, + fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + workers: 1, reporter: 'html', + + // Global setup for authentication + // Authenticates once before all tests and saves the session state + globalSetup: require.resolve('@redhat-cloud-services/playwright-test-auth/global-setup'), + use: { + baseURL: process.env.PLAYWRIGHT_BASE_URL || 'https://stage.foo.redhat.com:1337', + ignoreHTTPSErrors: true, + + // Storage state for authenticated sessions + // Global setup saves authentication here, all tests reuse it + storageState: 'playwright/.auth/user.json', + trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', diff --git a/playwright/README.md b/playwright/README.md index 874907d8..ba6f5dd2 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -14,7 +14,7 @@ Before running the tests, you need to: 2. **Set up environment variables** - `E2E_USER` - Red Hat SSO username for testing - `E2E_PASSWORD` - Red Hat SSO password for testing - - `APP_TEST_HOST_PORT` (optional) - Custom URL for the test environment + - `PLAYWRIGHT_BASE_URL` (optional) - Custom URL for the test environment ## Running Tests @@ -29,7 +29,7 @@ E2E_USER=your-username E2E_PASSWORD=your-password npm run playwright -- test Override the default test URL: ```bash -APP_TEST_HOST_PORT=localhost:8080 E2E_USER=your-username E2E_PASSWORD=your-password npm run playwright -- test +PLAYWRIGHT_BASE_URL=localhost:8080 E2E_USER=your-username E2E_PASSWORD=your-password npm run playwright -- test ``` ### Running Specific Tests @@ -56,36 +56,12 @@ E2E_USER=your-username E2E_PASSWORD=your-password npm run playwright -- test --d ## Writing Tests -### Authenticated Tests - -Most tests require user authentication. Use `authTest` instead of `test` to automatically handle login: - -```javascript -import { authTest, expect } from './fixtures.js'; - -authTest.use({ ignoreHTTPSErrors: true }); - -authTest.describe('my feature', () => { - authTest('should do something', async ({ page }) => { - // User is already logged in here - await page.goto('/settings/integrations'); - await expect(page.locator('h1')).toBeVisible(); - }); -}); -``` - ### Using Authentication Utilities -You can also use the authentication functions directly: +You can use the authentication functions directly from the shared package: ```javascript -import { ensureLoggedIn, login, disableCookiePrompt } from './fixtures.js'; - -// Ensure user is logged in (checks if already logged in first) -await ensureLoggedIn(page); - -// Direct login -await login(page, 'username', 'password'); +import { disableCookiePrompt } from '@redhat-cloud-services/playwright-test-auth'; // Disable cookie prompts await disableCookiePrompt(page); @@ -116,7 +92,6 @@ The Playwright configuration is in `playwright.config.js` and includes: ``` playwright/ ├── README.md # This file -├── fixtures.js # Authentication utilities and authTest fixture ├── integrations.spec.js # Integration page tests └── [other test files] ``` @@ -132,9 +107,9 @@ playwright/ ### Tests Timeout - Check that your local dev server is running -- Verify the `APP_TEST_HOST_PORT` matches your actual server +- Verify the `PLAYWRIGHT_BASE_URL` matches your actual server - Check network connectivity to external dependencies ### Certificate Errors -Tests use `ignoreHTTPSErrors: true` to handle self-signed certificates in development environments. This is configured per-test using `authTest.use()`. +Tests use `ignoreHTTPSErrors: true` to handle self-signed certificates in development environments. This is configured in the Playwright config and applied during globalSetup. diff --git a/playwright/fixtures.js b/playwright/fixtures.js deleted file mode 100644 index 1714d6ee..00000000 --- a/playwright/fixtures.js +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from '@playwright/test'; - -const APP_TEST_HOST_PORT = process.env.APP_TEST_HOST_PORT || 'stage.foo.redhat.com:1337'; - -// Prevents inconsistent cookie prompting that is problematic for UI testing -export async function disableCookiePrompt(page) { - await page.route('**/*', async (route, request) => { - if (request.url().includes('consent.trustarc.com') && request.resourceType() !== 'document') { - await route.abort(); - } else { - await route.continue(); - } - }); -} - -export async function login(page, user, password) { - // Fail in a friendly way if the proxy config is not set up correctly - await expect(page.locator('text=Lockdown'), 'proxy config incorrect').toHaveCount(0); - - // Wait for and fill username field - await page.getByLabel('Red Hat login').first().fill(user); - await page.getByRole('button', { name: 'Next' }).click(); - - // Wait for and fill password field - await page.getByLabel('Password').first().fill(password); - await page.getByRole('button', { name: 'Log in' }).click(); - - // confirm login was valid - await expect(page.getByText('Invalid login')).not.toBeVisible(); -} - -export async function ensureLoggedIn(page) { - // Block cookie prompts before navigating - await disableCookiePrompt(page); - - await page.goto(`https://${APP_TEST_HOST_PORT}`, { waitUntil: 'load', timeout: 60000 }); - - const loggedIn = await page.locator('#chrome-app-render-root').isVisible(); - - if (!loggedIn) { - const user = process.env.E2E_USER; - const password = process.env.E2E_PASSWORD; - - if (!user || !password) { - throw new Error( - 'E2E_USER and E2E_PASSWORD environment variables are required for authentication.\n' + - 'Please set them before running the tests:\n' + - 'E2E_USER=your-username E2E_PASSWORD=your-password npm run playwright -- test', - ); - } - - // make sure the SSO prompt is loaded for login - await page.waitForLoadState('load'); - await expect(page.locator('#username-verification')).toBeVisible(); - await login(page, user, password); - await page.waitForLoadState('load'); - await expect(page.getByText('Invalid login')).not.toBeVisible(); - - // long wait for the page to load; stage can be delicate - await page.waitForTimeout(5000); - await expect(page.getByLabel('Settings menu'), 'settings menu not displayed').toBeVisible(); - - // conditionally accept cookie prompt - const acceptAllButton = page.getByRole('button', { name: 'Accept all' }); - if (await acceptAllButton.isVisible()) { - await acceptAllButton.click(); - } - } -} - -export const authTest = test.extend({ - authenticatedPage: [ - async ({ page }, use) => { - // This code runs before every test - await ensureLoggedIn(page); - await use(page); - // Code after use() would run after every test - }, - { auto: true }, - ], -}); - -export { expect }; diff --git a/playwright/integrations.spec.js b/playwright/integrations.spec.js index c741bc9b..4228cd87 100644 --- a/playwright/integrations.spec.js +++ b/playwright/integrations.spec.js @@ -1,9 +1,13 @@ -import { authTest, expect } from './fixtures.js'; +import { expect, test } from '@playwright/test'; +import { disableCookiePrompt } from '@redhat-cloud-services/playwright-test-auth'; -authTest.use({ ignoreHTTPSErrors: true }); +test.describe('integrations application', async () => { + test('navigate to integrations via settings dropdown', async ({ page }) => { + // Block cookie prompts before navigation + await disableCookiePrompt(page); -authTest.describe('integrations application', async () => { - authTest('navigate to integrations via settings dropdown', async ({ page }) => { + // Navigate to the application (already authenticated via globalSetup) + await page.goto('/'); // Click on the settings icon await page.getByLabel('Settings menu').click();