The lever the plan deliberately held back, applied now that there is evidence it is needed. Better failure messages and a wider assertion timeout were not enough. Measured on this branch, same commit, same machine. Two parallel runs each failed 3 of 155, and not the same three: password-reset and admin-inventory-filters in one, admin-email-settings, admin-inventory-filters and resend-verification in the next. All 16 tests from those three specs then passed when run serially, and the full suite passed 155 of 155 twice in a row. Failures that move between runs of identical code are contention, not defects — specs asserting over tables other specs are concurrently writing to. The cost is about three minutes, roughly one minute parallel against 3.9 and 4.0 serially. A red run that means something is worth three minutes; the previous state was a suite whose result nobody could act on, which is what #245 had to skip a real test to work around. If that time is ever needed back, the cheaper fix is giving each worker its own database rather than raising this number and reopening #241. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
3.1 KiB
TypeScript
Executable File
64 lines
3.1 KiB
TypeScript
Executable File
import { defineConfig, devices } from '@playwright/test';
|
|
|
|
export default defineConfig({
|
|
testDir: './tests/e2e',
|
|
// Kept true so a file's tests are independent units, but with one worker it
|
|
// no longer interleaves them. See `workers` below.
|
|
fullyParallel: true,
|
|
// One worker, because the suite shares a single database.
|
|
//
|
|
// This was the lever #241 deliberately held back, to see first whether better
|
|
// failure messages and a wider assertion timeout were enough. They were not.
|
|
// Measured on this branch, same commit, same machine: parallel runs failed 3
|
|
// of 155 twice over, and the three were not the same three — password-reset
|
|
// and admin-inventory-filters in one run, admin-email-settings,
|
|
// admin-inventory-filters and resend-verification in the next. Run serially,
|
|
// all 16 of those same tests passed; the full suite then passed 155 of 155
|
|
// twice in a row.
|
|
//
|
|
// So the failures were contention, not defects: specs asserting over tables
|
|
// that other specs were concurrently writing to. The cost is about 3 minutes
|
|
// — roughly 1 minute parallel against 3.9 and 4.0 serially — and a red run
|
|
// that means something is worth three minutes.
|
|
//
|
|
// The cheaper fix, if that time is ever needed back, is giving each worker
|
|
// its own database rather than raising this number and reopening #241.
|
|
workers: 1,
|
|
retries: process.env.CI ? 1 : 0,
|
|
reporter: [['list']],
|
|
use: {
|
|
baseURL: 'http://localhost:5173',
|
|
trace: 'on-first-retry',
|
|
// The app turns off antd's transitions under this preference. Animated
|
|
// popups never settle long enough for Playwright's stability check when
|
|
// the machine is loaded, which showed up as clicks timing out on a button
|
|
// that was plainly visible and enabled.
|
|
reducedMotion: 'reduce'
|
|
},
|
|
// Playwright's default is 5s, and nothing overrode it. That is generous on an
|
|
// idle laptop and tight on this runner: a full CI pass takes around 21.5
|
|
// minutes on a single machine that also builds, migrates and runs three other
|
|
// suites, and the #239 failure reported exactly `Timeout: 5000ms`.
|
|
//
|
|
// Costs nothing on a green run. This bounds how long a *failing* assertion
|
|
// waits before giving up, not how long a passing one takes — a locator that
|
|
// resolves in 200ms still resolves in 200ms.
|
|
expect: {
|
|
timeout: 10000
|
|
},
|
|
webServer: {
|
|
command: 'npm run dev',
|
|
url: 'http://localhost:5173',
|
|
// Forwarded so the dev server instruments its modules when the run is a
|
|
// coverage run. Without this the tests still pass and every page reports no
|
|
// coverage at all, which publishes a 0% that reads as "the tests stopped
|
|
// covering things" rather than "collection was never switched on".
|
|
env: { COVERAGE: process.env.COVERAGE ?? '' },
|
|
// Reusing a server that was started without COVERAGE would silently collect
|
|
// nothing, so a coverage run always starts its own.
|
|
reuseExistingServer: !process.env.CI && process.env.COVERAGE !== 'true',
|
|
timeout: 30000
|
|
},
|
|
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }]
|
|
});
|