import { defineConfig, devices } from "@playwright/test"; /** * AI Software Factory — Playwright configuration. * * IMPORTANT: there is intentionally NO `webServer` block. The full stack * (web :3000, api :4000, worker, realtime :4001 + postgres/redis/minio/ * opensearch) is started EXTERNALLY before this suite runs: * * local dev: pnpm docker:up && pnpm db:deploy && pnpm dev * CI: see .github/workflows/ci.yml (e2e job: services + turbo dev) * * The suite fails fast (with a clear message) in global-setup if the API * is not reachable. */ export default defineConfig({ testDir: "./specs", // Global setup registers/logs in the demo user and writes .auth/user.json. globalSetup: require.resolve("./global-setup"), // One browser context per test (isolation); authenticated state comes from // fixtures (tests/e2e/fixtures.ts), not from here. fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, // Cap at 2 in ALL environments (decision TASK-0128, C-13.5): a single // spec file with `fullyParallel` and the default (≈ CPU) worker count // launches 4+ browser contexts (projects × tests) against the shared, // unbuilt dev stack at once. Note: the owner-test flake that first // pointed at this cap was actually the ready-wait reload loop // livelocking the ~3s auth bootstrap (see C-13.5 in // docs/tasks/phase-13-api.md for the full corrected diagnosis); the // cap is retained as a reasonable bound on concurrent contexts for the // dev stack (CI parity) — it is not the root cause of that flake. workers: 2, reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : [["list"], ["html", { open: "on-failure" }]], timeout: 60_000, expect: { timeout: 10_000 }, use: { // The web app origin. API calls in specs go through fixtures (which // target the API URL from env, default http://localhost:4000). baseURL: process.env.E2E_BASE_URL ?? "http://localhost:3000", actionTimeout: 15_000, navigationTimeout: 30_000, trace: "on-first-retry", screenshot: "only-on-failure", video: "retain-on-failure", }, projects: [ { name: "chromium-desktop", use: { ...devices["Desktop Chrome"], viewport: { width: 1440, height: 900 }, }, }, { name: "chromium-mobile", use: { // MOBILE-AS-CHROMIUM — decision 2024-TASK-0128 (recorded in // docs/tasks/phase-13-api.md): // // We test the MOBILE LAYOUT (375×812 viewport, dpr 3 — the exact // iPhone 13 geometry, so the app's < 640 px single-column rules // and the `sm:`/`md:` Tailwind breakpoints fire at the right // widths), but with a PLAIN desktop chromium context: // // viewport: { width: 375, height: 812 }, deviceScaleFactor: 3 // // We deliberately do NOT spread devices["iPhone 13"] because its // `isMobile: true` makes Chromium process the page's // `width=device-width` viewport meta with a separate visual // viewport. Playwright 1.49's CDP actionability hit-test then // intermittently desyncs on `isMobile` pages (a button's center // resolves onto the backdrop/footer while the page's own // `elementFromPoint` returns the button; `innerWidth` even reads // ~670 on a 375px page in some runs), which hangs every // locator.click()/focus() on mobile-only runs. Without isMobile // the layout viewport IS the 375px viewport, the hit-test is // stable, and the rendered layout is pixel-identical — the // viewport meta matters for the app's CSS, and the app renders // the same at 375px either way. What we lose: the iPhone user // agent and hasTouch (this app does no UA sniffing and no // touch-only interactions; the DoD row under test is "Mobile // behavior" = the < 640 px layout, which is fully covered). // // (Historically this project spread devices["iPhone 13"] pinned // to chromium because the profile's defaultBrowserType is // webkit, whose system deps are not installed on this host or in // CI — plain chromium needs none.) browserName: "chromium", viewport: { width: 375, height: 812 }, deviceScaleFactor: 3, }, // Mobile is a coverage check, not the primary surface; keep the same // spec set but allow a slightly higher flake budget. retries: process.env.CI ? 3 : 0, }, ], outputDir: "test-results", });