Repository navigation
Expand file tree
/
Copy pathplaywright.config.ts
More file actions
158 lines (143 loc) · 6.87 KB
/
Copy pathplaywright.config.ts
File metadata and controls
158 lines (143 loc) · 6.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { defineConfig, devices, ViewportSize } from '@playwright/test';
import { e2eChromiumArgs } from './packages/e2e/utils/launch-options';
const isCI = !!process.env.CI;
const viewport: ViewportSize = {
width: 1200,
height: 720
};
// The literal address, not `localhost`, and tools/serve-static.mjs binds the same one: resolving the
// name leaves it to chance whether the two ends pick ::1 or 127.0.0.1, and a disagreement surfaces
// here as `webServer` timing out against a server that is running fine.
const baseURL = process.env.BASE_URL || 'http://127.0.0.1:4200';
// Builds dev-e2e in its production configuration and serves dist/e2e as static files. Not `ng serve`:
// Vite appends an inline source map to every JavaScript response, which was most of what a test
// downloaded — see docs/e2e-performance.md.
const webServerCommand = process.env.WEB_SERVER_COMMAND || 'node tools/e2e/serve.mjs';
/**
* Every worker drives its own browser, and each browser is several processes, so the useful ceiling
* is CPU: measured in the Docker image on 64 threads, the suite stops getting faster at 32 workers
* and only accumulates latency and contention flakes beyond that (docs/e2e-performance.md). '100%'
* suits a 4-vCPU CI runner; tools/e2e's compose file defaults to '50%' instead, because a container
* reports every core on the host (Playwright reads `os.cpus()`, which no cgroup or cpuset limit
* affects), and CI sets it back to '100%'.
*
* Playwright only accepts a string when it is a percentage, so anything else has to become a number.
* With the variable unset this behaves exactly as it did before.
*
* The value is validated rather than passed through, because Playwright's own guard only rejects
* `workers <= 0` — and `NaN <= 0` is false. A typo like `PLAYWRIGHT_WORKERS=amx` would therefore
* reach the dispatcher's `for (i = 0; i < workers; i++)` loop, spawn zero workers, run zero tests,
* write no report, and still exit 0: a green suite that tested nothing.
*/
const resolveWorkers = () => {
const override = process.env.PLAYWRIGHT_WORKERS?.trim();
if (!override) {
return isCI ? '100%' : undefined;
}
if (override.endsWith('%')) {
const percentage = Number(override.slice(0, -1));
if (!Number.isFinite(percentage) || percentage <= 0) {
throw new Error(`PLAYWRIGHT_WORKERS must be a positive percentage, got ${JSON.stringify(override)}.`);
}
return override;
}
const workers = Number(override);
if (!Number.isInteger(workers) || workers <= 0) {
throw new Error(
`PLAYWRIGHT_WORKERS must be a positive integer or a percentage, got ${JSON.stringify(override)}.`
);
}
return workers;
};
/**
* Retries hide flakiness rather than remove it: a test that fails and then passes is reported as
* flaky and does not fail the run, so a suite can be reliably green and still be unreliable. The
* default is therefore 0 everywhere, CI included — an unstable test fails the run and is named
* rather than absorbed. PLAYWRIGHT_RETRIES is the escape hatch for a run that has to be nursed
* through a known flake.
*
* Validated for the same reason as resolveWorkers above: Playwright's own guard rejects a negative
* number but not NaN, so `PLAYWRIGHT_RETRIES=none` would reach the runner and be treated as no
* retries at all — the right answer by accident, and the wrong one as soon as the value was meant
* to be 2.
*/
const resolveRetries = () => {
const override = process.env.PLAYWRIGHT_RETRIES?.trim();
if (!override) {
return 0;
}
const retries = Number(override);
if (!Number.isInteger(retries) || retries < 0) {
throw new Error(`PLAYWRIGHT_RETRIES must be a non-negative integer, got ${JSON.stringify(override)}.`);
}
return retries;
};
/** @see https://playwright.dev/docs/test-configuration */
export default defineConfig({
testDir: __dirname,
testMatch: ['**/*.playwright-spec.ts'],
tsconfig: 'tsconfig.playwright-spec.json',
timeout: 15 * 1000,
fullyParallel: true,
forbidOnly: isCI,
retries: resolveRetries(),
workers: resolveWorkers(),
reporter: [
['list', { printSteps: true }],
['html', { open: 'never' }]
],
projects: [
{
name: 'Chrome',
use: {
...devices['Desktop Chrome HiDPI'],
viewport,
launchOptions: { args: [...e2eChromiumArgs] }
}
}
],
expect: {
// These baselines are compared with threshold: 0, so they are tied to one exact browser
// build. @playwright/test is pinned to an exact version in package.json for that reason:
// even a patch release can bump the bundled Chromium — 1.55.0 shipped build 1187 and
// 1.55.1 shipped 1193 — and that invalidates every screenshot. Upgrade it on its own
// branch and refresh the baselines with /approve-snapshots in the same pull request.
toHaveScreenshot: {
pathTemplate: '{testFileDir}/__screenshots__/{arg}{ext}',
threshold: 0,
scale: 'device',
animations: 'disabled'
}
},
webServer: {
command: webServerCommand,
url: baseURL,
timeout: 10 * 60 * 1000,
// The server builds once and then serves files, where `ng serve` watched, so a server left
// running from an earlier session is reused exactly as it is. Restart it after changing
// anything it serves; CI starts its own.
reuseExistingServer: !isCI
},
use: {
baseURL: baseURL,
// `on-first-retry` was the cheap choice while retries existed: the trace came free on the
// retry a failing test was going to get anyway. With retries at 0 that never fires, and a
// failure would leave only the screenshot diff — the wrong artifact for the flakes here,
// since a wait that resolved a frame early tells you nothing in a still image.
//
// `screenshots: false` drops the trace's screencast, which is the expensive half twice
// over. It takes CPU from every test, including the ones that pass and discard the trace,
// and the flakes this suite has are the ones that only appear under load (see
// docs/e2e-flakiness.md), so the recorder would manufacture the failures it exists to
// explain. It also dominates trace size, which decides how large the uploaded report gets
// on the run where a Chromium bump invalidates every baseline at once. What is kept — DOM
// snapshots, network, the action log — is what explains a screenshot flake; the pixels are
// already attached as -actual.png, -expected.png and -diff.png.
trace: { mode: 'retain-on-failure', screenshots: false },
contextOptions: {
deviceScaleFactor: 2,
reducedMotion: 'reduce',
viewport
}
}
});