From 3e72ddde7881be1948d9ddc75a1d0a0107695eb4 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Thu, 28 May 2026 07:44:07 +0200 Subject: [PATCH] test(dashboard): stabilize the e2e suite under parallel runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues found while running the full B1–B8 suite together: - The B1 logout test was driving the shared admin storageState token, invalidating it for every subsequent test. Switched it to a fresh login so its session is disposable. - Bumped navigationTimeout to 30s and capped local workers at 4 to cope with the Vite dev server's first-compile cost under parallel load. Local also gets one retry to absorb intermittent warmup flakiness. - Cleared a few lint warnings (unused appId / _adminPage vars) and belt-and-braces gitignore for playwright artifacts written to the repo root when the CLI is invoked from there by accident. Suite now: 55/55 passing in ~21s. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 4 ++++ dashboard/playwright.config.ts | 9 ++++++--- dashboard/tests/e2e/auth/auth.spec.ts | 9 ++++++++- dashboard/tests/e2e/members/members.spec.ts | 9 ++++----- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 37b4405..c6478c9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,10 @@ config.local.toml /dashboard/playwright-report /dashboard/test-results /dashboard/.playwright +# When playwright is invoked from the repo root by accident, these +# also land here. +/playwright-report +/test-results # Caddy /caddy/data diff --git a/dashboard/playwright.config.ts b/dashboard/playwright.config.ts index 33c9049..1d6151c 100644 --- a/dashboard/playwright.config.ts +++ b/dashboard/playwright.config.ts @@ -15,15 +15,18 @@ export default defineConfig({ outputDir: './tests/e2e/.results', fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? 2 : undefined, + // Local: 1 retry to absorb dev-server warmup flakiness. CI: 2. + retries: process.env.CI ? 2 : 1, + // Cap at 4 workers locally to keep the shared Vite dev server + // from getting stampeded during cold-start compiles. + workers: process.env.CI ? 2 : 4, reporter: process.env.CI ? [['html'], ['github']] : 'html', globalSetup: './tests/e2e/global-setup.ts', expect: { timeout: 5_000 }, use: { baseURL: DASHBOARD_BASE, actionTimeout: 10_000, - navigationTimeout: 15_000, + navigationTimeout: 30_000, trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' diff --git a/dashboard/tests/e2e/auth/auth.spec.ts b/dashboard/tests/e2e/auth/auth.spec.ts index 85443f1..c109a04 100644 --- a/dashboard/tests/e2e/auth/auth.spec.ts +++ b/dashboard/tests/e2e/auth/auth.spec.ts @@ -97,9 +97,16 @@ test.describe('B1 auth — authenticated', () => { await page.goto('/admin/login'); await expect(page).toHaveURL(/\/admin\/apps$/); }); +}); + +test.describe('B1 auth — logout', () => { + // Logout must NOT use the shared storageState token, or it would + // invalidate the session every other test relies on. Each run + // here logs in fresh so its session is disposable. + test.use({ storageState: { cookies: [], origins: [] } }); test('logout clears the session and lands on /login', async ({ page }) => { - await page.goto('/admin/apps'); + await loginAsAdmin(page); await expect(page.getByRole('heading', { name: 'Apps', level: 1 })).toBeVisible(); await logout(page); const token = await page.evaluate(() => localStorage.getItem('picloud.admin.token')); diff --git a/dashboard/tests/e2e/members/members.spec.ts b/dashboard/tests/e2e/members/members.spec.ts index 5d244eb..8d20af5 100644 --- a/dashboard/tests/e2e/members/members.spec.ts +++ b/dashboard/tests/e2e/members/members.spec.ts @@ -72,7 +72,7 @@ test.describe('B5 app members', () => { test('invite a member-role user, then remove them', async ({ page, uniqueSlug, uniqueUsername }) => { const slug = uniqueSlug('mem'); const username = uniqueUsername('inv'); - const appId = await createApp(slug); + await createApp(slug); const userId = await createMemberUser(username); cleanup.app(slug); cleanup.adminUser(userId); @@ -106,7 +106,7 @@ test.describe('B5 app members', () => { }) => { const slug = uniqueSlug('mem'); const username = uniqueUsername('role'); - const appId = await createApp(slug); + await createApp(slug); const userId = await createMemberUser(username); cleanup.app(slug); cleanup.adminUser(userId); @@ -133,14 +133,13 @@ test.describe('B5 app members', () => { test('non-app-admin viewers do not see the Members tab', async ({ browser, - page: _adminPage, uniqueSlug, uniqueUsername }) => { const slug = uniqueSlug('mem'); const username = uniqueUsername('viewer'); const password = 'e2e-member-pw'; - const appId = await createApp(slug); + await createApp(slug); const userId = await createMemberUser(username); cleanup.app(slug); cleanup.adminUser(userId); @@ -183,7 +182,7 @@ test.describe('B5 app members adversarial', () => { }) => { const slug = uniqueSlug('mem'); const username = uniqueUsername('rolelist'); - const appId = await createApp(slug); + await createApp(slug); const userId = await createMemberUser(username); cleanup.app(slug); cleanup.adminUser(userId);