summaryrefslogtreecommitdiff
path: root/player-server
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-18 14:40:34 +0300
committerPaul Buetow <paul@buetow.org>2026-05-18 14:40:34 +0300
commitd1bc3cf3bf84be50f1dd400e60da1ab8176184ec (patch)
treec30daf7e52bd51ea03183f4ea9f083baf27c725e /player-server
parentb5f97eb3adae3a80cc4fd47ea34a5dc83cba01b0 (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.md125
-rw-r--r--player-server/test/e2e-web/package-lock.json111
-rw-r--r--player-server/test/e2e-web/package.json17
-rw-r--r--player-server/test/e2e-web/playwright.config.ts57
-rw-r--r--player-server/test/e2e-web/tsconfig.json15
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"]
+}