Ctrl K
ComunidadGuías…

playwright.config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import fs from 'node:fs'
import path from 'node:path'
import { defineConfig, devices } from '@playwright/test'
import { perRunWorkers } from './tests/e2e/browser/pw-concurrency.js'
import { engageGate } from './tests/e2e/browser/pw-gate.js'
// Every test browser is silent: an init script in every frame, and Chromium's own mute.
import { CHROMIUM_SILENT } from './tests/e2e/browser/pw-silence.js'
// Linux WebKit paints on the CPU: its GL painting threads crash the page on software GL.
import './tests/e2e/browser/pw-webkit-paint.js'
import { sideConfigSpecs } from './tests/e2e/browser/side-config-specs.js'
import { bunCommand } from './tests/helpers/bun-runtime.js'

const testPort = Number(process.env.PW_TEST_PORT ?? 3457)

// Machine-wide admission control. MUST run here, at config load: Playwright boots
// the webServer plugin before globalSetup, so this is the only hook early enough
// to queue behind a concurrent run and to reap an orphaned fixture server before
// `reuseExistingServer` silently attaches to it. See tests/e2e/browser/pw-gate.ts.
engageGate(testPort)

// Specs that swap the fixture server's one host fixture (POST /api/test/host-fixture)
// race each other when two workers run them at once: one loads its hosts while the
// other's test reads them (2026-10-29: banner-placement-round4 N3-2 failed 2 of 3 runs
// beside system-attention :267). They run in their own project, one at a time, while
// every other spec stays parallel. Found by what they call, so a new one joins itself.
const HOST_FIXTURE_CALL = /\b(?:loadFixture|hostFixture|resetServerHostFixture)\(/
const browserDir = path.join(import.meta.dirname, 'tests/e2e/browser')
const hostFixtureSpecs = fs.readdirSync(browserDir)
  .filter((f) => f.endsWith('.spec.ts') && HOST_FIXTURE_CALL.test(fs.readFileSync(path.join(browserDir, f), 'utf8')))
  .map((f) => `**/${f}`)
// Specs with their own config and fixture (replica, cloud host, host alone, ...).
// Every project names them again: a project's testIgnore replaces the top-level one.
const sideSpecs = sideConfigSpecs(browserDir)

export default defineConfig({
  testDir: 'tests/e2e/browser',
  // Their own fixture and config (tests/e2e/browser/side-config-specs.ts); the main
  // config runs replica-smoke.spec.ts for the replica path instead.
  testIgnore: sideSpecs,
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  // `undefined` here used to mean "half the cores" (7 on this Mac) — 28 chromium
  // processes and ~2.7 GB per run, with nothing coordinating concurrent runs.
  // Overrides: PW_WORKERS (per run), PW_NO_GATE=1 (skip the port lease).
  workers: perRunWorkers(),
  reporter: 'list',
  // A CI runner has 4 cores and runs 4 workers: one cold load of the dev mode fixture
  // (about 1,700 module requests) took 12 to 14 s there, so a test that loads the app
  // twice ran out of 30 s with nothing wrong (2026-11-01: about 9 of 21 failures of one
  // shard). Assertions keep their own timeouts; this is only the test's ceiling.
  timeout: process.env.CI ? 60_000 : 30_000,

  use: {
    baseURL: `http://localhost:${testPort}`,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    // PW_VIDEO=1 records every test (demo/verification clips); pair with
    // `--output <dir>` outside test-results/, which peer runs wipe.
    video: process.env.PW_VIDEO ? 'on' : 'off',
    // The task panel opens Flat (web/src/components/tasks/panel-design.ts), its status
    // sections and no list. The specs written against the list remember By project, in
    // every page's storage, so a spec that serves its own ui-prefs keeps it too; a spec
    // about Flat presets it (status-section-helpers.ts presetPanelDesign).
    storageState: {
      cookies: [],
      origins: [{ origin: `http://localhost:${testPort}`, localStorage: [{ name: 'walnut-todo-panel-design', value: 'project' }] }],
    },
  },

  projects: [
    // Chromium mutes itself headless; `--mute-audio` keeps a headed run (--headed, a
    // debug session) silent too. WebKit has no such switch: pw-silence.ts silences
    // every frame of every context (2026-11-08).
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], launchOptions: { args: CHROMIUM_SILENT } },
      testIgnore: [...sideSpecs, ...hostFixtureSpecs],
    },
    {
      name: 'chromium-host-fixture',
      use: { ...devices['Desktop Chrome'], launchOptions: { args: CHROMIUM_SILENT } },
      testMatch: hostFixtureSpecs,
      testIgnore: sideSpecs,
      workers: 1,
    },
    // The desktop app is a WKWebView, so a desktop-only scroll/paint bug needs a
    // WebKit run to count as verified. Opt in: PW_WEBKIT=1 … --project webkit
    // The replica smoke is one chromium spec (its WebKit runs are replica.config.ts's).
    // The host fixture specs run one at a time there too, as on chromium-host-fixture
    // (2026-11-06: with 2 workers, BP-C58's fixture hosts reached BP-C49's page and
    // BP-C68's fixture was swapped under it, on every commit). Both projects are named
    // `webkit`, so `--project webkit` keeps selecting every WebKit spec (Playwright
    // matches projects by name and gives each its own id and worker limit).
    ...(process.env.PW_WEBKIT ? [
      { name: 'webkit', use: { ...devices['Desktop Safari'] }, testIgnore: [...sideSpecs, ...hostFixtureSpecs, '**/replica-smoke.spec.ts'] },
      { name: 'webkit', use: { ...devices['Desktop Safari'] }, testMatch: hostFixtureSpecs, testIgnore: sideSpecs, workers: 1 },
    ] : []),
  ],

  webServer: {
    // The fixture server runs on bun (tests/helpers/bun-runtime.ts), which runs
    // its TypeScript as it is; only this runner stays on Node. Never `npx`: its
    // registry resolution alone once took 88s here, past the whole boot budget.
    command: `${bunCommand()} tests/e2e/browser/test-server.ts`,
    url: `http://localhost:${testPort}/api/dashboard`,
    reuseExistingServer: !process.env.CI,
    // Cold boot of the fixture (real server + vite + isolated daemon) measured at
    // ~20s on an idle Mac; under load it blew past the old 30s ceiling and the run
    // died with "Timed out waiting 30000ms from config.webServer" — which reads
    // like an app bug but is just a starved machine. 120s absorbs that.
    timeout: 120_000,
    stdout: 'pipe',
    stderr: 'pipe',
    // Without this Playwright ends EVERY run with SIGKILL on the fixture's process
    // group (playwright-core processLauncher), so test-server.ts's SIGTERM handler
    // — the only thing that stops its isolated daemon and removes its tmpdir —
    // never ran, and each run left a `walnut-pw-<ts>` dir behind (401 of them on
    // 2026-09-13). SIGTERM first; SIGKILL still follows if the handler hangs.
    gracefulShutdown: { signal: 'SIGTERM', timeout: 15_000 },
  },
})