From cd422f65e5ea48d4e849c9a488d173b55792f835 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Mon, 25 May 2026 23:19:39 +0100 Subject: [PATCH 1/4] Add E2E retry support with SQLite snapshot/restore and bail-on-failure Enables Playwright retries (3 attempts) for the E2E suite while preserving the serial, state-dependent nature of the 14 specs. On retry, the backend server is killed, both the SQLite DB and settings JSON are restored from the last known-good snapshot, and the server is restarted before the spec group re-runs. maxFailures: 1 ensures downstream specs are skipped immediately once a spec exhausts all retries, preventing meaningless results from running against incomplete state. - Add e2e/db-snapshot.ts: snapshotState(), snapshotExists(), restoreStateAndRestartServer() - Export SERVER_PORT and waitForServer from global-setup.ts for reuse - Set retries: 3 and maxFailures: 1 in playwright.config.ts - Add beforeAll (restore on retry) and afterEach (snapshot on pass) hooks to all 14 spec files Co-Authored-By: Claude Sonnet 4.6 --- client-v3/e2e/db-snapshot.ts | 68 +++++++++++++++++++ client-v3/e2e/global-setup.ts | 4 +- client-v3/e2e/tests/01-first-run.spec.ts | 13 ++++ client-v3/e2e/tests/02-auth.spec.ts | 13 ++++ client-v3/e2e/tests/03-system-config.spec.ts | 13 ++++ .../e2e/tests/04-show-config-show.spec.ts | 13 ++++ .../tests/05-show-config-acts-scenes.spec.ts | 13 ++++ .../tests/06-show-config-characters.spec.ts | 13 ++++ .../e2e/tests/07-show-config-stage.spec.ts | 13 ++++ .../e2e/tests/08-show-config-cues.spec.ts | 13 ++++ .../e2e/tests/09-show-config-mics.spec.ts | 13 ++++ .../e2e/tests/10-show-config-script.spec.ts | 13 ++++ .../tests/11-show-config-revisions.spec.ts | 13 ++++ .../e2e/tests/12-show-config-sessions.spec.ts | 13 ++++ client-v3/e2e/tests/13-live-show.spec.ts | 13 ++++ client-v3/e2e/tests/14-user-settings.spec.ts | 13 ++++ client-v3/playwright.config.ts | 3 +- 17 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 client-v3/e2e/db-snapshot.ts diff --git a/client-v3/e2e/db-snapshot.ts b/client-v3/e2e/db-snapshot.ts new file mode 100644 index 00000000..6286ba79 --- /dev/null +++ b/client-v3/e2e/db-snapshot.ts @@ -0,0 +1,68 @@ +import fs from 'fs'; +import path from 'path'; +import { spawn } from 'child_process'; +import { PID_FILE, TMPDIR_FILE, SERVER_PORT, waitForServer } from './global-setup.js'; + +function getPaths() { + const tempDir = fs.readFileSync(TMPDIR_FILE, 'utf-8').trim(); + return { + db: path.join(tempDir, 'digiscript.sqlite'), + dbSnapshot: path.join(tempDir, 'digiscript.sqlite.snapshot'), + config: path.join(tempDir, 'digiscript.json'), + configSnapshot: path.join(tempDir, 'digiscript.json.snapshot'), + serverDir: path.resolve(process.cwd(), '..', 'server'), + }; +} + +export function snapshotExists(): boolean { + const { dbSnapshot } = getPaths(); + return fs.existsSync(dbSnapshot); +} + +/** Copy the DB and config to snapshot files. Call after each passing test. */ +export function snapshotState(): void { + const { db, dbSnapshot, config, configSnapshot } = getPaths(); + fs.copyFileSync(db, dbSnapshot); + fs.copyFileSync(config, configSnapshot); +} + +/** + * Restore DB and config from the last snapshot, then restart the server. + * Call at the start of a retry to bring the backend back to last known-good state. + */ +export async function restoreStateAndRestartServer(): Promise { + const { db, dbSnapshot, config, configSnapshot, serverDir } = getPaths(); + + // Kill existing server + if (fs.existsSync(PID_FILE)) { + const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10); + try { + process.kill(pid, 'SIGKILL'); + } catch { + // process already gone + } + await new Promise((r) => setTimeout(r, 500)); + } + + // Remove any stale journal file (server uses DELETE journal mode, not WAL) + try { + fs.rmSync(`${db}-journal`); + } catch { + // no journal file present + } + + // Restore DB and config from snapshot + fs.copyFileSync(dbSnapshot, db); + fs.copyFileSync(configSnapshot, config); + + // Respawn server with the restored config + const server = spawn( + 'python3', + ['main.py', `--port=${SERVER_PORT}`, `--settings_path=${config}`, '--debug=false'], + { cwd: serverDir, detached: true, stdio: 'ignore' } + ); + fs.writeFileSync(PID_FILE, String(server.pid!)); + server.unref(); + + await waitForServer(); +} diff --git a/client-v3/e2e/global-setup.ts b/client-v3/e2e/global-setup.ts index c26e2108..b1cb644c 100644 --- a/client-v3/e2e/global-setup.ts +++ b/client-v3/e2e/global-setup.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; -const SERVER_PORT = 8888; +export const SERVER_PORT = 8888; const HEALTH_URL = `http://localhost:${SERVER_PORT}/api/v1/health`; export const PID_FILE = path.join(os.tmpdir(), 'digiscript-e2e-server.pid'); @@ -85,7 +85,7 @@ async function killStaleServer(): Promise { } } -async function waitForServer(timeoutMs = 30_000): Promise { +export async function waitForServer(timeoutMs = 30_000): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { try { diff --git a/client-v3/e2e/tests/01-first-run.spec.ts b/client-v3/e2e/tests/01-first-run.spec.ts index 272c7443..9efd6860 100644 --- a/client-v3/e2e/tests/01-first-run.spec.ts +++ b/client-v3/e2e/tests/01-first-run.spec.ts @@ -5,9 +5,22 @@ */ import { test, expect, type BrowserContext, type Page } from '@playwright/test'; import { UI_BASE, ADMIN_PASSWORD, waitForAppReady } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/02-auth.spec.ts b/client-v3/e2e/tests/02-auth.spec.ts index 489a88d3..f4a26155 100644 --- a/client-v3/e2e/tests/02-auth.spec.ts +++ b/client-v3/e2e/tests/02-auth.spec.ts @@ -10,9 +10,22 @@ import { waitForAppReady, loginAsAdmin, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/03-system-config.spec.ts b/client-v3/e2e/tests/03-system-config.spec.ts index b0742aee..94a1265a 100644 --- a/client-v3/e2e/tests/03-system-config.spec.ts +++ b/client-v3/e2e/tests/03-system-config.spec.ts @@ -11,9 +11,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/04-show-config-show.spec.ts b/client-v3/e2e/tests/04-show-config-show.spec.ts index 8bac56a6..14dc5c30 100644 --- a/client-v3/e2e/tests/04-show-config-show.spec.ts +++ b/client-v3/e2e/tests/04-show-config-show.spec.ts @@ -10,9 +10,22 @@ import { confirmModal, waitForModalClosed, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts index ba9adfa7..cb9da787 100644 --- a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts +++ b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts @@ -12,9 +12,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/06-show-config-characters.spec.ts b/client-v3/e2e/tests/06-show-config-characters.spec.ts index a68aacbc..f5a4c89b 100644 --- a/client-v3/e2e/tests/06-show-config-characters.spec.ts +++ b/client-v3/e2e/tests/06-show-config-characters.spec.ts @@ -11,9 +11,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/07-show-config-stage.spec.ts b/client-v3/e2e/tests/07-show-config-stage.spec.ts index 00f67d72..3b7fbade 100644 --- a/client-v3/e2e/tests/07-show-config-stage.spec.ts +++ b/client-v3/e2e/tests/07-show-config-stage.spec.ts @@ -12,9 +12,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/08-show-config-cues.spec.ts b/client-v3/e2e/tests/08-show-config-cues.spec.ts index 39bc0e89..5e1148fb 100644 --- a/client-v3/e2e/tests/08-show-config-cues.spec.ts +++ b/client-v3/e2e/tests/08-show-config-cues.spec.ts @@ -11,9 +11,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/09-show-config-mics.spec.ts b/client-v3/e2e/tests/09-show-config-mics.spec.ts index b737e001..8f58aa69 100644 --- a/client-v3/e2e/tests/09-show-config-mics.spec.ts +++ b/client-v3/e2e/tests/09-show-config-mics.spec.ts @@ -11,9 +11,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/10-show-config-script.spec.ts b/client-v3/e2e/tests/10-show-config-script.spec.ts index 4ade7b50..b548fccb 100644 --- a/client-v3/e2e/tests/10-show-config-script.spec.ts +++ b/client-v3/e2e/tests/10-show-config-script.spec.ts @@ -12,9 +12,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/11-show-config-revisions.spec.ts b/client-v3/e2e/tests/11-show-config-revisions.spec.ts index 9c9cd8c9..5e2e1844 100644 --- a/client-v3/e2e/tests/11-show-config-revisions.spec.ts +++ b/client-v3/e2e/tests/11-show-config-revisions.spec.ts @@ -11,9 +11,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/12-show-config-sessions.spec.ts b/client-v3/e2e/tests/12-show-config-sessions.spec.ts index 3008bb69..bfd6f263 100644 --- a/client-v3/e2e/tests/12-show-config-sessions.spec.ts +++ b/client-v3/e2e/tests/12-show-config-sessions.spec.ts @@ -12,9 +12,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/13-live-show.spec.ts b/client-v3/e2e/tests/13-live-show.spec.ts index 1e3b5886..a9d8e07d 100644 --- a/client-v3/e2e/tests/13-live-show.spec.ts +++ b/client-v3/e2e/tests/13-live-show.spec.ts @@ -16,9 +16,22 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let leaderCtx: BrowserContext; let followerCtx: BrowserContext; let leaderPage: Page; diff --git a/client-v3/e2e/tests/14-user-settings.spec.ts b/client-v3/e2e/tests/14-user-settings.spec.ts index 6b51f2a4..22f984e7 100644 --- a/client-v3/e2e/tests/14-user-settings.spec.ts +++ b/client-v3/e2e/tests/14-user-settings.spec.ts @@ -10,9 +10,22 @@ import { waitForAppReady, confirmDialog, } from '../helpers.js'; +import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); +test.beforeAll(async ({}, testInfo) => { + if (testInfo.retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } +}); + +test.afterEach(async ({}, testInfo) => { + if (testInfo.status === 'passed') { + snapshotState(); + } +}); + let ctx: BrowserContext; let page: Page; diff --git a/client-v3/playwright.config.ts b/client-v3/playwright.config.ts index 41d1f37a..ae01964f 100644 --- a/client-v3/playwright.config.ts +++ b/client-v3/playwright.config.ts @@ -6,7 +6,8 @@ export default defineConfig({ testDir: './e2e/tests', fullyParallel: false, workers: 1, - retries: 0, + retries: 3, + maxFailures: 1, reporter: [ ['html', { outputFolder: 'playwright-report', open: 'never' }], ['junit', { outputFile: 'junit/playwright-results.xml' }], From 8e23ac7d4eb57297a7be17d05c7bbd6df2d09c9f Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Mon, 25 May 2026 23:22:54 +0100 Subject: [PATCH 2/4] Fix no-empty-pattern lint errors in E2E retry hooks Replace ({}, testInfo) with (_fixtures, testInfo) in the beforeAll/afterEach hooks added to all 14 spec files. Empty destructuring patterns are disallowed by the no-empty-pattern ESLint rule. Co-Authored-By: Claude Sonnet 4.6 --- client-v3/e2e/tests/01-first-run.spec.ts | 4 ++-- client-v3/e2e/tests/02-auth.spec.ts | 4 ++-- client-v3/e2e/tests/03-system-config.spec.ts | 4 ++-- client-v3/e2e/tests/04-show-config-show.spec.ts | 4 ++-- client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts | 4 ++-- client-v3/e2e/tests/06-show-config-characters.spec.ts | 4 ++-- client-v3/e2e/tests/07-show-config-stage.spec.ts | 4 ++-- client-v3/e2e/tests/08-show-config-cues.spec.ts | 4 ++-- client-v3/e2e/tests/09-show-config-mics.spec.ts | 4 ++-- client-v3/e2e/tests/10-show-config-script.spec.ts | 4 ++-- client-v3/e2e/tests/11-show-config-revisions.spec.ts | 4 ++-- client-v3/e2e/tests/12-show-config-sessions.spec.ts | 4 ++-- client-v3/e2e/tests/13-live-show.spec.ts | 4 ++-- client-v3/e2e/tests/14-user-settings.spec.ts | 4 ++-- 14 files changed, 28 insertions(+), 28 deletions(-) diff --git a/client-v3/e2e/tests/01-first-run.spec.ts b/client-v3/e2e/tests/01-first-run.spec.ts index 9efd6860..0c17774a 100644 --- a/client-v3/e2e/tests/01-first-run.spec.ts +++ b/client-v3/e2e/tests/01-first-run.spec.ts @@ -9,13 +9,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/02-auth.spec.ts b/client-v3/e2e/tests/02-auth.spec.ts index f4a26155..dec7520b 100644 --- a/client-v3/e2e/tests/02-auth.spec.ts +++ b/client-v3/e2e/tests/02-auth.spec.ts @@ -14,13 +14,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/03-system-config.spec.ts b/client-v3/e2e/tests/03-system-config.spec.ts index 94a1265a..d68b6a24 100644 --- a/client-v3/e2e/tests/03-system-config.spec.ts +++ b/client-v3/e2e/tests/03-system-config.spec.ts @@ -15,13 +15,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/04-show-config-show.spec.ts b/client-v3/e2e/tests/04-show-config-show.spec.ts index 14dc5c30..45753f1d 100644 --- a/client-v3/e2e/tests/04-show-config-show.spec.ts +++ b/client-v3/e2e/tests/04-show-config-show.spec.ts @@ -14,13 +14,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts index cb9da787..43466cf4 100644 --- a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts +++ b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts @@ -16,13 +16,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/06-show-config-characters.spec.ts b/client-v3/e2e/tests/06-show-config-characters.spec.ts index f5a4c89b..6c43ddf4 100644 --- a/client-v3/e2e/tests/06-show-config-characters.spec.ts +++ b/client-v3/e2e/tests/06-show-config-characters.spec.ts @@ -15,13 +15,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/07-show-config-stage.spec.ts b/client-v3/e2e/tests/07-show-config-stage.spec.ts index 3b7fbade..69c924d7 100644 --- a/client-v3/e2e/tests/07-show-config-stage.spec.ts +++ b/client-v3/e2e/tests/07-show-config-stage.spec.ts @@ -16,13 +16,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/08-show-config-cues.spec.ts b/client-v3/e2e/tests/08-show-config-cues.spec.ts index 5e1148fb..9b3ce257 100644 --- a/client-v3/e2e/tests/08-show-config-cues.spec.ts +++ b/client-v3/e2e/tests/08-show-config-cues.spec.ts @@ -15,13 +15,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/09-show-config-mics.spec.ts b/client-v3/e2e/tests/09-show-config-mics.spec.ts index 8f58aa69..3b10f0cb 100644 --- a/client-v3/e2e/tests/09-show-config-mics.spec.ts +++ b/client-v3/e2e/tests/09-show-config-mics.spec.ts @@ -15,13 +15,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/10-show-config-script.spec.ts b/client-v3/e2e/tests/10-show-config-script.spec.ts index b548fccb..992166c8 100644 --- a/client-v3/e2e/tests/10-show-config-script.spec.ts +++ b/client-v3/e2e/tests/10-show-config-script.spec.ts @@ -16,13 +16,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/11-show-config-revisions.spec.ts b/client-v3/e2e/tests/11-show-config-revisions.spec.ts index 5e2e1844..55c75b87 100644 --- a/client-v3/e2e/tests/11-show-config-revisions.spec.ts +++ b/client-v3/e2e/tests/11-show-config-revisions.spec.ts @@ -15,13 +15,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/12-show-config-sessions.spec.ts b/client-v3/e2e/tests/12-show-config-sessions.spec.ts index bfd6f263..93b590c1 100644 --- a/client-v3/e2e/tests/12-show-config-sessions.spec.ts +++ b/client-v3/e2e/tests/12-show-config-sessions.spec.ts @@ -16,13 +16,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/13-live-show.spec.ts b/client-v3/e2e/tests/13-live-show.spec.ts index a9d8e07d..44542d46 100644 --- a/client-v3/e2e/tests/13-live-show.spec.ts +++ b/client-v3/e2e/tests/13-live-show.spec.ts @@ -20,13 +20,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } diff --git a/client-v3/e2e/tests/14-user-settings.spec.ts b/client-v3/e2e/tests/14-user-settings.spec.ts index 22f984e7..81bf1d4a 100644 --- a/client-v3/e2e/tests/14-user-settings.spec.ts +++ b/client-v3/e2e/tests/14-user-settings.spec.ts @@ -14,13 +14,13 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async ({}, testInfo) => { +test.beforeAll(async (_fixtures, testInfo) => { if (testInfo.retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async ({}, testInfo) => { +test.afterEach(async (_fixtures, testInfo) => { if (testInfo.status === 'passed') { snapshotState(); } From e1aa7c4161a0bc0c3674c98261f193b0e52327b5 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Mon, 25 May 2026 23:28:36 +0100 Subject: [PATCH 3/4] Use test.info() instead of testInfo parameter in retry hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playwright requires the first argument of beforeAll/afterEach to use object destructuring syntax — a plain identifier causes a fixture parser error. Using test.info() as a static accessor avoids the parameter entirely, satisfying both Playwright and the no-empty-pattern ESLint rule. Co-Authored-By: Claude Sonnet 4.6 --- client-v3/e2e/tests/01-first-run.spec.ts | 8 ++++---- client-v3/e2e/tests/02-auth.spec.ts | 8 ++++---- client-v3/e2e/tests/03-system-config.spec.ts | 8 ++++---- client-v3/e2e/tests/04-show-config-show.spec.ts | 8 ++++---- client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts | 8 ++++---- client-v3/e2e/tests/06-show-config-characters.spec.ts | 8 ++++---- client-v3/e2e/tests/07-show-config-stage.spec.ts | 8 ++++---- client-v3/e2e/tests/08-show-config-cues.spec.ts | 8 ++++---- client-v3/e2e/tests/09-show-config-mics.spec.ts | 8 ++++---- client-v3/e2e/tests/10-show-config-script.spec.ts | 8 ++++---- client-v3/e2e/tests/11-show-config-revisions.spec.ts | 8 ++++---- client-v3/e2e/tests/12-show-config-sessions.spec.ts | 8 ++++---- client-v3/e2e/tests/13-live-show.spec.ts | 8 ++++---- client-v3/e2e/tests/14-user-settings.spec.ts | 8 ++++---- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/client-v3/e2e/tests/01-first-run.spec.ts b/client-v3/e2e/tests/01-first-run.spec.ts index 0c17774a..ac2b2454 100644 --- a/client-v3/e2e/tests/01-first-run.spec.ts +++ b/client-v3/e2e/tests/01-first-run.spec.ts @@ -9,14 +9,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/02-auth.spec.ts b/client-v3/e2e/tests/02-auth.spec.ts index dec7520b..4419cd80 100644 --- a/client-v3/e2e/tests/02-auth.spec.ts +++ b/client-v3/e2e/tests/02-auth.spec.ts @@ -14,14 +14,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/03-system-config.spec.ts b/client-v3/e2e/tests/03-system-config.spec.ts index d68b6a24..45d21953 100644 --- a/client-v3/e2e/tests/03-system-config.spec.ts +++ b/client-v3/e2e/tests/03-system-config.spec.ts @@ -15,14 +15,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/04-show-config-show.spec.ts b/client-v3/e2e/tests/04-show-config-show.spec.ts index 45753f1d..3d620630 100644 --- a/client-v3/e2e/tests/04-show-config-show.spec.ts +++ b/client-v3/e2e/tests/04-show-config-show.spec.ts @@ -14,14 +14,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts index 43466cf4..e376f99d 100644 --- a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts +++ b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts @@ -16,14 +16,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/06-show-config-characters.spec.ts b/client-v3/e2e/tests/06-show-config-characters.spec.ts index 6c43ddf4..29dfb7ba 100644 --- a/client-v3/e2e/tests/06-show-config-characters.spec.ts +++ b/client-v3/e2e/tests/06-show-config-characters.spec.ts @@ -15,14 +15,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/07-show-config-stage.spec.ts b/client-v3/e2e/tests/07-show-config-stage.spec.ts index 69c924d7..381466c3 100644 --- a/client-v3/e2e/tests/07-show-config-stage.spec.ts +++ b/client-v3/e2e/tests/07-show-config-stage.spec.ts @@ -16,14 +16,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/08-show-config-cues.spec.ts b/client-v3/e2e/tests/08-show-config-cues.spec.ts index 9b3ce257..a0ea62aa 100644 --- a/client-v3/e2e/tests/08-show-config-cues.spec.ts +++ b/client-v3/e2e/tests/08-show-config-cues.spec.ts @@ -15,14 +15,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/09-show-config-mics.spec.ts b/client-v3/e2e/tests/09-show-config-mics.spec.ts index 3b10f0cb..d284c6b9 100644 --- a/client-v3/e2e/tests/09-show-config-mics.spec.ts +++ b/client-v3/e2e/tests/09-show-config-mics.spec.ts @@ -15,14 +15,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/10-show-config-script.spec.ts b/client-v3/e2e/tests/10-show-config-script.spec.ts index 992166c8..e7039274 100644 --- a/client-v3/e2e/tests/10-show-config-script.spec.ts +++ b/client-v3/e2e/tests/10-show-config-script.spec.ts @@ -16,14 +16,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/11-show-config-revisions.spec.ts b/client-v3/e2e/tests/11-show-config-revisions.spec.ts index 55c75b87..f6a8ac9d 100644 --- a/client-v3/e2e/tests/11-show-config-revisions.spec.ts +++ b/client-v3/e2e/tests/11-show-config-revisions.spec.ts @@ -15,14 +15,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/12-show-config-sessions.spec.ts b/client-v3/e2e/tests/12-show-config-sessions.spec.ts index 93b590c1..ba6e2f79 100644 --- a/client-v3/e2e/tests/12-show-config-sessions.spec.ts +++ b/client-v3/e2e/tests/12-show-config-sessions.spec.ts @@ -16,14 +16,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/13-live-show.spec.ts b/client-v3/e2e/tests/13-live-show.spec.ts index 44542d46..32594dd9 100644 --- a/client-v3/e2e/tests/13-live-show.spec.ts +++ b/client-v3/e2e/tests/13-live-show.spec.ts @@ -20,14 +20,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); diff --git a/client-v3/e2e/tests/14-user-settings.spec.ts b/client-v3/e2e/tests/14-user-settings.spec.ts index 81bf1d4a..62a9030c 100644 --- a/client-v3/e2e/tests/14-user-settings.spec.ts +++ b/client-v3/e2e/tests/14-user-settings.spec.ts @@ -14,14 +14,14 @@ import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../ test.describe.configure({ mode: 'serial' }); -test.beforeAll(async (_fixtures, testInfo) => { - if (testInfo.retry > 0 && snapshotExists()) { +test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { await restoreStateAndRestartServer(); } }); -test.afterEach(async (_fixtures, testInfo) => { - if (testInfo.status === 'passed') { +test.afterEach(async () => { + if (test.info().status === 'passed') { snapshotState(); } }); From fc8908dfa530903573d6329856c5717abd2b6d38 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Mon, 25 May 2026 23:39:06 +0100 Subject: [PATCH 4/4] Refactor retry hooks into registerRetryHooks() to eliminate duplication Extract the beforeAll/afterEach retry logic from all 14 spec files into a single registerRetryHooks() function in db-snapshot.ts. Each spec now calls registerRetryHooks() instead of repeating the 10-line hook block, reducing ~140 duplicated lines to one call site per spec. Co-Authored-By: Claude Sonnet 4.6 --- client-v3/e2e/db-snapshot.ts | 15 +++++++++++++++ client-v3/e2e/tests/01-first-run.spec.ts | 14 ++------------ client-v3/e2e/tests/02-auth.spec.ts | 14 ++------------ client-v3/e2e/tests/03-system-config.spec.ts | 14 ++------------ client-v3/e2e/tests/04-show-config-show.spec.ts | 14 ++------------ .../e2e/tests/05-show-config-acts-scenes.spec.ts | 14 ++------------ .../e2e/tests/06-show-config-characters.spec.ts | 14 ++------------ client-v3/e2e/tests/07-show-config-stage.spec.ts | 14 ++------------ client-v3/e2e/tests/08-show-config-cues.spec.ts | 14 ++------------ client-v3/e2e/tests/09-show-config-mics.spec.ts | 14 ++------------ client-v3/e2e/tests/10-show-config-script.spec.ts | 14 ++------------ .../e2e/tests/11-show-config-revisions.spec.ts | 14 ++------------ .../e2e/tests/12-show-config-sessions.spec.ts | 14 ++------------ client-v3/e2e/tests/13-live-show.spec.ts | 14 ++------------ client-v3/e2e/tests/14-user-settings.spec.ts | 14 ++------------ 15 files changed, 43 insertions(+), 168 deletions(-) diff --git a/client-v3/e2e/db-snapshot.ts b/client-v3/e2e/db-snapshot.ts index 6286ba79..e4dd528e 100644 --- a/client-v3/e2e/db-snapshot.ts +++ b/client-v3/e2e/db-snapshot.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; import { spawn } from 'child_process'; +import { test } from '@playwright/test'; import { PID_FILE, TMPDIR_FILE, SERVER_PORT, waitForServer } from './global-setup.js'; function getPaths() { @@ -66,3 +67,17 @@ export async function restoreStateAndRestartServer(): Promise { await waitForServer(); } + +/** Register beforeAll/afterEach hooks for retry support. Call once at the top of each spec file. */ +export function registerRetryHooks(): void { + test.beforeAll(async () => { + if (test.info().retry > 0 && snapshotExists()) { + await restoreStateAndRestartServer(); + } + }); + test.afterEach(async () => { + if (test.info().status === 'passed') { + snapshotState(); + } + }); +} diff --git a/client-v3/e2e/tests/01-first-run.spec.ts b/client-v3/e2e/tests/01-first-run.spec.ts index ac2b2454..d596884c 100644 --- a/client-v3/e2e/tests/01-first-run.spec.ts +++ b/client-v3/e2e/tests/01-first-run.spec.ts @@ -5,21 +5,11 @@ */ import { test, expect, type BrowserContext, type Page } from '@playwright/test'; import { UI_BASE, ADMIN_PASSWORD, waitForAppReady } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/02-auth.spec.ts b/client-v3/e2e/tests/02-auth.spec.ts index 4419cd80..f2d9c618 100644 --- a/client-v3/e2e/tests/02-auth.spec.ts +++ b/client-v3/e2e/tests/02-auth.spec.ts @@ -10,21 +10,11 @@ import { waitForAppReady, loginAsAdmin, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/03-system-config.spec.ts b/client-v3/e2e/tests/03-system-config.spec.ts index 45d21953..7d85e214 100644 --- a/client-v3/e2e/tests/03-system-config.spec.ts +++ b/client-v3/e2e/tests/03-system-config.spec.ts @@ -11,21 +11,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/04-show-config-show.spec.ts b/client-v3/e2e/tests/04-show-config-show.spec.ts index 3d620630..0968a8f5 100644 --- a/client-v3/e2e/tests/04-show-config-show.spec.ts +++ b/client-v3/e2e/tests/04-show-config-show.spec.ts @@ -10,21 +10,11 @@ import { confirmModal, waitForModalClosed, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts index e376f99d..e39372cd 100644 --- a/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts +++ b/client-v3/e2e/tests/05-show-config-acts-scenes.spec.ts @@ -12,21 +12,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/06-show-config-characters.spec.ts b/client-v3/e2e/tests/06-show-config-characters.spec.ts index 29dfb7ba..94978a15 100644 --- a/client-v3/e2e/tests/06-show-config-characters.spec.ts +++ b/client-v3/e2e/tests/06-show-config-characters.spec.ts @@ -11,21 +11,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/07-show-config-stage.spec.ts b/client-v3/e2e/tests/07-show-config-stage.spec.ts index 381466c3..c85dd0b7 100644 --- a/client-v3/e2e/tests/07-show-config-stage.spec.ts +++ b/client-v3/e2e/tests/07-show-config-stage.spec.ts @@ -12,21 +12,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/08-show-config-cues.spec.ts b/client-v3/e2e/tests/08-show-config-cues.spec.ts index a0ea62aa..102b0f6c 100644 --- a/client-v3/e2e/tests/08-show-config-cues.spec.ts +++ b/client-v3/e2e/tests/08-show-config-cues.spec.ts @@ -11,21 +11,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/09-show-config-mics.spec.ts b/client-v3/e2e/tests/09-show-config-mics.spec.ts index d284c6b9..f8c36b5d 100644 --- a/client-v3/e2e/tests/09-show-config-mics.spec.ts +++ b/client-v3/e2e/tests/09-show-config-mics.spec.ts @@ -11,21 +11,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/10-show-config-script.spec.ts b/client-v3/e2e/tests/10-show-config-script.spec.ts index e7039274..b32cf760 100644 --- a/client-v3/e2e/tests/10-show-config-script.spec.ts +++ b/client-v3/e2e/tests/10-show-config-script.spec.ts @@ -12,21 +12,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/11-show-config-revisions.spec.ts b/client-v3/e2e/tests/11-show-config-revisions.spec.ts index f6a8ac9d..1d1925ee 100644 --- a/client-v3/e2e/tests/11-show-config-revisions.spec.ts +++ b/client-v3/e2e/tests/11-show-config-revisions.spec.ts @@ -11,21 +11,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/12-show-config-sessions.spec.ts b/client-v3/e2e/tests/12-show-config-sessions.spec.ts index ba6e2f79..1b9a07dc 100644 --- a/client-v3/e2e/tests/12-show-config-sessions.spec.ts +++ b/client-v3/e2e/tests/12-show-config-sessions.spec.ts @@ -12,21 +12,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page; diff --git a/client-v3/e2e/tests/13-live-show.spec.ts b/client-v3/e2e/tests/13-live-show.spec.ts index 32594dd9..3813d7a1 100644 --- a/client-v3/e2e/tests/13-live-show.spec.ts +++ b/client-v3/e2e/tests/13-live-show.spec.ts @@ -16,21 +16,11 @@ import { waitForModalClosed, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let leaderCtx: BrowserContext; let followerCtx: BrowserContext; diff --git a/client-v3/e2e/tests/14-user-settings.spec.ts b/client-v3/e2e/tests/14-user-settings.spec.ts index 62a9030c..63d22388 100644 --- a/client-v3/e2e/tests/14-user-settings.spec.ts +++ b/client-v3/e2e/tests/14-user-settings.spec.ts @@ -10,21 +10,11 @@ import { waitForAppReady, confirmDialog, } from '../helpers.js'; -import { snapshotExists, snapshotState, restoreStateAndRestartServer } from '../db-snapshot.js'; +import { registerRetryHooks } from '../db-snapshot.js'; test.describe.configure({ mode: 'serial' }); -test.beforeAll(async () => { - if (test.info().retry > 0 && snapshotExists()) { - await restoreStateAndRestartServer(); - } -}); - -test.afterEach(async () => { - if (test.info().status === 'passed') { - snapshotState(); - } -}); +registerRetryHooks(); let ctx: BrowserContext; let page: Page;