Repository navigation
Expand file tree
/
Copy pathplaywright.config.ts
More file actions
167 lines (161 loc) · 7.78 KB
/
Copy pathplaywright.config.ts
File metadata and controls
167 lines (161 loc) · 7.78 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
159
160
161
162
163
164
165
166
167
import { defineConfig, devices } from "@playwright/test";
import { DESKTOP_SETTINGS_STORAGE_KEY } from "./apps/geolibre-desktop/src/lib/storage-keys";
// `E2E_PORT` moves the preview server off the default, so a second checkout (or
// a parallel run) can serve its own build without reusing another one's.
const PORT = Number(process.env.E2E_PORT) || 4173;
const BASE_URL = `http://localhost:${PORT}`;
/**
* The `core` suite: the app boots and renders a map, and its shared UI surfaces
* still work. This is the only suite that gates a pull request, so it is kept
* to the checks that would break *every* user — not per-feature integration.
*
* Everything else lands in the `features` project, which runs nightly (see
* `.github/workflows/e2e-full.yml`). Feature specs are not less valuable — most
* were written for a specific shipped regression — they are just too slow to
* charge to every commit: the full suite is ~36 min serial, of which these are
* ~27 min. Moving a spec between the two lists is the intended way to trade
* per-commit cost against how fast a regression is caught.
*/
const CORE_SPECS = [
"smoke.spec.ts", // the app loads, a layer renders, the attribute table opens
"theme.spec.ts",
"a11y.spec.ts",
// Add, restyle and export a layer with the keyboard alone: one ~6 s test,
// so a control dropped from the tab order fails its own PR (#2858). The
// wider per-dialog axe sweep, a11y-screens.spec.ts, stays nightly (~70 s).
"keyboard-only.spec.ts",
"layer-panel.spec.ts",
"drop-overlay.spec.ts",
"error-handling.spec.ts",
"attribute-status.spec.ts",
"rtl.spec.ts",
"pwa.spec.ts",
"deployment-policy.spec.ts",
// Project -> Save -> reopen. Moved from `features` after an unexpected save
// prompt broke every saving spec and only the nightly run noticed (#2858).
"layer-groups.spec.ts",
// Holds the Plugins docs to the app's deep-link names, so a new built-in
// plugin fails its own PR instead of the next night's run (#2858). Its
// registry install flows are in plugin-deep-link-registry.spec.ts, nightly.
"plugin-deep-link.spec.ts",
// Moved to `features` once core reached ~11 min wall-clock (~25 min of test
// time): set-view, paste-style, style-manager and identify-restore. Each
// covers one dialog or interaction, not something every user hits, and
// together they were ~40% of core's test time and most of its retries.
];
const coreMatch = CORE_SPECS.map((spec) => `**/${spec}`);
/**
* The per-commit engine smoke pass (`core-engines`): switch to Cesium, then to
* ArcGIS, add GeoJSON, identify a feature. The deep engine specs stay nightly;
* this catches an engine that no longer mounts or draws project layers at all.
*
* It is its own project because a software-rendered 3D view saturates the CPU
* on its own. Run beside the `core` workers on a 4-vCPU runner it starved them
* and itself — clicks and screenshots timing out on both sides — so it runs
* alone, one worker, after `core` finishes (#2858). ArcGIS loads its SDK from
* js.arcgis.com, the one network dependency in the per-commit gate.
*/
const coreEnginesMatch = ["**/engine-smoke.spec.ts"];
// MapLibre needs a WebGL context; force software ANGLE/SwiftShader so the map
// initializes on headless CI runners without a real GPU.
const chromium = {
...devices["Desktop Chrome"],
launchOptions: { args: ["--use-gl=angle", "--use-angle=swiftshader"] },
};
// End-to-end smoke tests run against the *built* web app served by `vite
// preview` (matching production output), not the dev server. The webServer
// command builds first so the suite is self-contained; locally an
// already-running preview is reused instead of rebuilding.
export default defineConfig({
testDir: "./e2e",
// Measured on a 4-vCPU runner (the GitHub default) with the software
// renderer, over a 24-test subset: 1 worker 259 s, 3 workers 192 s, 6 workers
// 165 s, 10 workers 164 s. The speedup is sublinear because SwiftShader is
// CPU-bound, and at 10 workers contention started failing tests that pass
// serially. 3 is the conservative point on that curve.
//
// Local runs stay serial: dev machines have far more cores, so Playwright's
// default (half of them) would oversubscribe well past where those flakes
// began. Pass `--workers=N` to override either way.
workers: process.env.CI ? 3 : 1,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
timeout: 60_000,
expect: { timeout: 15_000 },
reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : [["list"]],
use: {
baseURL: BASE_URL,
// Seed the first-launch UI-profile onboarding (issue #500) as already
// completed. Otherwise its modal wizard opens on every fresh context and its
// overlay intercepts pointer events, timing out any spec that clicks through
// the UI. The partial blob is merged with defaults by the settings loader.
storageState: {
cookies: [],
origins: [
{
origin: BASE_URL,
localStorage: [
{
name: DESKTOP_SETTINGS_STORAGE_KEY,
value: JSON.stringify({ uiProfile: { onboarded: true } }),
},
],
},
],
},
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "off",
},
// The projects partition `e2e/` — nothing is listed twice, so a plain
// `playwright test` still runs every spec exactly once. CI narrows to the
// per-commit gate with `--project=core --project=core-engines`.
projects: [
{ name: "core", testMatch: coreMatch, use: chromium },
// Depends on `core` so it starts only once those workers are done, and
// caps itself at one worker. `--no-deps` runs it on its own locally.
{
name: "core-engines",
testMatch: coreEnginesMatch,
dependencies: ["core"],
workers: 1,
use: chromium,
},
// `e2e/enterprise-sso/` needs a live API and Keycloak, and `e2e/preview/`
// a deployed site; each has its own config and job.
{
name: "features",
testIgnore: [...coreMatch, ...coreEnginesMatch, "**/enterprise-sso/**", "**/preview/**"],
use: chromium,
},
],
webServer: {
command: `npm run build && npm run preview -w geolibre-desktop -- --port ${PORT} --strictPort`,
// Build without a Cesium Ion token from the shell. `vite.config.ts` bakes
// `CESIUM_TOKEN`/`VITE_CESIUM_TOKEN` into the bundle, so a machine with one
// exported would produce a *different* app than CI builds: the 3D globe
// would come up with Ion terrain and imagery, and `cesium-globe.spec.ts`
// could no longer tell the keyless path from the tokened one.
//
// This covers the shell only. The bridge in `vite.config.ts` falls through
// to `loadEnv()` when the prefixed name is falsy, so a token in
// `apps/geolibre-desktop/.env.local` is still picked up despite these being
// blanked. That case is not silently tolerated — `cesium-globe.spec.ts`
// asserts the tokenless hint and fails with a message naming the file.
env: { CESIUM_TOKEN: "", VITE_CESIUM_TOKEN: "" },
url: BASE_URL,
// Reuse locally (the repo's existing DX trade-off), never in CI. Note the
// interaction with `env` above: Playwright skips the whole command when it
// reuses a server, so the override is *not* applied to a reused one. That
// is why `cesium-globe.spec.ts` asserts the tokenless hint rather than
// trusting this — a reused tokened build fails there with an explanation
// instead of silently exercising the wrong path. In CI, where determinism
// actually matters, this is false and the override always applies.
reuseExistingServer: !process.env.CI,
// The build (tsc -b + vite build) runs as part of this command, so allow
// generous startup time on cold CI runners.
timeout: 300_000,
stdout: "pipe",
stderr: "pipe",
},
});