diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-18 14:40:34 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-18 14:40:34 +0300 |
| commit | d1bc3cf3bf84be50f1dd400e60da1ab8176184ec (patch) | |
| tree | c30daf7e52bd51ea03183f4ea9f083baf27c725e /player-server | |
| parent | b5f97eb3adae3a80cc4fd47ea34a5dc83cba01b0 (diff) | |
Choose Playwright for web UI smoke tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'player-server')
| -rw-r--r-- | player-server/test/e2e-web/README.md | 125 | ||||
| -rw-r--r-- | player-server/test/e2e-web/package-lock.json | 111 | ||||
| -rw-r--r-- | player-server/test/e2e-web/package.json | 17 | ||||
| -rw-r--r-- | player-server/test/e2e-web/playwright.config.ts | 57 | ||||
| -rw-r--r-- | player-server/test/e2e-web/tsconfig.json | 15 |
5 files changed, 325 insertions, 0 deletions
diff --git a/player-server/test/e2e-web/README.md b/player-server/test/e2e-web/README.md new file mode 100644 index 0000000..0141526 --- /dev/null +++ b/player-server/test/e2e-web/README.md @@ -0,0 +1,125 @@ +# Player — Web UI Smoke Tests (Playwright) + +This directory contains a Playwright (TypeScript) smoke suite that exercises +the Player web UI end-to-end against a running server. + +## Prerequisites + +| Requirement | Version | +|-------------|---------| +| Node.js | 18+ | +| npm | 8+ | +| Chromium | installed via Playwright (see below) | + +The Player server binary must be running before the tests are launched. +The tests do **not** start or stop the server themselves. + +## First-time setup + +```sh +# From this directory: +npm install +npm run install-browsers # downloads Chromium for Playwright +``` + +## Starting the server + +The tests default to `http://localhost:8080`. You can override the address with +the `PLAYER_URL` environment variable. + +Start the server **from the `player-server/` directory** (not from the project root). +The binary resolves the embedded `web/` static asset directory relative to its working +directory, so running it from a different location will cause static files to 404. + +```sh +# From player-server/ — use testmedia/ as the media library. +MEDIA_ROOT=./testmedia SECURE_COOKIES=false DB_PATH=/tmp/player-e2e.db ./player +``` + +Key environment variables for the test server: + +| Variable | Value | Reason | +|-----------------|----------------|-----------------------------------------------------| +| `MEDIA_ROOT` | `./testmedia` | Provides pre-existing sets so the grid test passes. | +| `SECURE_COOKIES`| `false` | Allows the session cookie over plain HTTP. | +| `DB_PATH` | `/tmp/player-e2e.db` | Isolates the test database from production. | + +## Running the tests + +```sh +# Headless (default — suitable for CI): +npm test + +# With a visible browser window (useful when debugging): +npm run test:headed + +# Interactive Playwright UI: +npm run test:ui +``` + +To run against a different server: + +```sh +PLAYER_URL=http://localhost:9090 npm test +``` + +## Test accounts + +The suite creates the following accounts on first run (idempotent on re-runs): + +| Account | Password | Role | +|----------------|----------------------|-----------| +| `e2e-admin` | `e2e-passw0rd!` | admin | +| `e2e-user` | `e2e-user-passw0rd!` | regular | + +If the server already has a bootstrapped admin account you must either wipe the +database (`rm /tmp/player-e2e.db`) or change `ADMIN_USER` / `ADMIN_PASS` in +`tests/helpers/server.ts` to match the existing credentials. + +## Test structure + +``` +test/e2e-web/ +├── playwright.config.ts # Playwright configuration (baseURL, timeouts, reporter) +├── package.json # npm dependencies +├── tsconfig.json # TypeScript configuration +├── README.md # this file +└── tests/ + ├── helpers/ + │ └── server.ts # API helpers: bootstrap, login, waitForServer + └── smoke.test.ts # Smoke suite (7 test groups) +``` + +## Smoke suite coverage + +| # | Test | What it checks | +|---|------------------------------------------|-------------------------------------------------| +| 1 | bootstrap page is reachable | `/bootstrap.html` renders | +| 2 | bootstrap form creates admin | Form submit redirects to `/login.html` | +| 3 | login form authenticates | Form submit lands on `/` with the header | +| 4 | login with wrong password shows error | Error message appears without page navigation | +| 5 | sets sidebar opens | Sidebar shows at least one set | +| 6 | set list shows set names | Set names are non-empty strings | +| 7 | clicking a set loads the media grid | Grid appears with at least one card | +| 8 | progress API accepts position update | POST /api/v1/progress returns 200 | +| 9 | admin button visible and opens panel | Gear button is un-hidden for admin; modal opens | +| 10| admin panel hidden for regular user | Gear button stays hidden | +| 11| admin API returns 403 for regular user | GET /api/v1/admin/users → 403 | +| 12| logout redirects to login | Session ends, browser lands on `/login.html` | + +## CI integration + +On CI set `CI=true` (Playwright reads this automatically) to: +- enable one retry per test +- fail on any `test.only` left in the source + +Example GitHub Actions step (after `npm ci` and `npm run install-browsers`): + +```yaml +- name: Run Playwright smoke tests + env: + CI: true + PLAYER_URL: http://localhost:8080 + run: npm test + working-directory: player-server/test/e2e-web +``` diff --git a/player-server/test/e2e-web/package-lock.json b/player-server/test/e2e-web/package-lock.json new file mode 100644 index 0000000..faf8ad5 --- /dev/null +++ b/player-server/test/e2e-web/package-lock.json @@ -0,0 +1,111 @@ +{ + "name": "player-e2e-web", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "player-e2e-web", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.0.0", + "typescript": "^5.8.0" + } + }, + "node_modules/@playwright/test": { + "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.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "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.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "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": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/player-server/test/e2e-web/package.json b/player-server/test/e2e-web/package.json new file mode 100644 index 0000000..4a24c61 --- /dev/null +++ b/player-server/test/e2e-web/package.json @@ -0,0 +1,17 @@ +{ + "name": "player-e2e-web", + "version": "1.0.0", + "description": "Playwright smoke tests for the Player web UI", + "private": true, + "scripts": { + "test": "playwright test", + "test:headed": "playwright test --headed", + "test:ui": "playwright test --ui", + "install-browsers": "playwright install --with-deps chromium" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.0.0", + "typescript": "^5.8.0" + } +} diff --git a/player-server/test/e2e-web/playwright.config.ts b/player-server/test/e2e-web/playwright.config.ts new file mode 100644 index 0000000..26782bf --- /dev/null +++ b/player-server/test/e2e-web/playwright.config.ts @@ -0,0 +1,57 @@ +import { defineConfig, devices } from '@playwright/test'; + +// Base URL for the running player server. Override with the PLAYER_URL env var +// when testing against a non-default address or port. +const baseURL = process.env.PLAYER_URL || 'http://localhost:8080'; + +export default defineConfig({ + // Look for test files only inside the tests/ subdirectory. + testDir: './tests', + + // Run each test file in its own isolated context. Tests within the same + // file share a browser context by default (serial execution per file). + fullyParallel: false, + + // Fail the CI run on test.only left in source. + forbidOnly: !!process.env.CI, + + // Retry once on CI to absorb transient timing flakes. + retries: process.env.CI ? 1 : 0, + + // Single worker: the tests mutate shared server state (bootstrap, user + // accounts) so parallelism across workers would cause races. + workers: 1, + + reporter: [['list'], ['html', { open: 'never' }]], + + // Allow up to 120 s for the global beforeAll hook which triggers a media + // rescan and waits for at least one set to appear in the database. + globalTimeout: 120_000, + + use: { + baseURL, + + // Headless by default; PWDEBUG=1 or --headed enables the browser window. + headless: true, + + // Capture a screenshot on failure for easier CI debugging. + screenshot: 'only-on-failure', + + // Record a trace on the first retry to capture the failure timeline. + trace: 'on-first-retry', + + // Generous navigation timeout for the Go server to respond on CI. + navigationTimeout: 15_000, + actionTimeout: 10_000, + }, + + projects: [ + { + // Run against Chromium only. The smoke suite exercises application + // logic, not cross-browser compatibility. Add Firefox/WebKit when + // cross-browser coverage is needed. + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/player-server/test/e2e-web/tsconfig.json b/player-server/test/e2e-web/tsconfig.json new file mode 100644 index 0000000..cb3bbda --- /dev/null +++ b/player-server/test/e2e-web/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": ".", + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist", "test-results"] +} |
