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/doTestAct.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2024–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jeff Witt.
|
|
3
4
|
© 2025–2026 Jonathan Robert Pool.
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
@@ -9,9 +10,10 @@
|
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
|
-
|
|
13
|
-
Performs the tests of an act.
|
|
14
|
-
This file is designed to be run as a child process
|
|
13
|
+
doTestAct
|
|
14
|
+
Performs the tests of an act, under process isolation.
|
|
15
|
+
This file is designed to be run as a child process: it reads the temporary report, performs
|
|
16
|
+
the act with procs/testAct.js, writes the revised report, and messages the parent.
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
// ERROR LOGGING
|
|
@@ -32,31 +34,8 @@ process.on('unhandledRejection', reason => {
|
|
|
32
34
|
|
|
33
35
|
// Module to perform file operations.
|
|
34
36
|
const fs = require('fs/promises');
|
|
35
|
-
//
|
|
36
|
-
const {
|
|
37
|
-
|
|
38
|
-
// CONSTANTS
|
|
39
|
-
|
|
40
|
-
/*
|
|
41
|
-
Tool XPath requirements.
|
|
42
|
-
none: Needs no script or extra load time.
|
|
43
|
-
own: Needs extra load time for its own XPath computations.
|
|
44
|
-
script: Needs the window.getXPath script.
|
|
45
|
-
attribute: Needs data-xpath attributes made with window.getXPath.
|
|
46
|
-
*/
|
|
47
|
-
const xPathNeeds = {
|
|
48
|
-
alfa: 'own',
|
|
49
|
-
aslint: 'own',
|
|
50
|
-
axe: 'attribute',
|
|
51
|
-
ed11y: 'script',
|
|
52
|
-
htmlcs: 'attribute',
|
|
53
|
-
ibm: 'attribute',
|
|
54
|
-
nuVal: 'attribute',
|
|
55
|
-
nuVnu: 'attribute',
|
|
56
|
-
qualWeb: 'attribute',
|
|
57
|
-
wave: 'script'
|
|
58
|
-
};
|
|
59
|
-
const accessibleNameNeeders = ['testaro'];
|
|
37
|
+
// Function to perform a test act.
|
|
38
|
+
const {performTestAct} = require('./testAct');
|
|
60
39
|
|
|
61
40
|
// FUNCTIONS
|
|
62
41
|
|
|
@@ -78,105 +57,32 @@ const doTestAct = async (reportPath, actIndex) => {
|
|
|
78
57
|
// Get the temporary report.
|
|
79
58
|
const reportJSON = await fs.readFile(reportPath, 'utf8');
|
|
80
59
|
const report = JSON.parse(reportJSON);
|
|
81
|
-
// Get a reference to the act in the report.
|
|
82
60
|
const act = report.acts[actIndex];
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const targetURL = act.launch && act.launch.target && act.launch.target.url || report.target.url;
|
|
90
|
-
// Launch a browser, navigate to the URL, and update the run-module page export.
|
|
91
|
-
page = await launch({
|
|
92
|
-
report,
|
|
93
|
-
actIndex,
|
|
94
|
-
tempBrowserID: browserID,
|
|
95
|
-
tempURL: targetURL,
|
|
96
|
-
xPathNeed: xPathNeeds[which] ?? 'none',
|
|
97
|
-
needsAccessibleName: accessibleNameNeeders.includes(which)
|
|
98
|
-
});
|
|
99
|
-
// If the launch aborted the job:
|
|
61
|
+
let status = 'ok';
|
|
62
|
+
let error = '';
|
|
63
|
+
try {
|
|
64
|
+
// Perform the act, revising the report.
|
|
65
|
+
await performTestAct({report, actIndex});
|
|
66
|
+
// If the launch aborted the job or no page existed, report that.
|
|
100
67
|
if (report.jobData?.aborted) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const reportJSON = JSON.stringify(report);
|
|
104
|
-
// Save the revised report.
|
|
105
|
-
await fs.writeFile(reportPath, reportJSON);
|
|
106
|
-
// Report this.
|
|
107
|
-
sendMessage({
|
|
108
|
-
status: 'error',
|
|
109
|
-
error: 'Page launch aborted'
|
|
110
|
-
});
|
|
111
|
-
process.exit(1);
|
|
68
|
+
status = 'error';
|
|
69
|
+
error = 'Page launch aborted';
|
|
112
70
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
// Make the act reporter perform the specified tests of the tool.
|
|
118
|
-
const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
|
|
119
|
-
// Add the data and result to the act.
|
|
120
|
-
act.data = actReport.data;
|
|
121
|
-
act.result = actReport.result;
|
|
122
|
-
// If the tool reported that the page prevented testing:
|
|
123
|
-
if (act.data && act.data.prevented) {
|
|
124
|
-
const {standardResult} = act.result;
|
|
125
|
-
// Add this to any standard result.
|
|
126
|
-
if (standardResult) {
|
|
127
|
-
standardResult.prevented = true;
|
|
128
|
-
}
|
|
129
|
-
// Add prevention data to the job data.
|
|
130
|
-
report.jobData.preventions[which] = act.data.error;
|
|
131
|
-
}
|
|
132
|
-
// Close the browser and its context, if they exist.
|
|
133
|
-
await browserClose(page);
|
|
134
|
-
const reportJSON = JSON.stringify(report);
|
|
135
|
-
// Save the revised report.
|
|
136
|
-
await fs.writeFile(reportPath, reportJSON);
|
|
137
|
-
// Send a completion message.
|
|
138
|
-
sendMessage({
|
|
139
|
-
status: 'ok',
|
|
140
|
-
});
|
|
141
|
-
process.exit(0);
|
|
71
|
+
else if (act.data?.prevented && act.data.error === 'No page') {
|
|
72
|
+
status = 'error';
|
|
73
|
+
error = 'ERROR: No page';
|
|
142
74
|
}
|
|
143
|
-
// If the tool invocation failed:
|
|
144
|
-
catch(error) {
|
|
145
|
-
// Close the browser and its context, if they exist.
|
|
146
|
-
await browserClose(page);
|
|
147
|
-
// Save the revised report.
|
|
148
|
-
const reportJSON = JSON.stringify(report);
|
|
149
|
-
await fs.writeFile(reportPath, reportJSON);
|
|
150
|
-
// Report the failure.
|
|
151
|
-
const message = error.message.slice(0, 400);
|
|
152
|
-
console.log(`ERROR: Test act ${act.which} failed (${message})`);
|
|
153
|
-
sendMessage({
|
|
154
|
-
status: 'error',
|
|
155
|
-
error: 'ERROR performing the act'
|
|
156
|
-
});
|
|
157
|
-
process.exit(1);
|
|
158
|
-
};
|
|
159
75
|
}
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
act.data.error = 'No page';
|
|
166
|
-
// Add the prevention to the job data.
|
|
167
|
-
report.jobData.preventions[which] = act.data.error;
|
|
168
|
-
const reportJSON = JSON.stringify(report);
|
|
169
|
-
// Save the revised report.
|
|
170
|
-
await fs.writeFile(reportPath, reportJSON);
|
|
171
|
-
// Report this.
|
|
172
|
-
const message = 'ERROR: No page';
|
|
173
|
-
console.log(message);
|
|
174
|
-
sendMessage({
|
|
175
|
-
status: 'error',
|
|
176
|
-
error: message
|
|
177
|
-
});
|
|
178
|
-
process.exit(1);
|
|
76
|
+
// If the tool invocation failed:
|
|
77
|
+
catch(err) {
|
|
78
|
+
console.log(`ERROR: Test act ${act.which} failed (${err.message.slice(0, 400)})`);
|
|
79
|
+
status = 'error';
|
|
80
|
+
error = 'ERROR performing the act';
|
|
179
81
|
}
|
|
82
|
+
// Save the revised report and report the outcome to the parent.
|
|
83
|
+
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
84
|
+
sendMessage(status === 'ok' ? {status} : {status, error});
|
|
85
|
+
process.exit(status === 'ok' ? 0 : 1);
|
|
180
86
|
};
|
|
181
87
|
process.on('uncaughtException', error => {
|
|
182
88
|
console.log(`ERROR: uncaughtException (${error.message})`);
|
package/procs/flow.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
flow.js
|
|
12
|
+
Builds report.flow: the running list of issues across a job's checkpoints. For each pair of
|
|
13
|
+
consecutive checkpoints it records which issues were added, persisted, and removed by the
|
|
14
|
+
acts between them, together with a structure diff of the two catalogs and a line diff of
|
|
15
|
+
the two ARIA snapshots. See docs/checkpoint-scanning.md (Phase 2). Runs on the unpruned
|
|
16
|
+
catalog at job end, and can run on a stored report whose catalog was pruned (the structure
|
|
17
|
+
diff then covers cited elements only).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// IMPORTS
|
|
21
|
+
|
|
22
|
+
const {diffLines} = require('diff');
|
|
23
|
+
const {getStructureDiff} = require('./scope');
|
|
24
|
+
|
|
25
|
+
// CONSTANTS
|
|
26
|
+
|
|
27
|
+
// The most changed lines an ARIA snapshot diff records.
|
|
28
|
+
const maxAriaChanges = 500;
|
|
29
|
+
|
|
30
|
+
// FUNCTIONS
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
Returns the identity of a standard instance for comparison across checkpoints:
|
|
34
|
+
tool, rule, element XPath, and start tag. The catalog index differs per checkpoint for the
|
|
35
|
+
same element, so the XPath stands for the element; the start tag distinguishes a replaced
|
|
36
|
+
element at the same path. Box and text are excluded because layout and copy shift without
|
|
37
|
+
the defect changing. A summary instance (no element) has an empty element key.
|
|
38
|
+
*/
|
|
39
|
+
const getIdentity = (tool, instance, catalog) => {
|
|
40
|
+
const entry = instance.catalogIndex !== undefined && instance.catalogIndex !== ''
|
|
41
|
+
? catalog[String(instance.catalogIndex)]
|
|
42
|
+
: null;
|
|
43
|
+
const pathID = entry ? entry.pathID ?? '' : instance.pathID ?? '';
|
|
44
|
+
const startTag = entry ? entry.startTag ?? '' : '';
|
|
45
|
+
return {
|
|
46
|
+
tool,
|
|
47
|
+
ruleID: instance.ruleID,
|
|
48
|
+
pathID,
|
|
49
|
+
startTag,
|
|
50
|
+
key: [tool, instance.ruleID, pathID, startTag].join('|')
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
// Returns whether an XPath is one of, or inside one of, the given root XPaths.
|
|
54
|
+
const isWithin = (pathID, roots) => roots.some(
|
|
55
|
+
root => pathID === root || pathID.startsWith(`${root}/`)
|
|
56
|
+
);
|
|
57
|
+
/*
|
|
58
|
+
Returns the issues found at a checkpoint, keyed by identity; the tools that observed it;
|
|
59
|
+
and, for each tool whose every act at the checkpoint was scoped to changed subtrees, the
|
|
60
|
+
XPaths of those subtrees (scopedRoots), since such a tool observed nothing outside them.
|
|
61
|
+
*/
|
|
62
|
+
const getCheckpointIssues = (report, checkpointIndex) => {
|
|
63
|
+
const catalog = report.catalog ?? {};
|
|
64
|
+
const issues = {};
|
|
65
|
+
const tools = new Set();
|
|
66
|
+
const unscopedTools = new Set();
|
|
67
|
+
const scopedRoots = {};
|
|
68
|
+
report.acts.forEach((act, actIndex) => {
|
|
69
|
+
if (act.type !== 'test' || act.checkpoint !== checkpointIndex) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// A prevented act observed nothing.
|
|
73
|
+
if (act.data && act.data.prevented) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const standardResult = act.result && act.result.standardResult;
|
|
77
|
+
if (! standardResult || standardResult.prevented) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
tools.add(act.which);
|
|
81
|
+
const scope = act.data && act.data.scope;
|
|
82
|
+
if (scope && scope.applied) {
|
|
83
|
+
scopedRoots[act.which] = [... (scopedRoots[act.which] ?? []), ... (scope.pathIDs ?? [])];
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
unscopedTools.add(act.which);
|
|
87
|
+
}
|
|
88
|
+
(standardResult.instances ?? []).forEach(instance => {
|
|
89
|
+
const identity = getIdentity(act.which, instance, catalog);
|
|
90
|
+
const issue = issues[identity.key] ??= {
|
|
91
|
+
tool: identity.tool,
|
|
92
|
+
ruleID: identity.ruleID,
|
|
93
|
+
pathID: identity.pathID,
|
|
94
|
+
startTag: identity.startTag,
|
|
95
|
+
what: instance.what,
|
|
96
|
+
ordinalSeverity: instance.ordinalSeverity,
|
|
97
|
+
outcome: instance.outcome,
|
|
98
|
+
count: 0,
|
|
99
|
+
actIndexes: []
|
|
100
|
+
};
|
|
101
|
+
issue.count += instance.count || 1;
|
|
102
|
+
if (! issue.actIndexes.includes(actIndex)) {
|
|
103
|
+
issue.actIndexes.push(actIndex);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
unscopedTools.forEach(tool => {
|
|
108
|
+
delete scopedRoots[tool];
|
|
109
|
+
});
|
|
110
|
+
return {issues, tools, scopedRoots};
|
|
111
|
+
};
|
|
112
|
+
// Returns a string ending with a newline, so the last line of a snapshot compares as a line.
|
|
113
|
+
const withFinalNewline = text => text && ! text.endsWith('\n') ? `${text}\n` : text;
|
|
114
|
+
// Returns a line diff of two ARIA snapshots: counts and the changed lines.
|
|
115
|
+
const getAriaDiff = (before, after) => {
|
|
116
|
+
const changes = [];
|
|
117
|
+
let addedLineCount = 0;
|
|
118
|
+
let removedLineCount = 0;
|
|
119
|
+
let truncated = false;
|
|
120
|
+
let line = 0;
|
|
121
|
+
diffLines(withFinalNewline(before ?? ''), withFinalNewline(after ?? '')).forEach(part => {
|
|
122
|
+
const lines = part.value.replace(/\n$/, '').split('\n');
|
|
123
|
+
if (part.added) {
|
|
124
|
+
addedLineCount += lines.length;
|
|
125
|
+
}
|
|
126
|
+
else if (part.removed) {
|
|
127
|
+
removedLineCount += lines.length;
|
|
128
|
+
}
|
|
129
|
+
if (part.added || part.removed) {
|
|
130
|
+
lines.forEach(text => {
|
|
131
|
+
if (changes.length < maxAriaChanges) {
|
|
132
|
+
changes.push({type: part.added ? 'added' : 'removed', line, text});
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
truncated = true;
|
|
136
|
+
}
|
|
137
|
+
if (part.added) {
|
|
138
|
+
line++;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
line += lines.length;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return {addedLineCount, removedLineCount, truncated, changes};
|
|
147
|
+
};
|
|
148
|
+
/*
|
|
149
|
+
Builds and returns the flow of a report, or null if the report has fewer than two
|
|
150
|
+
checkpoints. For each pair of consecutive checkpoints, the issues of the tools that observed
|
|
151
|
+
both are compared: added (only at the later), persisted (at both), removed (only at the
|
|
152
|
+
earlier). Tools that observed only one of the two are listed as notObserved, since their
|
|
153
|
+
issues cannot be compared. An earlier issue that a tool did not re-test at the later
|
|
154
|
+
checkpoint, because its acts there were all scoped to changed subtrees and the issue's
|
|
155
|
+
element lies outside them, is listed as notRetested rather than removed.
|
|
156
|
+
*/
|
|
157
|
+
exports.getFlow = report => {
|
|
158
|
+
const checkpoints = report.checkpoints ?? [];
|
|
159
|
+
if (checkpoints.length < 2) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const observations = checkpoints.map(checkpoint => getCheckpointIssues(report, checkpoint.index));
|
|
163
|
+
const summaries = checkpoints.map((checkpoint, index) => {
|
|
164
|
+
const {issues, tools} = observations[index];
|
|
165
|
+
return {
|
|
166
|
+
index: checkpoint.index,
|
|
167
|
+
name: checkpoint.name,
|
|
168
|
+
kind: checkpoint.kind,
|
|
169
|
+
url: checkpoint.url,
|
|
170
|
+
actIndex: checkpoint.actIndex,
|
|
171
|
+
testActs: checkpoint.testActs,
|
|
172
|
+
tools: Array.from(tools).sort(),
|
|
173
|
+
issueCount: Object.keys(issues).length
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
const deltas = [];
|
|
177
|
+
for (let index = 1; index < checkpoints.length; index++) {
|
|
178
|
+
const before = observations[index - 1];
|
|
179
|
+
const after = observations[index];
|
|
180
|
+
const tools = Array.from(before.tools).filter(tool => after.tools.has(tool)).sort();
|
|
181
|
+
const notObserved = Array.from(new Set([... before.tools, ... after.tools]))
|
|
182
|
+
.filter(tool => ! tools.includes(tool))
|
|
183
|
+
.sort();
|
|
184
|
+
const comparable = issue => tools.includes(issue.tool);
|
|
185
|
+
const added = [];
|
|
186
|
+
const persisted = [];
|
|
187
|
+
const removed = [];
|
|
188
|
+
const notRetested = [];
|
|
189
|
+
Object.keys(after.issues).forEach(key => {
|
|
190
|
+
const issue = after.issues[key];
|
|
191
|
+
if (comparable(issue)) {
|
|
192
|
+
(before.issues[key] ? persisted : added).push(issue);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
Object.keys(before.issues).forEach(key => {
|
|
196
|
+
const issue = before.issues[key];
|
|
197
|
+
if (comparable(issue) && ! after.issues[key]) {
|
|
198
|
+
const roots = after.scopedRoots[issue.tool];
|
|
199
|
+
const retested = ! roots || (issue.pathID && isWithin(issue.pathID, roots));
|
|
200
|
+
(retested ? removed : notRetested).push(issue);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// The structure diff recorded when the checkpoint was made, else one computed now (on a
|
|
204
|
+
// stored report, over the cited entries only).
|
|
205
|
+
const structure = checkpoints[index].structure
|
|
206
|
+
?? getStructureDiff(report, checkpoints[index - 1].index, checkpoints[index].index);
|
|
207
|
+
deltas.push({
|
|
208
|
+
from: checkpoints[index - 1].index,
|
|
209
|
+
to: checkpoints[index].index,
|
|
210
|
+
tools,
|
|
211
|
+
notObserved,
|
|
212
|
+
added,
|
|
213
|
+
persisted,
|
|
214
|
+
removed,
|
|
215
|
+
notRetested,
|
|
216
|
+
structure,
|
|
217
|
+
aria: getAriaDiff(checkpoints[index - 1].ariaSnapshot, checkpoints[index].ariaSnapshot)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return {checkpoints: summaries, deltas};
|
|
221
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
generateRuleRegistry
|
|
12
|
+
Build tool: writes testaro/registry.ts from the rule modules present in the
|
|
13
|
+
testaro directory (issue #73). Run with npm run build:registry; the CI
|
|
14
|
+
workflow regenerates the registry and fails on drift, so the committed
|
|
15
|
+
registry always matches the rule modules on disk.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// IMPORTS
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// CONSTANTS
|
|
24
|
+
|
|
25
|
+
const ruleDir = path.join(__dirname, '..', 'testaro');
|
|
26
|
+
const registryPath = path.join(ruleDir, 'registry.ts');
|
|
27
|
+
|
|
28
|
+
// OPERATION
|
|
29
|
+
|
|
30
|
+
// Get the rule IDs: every TypeScript rule module in the directory.
|
|
31
|
+
const ruleIDs = fs.readdirSync(ruleDir)
|
|
32
|
+
.filter(name => name.endsWith('.ts') && ! name.endsWith('.d.ts'))
|
|
33
|
+
.map(name => name.replace(/\.ts$/, ''))
|
|
34
|
+
.filter(name => name !== 'registry')
|
|
35
|
+
.sort();
|
|
36
|
+
// Compose the registry source.
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(
|
|
39
|
+
'/*',
|
|
40
|
+
' © 2026 Jeff Witt.',
|
|
41
|
+
'',
|
|
42
|
+
' Licensed under the MIT License. See LICENSE file at the project root or',
|
|
43
|
+
' https://opensource.org/license/mit/ for details.',
|
|
44
|
+
'',
|
|
45
|
+
' SPDX-License-Identifier: MIT',
|
|
46
|
+
'*/',
|
|
47
|
+
'',
|
|
48
|
+
'/*',
|
|
49
|
+
' registry',
|
|
50
|
+
' GENERATED by procs/generateRuleRegistry.js (npm run build:registry); do not edit.',
|
|
51
|
+
' Maps each rule ID to a lazy loader of its module, preserving on-demand loading',
|
|
52
|
+
' while giving consumers a checked RuleID union and a typed reporter (issue #73).',
|
|
53
|
+
'*/',
|
|
54
|
+
'',
|
|
55
|
+
'// TYPES',
|
|
56
|
+
'',
|
|
57
|
+
'// The report every rule reporter resolves with.',
|
|
58
|
+
'export interface RuleReport {',
|
|
59
|
+
' data: unknown;',
|
|
60
|
+
' totals: number[];',
|
|
61
|
+
' standardInstances: unknown[];',
|
|
62
|
+
'}',
|
|
63
|
+
'// The module shape of a rule.',
|
|
64
|
+
'export interface RuleModule {',
|
|
65
|
+
' reporter: (...args: any[]) => Promise<RuleReport>;',
|
|
66
|
+
'}',
|
|
67
|
+
'',
|
|
68
|
+
'// The IDs of all rule modules.',
|
|
69
|
+
'export type RuleID =');
|
|
70
|
+
ruleIDs.forEach((id, index) => {
|
|
71
|
+
lines.push(` | '${id}'${index === ruleIDs.length - 1 ? ';' : ''}`);
|
|
72
|
+
});
|
|
73
|
+
lines.push(
|
|
74
|
+
'',
|
|
75
|
+
'// CONSTANTS',
|
|
76
|
+
'',
|
|
77
|
+
'// Lazy loaders of the rule modules, keyed by rule ID.',
|
|
78
|
+
'export const ruleModules = {');
|
|
79
|
+
ruleIDs.forEach(id => {
|
|
80
|
+
lines.push(` ${id}: () => require('./${id}') as typeof import('./${id}'),`);
|
|
81
|
+
});
|
|
82
|
+
lines.push(
|
|
83
|
+
'} satisfies Record<RuleID, () => RuleModule>;',
|
|
84
|
+
'');
|
|
85
|
+
// Write the registry if it changed.
|
|
86
|
+
const source = lines.join('\n');
|
|
87
|
+
const existing = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, 'utf8') : '';
|
|
88
|
+
if (source !== existing) {
|
|
89
|
+
fs.writeFileSync(registryPath, source);
|
|
90
|
+
console.log(`Wrote ${registryPath} with ${ruleIDs.length} rules`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log(`Registry is current (${ruleIDs.length} rules)`);
|
|
94
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
getSource (hand-written declarations)
|
|
12
|
+
Minimal typings for the surface of procs/getSource.js that converted
|
|
13
|
+
TypeScript files use. Delete this file when getSource.js itself is converted
|
|
14
|
+
(issue #73).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {Page} from 'playwright';
|
|
18
|
+
|
|
19
|
+
export interface SourceData {
|
|
20
|
+
prevented: boolean;
|
|
21
|
+
source: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Gets the source of the document of a page.
|
|
26
|
+
export function getSource(page: Page): Promise<SourceData>;
|
package/procs/getSource.js
CHANGED