From e02631f94cac4c10350df86dbdc48d9b37a43ac8 Mon Sep 17 00:00:00 2001 From: Matt Borgeson Date: Thu, 30 Apr 2026 02:50:54 -0700 Subject: [PATCH] fix(e2e): add smoke fail-fast project to abort on broken-infra runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a one-shot smoke project that runs before every other Playwright project. It validates the most fundamental contract — dev server up, React app served, router redirects unauthenticated users to /login — in a single navigation. When it fails, Playwright skips downstream projects via the new dependencies wiring, so the suite exits in <2 min instead of running ~40 min of redundant red across 400+ tests. The smoke test is intentionally standalone: no auth, no storageState, no backend dependency beyond the login page being reachable. This lets it compose cleanly with the auth setup project that PR #12 introduces; chromium and ci can list both as dependencies in any order without coupling smoke to credential availability. - e2e/smoke.spec.ts: single test, asserts /login redirect and email field visible (proves React rendered, not a hydration crash) - playwright.config.ts: new "smoke" project (testMatch on smoke spec, retries: 0). chromium and ci gain dependencies: ['smoke'] and testIgnore on smoke.spec.ts so the smoke test only runs once. Verified: tsc --noEmit clean; playwright test --list shows 1 smoke test under the smoke project and 404 tests under chromium (smoke excluded as expected). Co-Authored-By: Claude Opus 4.7 (1M context) --- e2e/smoke.spec.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ playwright.config.ts | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 e2e/smoke.spec.ts diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts new file mode 100644 index 0000000..0753c25 --- /dev/null +++ b/e2e/smoke.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from '@playwright/test'; + +/** + * Smoke fail-fast test + * + * One-shot infrastructure check that runs BEFORE every other Playwright project + * via the `smoke` project + `dependencies: ['smoke']` wiring in playwright.config.ts. + * + * Purpose: + * Detect the catastrophic, "nothing will pass" failures (dev server down, + * broken build, routing wedged) and abort the suite in <2 minutes instead of + * running ~40 minutes of redundant red across hundreds of dependent tests. + * + * Scope (intentionally narrow): + * - Standalone — no auth, no storageState, no backend dependency beyond the + * login page being reachable. Composes cleanly with the auth setup project + * (PR #12); both can run as dependencies of `chromium`/`ci` in any order. + * - Tests only the most fundamental contract: dev server serves the React + * app and the auth-gated router redirects unauthenticated users to /login. + * + * Failure semantics: + * - retries: 0 (configured at the project level) — flakes are not tolerated + * here; if smoke is flaky, fix smoke. A pass means downstream tests have a + * chance; a fail means they have no chance, so Playwright skips them. + */ +test.describe('Smoke', () => { + test('dev server serves the app and routing redirects to /login', async ({ page }) => { + // Hitting the root should bounce an unauthenticated user to /login. + // This single navigation proves three things at once: + // 1. The dev server is reachable on baseURL (http://localhost:5173). + // 2. Vite served the React bundle (HTML + JS). + // 3. The router booted and the auth gate redirect ran. + await page.goto('/'); + + await expect(page).toHaveURL(/\/login$/, { timeout: 10_000 }); + + // And the login page actually rendered — not a blank white screen from a + // hydration crash. getByLabel matches the + // pair in src/features/auth/LoginPage.tsx. + await expect(page.getByLabel(/email/i)).toBeVisible({ timeout: 5_000 }); + }); +}); diff --git a/playwright.config.ts b/playwright.config.ts index b9eca63..a2ea9a0 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -7,12 +7,21 @@ const isCI = !!process.env.CI; * B&R Capital Real Estate Analytics Dashboard * * Projects: - * - "chromium" (default): Local development — all browsers, no retries, HTML reporter - * - "ci": CI pipeline — chromium only, 1 retry, 30s timeout, JSON + HTML reporters + * - "smoke": One-shot infrastructure check — dev server up, app renders, + * routing works. Runs FIRST. If it fails, downstream projects are skipped + * and the suite exits in <2 min instead of ~40 min of redundant red. + * - "setup": Authenticates a browser context and saves storage state to + * e2e/.auth/admin.json so subsequent UI tests don't bounce to /login. + * Runs after smoke so a broken dev server fails before we waste time + * trying to log in. + * - "chromium" (default): Local development — depends on smoke + setup. + * - "ci": CI pipeline — chromium only, depends on smoke + setup, 1 retry, + * 30s timeout, JSON + HTML reporters. * * Usage: * Local: npx playwright test * CI: npx playwright test --project=ci + * Smoke: npx playwright test --project=smoke */ export default defineConfig({ testDir: './e2e', @@ -34,11 +43,24 @@ export default defineConfig({ }, projects: [ - /* Setup project — runs once before browser tests to authenticate and - * persist storage state to e2e/.auth/admin.json. */ + /* Smoke fail-fast project — runs FIRST. If dev server / build / routing + * is broken, downstream projects are skipped and the suite exits fast. + * Standalone: no auth, no storageState, no backend dependency beyond + * the login page being reachable. */ + { + name: 'smoke', + testMatch: /smoke\.spec\.ts/, + retries: 0, + use: { ...devices['Desktop Chrome'] }, + }, + + /* Setup project — runs after smoke. Authenticates a browser context + * and persists storage state to e2e/.auth/admin.json so subsequent UI + * tests don't bounce to /login. */ { name: 'setup', testMatch: /global\.setup\.ts/, + dependencies: ['smoke'], }, /* Local development project — runs by default when no --project is specified */ @@ -48,7 +70,8 @@ export default defineConfig({ ...devices['Desktop Chrome'], storageState: 'e2e/.auth/admin.json', }, - dependencies: ['setup'], + testIgnore: /(smoke|global\.setup)\.spec\.ts/, + dependencies: ['smoke', 'setup'], }, /* CI project — single browser, stricter timeouts, retries on failure */ @@ -60,7 +83,8 @@ export default defineConfig({ /* CI-specific: capture video on first retry for debugging */ video: 'on-first-retry', }, - dependencies: ['setup'], + testIgnore: /(smoke|global\.setup)\.spec\.ts/, + dependencies: ['smoke', 'setup'], retries: 1, timeout: 30_000, },