testaro 78.0.8 → 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 +24 -18
- package/CONTAINERS.md +9 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +73 -19
- 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 +372 -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 +312 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +35 -0
- package/procs/config.js +62 -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/job.js +115 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +239 -108
- package/procs/nu.d.ts +48 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- 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 +236 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +81 -69
- 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 +38 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +31 -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 +29 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +30 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +27 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +20 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +59 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +32 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +317 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +17 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +33 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +57 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +25 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +113 -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 +19 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +185 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +100 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +64 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +28 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +61 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +54 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +163 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +155 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +14 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +17 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +31 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +17 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +43 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +79 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +13 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +25 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +22 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +30 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +41 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +92 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +39 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +16 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +18 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +36 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +57 -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 +27 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +30 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +248 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +272 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +130 -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 +25 -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 +15 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +19 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +138 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +272 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +199 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +141 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +174 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +52 -0
- package/tests/ibm.js +164 -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 +141 -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 +302 -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 +745 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +166 -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 +61 -9
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- 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/launch.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
// IMPORTS
|
|
18
18
|
|
|
19
19
|
const {addError} = require('./error');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
20
22
|
const {posix: posixPath} = require('path');
|
|
21
23
|
const headedBrowser = process.env.HEADED_BROWSER === 'true';
|
|
22
24
|
// Two flavors of Playwright:
|
|
@@ -30,6 +32,10 @@ const headedBrowser = process.env.HEADED_BROWSER === 'true';
|
|
|
30
32
|
const playwrightCore = require('playwright');
|
|
31
33
|
const playwrightExtra = require('playwright-extra');
|
|
32
34
|
const {isBrowserID, isDeviceID, isURL, isValidJob} = require('./job');
|
|
35
|
+
// The in-page script defining window.getXPath.
|
|
36
|
+
const {getXPathSource} = require('./xPathScript');
|
|
37
|
+
// Deployment options (navigation, scanner identity, pre-scan scroll).
|
|
38
|
+
const {getNavigation, getScannerId, getScroll, waitStates} = require('./config');
|
|
33
39
|
|
|
34
40
|
// CONSTANTS
|
|
35
41
|
|
|
@@ -81,6 +87,43 @@ const wait = exports.wait = ms => {
|
|
|
81
87
|
// Removes any trailing slashes from a URL, for redirection comparison.
|
|
82
88
|
const deSlash = url => (url || '').replace(/\/+$/, '');
|
|
83
89
|
// Close a browser context and/or its browser, if they exist.
|
|
90
|
+
// Grace period for browser-close operations. Chromium can acknowledge a page
|
|
91
|
+
// close without performing it when the close races a navigation commit
|
|
92
|
+
// (https://issues.chromium.org/issues/536385539, observed on pages that
|
|
93
|
+
// navigate via meta refresh during testing); the close promise then never
|
|
94
|
+
// settles — it neither resolves nor rejects — so an unguarded await would
|
|
95
|
+
// hang the act forever. Errors are discarded, as before.
|
|
96
|
+
const CLOSE_GRACE_MS = 10000;
|
|
97
|
+
const settleWithin = promise => Promise.race([
|
|
98
|
+
promise.catch(error => {}),
|
|
99
|
+
new Promise(resolve => {
|
|
100
|
+
const timer = setTimeout(resolve, CLOSE_GRACE_MS);
|
|
101
|
+
// Do not keep the process alive for an abandoned close.
|
|
102
|
+
if (typeof timer.unref === 'function') {
|
|
103
|
+
timer.unref();
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
]);
|
|
107
|
+
/*
|
|
108
|
+
The browser shared by the launches of a job under browser or page isolation, if any. When set,
|
|
109
|
+
launchOnce creates contexts in it instead of launching browsers, and browserClose closes only
|
|
110
|
+
the context of a page it owns. The job closes it with closeSharedBrowser at its end.
|
|
111
|
+
*/
|
|
112
|
+
let sharedBrowser = null;
|
|
113
|
+
let sharedBrowserID = '';
|
|
114
|
+
exports.setSharedBrowser = (browser, browserID) => {
|
|
115
|
+
sharedBrowser = browser;
|
|
116
|
+
sharedBrowserID = browserID;
|
|
117
|
+
};
|
|
118
|
+
exports.getSharedBrowser = () => sharedBrowser;
|
|
119
|
+
exports.closeSharedBrowser = async () => {
|
|
120
|
+
if (sharedBrowser) {
|
|
121
|
+
const browser = sharedBrowser;
|
|
122
|
+
sharedBrowser = null;
|
|
123
|
+
sharedBrowserID = '';
|
|
124
|
+
await settleWithin(browser.close());
|
|
125
|
+
}
|
|
126
|
+
};
|
|
84
127
|
const browserClose = exports.browserClose = async page => {
|
|
85
128
|
if (page) {
|
|
86
129
|
// Get the context (i.e. window) of the page and the browser of the context. These are methods, not properties; referencing them as properties made this function silently fail to close anything, because a function object has no close method and the resulting TypeError was caught and discarded.
|
|
@@ -88,15 +131,10 @@ const browserClose = exports.browserClose = async page => {
|
|
|
88
131
|
if (browserContext) {
|
|
89
132
|
// The browser is null for a context not owned by a browser, such as a persistent context.
|
|
90
133
|
const browser = browserContext.browser();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (browser) {
|
|
96
|
-
try {
|
|
97
|
-
await browser.close();
|
|
98
|
-
}
|
|
99
|
-
catch(error) {}
|
|
134
|
+
await settleWithin(browserContext.close());
|
|
135
|
+
// Close the browser too, unless the job shares it.
|
|
136
|
+
if (browser && browser !== sharedBrowser) {
|
|
137
|
+
await settleWithin(browser.close());
|
|
100
138
|
}
|
|
101
139
|
}
|
|
102
140
|
}
|
|
@@ -130,10 +168,18 @@ const normalizeURL = url => {
|
|
|
130
168
|
};
|
|
131
169
|
// Visits a URL and returns the response of the server.
|
|
132
170
|
const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
133
|
-
// If the URL is
|
|
171
|
+
// If the URL is that of a local file:
|
|
134
172
|
if (url.startsWith('file://')) {
|
|
135
|
-
|
|
136
|
-
|
|
173
|
+
const filePath = url.replace(/^file:\/+/, '/');
|
|
174
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
175
|
+
// If the path is not absolute (inside the project or existing on disk), it is relative to
|
|
176
|
+
// the project root (file://validation/…), as job files write it: make it absolute.
|
|
177
|
+
if (! filePath.startsWith(`${projectRoot}/`) && ! fs.existsSync(filePath)) {
|
|
178
|
+
url = `file://${projectRoot}/${filePath.replace(/^\/+/, '')}`;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
url = `file://${filePath}`;
|
|
182
|
+
}
|
|
137
183
|
}
|
|
138
184
|
// Visit the URL.
|
|
139
185
|
const startTime = Date.now();
|
|
@@ -209,8 +255,8 @@ const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
209
255
|
let waitSeconds = 5;
|
|
210
256
|
if (retryHeader) {
|
|
211
257
|
waitSeconds = Number.isNaN(Number(retryHeader))
|
|
212
|
-
|
|
213
|
-
|
|
258
|
+
? Math.ceil((new Date(retryHeader) - new Date()) / 1000)
|
|
259
|
+
: Number(retryHeader);
|
|
214
260
|
}
|
|
215
261
|
// Return this.
|
|
216
262
|
console.log(
|
|
@@ -272,6 +318,63 @@ const getNonce = exports.getNonce = async response => {
|
|
|
272
318
|
// Return the nonce, if any.
|
|
273
319
|
return nonce;
|
|
274
320
|
};
|
|
321
|
+
// Path of the dom-accessibility-api bundle.
|
|
322
|
+
const nameComputationPath = require.resolve('../dist/nameComputation.js');
|
|
323
|
+
// Defines the accessible-name window methods in the page. Runs inside the page: closure-free.
|
|
324
|
+
const installAccessibleName = () => {
|
|
325
|
+
// Add a window method to compute the accessible name of an element.
|
|
326
|
+
window.getAccessibleName = element => {
|
|
327
|
+
const nameIsComputable = element?.nodeType === Node.ELEMENT_NODE
|
|
328
|
+
&& typeof window.computeAccessibleName === 'function';
|
|
329
|
+
return nameIsComputable ? window.computeAccessibleName(element) : '';
|
|
330
|
+
};
|
|
331
|
+
// Add a window method to return a standard proto-instance.
|
|
332
|
+
window.getProtoInstance = (
|
|
333
|
+
element, ruleID, what, count = 1, ordinalSeverity, summaryTagName = '', outcome = 'failed'
|
|
334
|
+
) => {
|
|
335
|
+
// If an element has been specified:
|
|
336
|
+
if (element) {
|
|
337
|
+
// Get its properties.
|
|
338
|
+
return {
|
|
339
|
+
ruleID,
|
|
340
|
+
what,
|
|
341
|
+
count,
|
|
342
|
+
ordinalSeverity,
|
|
343
|
+
outcome,
|
|
344
|
+
pathID: window.getXPath(element)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Otherwise, i.e. if no element has been specified, return a summary instance.
|
|
348
|
+
return {
|
|
349
|
+
ruleID,
|
|
350
|
+
what,
|
|
351
|
+
count,
|
|
352
|
+
ordinalSeverity,
|
|
353
|
+
outcome
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
const accessibleNameSource = `(${installAccessibleName.toString()})();`;
|
|
358
|
+
// Prepares an already loaded page (the live page of a checkpoint, under page isolation) for a
|
|
359
|
+
// tool, as launchOnce prepares a page it creates: XPath script or attributes, and accessible
|
|
360
|
+
// names. Evaluates scripts rather than adding script elements, so the DOM is unchanged.
|
|
361
|
+
exports.preparePage = async (page, {xPathNeed = 'script', needsAccessibleName = false} = {}) => {
|
|
362
|
+
if (xPathNeed !== 'none') {
|
|
363
|
+
await page.evaluate(getXPathSource);
|
|
364
|
+
}
|
|
365
|
+
if (xPathNeed === 'attribute') {
|
|
366
|
+
await page.evaluate(() => {
|
|
367
|
+
document.querySelectorAll('*').forEach(element => {
|
|
368
|
+
element.setAttribute('data-xpath', window.getXPath(element));
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (needsAccessibleName) {
|
|
373
|
+
const nameComputationSource = await fs.promises.readFile(nameComputationPath, 'utf8');
|
|
374
|
+
await page.evaluate(nameComputationSource);
|
|
375
|
+
await page.evaluate(accessibleNameSource);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
275
378
|
// Creates a browser, context, and page; navigates to a URL; and returns the page.
|
|
276
379
|
const launchOnce = async opts => {
|
|
277
380
|
// Get the arguments.
|
|
@@ -286,7 +389,9 @@ const launchOnce = async opts => {
|
|
|
286
389
|
needsAccessibleName = false,
|
|
287
390
|
// Extra Playwright context options (e.g. deviceScaleFactor for device-pixel page
|
|
288
391
|
// images), spread last so they win over the defaults.
|
|
289
|
-
contextOverrides = {}
|
|
392
|
+
contextOverrides = {},
|
|
393
|
+
// Whether to replay the active checkpoint's acts after navigating (test-act launches).
|
|
394
|
+
replay = true
|
|
290
395
|
} = opts;
|
|
291
396
|
const act = report.acts[actIndex] ?? {};
|
|
292
397
|
const {device} = report;
|
|
@@ -360,10 +465,29 @@ const launchOnce = async opts => {
|
|
|
360
465
|
// Disable the sandbox.
|
|
361
466
|
browserOptions.chromiumSandbox = false;
|
|
362
467
|
}
|
|
468
|
+
// If the job specifies a branded Chromium channel (chrome or msedge), run that installed
|
|
469
|
+
// browser instead of the bundled Chromium build; bundled or absent keeps the default.
|
|
470
|
+
const {browserChannel} = report;
|
|
471
|
+
if (browserID === 'chromium' && browserChannel && browserChannel !== 'bundled') {
|
|
472
|
+
browserOptions.channel = browserChannel;
|
|
473
|
+
}
|
|
363
474
|
let browser, browserContext;
|
|
364
475
|
try {
|
|
365
|
-
//
|
|
366
|
-
browser
|
|
476
|
+
// Use the job's shared browser if it exists and is of the specified type; otherwise
|
|
477
|
+
// create a browser of the specified type, and share it if the job shares browsers.
|
|
478
|
+
if (sharedBrowser && sharedBrowserID === browserID && sharedBrowser.isConnected()) {
|
|
479
|
+
browser = sharedBrowser;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
browser = await browserType.launch(browserOptions);
|
|
483
|
+
if (report.jobData?.isolation && report.jobData.isolation !== 'process') {
|
|
484
|
+
if (sharedBrowser) {
|
|
485
|
+
await settleWithin(sharedBrowser.close());
|
|
486
|
+
}
|
|
487
|
+
sharedBrowser = browser;
|
|
488
|
+
sharedBrowserID = browserID;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
367
491
|
// Create a context (i.e. window) for it.
|
|
368
492
|
const contextOptions = {
|
|
369
493
|
...device.windowOptions,
|
|
@@ -377,7 +501,9 @@ const launchOnce = async opts => {
|
|
|
377
501
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
378
502
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
379
503
|
'DNT': '1',
|
|
380
|
-
'Upgrade-Insecure-Requests': '1'
|
|
504
|
+
'Upgrade-Insecure-Requests': '1',
|
|
505
|
+
// A stable scanner identity for firewalls, if configured (job scannerId or SCANNER_ID).
|
|
506
|
+
...(getScannerId(report) ? {'X-YRA-Scanner': getScannerId(report)} : {})
|
|
381
507
|
},
|
|
382
508
|
// Caller-specified context options (see contextOverrides above).
|
|
383
509
|
...contextOverrides
|
|
@@ -445,99 +571,76 @@ const launchOnce = async opts => {
|
|
|
445
571
|
});
|
|
446
572
|
// If an XPath computation script is required:
|
|
447
573
|
if (xPathNeed !== 'none') {
|
|
448
|
-
// Add
|
|
449
|
-
await page.addInitScript(
|
|
450
|
-
window.getXPath = element => {
|
|
451
|
-
if (! element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
452
|
-
return '';
|
|
453
|
-
}
|
|
454
|
-
const segments = [];
|
|
455
|
-
// As long as the current node is an element:
|
|
456
|
-
while (element && element.nodeType === Node.ELEMENT_NODE) {
|
|
457
|
-
const tag = element.tagName.toLowerCase();
|
|
458
|
-
// If it is the html element:
|
|
459
|
-
if (element === document.documentElement) {
|
|
460
|
-
// Prepend it to the segment array
|
|
461
|
-
segments.unshift('html');
|
|
462
|
-
// Stop traversing.
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
// Otherwise, get its parent node.
|
|
466
|
-
const parent = element.parentNode;
|
|
467
|
-
// If (abnormally) the parent node is not an element:
|
|
468
|
-
if (! parent || parent.nodeType !== Node.ELEMENT_NODE) {
|
|
469
|
-
// Prepend the element (not the parent) to the segment array.
|
|
470
|
-
segments.unshift(tag);
|
|
471
|
-
// Stop traversing, leaving the segment array partial.
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
// Get the subscript of the element if it is not the body element.
|
|
475
|
-
const cohort = Array
|
|
476
|
-
.from(parent.childNodes)
|
|
477
|
-
.filter(
|
|
478
|
-
childNode => childNode.nodeType === Node.ELEMENT_NODE
|
|
479
|
-
&& childNode.tagName === element.tagName
|
|
480
|
-
);
|
|
481
|
-
const subscript = tag === 'body' ? '' : `[${cohort.indexOf(element) + 1}]`;
|
|
482
|
-
// Prepend the element identifier to the segment array.
|
|
483
|
-
segments.unshift(`${tag}${subscript}`);
|
|
484
|
-
// Continue the traversal with the parent of the current element.
|
|
485
|
-
element = parent;
|
|
486
|
-
}
|
|
487
|
-
// Return the XPath.
|
|
488
|
-
return `/${segments.join('/')}`;
|
|
489
|
-
};
|
|
490
|
-
});
|
|
574
|
+
// Add the shared script to the page to add a window method to get the XPath of an element.
|
|
575
|
+
await page.addInitScript({content: getXPathSource});
|
|
491
576
|
}
|
|
492
577
|
// If an accessible-name computation script is needed:
|
|
493
578
|
if (needsAccessibleName) {
|
|
494
579
|
// Add the dom-accessibility-api script to the page to compute an accessible name.
|
|
495
|
-
await page.addInitScript({path:
|
|
496
|
-
// Add
|
|
497
|
-
await page.addInitScript(
|
|
498
|
-
// Add a window method to compute the accessible name of an element.
|
|
499
|
-
window.getAccessibleName = element => {
|
|
500
|
-
const nameIsComputable = element?.nodeType === Node.ELEMENT_NODE
|
|
501
|
-
&& typeof window.computeAccessibleName === 'function';
|
|
502
|
-
return nameIsComputable ? window.computeAccessibleName(element) : '';
|
|
503
|
-
};
|
|
504
|
-
// Add a window method to return a standard proto-instance.
|
|
505
|
-
window.getProtoInstance = (
|
|
506
|
-
element, ruleID, what, count = 1, ordinalSeverity, summaryTagName = ''
|
|
507
|
-
) => {
|
|
508
|
-
// If an element has been specified:
|
|
509
|
-
if (element) {
|
|
510
|
-
// Get its properties.
|
|
511
|
-
return {
|
|
512
|
-
ruleID,
|
|
513
|
-
what,
|
|
514
|
-
count,
|
|
515
|
-
ordinalSeverity,
|
|
516
|
-
pathID: window.getXPath(element)
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
// Otherwise, i.e. if no element has been specified, return a summary instance.
|
|
520
|
-
return {
|
|
521
|
-
ruleID,
|
|
522
|
-
what,
|
|
523
|
-
count,
|
|
524
|
-
ordinalSeverity
|
|
525
|
-
};
|
|
526
|
-
};
|
|
527
|
-
});
|
|
580
|
+
await page.addInitScript({path: nameComputationPath});
|
|
581
|
+
// Add the script defining the accessible-name window methods.
|
|
582
|
+
await page.addInitScript({content: accessibleNameSource});
|
|
528
583
|
}
|
|
529
|
-
// Base the wait on the need of the tool
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
584
|
+
// Base the wait on the need of the tool, the configured load state (job navigation
|
|
585
|
+
// property or NAV_WAIT_UNTIL; networkidle by default), and the retry history: a partial
|
|
586
|
+
// relaxation steps one load state looser, a full one waits only for domcontentloaded.
|
|
587
|
+
const navigation = getNavigation(report);
|
|
588
|
+
let waitUntil = xPathNeed === 'none' ? 'domcontentloaded' : navigation.waitUntil;
|
|
589
|
+
if (relaxWait === 'partly') {
|
|
590
|
+
waitUntil = waitStates[Math.min(waitStates.indexOf(waitUntil) + 1, waitStates.length - 1)];
|
|
533
591
|
}
|
|
534
592
|
if (relaxWait === 'fully') {
|
|
535
593
|
waitUntil = 'domcontentloaded';
|
|
536
594
|
}
|
|
537
595
|
// Navigate to the specified URL and wait for the stability required by the next action.
|
|
538
|
-
const navResult = await goTo(report, page, url,
|
|
596
|
+
const navResult = await goTo(report, page, url, navigation.timeout, waitUntil);
|
|
539
597
|
// If the navigation succeeded:
|
|
540
598
|
if (navResult.success) {
|
|
599
|
+
// If the job asks for a full-height scroll (job scroll property or PRESCAN_SCROLL), scroll
|
|
600
|
+
// the page in viewport steps so lazily loaded content is present before cataloguing,
|
|
601
|
+
// imaging, and each tool, then return to the top. Bounded in steps so an infinite scroll
|
|
602
|
+
// cannot hang the launch; never throws.
|
|
603
|
+
if (getScroll(report)) {
|
|
604
|
+
try {
|
|
605
|
+
const viewportHeight = (await page.evaluate(() => window.innerHeight)) || 800;
|
|
606
|
+
for (let step = 0, y = 0; step < 25; step++, y += viewportHeight) {
|
|
607
|
+
const atBottom = await page.evaluate(dy => {
|
|
608
|
+
window.scrollTo(0, dy);
|
|
609
|
+
return dy + window.innerHeight >= document.body.scrollHeight;
|
|
610
|
+
}, y);
|
|
611
|
+
await page.waitForTimeout(120);
|
|
612
|
+
if (atBottom) {
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
await page.evaluate(() => window.scrollTo(0, 0));
|
|
617
|
+
await page.waitForTimeout(250);
|
|
618
|
+
}
|
|
619
|
+
catch(error) {
|
|
620
|
+
console.log(`ERROR: Pre-scan scroll failed (${error.message})`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// If the launch is for a test act at a checkpoint reached by interaction:
|
|
624
|
+
const checkpoint = report.checkpoints?.[report.activeCheckpoint];
|
|
625
|
+
if (checkpoint && checkpoint.kind === 'interaction' && replay) {
|
|
626
|
+
// Re-enact the acts that reached the checkpoint, before any XPath stamping, so that
|
|
627
|
+
// elements the acts reveal or create are stamped like the rest. A failure closes
|
|
628
|
+
// the page and reports the act, so launch() can retry or give up.
|
|
629
|
+
const {replayActs} = require('./actDo');
|
|
630
|
+
const {getDomDigest} = require('./checkpoint');
|
|
631
|
+
const replayResult = await replayActs(page, report, checkpoint);
|
|
632
|
+
page = replayResult.page;
|
|
633
|
+
const digest = await getDomDigest(page);
|
|
634
|
+
act.data ??= {};
|
|
635
|
+
act.data.replay = {
|
|
636
|
+
checkpoint: checkpoint.index,
|
|
637
|
+
acts: replayResult.actCount,
|
|
638
|
+
elapsedMs: replayResult.elapsedMs,
|
|
639
|
+
fidelity: checkpoint.domDigest
|
|
640
|
+
? (digest === checkpoint.domDigest ? 'exact' : 'divergent')
|
|
641
|
+
: 'unknown'
|
|
642
|
+
};
|
|
643
|
+
}
|
|
541
644
|
// If XPath attributes are needed:
|
|
542
645
|
if (xPathNeed === 'attribute') {
|
|
543
646
|
// Use the added script to add them.
|
|
@@ -565,8 +668,8 @@ const launchOnce = async opts => {
|
|
|
565
668
|
else {
|
|
566
669
|
const {rejectionData} = navResult;
|
|
567
670
|
const addendum = rejectionData
|
|
568
|
-
|
|
569
|
-
|
|
671
|
+
? ` (rejection data: ${JSON.stringify(rejectionData, null, 2)})`
|
|
672
|
+
: '';
|
|
570
673
|
// Throw an error.
|
|
571
674
|
throw new Error(`Navigation failed: ${navResult.error}${addendum}`);
|
|
572
675
|
}
|
|
@@ -598,18 +701,26 @@ const launchOnce = async opts => {
|
|
|
598
701
|
};
|
|
599
702
|
// Manages browser launching and navigating and returns a page.
|
|
600
703
|
exports.launch = async (opts = {}) => {
|
|
601
|
-
let {tempBrowserID = ''} = opts;
|
|
704
|
+
let {tempBrowserID = '', tempURL = ''} = opts;
|
|
602
705
|
const {
|
|
603
706
|
report = {},
|
|
604
707
|
actIndex = 0,
|
|
605
|
-
tempURL = '',
|
|
606
708
|
headEmulation = 'high',
|
|
607
709
|
xPathNeed = 'script',
|
|
608
710
|
needsAccessibleName = false,
|
|
609
711
|
retries = 2,
|
|
610
712
|
// Extra Playwright context options, passed through to launchOnce.
|
|
611
|
-
contextOverrides = {}
|
|
713
|
+
contextOverrides = {},
|
|
714
|
+
// Whether a test-act launch replays the active checkpoint's acts (launches for
|
|
715
|
+
// interaction acts and for the catalog pass do not).
|
|
716
|
+
replay = actIndex !== null
|
|
612
717
|
} = opts;
|
|
718
|
+
// If the launch is for a test act at a later checkpoint, navigate to that checkpoint's
|
|
719
|
+
// origin: its URL if navigation reached it, else the URL its replayed acts start from.
|
|
720
|
+
const checkpoint = report.checkpoints?.[report.activeCheckpoint];
|
|
721
|
+
if (replay && checkpoint && report.activeCheckpoint > 0) {
|
|
722
|
+
tempURL = checkpoint.kind === 'navigation' ? checkpoint.url : checkpoint.launchURL;
|
|
723
|
+
}
|
|
613
724
|
// If the report is valid:
|
|
614
725
|
const jobValidation = isValidJob(report);
|
|
615
726
|
if (jobValidation.isValid) {
|
|
@@ -625,7 +736,8 @@ exports.launch = async (opts = {}) => {
|
|
|
625
736
|
headEmulation,
|
|
626
737
|
xPathNeed,
|
|
627
738
|
needsAccessibleName,
|
|
628
|
-
contextOverrides
|
|
739
|
+
contextOverrides,
|
|
740
|
+
replay
|
|
629
741
|
}
|
|
630
742
|
);
|
|
631
743
|
// If the launch and navigation succeeded:
|
|
@@ -636,8 +748,21 @@ exports.launch = async (opts = {}) => {
|
|
|
636
748
|
// Otherwise, i.e. if the launch or navigation failed:
|
|
637
749
|
else {
|
|
638
750
|
let unusedBrowserIDs = ['chromium', 'webkit', 'firefox'].filter(id => id !== tempBrowserID);
|
|
639
|
-
let retriesLeft = retries;
|
|
640
751
|
let {error} = launchResult;
|
|
752
|
+
// A checkpoint replay failure is deterministic: do not retry it.
|
|
753
|
+
let retriesLeft = error.includes('checkpoint replay failed') ? 0 : retries;
|
|
754
|
+
// If configured (job navigation.failFast4xx or NAV_FAIL_FAST_4XX), a 4xx response other
|
|
755
|
+
// than 408 (request timeout) and 429 (rate limited) is a definitive refusal (firewall
|
|
756
|
+
// block, authentication wall): retrying and switching browsers cannot change it and, under
|
|
757
|
+
// the testaro tool's per-rule relaunch, compounds into long hangs. Stop at once.
|
|
758
|
+
const navStatus = Number((/status(\d{3})/.exec(error) || [])[1]);
|
|
759
|
+
if (
|
|
760
|
+
getNavigation(report).failFast4xx
|
|
761
|
+
&& navStatus >= 400 && navStatus < 500 && navStatus !== 408 && navStatus !== 429
|
|
762
|
+
) {
|
|
763
|
+
retriesLeft = 0;
|
|
764
|
+
unusedBrowserIDs = [];
|
|
765
|
+
}
|
|
641
766
|
// As long as retries remain, decrement the allowed retry count and:
|
|
642
767
|
while (retriesLeft) {
|
|
643
768
|
// Prepare to wait 1 second before a retry.
|
|
@@ -668,7 +793,8 @@ exports.launch = async (opts = {}) => {
|
|
|
668
793
|
headEmulation,
|
|
669
794
|
xPathNeed,
|
|
670
795
|
needsAccessibleName,
|
|
671
|
-
contextOverrides
|
|
796
|
+
contextOverrides,
|
|
797
|
+
replay
|
|
672
798
|
}
|
|
673
799
|
);
|
|
674
800
|
// If the launch and navigation succeeded:
|
|
@@ -681,6 +807,11 @@ exports.launch = async (opts = {}) => {
|
|
|
681
807
|
error = launchResult.error;
|
|
682
808
|
// Report this.
|
|
683
809
|
console.log(`WARNING: Retry failed (${error})`);
|
|
810
|
+
// A checkpoint replay failure is deterministic: stop retrying.
|
|
811
|
+
if (error.includes('checkpoint replay failed')) {
|
|
812
|
+
retriesLeft = 0;
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
684
815
|
// If a browser type was specified, retries are exhausted, and browser types are not:
|
|
685
816
|
if (tempBrowserID && unusedBrowserIDs.length && ! retriesLeft) {
|
|
686
817
|
// Change the browser type.
|
|
@@ -700,7 +831,7 @@ exports.launch = async (opts = {}) => {
|
|
|
700
831
|
actIndex === null ? true : abortAssertively,
|
|
701
832
|
report,
|
|
702
833
|
actIndex,
|
|
703
|
-
`Launch or navigation failed; retries and browser types exhausted`
|
|
834
|
+
`Launch or navigation failed; retries and browser types exhausted (${error})`
|
|
704
835
|
);
|
|
705
836
|
}
|
|
706
837
|
// Return a failure.
|
package/procs/nu.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
nu (hand-written declarations)
|
|
12
|
+
Minimal typings for the surface of procs/nu.js that converted TypeScript
|
|
13
|
+
files use. Delete this file when nu.js itself is converted (issue #73).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {Page} from 'playwright';
|
|
17
|
+
|
|
18
|
+
// The content obtained for a nuVal or nuVnu test.
|
|
19
|
+
export interface NuContent {
|
|
20
|
+
withSource: boolean | undefined;
|
|
21
|
+
testTarget: string | null;
|
|
22
|
+
prevented?: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
// One message of a curated Nu Html Checker result.
|
|
26
|
+
export interface NuMessage {
|
|
27
|
+
type: string;
|
|
28
|
+
subType?: string;
|
|
29
|
+
message: string;
|
|
30
|
+
extract?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
// A curated Nu Html Checker result; curate guarantees the messages array.
|
|
34
|
+
export interface NuResult {
|
|
35
|
+
messages: NuMessage[];
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Gets the content for a nuVal or nuVnu test.
|
|
40
|
+
export function getContent(page: Page, withSource: boolean | undefined): Promise<NuContent>;
|
|
41
|
+
// Postprocesses a result from nuVal or nuVnu tests; undefined when nuData is falsy.
|
|
42
|
+
export function curate(
|
|
43
|
+
data: {prevented?: boolean; error?: string},
|
|
44
|
+
nuData: unknown,
|
|
45
|
+
rules: string[] | undefined
|
|
46
|
+
): Promise<NuResult | undefined>;
|
|
47
|
+
// Gets an excerpt of a message extract, or null if the catalog reports the element.
|
|
48
|
+
export function getExtractExcerpt(extract: string | null | undefined): string | null;
|