testaro 78.0.6 → 78.4.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/.gitattributes +94 -0
- package/.github/workflows/ci.yml +58 -0
- package/.github/workflows/publish.yml +49 -0
- package/.github/workflows/typescript.yml +35 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +25 -19
- package/CONTAINERS.md +10 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +75 -20
- package/UPGRADES.md +4 -0
- package/actSpecs-doc.md +13 -1
- package/actSpecs.js +29 -5
- package/call.js +6 -5
- package/docker-compose.yml +8 -2
- package/docs/checkpoint-scanning.md +199 -0
- package/docs/standard-result-outcome.md +165 -0
- package/env.example +44 -10
- package/eslint.config.mjs +101 -0
- package/netWatch.js +373 -222
- package/package.json +46 -31
- package/pour/README.md +47 -0
- package/pour/pour.min.js +8 -0
- package/procs/actDo.js +808 -0
- package/procs/catalog.d.ts +17 -0
- package/procs/catalog.js +313 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +35 -0
- package/procs/config.js +63 -0
- package/procs/dateTime.js +2 -1
- package/procs/doActs.js +442 -849
- package/procs/doTestAct.js +27 -121
- package/procs/flow.js +221 -0
- package/procs/generateRuleRegistry.js +94 -0
- package/procs/getSource.d.ts +26 -0
- package/procs/getSource.js +1 -0
- package/procs/job.js +116 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +239 -108
- package/procs/nu.d.ts +48 -0
- package/procs/nu.js +1 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- package/procs/shoot.js +3 -1
- package/procs/standard.d.ts +18 -0
- package/procs/standard.js +83 -0
- package/procs/standard.ts +126 -0
- package/procs/testAct.js +145 -0
- package/procs/testaro.d.ts +20 -0
- package/procs/testaro.js +237 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +82 -64
- package/procs/xPath.ts +106 -0
- package/procs/xPathScript.d.ts +6 -0
- package/procs/xPathScript.js +70 -0
- package/run.js +3 -2
- package/surea11y/README.md +44 -0
- package/surea11y/surea11y.browser.js +14 -0
- package/testaro/adbID.d.ts +3 -0
- package/testaro/adbID.js +39 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +32 -34
- package/testaro/allCapStyle.ts +55 -0
- package/testaro/allCaps.d.ts +16 -0
- package/testaro/allCaps.js +179 -151
- package/testaro/allCaps.ts +228 -0
- package/testaro/allHidden.d.ts +13 -0
- package/testaro/allHidden.js +30 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +31 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +28 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +21 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +60 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +33 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +318 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +18 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +34 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +58 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +26 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +114 -104
- package/testaro/dupAtt.ts +144 -0
- package/testaro/elements.d.ts +6 -0
- package/testaro/elements.js +153 -153
- package/testaro/elements.ts +215 -0
- package/testaro/embAc.d.ts +3 -0
- package/testaro/embAc.js +20 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +186 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +101 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +65 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +29 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +62 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +55 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +164 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +156 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +15 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +18 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +32 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +18 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +44 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +80 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +14 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +26 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +23 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +31 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +42 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +93 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +40 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +17 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +19 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +37 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +58 -59
- package/testaro/radioSet.ts +79 -0
- package/testaro/registry.d.ts +63 -0
- package/testaro/registry.js +67 -0
- package/testaro/registry.ts +141 -0
- package/testaro/role.d.ts +3 -0
- package/testaro/role.js +28 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +31 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +249 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +273 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +131 -132
- package/testaro/targetsNear.ts +160 -0
- package/testaro/textNodes.d.ts +6 -0
- package/testaro/textNodes.js +139 -135
- package/testaro/textNodes.ts +185 -0
- package/testaro/textSem.d.ts +3 -0
- package/testaro/textSem.js +26 -28
- package/testaro/textSem.ts +47 -0
- package/testaro/title.d.ts +9 -0
- package/testaro/title.js +16 -13
- package/testaro/title.ts +31 -0
- package/testaro/titledEl.d.ts +3 -0
- package/testaro/titledEl.js +16 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +20 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +139 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +273 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +200 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +142 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +175 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +52 -0
- package/tests/ibm.js +165 -166
- package/tests/ibm.ts +251 -0
- package/tests/nuVal.d.ts +13 -0
- package/tests/nuVal.js +107 -112
- package/tests/nuVal.ts +145 -0
- package/tests/nuVnu.d.ts +15 -0
- package/tests/nuVnu.js +142 -111
- package/tests/nuVnu.ts +144 -0
- package/tests/pour.d.ts +31 -0
- package/tests/pour.js +242 -0
- package/tests/pour.ts +273 -0
- package/tests/qualWeb.d.ts +39 -0
- package/tests/qualWeb.js +303 -272
- package/tests/qualWeb.ts +415 -0
- package/tests/surea11y.d.ts +33 -0
- package/tests/surea11y.js +288 -0
- package/tests/surea11y.ts +334 -0
- package/tests/testaro.d.ts +25 -0
- package/tests/testaro.js +746 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +167 -177
- package/tests/wave.ts +252 -0
- package/tsconfig.json +17 -0
- package/types.d.ts +243 -0
- package/types.js +10 -0
- package/types.ts +376 -0
- package/validation/act/README.md +46 -0
- package/validation/act/capture.js +424 -0
- package/validation/act/chromium-issue-draft.md +66 -0
- package/validation/act/fp-triage-2026-08-22.md +86 -0
- package/validation/act/isolation-notes.md +159 -0
- package/validation/act/playwright-issue-draft.md +73 -0
- package/validation/act/propose-mappings.js +0 -0
- package/validation/act/repro-cdp-raw.js +105 -0
- package/validation/act/repro-metarefresh.js +65 -0
- package/validation/act/score.js +184 -0
- package/validation/act/stage3a-stress-report.md +69 -0
- package/validation/act/stage3b-mapping-proposals.md +94 -0
- package/validation/act/stage3b-triage-draft.md +138 -0
- package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
- package/validation/executors/netWatch.js +180 -90
- package/validation/executors/test.js +17 -2
- package/validation/executors/tests.js +118 -10
- package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
- package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
- package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
- package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
- package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
- package/validation/knownFailures.json +1 -0
- package/validation/tests/jobProperties/adbID.json +27 -2
- package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
- package/validation/tests/jobProperties/allCaps.json +25 -0
- package/validation/tests/jobProperties/allHidden.json +133 -13
- package/validation/tests/jobProperties/allSlanted.json +3 -3
- package/validation/tests/jobProperties/altScheme.json +23 -8
- package/validation/tests/jobProperties/attVal.json +57 -57
- package/validation/tests/jobProperties/autocomplete.json +11 -1
- package/validation/tests/jobProperties/bulk.json +6 -1
- package/validation/tests/jobProperties/buttonMenu.json +63 -42
- package/validation/tests/jobProperties/captionLoc.json +1 -6
- package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
- package/validation/tests/jobProperties/checkpoint-page.json +397 -0
- package/validation/tests/jobProperties/checkpoint.json +395 -0
- package/validation/tests/jobProperties/datalistRef.json +20 -5
- package/validation/tests/jobProperties/distortion.json +28 -3
- package/validation/tests/jobProperties/docType.json +2 -2
- package/validation/tests/jobProperties/dupAtt.json +48 -33
- package/validation/tests/jobProperties/elements.json +28 -28
- package/validation/tests/jobProperties/embAc.json +36 -31
- package/validation/tests/jobProperties/focAndOp.json +284 -0
- package/validation/tests/jobProperties/focInd.json +34 -34
- package/validation/tests/jobProperties/focVis.json +1 -1
- package/validation/tests/jobProperties/hover.json +39 -37
- package/validation/tests/jobProperties/hr.json +13 -3
- package/validation/tests/jobProperties/imageLink.json +0 -5
- package/validation/tests/jobProperties/labClash.json +68 -33
- package/validation/tests/jobProperties/legendLoc.json +1 -6
- package/validation/tests/jobProperties/lineHeight.json +2 -2
- package/validation/tests/jobProperties/linkAmb.json +13 -18
- package/validation/tests/jobProperties/linkExt.json +1 -1
- package/validation/tests/jobProperties/linkOldAtt.json +12 -2
- package/validation/tests/jobProperties/linkTo.json +1 -1
- package/validation/tests/jobProperties/linkUl.json +65 -65
- package/validation/tests/jobProperties/miniText.json +7 -2
- package/validation/tests/jobProperties/motion.json +4 -50
- package/validation/tests/jobProperties/nonTable.json +48 -3
- package/validation/tests/jobProperties/optRoleSel.json +12 -2
- package/validation/tests/jobProperties/phOnly.json +6 -16
- package/validation/tests/jobProperties/pseudoP.json +17 -2
- package/validation/tests/jobProperties/radioSet.json +30 -30
- package/validation/tests/jobProperties/role.json +25 -5
- package/validation/tests/jobProperties/secHeading.json +26 -21
- package/validation/tests/jobProperties/styleDiff.json +35 -5
- package/validation/tests/jobProperties/tabNav.json +3 -1
- package/validation/tests/jobProperties/targetsNear.json +237 -0
- package/validation/tests/jobProperties/textNodes.json +29 -29
- package/validation/tests/jobProperties/textSem.json +26 -1
- package/validation/tests/jobProperties/title.json +12 -2
- package/validation/tests/jobProperties/titledEl.json +39 -9
- package/validation/tests/jobProperties/userPath.json +317 -0
- package/validation/tests/jobProperties/zIndex.json +40 -35
- package/validation/tests/targets/allCapStyle/index.html +26 -0
- package/validation/tests/targets/checkpoint/index.html +38 -0
- package/validation/tests/targets/datalistRef/index.html +1 -1
- package/validation/tests/targets/focAndOp/bad.html +29 -0
- package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
- package/validation/tests/targets/focInd/bad.html +2 -1
- package/validation/tests/targets/headEl/index.html +10 -1
- package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
- package/validation/tests/targets/userPath/index.html +42 -0
- package/validation/validateTest.js +62 -10
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- package/memory/MEMORY.md +0 -3
- package/memory/project_validation_pause.md +0 -10
- package/validation/tests/jobProperties/linkTitle.json +0 -127
- package/validation/tests/jobProperties/opFoc.json +0 -164
- package/validation/tests/jobProperties/targetSmall.json +0 -152
- package/validation/tests/jobProperties/targetTiny.json +0 -142
- package/validation/tests/targets/focOp/bad.html +0 -25
- package/validation/tests/targets/linkTitle/index.html +0 -24
- package/validation/tests/targets/opFoc/bad.html +0 -26
- package/validation/tests/targets/opFoc/good.html +0 -23
package/procs/scope.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2026 Jeff Witt.
|
|
3
|
+
|
|
4
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
+
https://opensource.org/license/mit/ for details.
|
|
6
|
+
|
|
7
|
+
SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
scope.js
|
|
12
|
+
Finds what changed between two checkpoints of a report, by comparing their catalog entries,
|
|
13
|
+
and reduces the changes to the smallest set of subtree roots that contains them. The roots
|
|
14
|
+
serve test acts with scope: 'changed' (docs/checkpoint-scanning.md, Phase 3) and the
|
|
15
|
+
structure diffs of report.flow (Phase 2).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
|
|
20
|
+
// The most roots a changed-scope test act may be given; above this, the whole page is tested.
|
|
21
|
+
const maxRoots = exports.maxRoots = 50;
|
|
22
|
+
|
|
23
|
+
// FUNCTIONS
|
|
24
|
+
|
|
25
|
+
// Returns whether one XPath (as the catalog records them) is a strict descendant of another.
|
|
26
|
+
const isDescendant = (pathID, ancestorPathID) => pathID.startsWith(`${ancestorPathID}/`);
|
|
27
|
+
// Returns the XPath of the parent of an element, or '' for the root.
|
|
28
|
+
const parentOf = pathID => pathID.slice(0, pathID.lastIndexOf('/')) || '';
|
|
29
|
+
// Returns the catalog entries of a checkpoint, keyed by XPath.
|
|
30
|
+
const getEntriesByPath = (report, checkpointIndex) => {
|
|
31
|
+
const entries = {};
|
|
32
|
+
const checkpoint = (report.checkpoints ?? [])[checkpointIndex];
|
|
33
|
+
const range = checkpoint && checkpoint.catalogRange;
|
|
34
|
+
const catalog = report.catalog ?? {};
|
|
35
|
+
// Read the checkpoint's index range if recorded, else every entry stamped with the checkpoint.
|
|
36
|
+
const keys = range
|
|
37
|
+
? Array.from({length: range[1] - range[0] + 1}, (_, offset) => String(range[0] + offset))
|
|
38
|
+
: Object.keys(catalog).filter(key => catalog[key].checkpoint === checkpointIndex);
|
|
39
|
+
keys.forEach(key => {
|
|
40
|
+
const entry = catalog[key];
|
|
41
|
+
if (entry && entry.pathID && entry.checkpoint === checkpointIndex) {
|
|
42
|
+
entries[entry.pathID] = entry;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return entries;
|
|
46
|
+
};
|
|
47
|
+
/*
|
|
48
|
+
Converts an XPath of the form the catalog records (/html/body/main[1]/div[2]) to a CSS
|
|
49
|
+
selector (html > body > main:nth-of-type(1) > div:nth-of-type(2)). The conversion is exact
|
|
50
|
+
because window.getXPath subscripts each element among its same-tag siblings, which is what
|
|
51
|
+
nth-of-type counts. Returns '' if the XPath has an unexpected shape.
|
|
52
|
+
*/
|
|
53
|
+
const xPathToSelector = exports.xPathToSelector = pathID => {
|
|
54
|
+
if (typeof pathID !== 'string' || ! pathID.startsWith('/')) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
const segments = pathID.slice(1).split('/');
|
|
58
|
+
const parts = [];
|
|
59
|
+
for (const segment of segments) {
|
|
60
|
+
const match = segment.match(/^([a-z][a-z0-9-]*)(?:\[(\d+)\])?$/);
|
|
61
|
+
if (! match) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
parts.push(match[2] ? `${match[1]}:nth-of-type(${match[2]})` : match[1]);
|
|
65
|
+
}
|
|
66
|
+
return parts.join(' > ');
|
|
67
|
+
};
|
|
68
|
+
/*
|
|
69
|
+
Compares the catalog entries of two checkpoints and returns the difference:
|
|
70
|
+
added: XPaths present only in the later checkpoint
|
|
71
|
+
removed: XPaths present only in the earlier one
|
|
72
|
+
changed: XPaths present in both whose start tags differ
|
|
73
|
+
textChanged: XPaths present in both, with the same start tag, whose text differs
|
|
74
|
+
roots: the smallest set of XPaths, in the later checkpoint, whose subtrees contain every
|
|
75
|
+
change (the parent of a removed element stands for it; a text change counts only where
|
|
76
|
+
no descendant changed, because the catalog text of an element includes its descendants')
|
|
77
|
+
counts: the sizes of the above
|
|
78
|
+
*/
|
|
79
|
+
const getStructureDiff = exports.getStructureDiff = (report, fromIndex, toIndex) => {
|
|
80
|
+
const before = getEntriesByPath(report, fromIndex);
|
|
81
|
+
const after = getEntriesByPath(report, toIndex);
|
|
82
|
+
const added = [];
|
|
83
|
+
const removed = [];
|
|
84
|
+
const changed = [];
|
|
85
|
+
const textChanged = [];
|
|
86
|
+
Object.keys(after).forEach(pathID => {
|
|
87
|
+
const entry = after[pathID];
|
|
88
|
+
const priorEntry = before[pathID];
|
|
89
|
+
if (! priorEntry) {
|
|
90
|
+
added.push(pathID);
|
|
91
|
+
}
|
|
92
|
+
else if ((priorEntry.startTag ?? '') !== (entry.startTag ?? '')) {
|
|
93
|
+
changed.push(pathID);
|
|
94
|
+
}
|
|
95
|
+
else if ((priorEntry.text ?? '') !== (entry.text ?? '')) {
|
|
96
|
+
textChanged.push(pathID);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
Object.keys(before).forEach(pathID => {
|
|
100
|
+
if (! after[pathID]) {
|
|
101
|
+
removed.push(pathID);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Elements whose own change (not their descendants') marks them as changed.
|
|
105
|
+
const structural = new Set([... added, ... changed]);
|
|
106
|
+
removed.forEach(pathID => {
|
|
107
|
+
// The nearest surviving ancestor stands for a removed element.
|
|
108
|
+
let ancestor = parentOf(pathID);
|
|
109
|
+
while (ancestor && ! after[ancestor]) {
|
|
110
|
+
ancestor = parentOf(ancestor);
|
|
111
|
+
}
|
|
112
|
+
if (ancestor) {
|
|
113
|
+
structural.add(ancestor);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const allChanged = new Set([... structural, ... textChanged]);
|
|
117
|
+
// A text change counts only where no descendant changed at all.
|
|
118
|
+
textChanged.forEach(pathID => {
|
|
119
|
+
const hasChangedDescendant = Array.from(allChanged).some(other => isDescendant(other, pathID));
|
|
120
|
+
if (! hasChangedDescendant) {
|
|
121
|
+
structural.add(pathID);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Collapse the changed elements to their outermost members.
|
|
125
|
+
const roots = Array.from(structural)
|
|
126
|
+
.filter(pathID => ! Array.from(structural).some(other => other !== pathID && isDescendant(pathID, other)))
|
|
127
|
+
.sort();
|
|
128
|
+
return {
|
|
129
|
+
added,
|
|
130
|
+
removed,
|
|
131
|
+
changed,
|
|
132
|
+
textChanged,
|
|
133
|
+
roots,
|
|
134
|
+
counts: {
|
|
135
|
+
before: Object.keys(before).length,
|
|
136
|
+
after: Object.keys(after).length,
|
|
137
|
+
added: added.length,
|
|
138
|
+
removed: removed.length,
|
|
139
|
+
changed: changed.length,
|
|
140
|
+
textChanged: textChanged.length,
|
|
141
|
+
roots: roots.length
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
// Returns the XPath of the nearest common ancestor-or-self of XPaths.
|
|
146
|
+
const getCommonAncestor = exports.getCommonAncestor = pathIDs => {
|
|
147
|
+
if (! pathIDs.length) {
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
let common = pathIDs[0].split('/');
|
|
151
|
+
pathIDs.slice(1).forEach(pathID => {
|
|
152
|
+
const segments = pathID.split('/');
|
|
153
|
+
let length = 0;
|
|
154
|
+
while (length < common.length && length < segments.length && common[length] === segments[length]) {
|
|
155
|
+
length++;
|
|
156
|
+
}
|
|
157
|
+
common = common.slice(0, length);
|
|
158
|
+
});
|
|
159
|
+
return common.join('/') || '/html';
|
|
160
|
+
};
|
|
161
|
+
/*
|
|
162
|
+
Returns the subtree roots a changed-scope test act at a checkpoint is to test: the CSS
|
|
163
|
+
selectors of the roots of the structure diff between the checkpoint and the previous one.
|
|
164
|
+
applied is false, with a reason, when the act cannot be scoped: no previous checkpoint, no
|
|
165
|
+
change at all, too many roots (maxRoots), or a root whose XPath cannot become a selector.
|
|
166
|
+
*/
|
|
167
|
+
exports.getChangedRoots = (report, checkpointIndex) => {
|
|
168
|
+
if (! Number.isInteger(checkpointIndex) || checkpointIndex < 1) {
|
|
169
|
+
return {applied: false, reason: 'no previous checkpoint to compare', roots: [], pathIDs: []};
|
|
170
|
+
}
|
|
171
|
+
// Use the diff recorded when the checkpoint was made (the previous checkpoint's uncited
|
|
172
|
+
// entries were pruned then), else compute it.
|
|
173
|
+
const checkpoint = (report.checkpoints ?? [])[checkpointIndex];
|
|
174
|
+
const diff = checkpoint && checkpoint.structure
|
|
175
|
+
? checkpoint.structure
|
|
176
|
+
: getStructureDiff(report, checkpointIndex - 1, checkpointIndex);
|
|
177
|
+
const {roots: pathIDs} = diff;
|
|
178
|
+
if (! pathIDs.length) {
|
|
179
|
+
return {applied: false, reason: 'no change since the previous checkpoint', roots: [], pathIDs};
|
|
180
|
+
}
|
|
181
|
+
if (pathIDs.length > maxRoots) {
|
|
182
|
+
return {
|
|
183
|
+
applied: false,
|
|
184
|
+
reason: `${pathIDs.length} changed subtrees exceed the limit of ${maxRoots}`,
|
|
185
|
+
roots: [],
|
|
186
|
+
pathIDs
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const roots = pathIDs.map(xPathToSelector);
|
|
190
|
+
if (roots.some(root => ! root)) {
|
|
191
|
+
return {applied: false, reason: 'a changed subtree has no CSS selector', roots: [], pathIDs};
|
|
192
|
+
}
|
|
193
|
+
// The nearest ancestor of all the roots, for tools that take one root only.
|
|
194
|
+
const commonPathID = getCommonAncestor(pathIDs);
|
|
195
|
+
return {applied: true, reason: '', roots, pathIDs, commonRoot: xPathToSelector(commonPathID)};
|
|
196
|
+
};
|
package/procs/shoot.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2026 Jeff Witt.
|
|
3
|
+
|
|
4
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
+
https://opensource.org/license/mit/ for details.
|
|
6
|
+
|
|
7
|
+
SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
shoot (hand-written declarations)
|
|
12
|
+
Minimal typings for the surface of procs/shoot.js that converted TypeScript
|
|
13
|
+
files use. Delete this file when shoot.js itself is converted (issue #73).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {Page} from 'playwright';
|
|
17
|
+
import type {Report} from '../types';
|
|
18
|
+
|
|
19
|
+
export interface ShootOptions {
|
|
20
|
+
exclusionSelector?: string | null;
|
|
21
|
+
// Color fidelity: 0 (grayscale), 2 (RGB), 4 (grayscale alpha), 6 (RGBA).
|
|
22
|
+
colorType?: number;
|
|
23
|
+
// Disposition: return, report, file.
|
|
24
|
+
action?: 'return' | 'report' | 'file';
|
|
25
|
+
// Screenshot scale.
|
|
26
|
+
scale?: 'css' | 'device';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Makes a screenshot of a page and returns, reports, or files it. With the return action,
|
|
30
|
+
// the resolution is the base64 encoding of the screenshot, or a falsy value on failure.
|
|
31
|
+
export function shoot(
|
|
32
|
+
page: Page, report: Report, options?: ShootOptions
|
|
33
|
+
): Promise<string | null | undefined>;
|
package/procs/shoot.js
CHANGED
|
@@ -57,7 +57,9 @@ exports.shoot = async (page, report, {
|
|
|
57
57
|
scale = 'css'
|
|
58
58
|
} = {}) => {
|
|
59
59
|
// Make and get a screenshot as a buffer.
|
|
60
|
-
let shot = await screenShot(
|
|
60
|
+
let shot = await screenShot(
|
|
61
|
+
page, exclusionSelector ? page.locator(exclusionSelector) : null, scale
|
|
62
|
+
);
|
|
61
63
|
// If it succeeded:
|
|
62
64
|
if (shot.length) {
|
|
63
65
|
// Get the screenshot as an object representation of a PNG image.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Outcome, StandardInstance, StandardResult, UncertaintyCode } from '../types';
|
|
2
|
+
export interface InstanceSpec {
|
|
3
|
+
ruleID: string;
|
|
4
|
+
what: string;
|
|
5
|
+
ordinalSeverity: number;
|
|
6
|
+
outcome?: Outcome;
|
|
7
|
+
uncertainty?: string;
|
|
8
|
+
needed?: string;
|
|
9
|
+
count?: number;
|
|
10
|
+
catalogIndex?: string | number;
|
|
11
|
+
pathID?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const OUTCOMES: Outcome[];
|
|
14
|
+
export declare const UNCERTAINTY_CODES: UncertaintyCode[];
|
|
15
|
+
export declare const getStandardResult: () => StandardResult;
|
|
16
|
+
export declare const getInstance: (spec: InstanceSpec) => StandardInstance;
|
|
17
|
+
export declare const pushInstance: (standardResult: StandardResult, spec: InstanceSpec) => StandardInstance;
|
|
18
|
+
export declare const addInstance: (standardResult: StandardResult, spec: InstanceSpec) => StandardInstance;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
© 2026 Jeff Witt.
|
|
4
|
+
|
|
5
|
+
Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
|
|
6
|
+
|
|
7
|
+
SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.addInstance = exports.pushInstance = exports.getInstance = exports.getStandardResult = exports.UNCERTAINTY_CODES = exports.OUTCOMES = void 0;
|
|
11
|
+
// ########## CONSTANTS
|
|
12
|
+
// Outcomes of instances.
|
|
13
|
+
exports.OUTCOMES = ['failed', 'cantTell'];
|
|
14
|
+
// Reasons for cantTell outcomes.
|
|
15
|
+
exports.UNCERTAINTY_CODES = [
|
|
16
|
+
'not-computable',
|
|
17
|
+
'judgement-required',
|
|
18
|
+
'runtime-dependent',
|
|
19
|
+
'spec-only',
|
|
20
|
+
'equivalence-unknown',
|
|
21
|
+
'out-of-scope'
|
|
22
|
+
];
|
|
23
|
+
// Maximum length of a needed description.
|
|
24
|
+
const NEEDED_MAX_LENGTH = 300;
|
|
25
|
+
// ########## FUNCTIONS
|
|
26
|
+
// Returns an empty standard result.
|
|
27
|
+
const getStandardResult = () => ({
|
|
28
|
+
prevented: false,
|
|
29
|
+
totals: [0, 0, 0, 0],
|
|
30
|
+
outcomeTotals: { failed: 0, cantTell: 0 },
|
|
31
|
+
instances: []
|
|
32
|
+
});
|
|
33
|
+
exports.getStandardResult = getStandardResult;
|
|
34
|
+
// Returns a standard instance, validating its certainty properties.
|
|
35
|
+
const getInstance = (spec) => {
|
|
36
|
+
const { ruleID, what, ordinalSeverity, outcome = 'failed', uncertainty, needed, count = 1, catalogIndex, pathID } = spec;
|
|
37
|
+
if (!exports.OUTCOMES.includes(outcome)) {
|
|
38
|
+
throw new Error(`Invalid outcome ${outcome} for rule ${ruleID}`);
|
|
39
|
+
}
|
|
40
|
+
const instance = {
|
|
41
|
+
ruleID,
|
|
42
|
+
what,
|
|
43
|
+
ordinalSeverity: Number(ordinalSeverity),
|
|
44
|
+
outcome
|
|
45
|
+
};
|
|
46
|
+
// If the outcome is cantTell, add any reason and reviewer guidance.
|
|
47
|
+
if (outcome === 'cantTell') {
|
|
48
|
+
if (uncertainty) {
|
|
49
|
+
if (!exports.UNCERTAINTY_CODES.includes(uncertainty)) {
|
|
50
|
+
throw new Error(`Invalid uncertainty ${uncertainty} for rule ${ruleID}`);
|
|
51
|
+
}
|
|
52
|
+
instance.uncertainty = uncertainty;
|
|
53
|
+
}
|
|
54
|
+
if (needed) {
|
|
55
|
+
instance.needed = String(needed).replace(/\s+/g, ' ').trim().slice(0, NEEDED_MAX_LENGTH);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
instance.count = count;
|
|
59
|
+
if (catalogIndex !== undefined) {
|
|
60
|
+
instance.catalogIndex = catalogIndex;
|
|
61
|
+
}
|
|
62
|
+
else if (pathID) {
|
|
63
|
+
instance.pathID = pathID;
|
|
64
|
+
}
|
|
65
|
+
return instance;
|
|
66
|
+
};
|
|
67
|
+
exports.getInstance = getInstance;
|
|
68
|
+
// Adds an instance to a standard result and increments its outcome total.
|
|
69
|
+
const pushInstance = (standardResult, spec) => {
|
|
70
|
+
const instance = (0, exports.getInstance)(spec);
|
|
71
|
+
standardResult.outcomeTotals ??= { failed: 0, cantTell: 0 };
|
|
72
|
+
standardResult.outcomeTotals[instance.outcome] += instance.count;
|
|
73
|
+
standardResult.instances.push(instance);
|
|
74
|
+
return instance;
|
|
75
|
+
};
|
|
76
|
+
exports.pushInstance = pushInstance;
|
|
77
|
+
// Adds an instance to a standard result and increments its severity and outcome totals.
|
|
78
|
+
const addInstance = (standardResult, spec) => {
|
|
79
|
+
const instance = (0, exports.pushInstance)(standardResult, spec);
|
|
80
|
+
standardResult.totals[instance.ordinalSeverity] += instance.count;
|
|
81
|
+
return instance;
|
|
82
|
+
};
|
|
83
|
+
exports.addInstance = addInstance;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2026 Jeff Witt.
|
|
3
|
+
|
|
4
|
+
Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
|
|
5
|
+
|
|
6
|
+
SPDX-License-Identifier: MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ########## IMPORTS
|
|
10
|
+
|
|
11
|
+
import type {Outcome, StandardInstance, StandardResult, UncertaintyCode} from '../types';
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
standard
|
|
15
|
+
Utilities for building standard results. Compiled to standard.js by tsc (issue #73); edit
|
|
16
|
+
this file, not the emitted one.
|
|
17
|
+
|
|
18
|
+
A standard result has these properties:
|
|
19
|
+
prevented: whether the rule engine was prevented from performing the act
|
|
20
|
+
totals: array of 4 integers, counting instances at ordinal severities 0 through 3
|
|
21
|
+
outcomeTotals: object counting instances by outcome ({failed, cantTell})
|
|
22
|
+
instances: array of standard instances
|
|
23
|
+
|
|
24
|
+
A standard instance has these properties:
|
|
25
|
+
ruleID: ID of the violated rule
|
|
26
|
+
what: description of the rule or of the violation
|
|
27
|
+
ordinalSeverity: severity of the violation (0 to 3)
|
|
28
|
+
outcome: 'failed' if the rule engine asserted a violation, or 'cantTell' if it reported that it could not determine whether the rule was violated (ACT Rules Format vocabulary)
|
|
29
|
+
uncertainty?: reason for a cantTell outcome, if the rule engine gave one (one of UNCERTAINTY_CODES)
|
|
30
|
+
needed?: what a reviewer must determine to resolve a cantTell outcome, if the rule engine said
|
|
31
|
+
count: how many violations of the rule the instance reports
|
|
32
|
+
catalogIndex?: key of the violating element in the catalog
|
|
33
|
+
pathID?: normalized XPath of the violating element, if it has no catalog index
|
|
34
|
+
|
|
35
|
+
The outcome is the authoritative certainty signal. Until ordinalSeverity is redefined as impact only, each tool's ordinalSeverity conventions are unchanged, so consumers must not infer certainty from ordinalSeverity.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// ########## TYPES
|
|
39
|
+
|
|
40
|
+
// The properties from which a standard instance is built.
|
|
41
|
+
export interface InstanceSpec {
|
|
42
|
+
ruleID: string;
|
|
43
|
+
what: string;
|
|
44
|
+
ordinalSeverity: number;
|
|
45
|
+
outcome?: Outcome;
|
|
46
|
+
uncertainty?: string;
|
|
47
|
+
needed?: string;
|
|
48
|
+
count?: number;
|
|
49
|
+
catalogIndex?: string | number;
|
|
50
|
+
pathID?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ########## CONSTANTS
|
|
54
|
+
|
|
55
|
+
// Outcomes of instances.
|
|
56
|
+
export const OUTCOMES: Outcome[] = ['failed', 'cantTell'];
|
|
57
|
+
// Reasons for cantTell outcomes.
|
|
58
|
+
export const UNCERTAINTY_CODES: UncertaintyCode[] = [
|
|
59
|
+
'not-computable',
|
|
60
|
+
'judgement-required',
|
|
61
|
+
'runtime-dependent',
|
|
62
|
+
'spec-only',
|
|
63
|
+
'equivalence-unknown',
|
|
64
|
+
'out-of-scope'
|
|
65
|
+
];
|
|
66
|
+
// Maximum length of a needed description.
|
|
67
|
+
const NEEDED_MAX_LENGTH = 300;
|
|
68
|
+
|
|
69
|
+
// ########## FUNCTIONS
|
|
70
|
+
|
|
71
|
+
// Returns an empty standard result.
|
|
72
|
+
export const getStandardResult = (): StandardResult => ({
|
|
73
|
+
prevented: false,
|
|
74
|
+
totals: [0, 0, 0, 0],
|
|
75
|
+
outcomeTotals: {failed: 0, cantTell: 0},
|
|
76
|
+
instances: []
|
|
77
|
+
});
|
|
78
|
+
// Returns a standard instance, validating its certainty properties.
|
|
79
|
+
export const getInstance = (spec: InstanceSpec): StandardInstance => {
|
|
80
|
+
const {
|
|
81
|
+
ruleID, what, ordinalSeverity, outcome = 'failed', uncertainty, needed, count = 1, catalogIndex, pathID
|
|
82
|
+
} = spec;
|
|
83
|
+
if (! OUTCOMES.includes(outcome)) {
|
|
84
|
+
throw new Error(`Invalid outcome ${outcome} for rule ${ruleID}`);
|
|
85
|
+
}
|
|
86
|
+
const instance: StandardInstance = {
|
|
87
|
+
ruleID,
|
|
88
|
+
what,
|
|
89
|
+
ordinalSeverity: Number(ordinalSeverity) as StandardInstance['ordinalSeverity'],
|
|
90
|
+
outcome
|
|
91
|
+
};
|
|
92
|
+
// If the outcome is cantTell, add any reason and reviewer guidance.
|
|
93
|
+
if (outcome === 'cantTell') {
|
|
94
|
+
if (uncertainty) {
|
|
95
|
+
if (! (UNCERTAINTY_CODES as string[]).includes(uncertainty)) {
|
|
96
|
+
throw new Error(`Invalid uncertainty ${uncertainty} for rule ${ruleID}`);
|
|
97
|
+
}
|
|
98
|
+
instance.uncertainty = uncertainty as UncertaintyCode;
|
|
99
|
+
}
|
|
100
|
+
if (needed) {
|
|
101
|
+
instance.needed = String(needed).replace(/\s+/g, ' ').trim().slice(0, NEEDED_MAX_LENGTH);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
instance.count = count;
|
|
105
|
+
if (catalogIndex !== undefined) {
|
|
106
|
+
instance.catalogIndex = catalogIndex;
|
|
107
|
+
}
|
|
108
|
+
else if (pathID) {
|
|
109
|
+
instance.pathID = pathID;
|
|
110
|
+
}
|
|
111
|
+
return instance;
|
|
112
|
+
};
|
|
113
|
+
// Adds an instance to a standard result and increments its outcome total.
|
|
114
|
+
export const pushInstance = (standardResult: StandardResult, spec: InstanceSpec): StandardInstance => {
|
|
115
|
+
const instance = getInstance(spec);
|
|
116
|
+
standardResult.outcomeTotals ??= {failed: 0, cantTell: 0};
|
|
117
|
+
standardResult.outcomeTotals[instance.outcome!] += instance.count!;
|
|
118
|
+
standardResult.instances!.push(instance);
|
|
119
|
+
return instance;
|
|
120
|
+
};
|
|
121
|
+
// Adds an instance to a standard result and increments its severity and outcome totals.
|
|
122
|
+
export const addInstance = (standardResult: StandardResult, spec: InstanceSpec): StandardInstance => {
|
|
123
|
+
const instance = pushInstance(standardResult, spec);
|
|
124
|
+
standardResult.totals![instance.ordinalSeverity] += instance.count!;
|
|
125
|
+
return instance;
|
|
126
|
+
};
|
package/procs/testAct.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2026 Jeff Witt.
|
|
3
|
+
|
|
4
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
+
https://opensource.org/license/mit/ for details.
|
|
6
|
+
|
|
7
|
+
SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
testAct.js
|
|
12
|
+
Performs a test act: launches (or prepares) a page for the tool, runs the tool's reporter,
|
|
13
|
+
and records the act's data and result in the report. Used by the child process of process
|
|
14
|
+
isolation (doTestAct.js) and directly by the acts loop under browser and page isolation.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// IMPORTS
|
|
18
|
+
|
|
19
|
+
const {browserClose, launch, preparePage} = require('./launch');
|
|
20
|
+
|
|
21
|
+
// CONSTANTS
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
Tool XPath requirements.
|
|
25
|
+
none: Needs no script or extra load time.
|
|
26
|
+
own: Needs extra load time for its own XPath computations.
|
|
27
|
+
script: Needs the window.getXPath script.
|
|
28
|
+
attribute: Needs data-xpath attributes made with window.getXPath.
|
|
29
|
+
*/
|
|
30
|
+
const xPathNeeds = exports.xPathNeeds = {
|
|
31
|
+
alfa: 'own',
|
|
32
|
+
aslint: 'own',
|
|
33
|
+
axe: 'attribute',
|
|
34
|
+
ed11y: 'script',
|
|
35
|
+
htmlcs: 'attribute',
|
|
36
|
+
ibm: 'attribute',
|
|
37
|
+
nuVal: 'attribute',
|
|
38
|
+
nuVnu: 'attribute',
|
|
39
|
+
pour: 'script',
|
|
40
|
+
qualWeb: 'attribute',
|
|
41
|
+
surea11y: 'script',
|
|
42
|
+
wave: 'script'
|
|
43
|
+
};
|
|
44
|
+
const accessibleNameNeeders = ['testaro'];
|
|
45
|
+
|
|
46
|
+
// FUNCTIONS
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
Performs the test act at an index of a report and revises the report. Under page isolation,
|
|
50
|
+
livePage is the live page of the current checkpoint, which tools that take a page use as it
|
|
51
|
+
is (after preparation); otherwise each tool gets a page launched for it. The testaro tool
|
|
52
|
+
launches its own pages per rule (starting from livePage if given).
|
|
53
|
+
*/
|
|
54
|
+
exports.performTestAct = async ({report, actIndex, livePage = null}) => {
|
|
55
|
+
const act = report.acts[actIndex];
|
|
56
|
+
const {which} = act;
|
|
57
|
+
const useLivePage = Boolean(livePage) && report.jobData?.isolation === 'page';
|
|
58
|
+
let page;
|
|
59
|
+
// If the tool is not Testaro:
|
|
60
|
+
if (which !== 'testaro') {
|
|
61
|
+
// If the tool is to use the live page, prepare it for the tool.
|
|
62
|
+
if (useLivePage) {
|
|
63
|
+
page = livePage;
|
|
64
|
+
await preparePage(page, {
|
|
65
|
+
xPathNeed: xPathNeeds[which] ?? 'none',
|
|
66
|
+
needsAccessibleName: accessibleNameNeeders.includes(which)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Otherwise, launch a browser and navigate to the URL (replaying the checkpoint's acts).
|
|
70
|
+
else {
|
|
71
|
+
const browserID = act.launch && act.launch.browserID || report.browserID;
|
|
72
|
+
const targetURL = act.launch && act.launch.target && act.launch.target.url || report.target.url;
|
|
73
|
+
page = await launch({
|
|
74
|
+
report,
|
|
75
|
+
actIndex,
|
|
76
|
+
tempBrowserID: browserID,
|
|
77
|
+
tempURL: targetURL,
|
|
78
|
+
xPathNeed: xPathNeeds[which] ?? 'none',
|
|
79
|
+
needsAccessibleName: accessibleNameNeeders.includes(which)
|
|
80
|
+
});
|
|
81
|
+
// If the launch aborted the job, stop.
|
|
82
|
+
if (report.jobData?.aborted) {
|
|
83
|
+
await browserClose(page);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// If the page exists after the launch, or if the tool is Testaro:
|
|
89
|
+
if (page || which === 'testaro') {
|
|
90
|
+
try {
|
|
91
|
+
// Make the act reporter perform the specified tests of the tool.
|
|
92
|
+
const actReport = await require(`../tests/${which}`)
|
|
93
|
+
.reporter(useLivePage && which === 'testaro' ? livePage : page, report, actIndex, 65);
|
|
94
|
+
// Add the data and result to the act, keeping any checkpoint replay record the launch
|
|
95
|
+
// added to the act's data and any scope record the acts loop added (which the tool may
|
|
96
|
+
// have extended).
|
|
97
|
+
const {replay, scope} = act.data ?? {};
|
|
98
|
+
const toolScope = actReport.data && actReport.data.scope;
|
|
99
|
+
act.data = actReport.data ?? {};
|
|
100
|
+
if (replay) {
|
|
101
|
+
act.data.replay = replay;
|
|
102
|
+
}
|
|
103
|
+
if (scope) {
|
|
104
|
+
act.data.scope = {... scope, ... (toolScope ?? {})};
|
|
105
|
+
}
|
|
106
|
+
act.result = actReport.result;
|
|
107
|
+
// If the tool reported that the page prevented testing:
|
|
108
|
+
if (act.data && act.data.prevented) {
|
|
109
|
+
const {standardResult} = act.result;
|
|
110
|
+
// Add this to any standard result.
|
|
111
|
+
if (standardResult) {
|
|
112
|
+
standardResult.prevented = true;
|
|
113
|
+
}
|
|
114
|
+
// Add prevention data to the job data.
|
|
115
|
+
report.jobData.preventions[which] = act.data.error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// If the tool invocation failed:
|
|
119
|
+
catch(error) {
|
|
120
|
+
const message = error.message.slice(0, 400);
|
|
121
|
+
console.log(`ERROR: Test act ${act.which} failed (${message})`);
|
|
122
|
+
act.data ??= {};
|
|
123
|
+
act.data.prevented = true;
|
|
124
|
+
act.data.error = `ERROR performing the act (${message})`;
|
|
125
|
+
report.jobData.preventions[which] = act.data.error;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
// Close the page and its context, unless it is the live page.
|
|
130
|
+
if (page && page !== livePage) {
|
|
131
|
+
await browserClose(page);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Otherwise, i.e. if the page does not exist after the launch:
|
|
136
|
+
else {
|
|
137
|
+
// Add this to the act.
|
|
138
|
+
act.data ??= {};
|
|
139
|
+
act.data.prevented = true;
|
|
140
|
+
act.data.error = report.jobData?.abortMessage || 'No page';
|
|
141
|
+
// Add the prevention to the job data.
|
|
142
|
+
report.jobData.preventions[which] = act.data.error;
|
|
143
|
+
console.log('ERROR: No page');
|
|
144
|
+
}
|
|
145
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Locator, Page } from 'playwright';
|
|
2
|
+
import type { Outcome, Report, SeverityTotals, StandardInstance, UncertaintyCode } from '../types';
|
|
3
|
+
export interface RuleResult {
|
|
4
|
+
data: Record<string, unknown>;
|
|
5
|
+
totals: SeverityTotals;
|
|
6
|
+
standardInstances: StandardInstance[];
|
|
7
|
+
}
|
|
8
|
+
interface BasicViolation {
|
|
9
|
+
loc: Locator;
|
|
10
|
+
what: string;
|
|
11
|
+
outcome?: Outcome;
|
|
12
|
+
uncertainty?: UncertaintyCode;
|
|
13
|
+
needed?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const doTest: (page: Page, report: Report, withItems: boolean, ruleID: string, candidateSelector: string, whats: string, severity: number, getBadWhatString: string) => Promise<RuleResult>;
|
|
16
|
+
export declare const getBasicResult: (report: Report, withItems: boolean, ruleID: string, ordinalSeverity: StandardInstance["ordinalSeverity"], whats: string, data: {
|
|
17
|
+
prevented?: boolean;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}, violations: BasicViolation[]) => Promise<RuleResult>;
|
|
20
|
+
export {};
|