@ploaness/config 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y.d.ts +13 -5
- package/dist/a11y.d.ts.map +1 -1
- package/dist/a11y.js +20 -11
- package/dist/a11y.js.map +1 -1
- package/dist/clock-preload.d.ts +2 -0
- package/dist/clock-preload.d.ts.map +1 -0
- package/dist/clock-preload.js +37 -0
- package/dist/clock-preload.js.map +1 -0
- package/dist/eslint-library.d.ts.map +1 -1
- package/dist/eslint-library.js +6 -1
- package/dist/eslint-library.js.map +1 -1
- package/dist/eslint-runtime.d.ts +11 -0
- package/dist/eslint-runtime.d.ts.map +1 -0
- package/dist/eslint-runtime.js +68 -0
- package/dist/eslint-runtime.js.map +1 -0
- package/dist/eslint.d.ts.map +1 -1
- package/dist/eslint.js +6 -1
- package/dist/eslint.js.map +1 -1
- package/dist/layout.d.ts +34 -16
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +228 -56
- package/dist/layout.js.map +1 -1
- package/dist/payload-view.d.ts +16 -0
- package/dist/payload-view.d.ts.map +1 -0
- package/dist/payload-view.js +35 -0
- package/dist/payload-view.js.map +1 -0
- package/dist/playwright.d.ts.map +1 -1
- package/dist/playwright.js +115 -12
- package/dist/playwright.js.map +1 -1
- package/dist/scanner.d.ts +99 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +278 -0
- package/dist/scanner.js.map +1 -0
- package/dist/settle.d.ts +8 -6
- package/dist/settle.d.ts.map +1 -1
- package/dist/settle.js +55 -42
- package/dist/settle.js.map +1 -1
- package/dist/sweep-disclosures.d.ts +10 -0
- package/dist/sweep-disclosures.d.ts.map +1 -0
- package/dist/sweep-disclosures.js +179 -0
- package/dist/sweep-disclosures.js.map +1 -0
- package/dist/sweep-discovery.d.ts +15 -0
- package/dist/sweep-discovery.d.ts.map +1 -0
- package/dist/sweep-discovery.js +336 -0
- package/dist/sweep-discovery.js.map +1 -0
- package/dist/sweep-interaction.d.ts +19 -0
- package/dist/sweep-interaction.d.ts.map +1 -0
- package/dist/sweep-interaction.js +351 -0
- package/dist/sweep-interaction.js.map +1 -0
- package/dist/sweep-queue.d.ts +24 -0
- package/dist/sweep-queue.d.ts.map +1 -0
- package/dist/sweep-queue.js +56 -0
- package/dist/sweep-queue.js.map +1 -0
- package/dist/sweep-reflow.d.ts +17 -0
- package/dist/sweep-reflow.d.ts.map +1 -0
- package/dist/sweep-reflow.js +210 -0
- package/dist/sweep-reflow.js.map +1 -0
- package/dist/sweep-resting.d.ts +25 -0
- package/dist/sweep-resting.d.ts.map +1 -0
- package/dist/sweep-resting.js +98 -0
- package/dist/sweep-resting.js.map +1 -0
- package/dist/sweep-shared.d.ts +353 -0
- package/dist/sweep-shared.d.ts.map +1 -0
- package/dist/sweep-shared.js +434 -0
- package/dist/sweep-shared.js.map +1 -0
- package/dist/sweep.d.ts +14 -0
- package/dist/sweep.d.ts.map +1 -0
- package/dist/sweep.js +157 -0
- package/dist/sweep.js.map +1 -0
- package/dist/vitest-core.d.ts +1 -1
- package/dist/vitest-core.js +1 -1
- package/dist/vitest-library.d.ts.map +1 -1
- package/dist/vitest-library.js +2 -1
- package/dist/vitest-library.js.map +1 -1
- package/package.json +10 -2
package/dist/playwright.js
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Everything here is policy rather than preference. `forbidOnly` is the end-to-end analogue of the
|
|
5
5
|
// focused-test ban ploaness already enforces for Vitest: a left-behind `test.only` would otherwise turn
|
|
6
|
-
// a local
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// a local browser-level run into one green test. The project's own specs run on one worker, because they
|
|
7
|
+
// may share database state and one server; the managed sweep's tests each load their pages fresh and
|
|
8
|
+
// judge each one alone, so they spread over parallel workers without any verdict depending on it. The
|
|
9
|
+
// project-specific inputs are the origin and any server its own specs need started beside the
|
|
9
10
|
// application, both declared under the `ploaness` key of its package.json rather than by editing this
|
|
10
11
|
// file. Neither reaches a rule: they say where the application answers and what else has to be running,
|
|
11
12
|
// and every threshold, ban and pinned spec above is the same whatever they say.
|
|
12
13
|
import { existsSync } from 'node:fs';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
14
15
|
import { defineConfig, devices } from '@playwright/test';
|
|
15
|
-
import { browserServerEndpoint, canReuseBrowserServer, portOf, runEnvironmentFiles, shellArgument, VERIFICATION_ENVIRONMENT_VARIABLE, } from '@ploaness/governance';
|
|
16
|
+
import { BROWSER_ENDPOINT_ENVIRONMENT_VARIABLE, BROWSER_TUNNEL_ENVIRONMENT_VARIABLE, browserServerEndpoint, CLOCK_ENVIRONMENT_VARIABLE, canReuseBrowserServer, coversEveryEngine, DEVICE_SCALE_FACTOR, portOf, REAL_CLOCK, runEnvironmentFiles, SWEEP_WORKERS, shellArgument, VERIFICATION_ENVIRONMENT_VARIABLE, } from '@ploaness/governance';
|
|
16
17
|
import { loadProductionEnvironment } from './production-environment.js';
|
|
17
18
|
import { projectSettings } from './project-settings.js';
|
|
18
19
|
// Read before anything else, because a spec module is what needs it. Playwright evaluates this config
|
|
@@ -23,6 +24,12 @@ import { projectSettings } from './project-settings.js';
|
|
|
23
24
|
// variables; the specs and the application do. A `globalSetup` hook would not serve, because it runs
|
|
24
25
|
// in a process of its own and its environment never reaches a worker.
|
|
25
26
|
const isVerification = process.env[VERIFICATION_ENVIRONMENT_VARIABLE] !== undefined;
|
|
27
|
+
// The verification clock reaches the runner and every server through `NODE_OPTIONS`, but Playwright
|
|
28
|
+
// starts its test workers without it, so a spec computing "today" would disagree with the application
|
|
29
|
+
// it drives. The config is evaluated in every worker, which makes it the place to load the clock.
|
|
30
|
+
if (process.env[CLOCK_ENVIRONMENT_VARIABLE] !== undefined) {
|
|
31
|
+
await import('./clock-preload.js');
|
|
32
|
+
}
|
|
26
33
|
if (isVerification) {
|
|
27
34
|
loadProductionEnvironment(process.cwd());
|
|
28
35
|
}
|
|
@@ -31,7 +38,17 @@ else {
|
|
|
31
38
|
process.loadEnvFile(file);
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
|
-
|
|
41
|
+
// Playwright starts each web server detached, in a process group of its own, and stops it when the run
|
|
42
|
+
// is interrupted - but it treats only SIGINT as an interruption. A run ended by SIGTERM or SIGHUP, as a
|
|
43
|
+
// cancelled CI job or a closed terminal ends it, left every server running on the port the next run
|
|
44
|
+
// needs. Both are raised again as SIGINT, once, so the runner stops the run and its servers itself; a
|
|
45
|
+
// second signal keeps its default. A test worker ignores SIGINT and is ended by its runner, so there the
|
|
46
|
+
// forwarded signal changes nothing.
|
|
47
|
+
for (const signal of ['SIGTERM', 'SIGHUP']) {
|
|
48
|
+
process.once(signal, () => {
|
|
49
|
+
process.kill(process.pid, 'SIGINT');
|
|
50
|
+
});
|
|
51
|
+
}
|
|
35
52
|
const isReuseExistingServer = canReuseBrowserServer(process.env);
|
|
36
53
|
const productionLauncher = fileURLToPath(new URL('production-server.js', import.meta.url));
|
|
37
54
|
// The web server is `next dev`, which compiles a route on its first request. The first hit to the heavy
|
|
@@ -39,7 +56,6 @@ const productionLauncher = fileURLToPath(new URL('production-server.js', import.
|
|
|
39
56
|
// compilation headroom instead of leaning on a retry to mask it.
|
|
40
57
|
const TEST_TIMEOUT_MS = 90_000;
|
|
41
58
|
const SERVER_TIMEOUT_MS = 180_000;
|
|
42
|
-
const CI_RETRIES = 2;
|
|
43
59
|
// Structural rather than Playwright's own `PlaywrightTestConfig`, for the reason `vitest.ts` records:
|
|
44
60
|
// the harness owns the config and the consumer owns the runner, so an emitted declaration should not
|
|
45
61
|
// make the runner's types a condition of reading this entry point.
|
|
@@ -50,6 +66,40 @@ const CI_RETRIES = 2;
|
|
|
50
66
|
// rather than setting PORT to the string "undefined".
|
|
51
67
|
const withPort = (base, port) => (port === undefined ? base : { ...base, PORT: port });
|
|
52
68
|
const declaredPort = portOf(projectSettings.serverUrl);
|
|
69
|
+
// Under verification the browsers are the pinned ones the CLI started in the Playwright image, so the
|
|
70
|
+
// same tree measures the same pixels on every machine. The application stays on the host: the remote
|
|
71
|
+
// browser reaches its loopback origins through the client, so cookies, headers and the production
|
|
72
|
+
// server are exactly what they are without the container. The remote browser reaches those origins
|
|
73
|
+
// directly where the container shares the host's network, and through the client's tunnel otherwise.
|
|
74
|
+
// Outside verification the host's Chromium runs.
|
|
75
|
+
const browserEndpoint = process.env[BROWSER_ENDPOINT_ENVIRONMENT_VARIABLE];
|
|
76
|
+
const remote = browserEndpoint === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: {
|
|
79
|
+
connectOptions: {
|
|
80
|
+
wsEndpoint: browserEndpoint,
|
|
81
|
+
...(process.env[BROWSER_TUNNEL_ENVIRONMENT_VARIABLE] !== undefined && { exposeNetwork: '<loopback>' }),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
// The sweep alone runs in the other engines; a project's own specs describe its behaviour once. A
|
|
85
|
+
// `delta` run's change scope keeps the sweep in Chromium.
|
|
86
|
+
const SWEEP_SPEC = /a11y\.e2e\.spec\.ts$/u;
|
|
87
|
+
const chromiumDevice = {
|
|
88
|
+
...devices['Desktop Chrome'],
|
|
89
|
+
deviceScaleFactor: DEVICE_SCALE_FACTOR,
|
|
90
|
+
...(browserEndpoint === undefined && { channel: 'chromium' }),
|
|
91
|
+
};
|
|
92
|
+
// The sweep's projects, which select its tests by tag. Discovery runs once, in Chromium, and every
|
|
93
|
+
// pass depends on it; the summary is its teardown, so it runs after every pass of every engine has
|
|
94
|
+
// finished, whatever their outcome.
|
|
95
|
+
const SWEEP_DISCOVERY = 'sweep-discovery';
|
|
96
|
+
const SWEEP_SUMMARY = 'sweep-summary';
|
|
97
|
+
// The application's clock, shifted by the preload in every server this config starts. The CLI sets the
|
|
98
|
+
// variable for a verification run; a project that declared the real clock gets none.
|
|
99
|
+
const clockOptions = isVerification && projectSettings.clock !== REAL_CLOCK
|
|
100
|
+
? ` --import=${new URL('clock-preload.js', import.meta.url).href}`
|
|
101
|
+
: '';
|
|
102
|
+
const clockEnvironment = clockOptions === '' ? {} : { [CLOCK_ENVIRONMENT_VARIABLE]: projectSettings.clock };
|
|
53
103
|
// Next's environment loader marks the process once it has loaded the environment files, and skips
|
|
54
104
|
// loading in any process that inherits the mark. A verification run loads this package's files above,
|
|
55
105
|
// and Playwright starts every server on top of the runner's own environment, so without clearing the
|
|
@@ -60,17 +110,65 @@ const declared = defineConfig({
|
|
|
60
110
|
testDir: './tests/e2e',
|
|
61
111
|
timeout: TEST_TIMEOUT_MS,
|
|
62
112
|
forbidOnly: true,
|
|
63
|
-
|
|
64
|
-
|
|
113
|
+
// Never retried: a retry that passes hides a verdict that is not reproducible, which is a defect of
|
|
114
|
+
// its own. Determinism is established by the fixed clock, motion, locale, timezone and pixel ratio
|
|
115
|
+
// below, and by the settle and stability checks every scan makes.
|
|
116
|
+
retries: 0,
|
|
117
|
+
workers: SWEEP_WORKERS,
|
|
65
118
|
// `open: 'never'` because the gate runs this non-interactively: a reporter that launches a browser
|
|
66
119
|
// on failure would hang the run rather than report it.
|
|
67
120
|
reporter: [['list'], ['html', { open: 'never' }]],
|
|
68
121
|
use: {
|
|
69
122
|
baseURL: projectSettings.serverUrl,
|
|
70
|
-
trace: 'on-
|
|
123
|
+
trace: 'retain-on-failure',
|
|
71
124
|
screenshot: 'only-on-failure',
|
|
125
|
+
reducedMotion: 'reduce',
|
|
126
|
+
locale: 'en-US',
|
|
127
|
+
timezoneId: 'UTC',
|
|
128
|
+
colorScheme: 'light',
|
|
129
|
+
deviceScaleFactor: DEVICE_SCALE_FACTOR,
|
|
130
|
+
serviceWorkers: 'block',
|
|
131
|
+
...remote,
|
|
72
132
|
},
|
|
73
|
-
projects: [
|
|
133
|
+
projects: [
|
|
134
|
+
{ name: 'chromium', testIgnore: SWEEP_SPEC, workers: 1, use: chromiumDevice },
|
|
135
|
+
{
|
|
136
|
+
name: SWEEP_DISCOVERY,
|
|
137
|
+
testMatch: SWEEP_SPEC,
|
|
138
|
+
grep: /@ploaness-discovery/u,
|
|
139
|
+
teardown: SWEEP_SUMMARY,
|
|
140
|
+
use: chromiumDevice,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'sweep-chromium',
|
|
144
|
+
testMatch: SWEEP_SPEC,
|
|
145
|
+
grep: /@ploaness-chromium/u,
|
|
146
|
+
dependencies: [SWEEP_DISCOVERY],
|
|
147
|
+
fullyParallel: true,
|
|
148
|
+
use: chromiumDevice,
|
|
149
|
+
},
|
|
150
|
+
...(coversEveryEngine(process.env)
|
|
151
|
+
? [
|
|
152
|
+
{
|
|
153
|
+
name: 'sweep-firefox',
|
|
154
|
+
testMatch: SWEEP_SPEC,
|
|
155
|
+
grep: /@ploaness-engine/u,
|
|
156
|
+
dependencies: [SWEEP_DISCOVERY],
|
|
157
|
+
fullyParallel: true,
|
|
158
|
+
use: { ...devices['Desktop Firefox'], deviceScaleFactor: DEVICE_SCALE_FACTOR },
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'sweep-webkit',
|
|
162
|
+
testMatch: SWEEP_SPEC,
|
|
163
|
+
grep: /@ploaness-engine/u,
|
|
164
|
+
dependencies: [SWEEP_DISCOVERY],
|
|
165
|
+
fullyParallel: true,
|
|
166
|
+
use: { ...devices['Desktop Safari'], deviceScaleFactor: DEVICE_SCALE_FACTOR },
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
{ name: SWEEP_SUMMARY, testMatch: SWEEP_SPEC, grep: /@ploaness-summary/u, use: chromiumDevice },
|
|
171
|
+
],
|
|
74
172
|
// Auxiliary servers first, then the application under test. Playwright awaits each entry in this
|
|
75
173
|
// list before starting the next, so the order is the difference between a run and a deadlock: a
|
|
76
174
|
// frontend that renders from a headless CMS cannot answer its own origin until that CMS answers, and
|
|
@@ -99,7 +197,11 @@ const declared = defineConfig({
|
|
|
99
197
|
// is long.
|
|
100
198
|
timeout: SERVER_TIMEOUT_MS,
|
|
101
199
|
...(server.cwd !== undefined && { cwd: server.cwd }),
|
|
102
|
-
env: withPort({
|
|
200
|
+
env: withPort({
|
|
201
|
+
NODE_OPTIONS: `--no-deprecation${clockOptions}`,
|
|
202
|
+
[NEXT_PROCESSED_ENVIRONMENT_MARK]: '',
|
|
203
|
+
...clockEnvironment,
|
|
204
|
+
}, portOf(server.url)),
|
|
103
205
|
})),
|
|
104
206
|
{
|
|
105
207
|
// Invoked as a bare binary rather than through a package script, because a nested package manager
|
|
@@ -115,7 +217,8 @@ const declared = defineConfig({
|
|
|
115
217
|
// exists to describe a non-default port made the run hang instead of work.
|
|
116
218
|
env: withPort({
|
|
117
219
|
NEXT_TELEMETRY_DISABLED: '1',
|
|
118
|
-
NODE_OPTIONS:
|
|
220
|
+
NODE_OPTIONS: `--no-deprecation${clockOptions}`,
|
|
221
|
+
...clockEnvironment,
|
|
119
222
|
...(isVerification && {
|
|
120
223
|
NODE_ENV: 'production',
|
|
121
224
|
HOSTNAME: browserServerEndpoint(projectSettings.serverUrl).host,
|
package/dist/playwright.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright.js","sourceRoot":"","sources":["../src/playwright.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,yFAAyF;AACzF,EAAE;AACF,mGAAmG;AACnG,wGAAwG;AACxG,
|
|
1
|
+
{"version":3,"file":"playwright.js","sourceRoot":"","sources":["../src/playwright.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,yFAAyF;AACzF,EAAE;AACF,mGAAmG;AACnG,wGAAwG;AACxG,yGAAyG;AACzG,qGAAqG;AACrG,sGAAsG;AACtG,8FAA8F;AAC9F,sGAAsG;AACtG,wGAAwG;AACxG,gFAAgF;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EACL,qCAAqC,EACrC,mCAAmC,EACnC,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,MAAM,EACN,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,iCAAiC,GAClC,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,sGAAsG;AACtG,sGAAsG;AACtG,qGAAqG;AACrG,kGAAkG;AAClG,sGAAsG;AACtG,qGAAqG;AACrG,sEAAsE;AACtE,MAAM,cAAc,GAAY,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,KAAK,SAAS,CAAA;AAE5F,oGAAoG;AACpG,sGAAsG;AACtG,kGAAkG;AAClG,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,KAAK,SAAS,EAAE,CAAC;IAC1D,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAA;AACpC,CAAC;AACD,IAAI,cAAc,EAAE,CAAC;IACnB,yBAAyB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;AAC1C,CAAC;KAAM,CAAC;IACN,KAAK,MAAM,IAAI,IAAI,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC;AAED,uGAAuG;AACvG,wGAAwG;AACxG,oGAAoG;AACpG,sGAAsG;AACtG,yGAAyG;AACzG,oCAAoC;AACpC,KAAK,MAAM,MAAM,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAS,EAAE;QAC9B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,qBAAqB,GAAY,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;AACzE,MAAM,kBAAkB,GAAW,aAAa,CAAC,IAAI,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAElG,wGAAwG;AACxG,mGAAmG;AACnG,iEAAiE;AACjE,MAAM,eAAe,GAAW,MAAM,CAAA;AACtC,MAAM,iBAAiB,GAAW,OAAO,CAAA;AAEzC,sGAAsG;AACtG,qGAAqG;AACrG,mEAAmE;AACnE,sGAAsG;AACtG,sGAAsG;AACtG,+BAA+B;AAC/B,mGAAmG;AACnG,sDAAsD;AACtD,MAAM,QAAQ,GAAG,CACf,IAAsC,EACtC,IAAwB,EACU,EAAE,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAE5F,MAAM,YAAY,GAAuB,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,CAAA;AAE1E,sGAAsG;AACtG,qGAAqG;AACrG,kGAAkG;AAClG,mGAAmG;AACnG,qGAAqG;AACrG,iDAAiD;AACjD,MAAM,eAAe,GAAuB,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAA;AAE9F,MAAM,MAAM,GACV,eAAe,KAAK,SAAS;IAC3B,CAAC,CAAC,EAAE;IACJ,CAAC,CAAC;QACE,cAAc,EAAE;YACd,UAAU,EAAE,eAAe;YAC3B,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,KAAK,SAAS,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;SACvG;KACF,CAAA;AAEP,kGAAkG;AAClG,0DAA0D;AAC1D,MAAM,UAAU,GAAW,uBAAuB,CAAA;AAElD,MAAM,cAAc,GAAsC;IACxD,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAC5B,iBAAiB,EAAE,mBAAmB;IACtC,GAAG,CAAC,eAAe,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;CAC9D,CAAA;AAED,mGAAmG;AACnG,mGAAmG;AACnG,oCAAoC;AACpC,MAAM,eAAe,GAAW,iBAAiB,CAAA;AACjD,MAAM,aAAa,GAAW,eAAe,CAAA;AAE7C,uGAAuG;AACvG,qFAAqF;AACrF,MAAM,YAAY,GAChB,cAAc,IAAI,eAAe,CAAC,KAAK,KAAK,UAAU;IACpD,CAAC,CAAC,aAAa,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE;IAClE,CAAC,CAAC,EAAE,CAAA;AAER,MAAM,gBAAgB,GACpB,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,0BAA0B,CAAC,EAAE,eAAe,CAAC,KAAK,EAAE,CAAA;AAEpF,kGAAkG;AAClG,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,kFAAkF;AAClF,MAAM,+BAA+B,GAAW,sBAAsB,CAAA;AAEtE,MAAM,QAAQ,GAAoC,YAAY,CAAC;IAC7D,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,eAAe;IACxB,UAAU,EAAE,IAAI;IAChB,oGAAoG;IACpG,mGAAmG;IACnG,kEAAkE;IAClE,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,aAAa;IACtB,mGAAmG;IACnG,uDAAuD;IACvD,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACjD,GAAG,EAAE;QACH,OAAO,EAAE,eAAe,CAAC,SAAS;QAClC,KAAK,EAAE,mBAAmB;QAC1B,UAAU,EAAE,iBAAiB;QAC7B,aAAa,EAAE,QAAQ;QACvB,MAAM,EAAE,OAAO;QACf,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,OAAO;QACpB,iBAAiB,EAAE,mBAAmB;QACtC,cAAc,EAAE,OAAO;QACvB,GAAG,MAAM;KACV;IACD,QAAQ,EAAE;QACR,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE;QAC7E;YACE,IAAI,EAAE,eAAe;YACrB,SAAS,EAAE,UAAU;YACrB,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE,aAAa;YACvB,GAAG,EAAE,cAAc;SACpB;QACD;YACE,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,UAAU;YACrB,IAAI,EAAE,qBAAqB;YAC3B,YAAY,EAAE,CAAC,eAAe,CAAC;YAC/B,aAAa,EAAE,IAAI;YACnB,GAAG,EAAE,cAAc;SACpB;QACD,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC;YAChC,CAAC,CAAC;gBACE;oBACE,IAAI,EAAE,eAAe;oBACrB,SAAS,EAAE,UAAU;oBACrB,IAAI,EAAE,mBAAmB;oBACzB,YAAY,EAAE,CAAC,eAAe,CAAC;oBAC/B,aAAa,EAAE,IAAI;oBACnB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,mBAAmB,EAAE;iBAC/E;gBACD;oBACE,IAAI,EAAE,cAAc;oBACpB,SAAS,EAAE,UAAU;oBACrB,IAAI,EAAE,mBAAmB;oBACzB,YAAY,EAAE,CAAC,eAAe,CAAC;oBAC/B,aAAa,EAAE,IAAI;oBACnB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,mBAAmB,EAAE;iBAC9E;aACF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,cAAc,EAAE;KAChG;IACD,iGAAiG;IACjG,gGAAgG;IAChG,qGAAqG;IACrG,mGAAmG;IACnG,+FAA+F;IAC/F,2FAA2F;IAC3F,yBAAyB;IACzB,EAAE;IACF,+FAA+F;IAC/F,qGAAqG;IACrG,gGAAgG;IAChG,qEAAqE;IACrE,SAAS,EAAE;QACT,iGAAiG;QACjG,iGAAiG;QACjG,kGAAkG;QAClG,0FAA0F;QAC1F,2FAA2F;QAC3F,wCAAwC;QACxC,GAAG,eAAe,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACnD,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,mBAAmB,EAAE,qBAAqB;YAC1C,gGAAgG;YAChG,gGAAgG;YAChG,WAAW;YACX,OAAO,EAAE,iBAAiB;YAC1B,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;YACpD,GAAG,EAAE,QAAQ,CACX;gBACE,YAAY,EAAE,mBAAmB,YAAY,EAAE;gBAC/C,CAAC,+BAA+B,CAAC,EAAE,EAAE;gBACrC,GAAG,gBAAgB;aACpB,EACD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CACnB;SACF,CAAC,CAAC;QACH;YACE,kGAAkG;YAClG,gGAAgG;YAChG,kGAAkG;YAClG,8FAA8F;YAC9F,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU;YAChH,GAAG,EAAE,eAAe,CAAC,SAAS;YAC9B,mBAAmB,EAAE,qBAAqB;YAC1C,OAAO,EAAE,iBAAiB;YAC1B,4FAA4F;YAC5F,8FAA8F;YAC9F,2EAA2E;YAC3E,GAAG,EAAE,QAAQ,CACX;gBACE,uBAAuB,EAAE,GAAG;gBAC5B,YAAY,EAAE,mBAAmB,YAAY,EAAE;gBAC/C,GAAG,gBAAgB;gBACnB,GAAG,CAAC,cAAc,IAAI;oBACpB,QAAQ,EAAE,YAAY;oBACtB,QAAQ,EAAE,qBAAqB,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,IAAI;iBAChE,CAAC;aACH,EACD,YAAY,CACb;SACF;KACF;CACF,CAAC,CAAA;AAEF,mGAAmG;AACnG,mGAAmG;AACnG,MAAM,MAAM,GAAsC,EAAE,GAAG,QAAQ,EAAE,CAAA;AAEjE,eAAe,MAAM,CAAA"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AxeBuilder } from '@axe-core/playwright';
|
|
2
|
+
import { type Page } from '@playwright/test';
|
|
3
|
+
import { type BrowserFinding, type DeclaredExemption, type FindingPlace, type FindingViewport, type JudgedPage, type LayoutSnapshot, type UsedExemption } from '@ploaness/governance';
|
|
4
|
+
/** Every rule the resting scan applies to elements, for judging which exemptions are stale. */
|
|
5
|
+
export declare const RESTING_RULES: readonly string[];
|
|
6
|
+
/** What scanning one place yields. */
|
|
7
|
+
export interface PlaceScan extends JudgedPage {
|
|
8
|
+
/** The measurement the findings were judged on, for the outline a failure screenshot draws. */
|
|
9
|
+
readonly snapshot: LayoutSnapshot | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** The engine a page runs in. */
|
|
12
|
+
export declare const engineOf: (page: Page) => string;
|
|
13
|
+
/** The route path or story a page shows, as a finding names it. */
|
|
14
|
+
export declare const locationOf: (page: Page) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Settle the page, and report a page that did not settle.
|
|
17
|
+
* @param page the page.
|
|
18
|
+
* @param place where it is.
|
|
19
|
+
* @returns a finding, or nothing when the page settled.
|
|
20
|
+
*/
|
|
21
|
+
export declare const settleFindings: (page: Page, place: FindingPlace) => Promise<readonly BrowserFinding[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Measure a settled page twice, and judge it by the resting rules: L1 to L4, T4, T6 and M1 to M4.
|
|
24
|
+
*
|
|
25
|
+
* Geometry that differs between the two measurements is a finding of its own, and the page is then
|
|
26
|
+
* judged on neither: every other verdict on a page that moves is a coin toss.
|
|
27
|
+
* @param page the settled page.
|
|
28
|
+
* @param place where it is.
|
|
29
|
+
* @returns the findings, the exemptions used and declared, and the measurement.
|
|
30
|
+
*/
|
|
31
|
+
export declare const judgeRestingPlace: (page: Page, place: FindingPlace) => Promise<PlaceScan>;
|
|
32
|
+
/**
|
|
33
|
+
* The axe scan of a page as findings, one per violating element.
|
|
34
|
+
* @param page the settled page.
|
|
35
|
+
* @param place where it is.
|
|
36
|
+
* @param build narrows the builder, for a scan of one rule or one element.
|
|
37
|
+
* @returns the findings.
|
|
38
|
+
*/
|
|
39
|
+
export declare const axeFindings: (page: Page, place: FindingPlace, build?: (builder: AxeBuilder) => AxeBuilder) => Promise<readonly BrowserFinding[]>;
|
|
40
|
+
/** The most requests a page retains for `requests()`; a full log may have lost earlier ones. */
|
|
41
|
+
export declare const RETAINED_REQUESTS: number;
|
|
42
|
+
/** What the browser reported while a page was at one place. */
|
|
43
|
+
export interface HealthReading {
|
|
44
|
+
readonly findings: readonly BrowserFinding[];
|
|
45
|
+
/** Whether the request log was full, so earlier requests of the navigation may be missing. */
|
|
46
|
+
readonly isRequestLogFull: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The page errors, console errors and failed requests since the page's last navigation, after which the
|
|
50
|
+
* stored console and error logs are cleared so the next place starts empty.
|
|
51
|
+
* @param page the page.
|
|
52
|
+
* @param place where it is.
|
|
53
|
+
* @param origins the origins the network guard allows.
|
|
54
|
+
* @returns the findings, and whether the request log may have lost requests.
|
|
55
|
+
*/
|
|
56
|
+
export declare const healthFindings: (page: Page, place: FindingPlace, origins: readonly string[]) => Promise<HealthReading>;
|
|
57
|
+
/** The findings of every place one page was scanned at, with the exemptions they used and declared. */
|
|
58
|
+
export interface PageScan {
|
|
59
|
+
readonly findings: readonly BrowserFinding[];
|
|
60
|
+
readonly used: readonly UsedExemption[];
|
|
61
|
+
readonly declared: readonly DeclaredExemption[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether a viewport is one the axe scans and the text-stress passes run at.
|
|
65
|
+
* @param viewport the viewport.
|
|
66
|
+
* @returns true for the phone and desktop viewports.
|
|
67
|
+
*/
|
|
68
|
+
export declare const isStressViewport: (viewport: FindingViewport) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Scan a page in place at every required viewport, then restore its viewport.
|
|
71
|
+
*
|
|
72
|
+
* What a project's own specification calls on a state the sweep cannot reach. The page is resized
|
|
73
|
+
* rather than reloaded, so the state the specification drove it into is what is judged. On a Payload
|
|
74
|
+
* admin view only the view's own content is judged, never Payload's navigation, header or frame.
|
|
75
|
+
* @param page the page, on the route and in the state to judge.
|
|
76
|
+
* @param shouldRunAxe whether to run axe as well, at the phone and desktop viewports.
|
|
77
|
+
* @returns every finding, stale exemptions included.
|
|
78
|
+
*/
|
|
79
|
+
export declare const scanPageInPlace: (page: Page, shouldRunAxe: boolean) => Promise<readonly BrowserFinding[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Measure the page as it stands at every required viewport, and report what is wrong with its layout,
|
|
82
|
+
* text and media.
|
|
83
|
+
*
|
|
84
|
+
* The page is resized in place rather than reloaded, so whatever state a spec drove it into - a
|
|
85
|
+
* signed-in view, an open dialog - is what gets measured. Its viewport is restored afterwards.
|
|
86
|
+
* @param page the page to measure, already on the route and in the state to judge.
|
|
87
|
+
* @returns one line per defect, empty when there is none.
|
|
88
|
+
*/
|
|
89
|
+
export declare const layoutDefectsOf: (page: Page) => Promise<readonly string[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Fail the calling test when the page has a layout, text or media defect at any required viewport.
|
|
92
|
+
*
|
|
93
|
+
* Call it in a project's own specification for a page the sweep cannot reach - one behind a sign-in,
|
|
94
|
+
* or one whose address exists only once a record does - after driving the page into the state to judge.
|
|
95
|
+
* @param page the page to measure.
|
|
96
|
+
* @returns nothing; it fails the test with one line per defect.
|
|
97
|
+
*/
|
|
98
|
+
export declare const expectNoLayoutDefects: (page: Page) => Promise<void>;
|
|
99
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAA+B,KAAK,IAAI,EAAgB,MAAM,kBAAkB,CAAA;AACvF,OAAO,EAEL,KAAK,cAAc,EAGnB,KAAK,iBAAiB,EAGtB,KAAK,YAAY,EACjB,KAAK,eAAe,EAapB,KAAK,UAAU,EAGf,KAAK,cAAc,EAQnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAA;AAwB7B,+FAA+F;AAC/F,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAgD,CAAA;AAE3F,sCAAsC;AACtC,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAA;CAC9C;AAED,iCAAiC;AACjC,eAAO,MAAM,QAAQ,GAAI,MAAM,IAAI,KAAG,MAAsE,CAAA;AAE5G,mEAAmE;AACnE,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,MAAsC,CAAA;AAQ9E;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,IAAI,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAGvG,CAAA;AAcD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAU,MAAM,IAAI,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,SAAS,CAyB1F,CAAA;AAqED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM,IAAI,EACV,OAAO,YAAY,EACnB,QAAO,CAAC,OAAO,EAAE,UAAU,KAAK,UAAyD,KACxF,OAAO,CAAC,SAAS,cAAc,EAAE,CA4BnC,CAAA;AAsBD,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,EAAE,MAAY,CAAA;AAE5C,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;IAC5C,8FAA8F;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAA;CACnC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,IAAI,EACV,OAAO,YAAY,EACnB,SAAS,SAAS,MAAM,EAAE,KACzB,OAAO,CAAC,aAAa,CA2BvB,CAAA;AAED,uGAAuG;AACvG,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;IAC5C,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAA;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAChD;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,eAAe,KAAG,OAG1D,CAAA;AASH;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAU,MAAM,IAAI,EAAE,cAAc,OAAO,KAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAO1G,CAAA;AAyCD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAU,MAAM,IAAI,KAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAG3E,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,IAAI,KAAG,OAAO,CAAC,IAAI,CAEpE,CAAA"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// The one scanner every browser check runs: the managed sweep's passes, and the helpers a project's own
|
|
2
|
+
// specifications call for a state the sweep cannot reach. A page judged through either is judged by the
|
|
3
|
+
// same rules, so a story, a signed-in view and a crawled route cannot disagree about what a defect is.
|
|
4
|
+
//
|
|
5
|
+
// Every verdict is a governance function over measured values. This module measures, and collects what
|
|
6
|
+
// the browser reports, and nothing else.
|
|
7
|
+
import { AxeBuilder } from '@axe-core/playwright';
|
|
8
|
+
import { expect } from '@playwright/test';
|
|
9
|
+
import { axeRulesExcludedAt, consolePageError, distinctFindings, FAILED_REQUEST_RULE, FOCUS_GUARD_RULE, FONT_LOAD_RULE, findDefiniteIncomplete, findingAt, findingLine, findUnloadedFonts, instability, isAllowedRequest, isArray, isFailedStatus, isFocusGuard, judgeLayout, LAYOUT_RULES, PAGE_ERROR_RULE, PAGE_NOT_STABLE_RULE, RESTING_ELEMENT_RULES, reportedText, restingCandidates, STRESS_VIEWPORTS, staleExemptions, } from '@ploaness/governance';
|
|
10
|
+
import { LAYOUT_SCAN_VIEWPORTS, measurePage } from './layout.js';
|
|
11
|
+
import { hasMarkedPayloadView, unmarkPayloadView, VIEW_ROOT_SELECTOR } from './payload-view.js';
|
|
12
|
+
import { projectSettings } from './project-settings.js';
|
|
13
|
+
import { settleForScan } from './settle.js';
|
|
14
|
+
/** Every rule the resting scan applies to elements, for judging which exemptions are stale. */
|
|
15
|
+
export const RESTING_RULES = [...LAYOUT_RULES, ...RESTING_ELEMENT_RULES];
|
|
16
|
+
/** The engine a page runs in. */
|
|
17
|
+
export const engineOf = (page) => page.context().browser()?.browserType().name() ?? 'chromium';
|
|
18
|
+
/** The route path or story a page shows, as a finding names it. */
|
|
19
|
+
export const locationOf = (page) => new URL(page.url()).pathname;
|
|
20
|
+
const SETTLE_MESSAGES = {
|
|
21
|
+
ceiling: 'the page kept changing until the settle ceiling; nothing measured on it can be trusted',
|
|
22
|
+
busy: 'a visible aria-busy="true" region never cleared',
|
|
23
|
+
interrupted: 'the page navigated or closed while it was settling',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Settle the page, and report a page that did not settle.
|
|
27
|
+
* @param page the page.
|
|
28
|
+
* @param place where it is.
|
|
29
|
+
* @returns a finding, or nothing when the page settled.
|
|
30
|
+
*/
|
|
31
|
+
export const settleFindings = async (page, place) => {
|
|
32
|
+
const outcome = await settleForScan(page);
|
|
33
|
+
return outcome === 'settled' ? [] : [findingAt(place, PAGE_NOT_STABLE_RULE, SETTLE_MESSAGES[outcome])];
|
|
34
|
+
};
|
|
35
|
+
// Two measurements inside one frame read the same layout whatever the page is doing, so the second is
|
|
36
|
+
// taken two animation frames after the first: a page that moves between frames shows it.
|
|
37
|
+
const nextFramesInPage = async () => {
|
|
38
|
+
const frame = async () => {
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
requestAnimationFrame(resolve);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
await frame();
|
|
44
|
+
await frame();
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Measure a settled page twice, and judge it by the resting rules: L1 to L4, T4, T6 and M1 to M4.
|
|
48
|
+
*
|
|
49
|
+
* Geometry that differs between the two measurements is a finding of its own, and the page is then
|
|
50
|
+
* judged on neither: every other verdict on a page that moves is a coin toss.
|
|
51
|
+
* @param page the settled page.
|
|
52
|
+
* @param place where it is.
|
|
53
|
+
* @returns the findings, the exemptions used and declared, and the measurement.
|
|
54
|
+
*/
|
|
55
|
+
export const judgeRestingPlace = async (page, place) => {
|
|
56
|
+
const first = await measurePage(page, place.location);
|
|
57
|
+
await page.evaluate(nextFramesInPage);
|
|
58
|
+
const second = await measurePage(page, place.location);
|
|
59
|
+
const moved = instability(first, second);
|
|
60
|
+
if (moved !== undefined) {
|
|
61
|
+
return {
|
|
62
|
+
findings: [findingAt(place, PAGE_NOT_STABLE_RULE, moved)],
|
|
63
|
+
used: [],
|
|
64
|
+
declared: [],
|
|
65
|
+
snapshot: second,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const judged = judgeLayout(second, place, {
|
|
69
|
+
minimumGap: projectSettings.layoutMinimumGap,
|
|
70
|
+
extra: restingCandidates(second),
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
...judged,
|
|
74
|
+
findings: [
|
|
75
|
+
...judged.findings,
|
|
76
|
+
...findUnloadedFonts(second).map((message) => findingAt(place, FONT_LOAD_RULE, message)),
|
|
77
|
+
],
|
|
78
|
+
snapshot: second,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
// A second copy of axe in the page - a Storybook accessibility addon checking what it rendered - shares
|
|
82
|
+
// axe's one-run-at-a-time lock, so a scan started while that copy runs is refused rather than queued.
|
|
83
|
+
const AXE_BUSY = 'Axe is already running';
|
|
84
|
+
const AXE_BUSY_RETRIES = 20;
|
|
85
|
+
const AXE_BUSY_WAIT_MS = 250;
|
|
86
|
+
const analyzed = async (page, builder, retries) => {
|
|
87
|
+
try {
|
|
88
|
+
return await builder.analyze();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (retries <= 0 || !(error instanceof Error && error.message.includes(AXE_BUSY))) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
await page.waitForTimeout(AXE_BUSY_WAIT_MS);
|
|
95
|
+
return await analyzed(page, builder, retries - 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const axeTarget = (target) => (isArray(target) ? target.map(String).join(' ') : String(target));
|
|
99
|
+
// Focus each element of the reported subtree that Tab can reach, and read where focus rests once the
|
|
100
|
+
// page's own focus handlers have run, which a focus guard does synchronously. The focus the page had
|
|
101
|
+
// is given back after each one.
|
|
102
|
+
const focusArrivalsInPage = (selector) => {
|
|
103
|
+
const subtree = document.querySelector(selector);
|
|
104
|
+
if (subtree === null) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const previous = document.activeElement;
|
|
108
|
+
return [subtree, ...Array.from(subtree.querySelectorAll('*'))]
|
|
109
|
+
.filter((element) => element.tabIndex >= 0 && !element.hasAttribute('disabled'))
|
|
110
|
+
.map((element) => {
|
|
111
|
+
element.focus({ preventScroll: true });
|
|
112
|
+
const active = document.activeElement;
|
|
113
|
+
const arrival = {
|
|
114
|
+
staysInSubtree: active !== null && subtree.contains(active),
|
|
115
|
+
restsOnBody: active === null || active === document.body,
|
|
116
|
+
restsHidden: active?.closest('[aria-hidden="true"]') !== null,
|
|
117
|
+
};
|
|
118
|
+
previous?.focus({ preventScroll: true });
|
|
119
|
+
return arrival;
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
// A subtree named by more than one selector lies inside a frame or a shadow root the probe cannot
|
|
123
|
+
// address from the document, so it stays reported.
|
|
124
|
+
const isFocusGuardAt = async (page, target) => isArray(target) &&
|
|
125
|
+
target.length === 1 &&
|
|
126
|
+
typeof target[0] === 'string' &&
|
|
127
|
+
isFocusGuard(await page.evaluate(focusArrivalsInPage, target[0]));
|
|
128
|
+
const reportedNodes = async (page, violation) => {
|
|
129
|
+
if (violation.id !== FOCUS_GUARD_RULE) {
|
|
130
|
+
return violation.nodes;
|
|
131
|
+
}
|
|
132
|
+
const guards = await Promise.all(violation.nodes.map(async (node) => await isFocusGuardAt(page, node.target)));
|
|
133
|
+
return violation.nodes.filter((_node, index) => guards[index] !== true);
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The axe scan of a page as findings, one per violating element.
|
|
137
|
+
* @param page the settled page.
|
|
138
|
+
* @param place where it is.
|
|
139
|
+
* @param build narrows the builder, for a scan of one rule or one element.
|
|
140
|
+
* @returns the findings.
|
|
141
|
+
*/
|
|
142
|
+
export const axeFindings = async (page, place, build = (builder) => builder) => {
|
|
143
|
+
const excluded = axeRulesExcludedAt(place.location);
|
|
144
|
+
const builder = build(new AxeBuilder({ page }));
|
|
145
|
+
const results = await analyzed(page, excluded.length === 0 ? builder : builder.disableRules([...excluded]), AXE_BUSY_RETRIES);
|
|
146
|
+
const reported = await Promise.all(results.violations.map(async (violation) => await reportedNodes(page, violation)));
|
|
147
|
+
return [
|
|
148
|
+
...results.violations.flatMap((violation, index) => (reported[index] ?? []).map((node) => findingAt(place, `axe:${violation.id}`, `${violation.help}: ${axeTarget(node.target)} - ${reportedText(node.failureSummary ?? '')}`))),
|
|
149
|
+
...findDefiniteIncomplete(results.incomplete).map((message) => findingAt(place, 'axe:incomplete', message)),
|
|
150
|
+
];
|
|
151
|
+
};
|
|
152
|
+
// A request cancelled because the page moved on is not a failure of the page.
|
|
153
|
+
const CANCELLED = /ERR_ABORTED|NS_BINDING_ABORTED|cancelled/iu;
|
|
154
|
+
const requestFinding = (place, request, origins) => {
|
|
155
|
+
const url = request.url();
|
|
156
|
+
if (!isAllowedRequest(url, origins)) {
|
|
157
|
+
return [findingAt(place, FAILED_REQUEST_RULE, `${request.method()} ${url} was blocked by the network guard`)];
|
|
158
|
+
}
|
|
159
|
+
const failure = request.failure();
|
|
160
|
+
if (failure !== null) {
|
|
161
|
+
return CANCELLED.test(failure.errorText)
|
|
162
|
+
? []
|
|
163
|
+
: [findingAt(place, FAILED_REQUEST_RULE, `${request.method()} ${url} failed: ${failure.errorText}`)];
|
|
164
|
+
}
|
|
165
|
+
const status = request.existingResponse()?.status();
|
|
166
|
+
return status !== undefined && isFailedStatus(status)
|
|
167
|
+
? [findingAt(place, FAILED_REQUEST_RULE, `${request.method()} ${url} answered ${String(status)}`)]
|
|
168
|
+
: [];
|
|
169
|
+
};
|
|
170
|
+
/** The most requests a page retains for `requests()`; a full log may have lost earlier ones. */
|
|
171
|
+
export const RETAINED_REQUESTS = 100;
|
|
172
|
+
/**
|
|
173
|
+
* The page errors, console errors and failed requests since the page's last navigation, after which the
|
|
174
|
+
* stored console and error logs are cleared so the next place starts empty.
|
|
175
|
+
* @param page the page.
|
|
176
|
+
* @param place where it is.
|
|
177
|
+
* @param origins the origins the network guard allows.
|
|
178
|
+
* @returns the findings, and whether the request log may have lost requests.
|
|
179
|
+
*/
|
|
180
|
+
export const healthFindings = async (page, place, origins) => {
|
|
181
|
+
const errors = await page.pageErrors();
|
|
182
|
+
const logged = await page.consoleMessages();
|
|
183
|
+
const messages = logged.map((message) => ({ type: message.type(), text: message.text() }));
|
|
184
|
+
const requests = await page.requests();
|
|
185
|
+
const lastNavigation = requests.findLastIndex((request) => request.isNavigationRequest() && request.frame() === page.mainFrame());
|
|
186
|
+
await page.clearPageErrors();
|
|
187
|
+
await page.clearConsoleMessages();
|
|
188
|
+
return {
|
|
189
|
+
findings: distinctFindings([
|
|
190
|
+
...errors.map((error) => findingAt(place, PAGE_ERROR_RULE, `uncaught ${reportedText(String(error))}`)),
|
|
191
|
+
...messages.flatMap((entry) => {
|
|
192
|
+
const message = consolePageError(entry);
|
|
193
|
+
return message === undefined ? [] : [findingAt(place, PAGE_ERROR_RULE, reportedText(message))];
|
|
194
|
+
}),
|
|
195
|
+
...requests
|
|
196
|
+
.slice(Math.max(0, lastNavigation))
|
|
197
|
+
.flatMap((request) => requestFinding(place, request, origins)),
|
|
198
|
+
]),
|
|
199
|
+
isRequestLogFull: requests.length >= RETAINED_REQUESTS && lastNavigation <= 0,
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Whether a viewport is one the axe scans and the text-stress passes run at.
|
|
204
|
+
* @param viewport the viewport.
|
|
205
|
+
* @returns true for the phone and desktop viewports.
|
|
206
|
+
*/
|
|
207
|
+
export const isStressViewport = (viewport) => STRESS_VIEWPORTS.some((stress) => stress.width === viewport.width && stress.height === viewport.height);
|
|
208
|
+
const placeOf = (page, viewport, state) => ({
|
|
209
|
+
location: locationOf(page),
|
|
210
|
+
state,
|
|
211
|
+
engine: engineOf(page),
|
|
212
|
+
viewport,
|
|
213
|
+
});
|
|
214
|
+
/**
|
|
215
|
+
* Scan a page in place at every required viewport, then restore its viewport.
|
|
216
|
+
*
|
|
217
|
+
* What a project's own specification calls on a state the sweep cannot reach. The page is resized
|
|
218
|
+
* rather than reloaded, so the state the specification drove it into is what is judged. On a Payload
|
|
219
|
+
* admin view only the view's own content is judged, never Payload's navigation, header or frame.
|
|
220
|
+
* @param page the page, on the route and in the state to judge.
|
|
221
|
+
* @param shouldRunAxe whether to run axe as well, at the phone and desktop viewports.
|
|
222
|
+
* @returns every finding, stale exemptions included.
|
|
223
|
+
*/
|
|
224
|
+
export const scanPageInPlace = async (page, shouldRunAxe) => {
|
|
225
|
+
const isView = await hasMarkedPayloadView(page);
|
|
226
|
+
try {
|
|
227
|
+
return await scanMarkedPage(page, shouldRunAxe, isView);
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
await unmarkPayloadView(page);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const scanMarkedPage = async (page, shouldRunAxe, isView) => {
|
|
234
|
+
const original = page.viewportSize();
|
|
235
|
+
const scans = await LAYOUT_SCAN_VIEWPORTS.reduce(async (earlier, viewport) => {
|
|
236
|
+
const done = await earlier;
|
|
237
|
+
await page.setViewportSize(viewport);
|
|
238
|
+
const place = placeOf(page, viewport, 'resting');
|
|
239
|
+
const unsettled = await settleFindings(page, place);
|
|
240
|
+
const judged = await judgeRestingPlace(page, place);
|
|
241
|
+
const axe = shouldRunAxe && isStressViewport(viewport)
|
|
242
|
+
? await axeFindings(page, place, (builder) => (isView ? builder.include(VIEW_ROOT_SELECTOR) : builder))
|
|
243
|
+
: [];
|
|
244
|
+
return [...done, { ...judged, findings: [...unsettled, ...judged.findings, ...axe] }];
|
|
245
|
+
}, Promise.resolve([]));
|
|
246
|
+
if (original !== null) {
|
|
247
|
+
await page.setViewportSize(original);
|
|
248
|
+
}
|
|
249
|
+
return distinctFindings([
|
|
250
|
+
...scans.flatMap((scan) => scan.findings),
|
|
251
|
+
...staleExemptions(scans.flatMap((scan) => scan.declared), scans.flatMap((scan) => scan.used), RESTING_RULES, engineOf(page)),
|
|
252
|
+
]);
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Measure the page as it stands at every required viewport, and report what is wrong with its layout,
|
|
256
|
+
* text and media.
|
|
257
|
+
*
|
|
258
|
+
* The page is resized in place rather than reloaded, so whatever state a spec drove it into - a
|
|
259
|
+
* signed-in view, an open dialog - is what gets measured. Its viewport is restored afterwards.
|
|
260
|
+
* @param page the page to measure, already on the route and in the state to judge.
|
|
261
|
+
* @returns one line per defect, empty when there is none.
|
|
262
|
+
*/
|
|
263
|
+
export const layoutDefectsOf = async (page) => {
|
|
264
|
+
const findings = await scanPageInPlace(page, false);
|
|
265
|
+
return findings.map((finding) => findingLine(finding));
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Fail the calling test when the page has a layout, text or media defect at any required viewport.
|
|
269
|
+
*
|
|
270
|
+
* Call it in a project's own specification for a page the sweep cannot reach - one behind a sign-in,
|
|
271
|
+
* or one whose address exists only once a record does - after driving the page into the state to judge.
|
|
272
|
+
* @param page the page to measure.
|
|
273
|
+
* @returns nothing; it fails the test with one line per defect.
|
|
274
|
+
*/
|
|
275
|
+
export const expectNoLayoutDefects = async (page) => {
|
|
276
|
+
expect(await layoutDefectsOf(page), `layout defects on ${locationOf(page)}`).toEqual([]);
|
|
277
|
+
};
|
|
278
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|