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);