import { defineConfig, devices } from '@playwright/test' /** * End-to-end, against the throwaway stack in docker-compose.test.yml. * * Four projects, because the bugs this suite exists to catch have all been * layout bugs on a phone: a drawer that opened behind the header, a diagram * that rendered as an empty screen in Safari, a popover two hundred pixels off * the left edge. None of those are visible at 1280×720, so a desktop-only * suite would have passed through every one of them. * * The API project runs the same journeys without a browser, where what is * being checked is the contract rather than the page. */ const BASE = process.env.E2E_BASE_URL || 'http://127.0.0.1:8095' export default defineConfig({ testDir: './tests', // A failing E2E test is usually a real failure, but a hung one is usually // the stack. Fail fast enough to say which. timeout: 60_000, // Generous, because this stack shares a machine with whatever else is on it // and a slow answer is not a wrong one. A genuinely missing element still // fails, fifteen seconds later. expect: { timeout: 15_000 }, fullyParallel: true, // Retries in CI only: locally a flake should be seen, not smoothed over. retries: process.env.CI ? 2 : 0, // Three, not "as many as there are cores": the stack under test is two // containers on the same box, and past three browsers they queue. workers: process.env.CI ? 2 : 3, reporter: process.env.CI ? [['list'], ['html', { open: 'never' }], ['junit', { outputFile: 'results/junit.xml' }]] : [['list'], ['html', { open: 'never' }]], use: { baseURL: BASE, // Kept only for the run that failed: a trace per test is gigabytes and // nobody opens the passing ones. trace: 'retain-on-failure', screenshot: 'only-on-failure', video: process.env.CI ? 'retain-on-failure' : 'off', actionTimeout: 15_000, }, // The API specs belong to the `api` project alone; without this every // browser project runs them too, which is four identical HTTP suites and // four times the sign-ins. testIgnore: /.*\.api\.spec\.js/, projects: [ { name: 'desktop', use: { ...devices['Desktop Chrome'], viewport: { width: 1440, height: 900 } } }, // Safari on a phone, because that is where an SVG with no intrinsic size // resolves to nothing and a 16px input rule stops the page zooming. { name: 'iphone', use: { ...devices['iPhone 13'] } }, { name: 'android', use: { ...devices['Pixel 7'] } }, // Between the two: the width where the reading rail becomes a drawer. { name: 'tablet', use: { ...devices['iPad Mini landscape'] } }, { name: 'api', testMatch: /.*\.api\.spec\.js/, testIgnore: [], use: { baseURL: BASE } }, ], })