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/tests/qualWeb.ts
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jeff Witt.
|
|
4
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
5
|
+
|
|
6
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
7
|
+
https://opensource.org/license/mit/ for details.
|
|
8
|
+
|
|
9
|
+
SPDX-License-Identifier: MIT
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// IMPORTS
|
|
13
|
+
|
|
14
|
+
import type {Page} from 'playwright';
|
|
15
|
+
import {getAttributeXPath, getXPathCatalogIndex} from '../procs/xPath';
|
|
16
|
+
import {getStandardResult, addInstance} from '../procs/standard';
|
|
17
|
+
import type {Act, Report, StandardInstance, StandardResult} from '../types';
|
|
18
|
+
/*
|
|
19
|
+
The @qualweb packages declare their types only in package-exports maps, which
|
|
20
|
+
this project's node10 module resolution cannot read, so the imports stay
|
|
21
|
+
requires and are untyped.
|
|
22
|
+
*/
|
|
23
|
+
const {QualWeb} = require('@qualweb/core');
|
|
24
|
+
const {ACTRules} = require('@qualweb/act-rules');
|
|
25
|
+
const {WCAGTechniques} = require('@qualweb/wcag-techniques');
|
|
26
|
+
const {BestPractices} = require('@qualweb/best-practices');
|
|
27
|
+
const {PlaywrightDriver} = require('@qualweb/playwright-driver');
|
|
28
|
+
import {qualWebDefaults} from '../procs/config';
|
|
29
|
+
|
|
30
|
+
// TYPES
|
|
31
|
+
|
|
32
|
+
// The qualWeb-act properties this reporter consumes.
|
|
33
|
+
interface QualWebAct extends Act {
|
|
34
|
+
rules?: string[];
|
|
35
|
+
// Whether the tool's browser runs stealth evasions and blocks ads and trackers (both default
|
|
36
|
+
// to true, or to QUALWEB_STEALTH and QUALWEB_ADBLOCK when set).
|
|
37
|
+
stealth?: boolean;
|
|
38
|
+
adBlock?: boolean;
|
|
39
|
+
}
|
|
40
|
+
// The section names of a QualWeb report.
|
|
41
|
+
type QwSection = 'act-rules' | 'wcag-techniques' | 'best-practices';
|
|
42
|
+
// One element reported by a QualWeb assertion result.
|
|
43
|
+
interface QwElement {
|
|
44
|
+
htmlCode?: string;
|
|
45
|
+
}
|
|
46
|
+
// One result of a QualWeb rule assertion.
|
|
47
|
+
interface QwRaResult {
|
|
48
|
+
verdict: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
elements?: QwElement[];
|
|
51
|
+
}
|
|
52
|
+
// The assertions for one QualWeb rule.
|
|
53
|
+
interface QwRuleAssertions {
|
|
54
|
+
metadata?: {
|
|
55
|
+
warning?: number;
|
|
56
|
+
failed?: number;
|
|
57
|
+
};
|
|
58
|
+
results: QwRaResult[];
|
|
59
|
+
}
|
|
60
|
+
// One module section of a QualWeb report.
|
|
61
|
+
interface QwModuleReport {
|
|
62
|
+
assertions?: Record<string, QwRuleAssertions>;
|
|
63
|
+
}
|
|
64
|
+
// The native result: the customHtml QualWeb report.
|
|
65
|
+
interface QwNativeResult {
|
|
66
|
+
system?: {
|
|
67
|
+
page?: {
|
|
68
|
+
dom?: unknown;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
modules?: Record<string, QwModuleReport>;
|
|
72
|
+
}
|
|
73
|
+
// The options of a QualWeb evaluation.
|
|
74
|
+
interface QualWebOptions {
|
|
75
|
+
log: {console: boolean; file: boolean};
|
|
76
|
+
crawlOptions: {
|
|
77
|
+
maxDepth: number;
|
|
78
|
+
maxUrls: number;
|
|
79
|
+
timeout: number;
|
|
80
|
+
maxParallelCrawls: number;
|
|
81
|
+
logging: boolean;
|
|
82
|
+
};
|
|
83
|
+
execute: {counter: boolean; act?: boolean; wcag?: boolean; bp?: boolean};
|
|
84
|
+
modules: unknown[];
|
|
85
|
+
html?: string;
|
|
86
|
+
'act-rules'?: {rules?: string[]; levels?: string[]; principles?: string[]};
|
|
87
|
+
'wcag-techniques'?: {techniques?: string[]; levels?: string[]; principles?: string[]};
|
|
88
|
+
'best-practices'?: {bestPractices?: string[]};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// CONSTANTS
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
QualWeb core engine with Playwright as driver, one instance per combination of options,
|
|
95
|
+
created on first use. The driver's constructor options are {launcher, launchOptions,
|
|
96
|
+
browser}, so its former {adBlock, stealth} options were ignored; stealth is provided by
|
|
97
|
+
launching through playwright-extra with the stealth plugin, and ad and tracker blocking by
|
|
98
|
+
the Ghostery blocker enabled on each page before it loads (QualWeb's beforePageLoad hook).
|
|
99
|
+
The blocker's filter lists are cached in the temporary directory.
|
|
100
|
+
*/
|
|
101
|
+
interface QualWebEngine {
|
|
102
|
+
start: (options: unknown) => Promise<void>;
|
|
103
|
+
evaluate: (options: unknown) => Promise<{customHtml: QwNativeResult}>;
|
|
104
|
+
stop: () => Promise<void>;
|
|
105
|
+
use: (plugin: unknown) => void;
|
|
106
|
+
}
|
|
107
|
+
const qualWebInstances: Record<string, QualWebEngine> = {};
|
|
108
|
+
const getQualWeb = (options: {stealth: boolean; adBlock: boolean}): QualWebEngine => {
|
|
109
|
+
const key = `${options.stealth}:${options.adBlock}`;
|
|
110
|
+
if (! qualWebInstances[key]) {
|
|
111
|
+
let launcher;
|
|
112
|
+
if (options.stealth) {
|
|
113
|
+
const {chromium} = require('playwright-extra');
|
|
114
|
+
chromium.use(require('puppeteer-extra-plugin-stealth')());
|
|
115
|
+
launcher = chromium;
|
|
116
|
+
}
|
|
117
|
+
const qualWeb: QualWebEngine = new QualWeb(undefined, new PlaywrightDriver(launcher ? {launcher} : {}));
|
|
118
|
+
if (options.adBlock) {
|
|
119
|
+
const {PlaywrightBlocker} = require('@ghostery/adblocker-playwright');
|
|
120
|
+
let blockerPromise: Promise<unknown> | null = null;
|
|
121
|
+
qualWeb.use({
|
|
122
|
+
async beforePageLoad(page: {nativePage: unknown}) {
|
|
123
|
+
blockerPromise ??= PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch, {
|
|
124
|
+
path: require('path').join(require('os').tmpdir(), 'qualweb-adblocker-engine.bin'),
|
|
125
|
+
read: require('fs').promises.readFile,
|
|
126
|
+
write: require('fs').promises.writeFile
|
|
127
|
+
}).catch((error: Error) => {
|
|
128
|
+
console.log(`qualWeb: ad blocker unavailable, continuing without it (${error.message})`);
|
|
129
|
+
return null;
|
|
130
|
+
});
|
|
131
|
+
const blocker = await blockerPromise as {enableBlockingInPage: (page: unknown) => Promise<void>} | null;
|
|
132
|
+
if (blocker) {
|
|
133
|
+
await blocker.enableBlockingInPage(page.nativePage);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
qualWebInstances[key] = qualWeb;
|
|
139
|
+
}
|
|
140
|
+
return qualWebInstances[key];
|
|
141
|
+
};
|
|
142
|
+
const actRulesModule = new ACTRules({});
|
|
143
|
+
const wcagModule = new WCAGTechniques({});
|
|
144
|
+
const bpModule = new BestPractices({});
|
|
145
|
+
// Mapping of QualWeb module violation types to ordinal severities.
|
|
146
|
+
const ordinalSeverities: Record<QwSection, Record<string, StandardInstance['ordinalSeverity']>> = {
|
|
147
|
+
'act-rules': {
|
|
148
|
+
'warning': 1,
|
|
149
|
+
'failed': 3
|
|
150
|
+
},
|
|
151
|
+
'wcag-techniques': {
|
|
152
|
+
'warning': 0,
|
|
153
|
+
'failed': 2
|
|
154
|
+
},
|
|
155
|
+
'best-practices': {
|
|
156
|
+
'warning': 0,
|
|
157
|
+
'failed': 1
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
qualWeb
|
|
163
|
+
Implements the QualWeb ruleset for accessibility.
|
|
164
|
+
Compiled to qualWeb.js by tsc (issue #73); edit this file, not the emitted one.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
// FUNCTIONS
|
|
168
|
+
|
|
169
|
+
// Conducts and reports the QualWeb tests.
|
|
170
|
+
export const reporter = async (page: Page, report: Report, actIndex: number, timeLimit: number) => {
|
|
171
|
+
const qualWebAct = report.acts[actIndex] as QualWebAct;
|
|
172
|
+
const qualWeb = getQualWeb({
|
|
173
|
+
stealth: qualWebAct.stealth ?? qualWebDefaults.stealth,
|
|
174
|
+
adBlock: qualWebAct.adBlock ?? qualWebDefaults.adBlock
|
|
175
|
+
});
|
|
176
|
+
const act = report.acts[actIndex] as QualWebAct;
|
|
177
|
+
const {rules} = act;
|
|
178
|
+
const clusterOptions = {
|
|
179
|
+
maxConcurrency: 1,
|
|
180
|
+
timeout: timeLimit * 1000,
|
|
181
|
+
monitor: false
|
|
182
|
+
};
|
|
183
|
+
// Initialize the act report.
|
|
184
|
+
const data: {prevented?: boolean; error?: string} = {};
|
|
185
|
+
const result: {nativeResult: QwNativeResult; standardResult: StandardResult} = {
|
|
186
|
+
nativeResult: {},
|
|
187
|
+
standardResult: {}
|
|
188
|
+
};
|
|
189
|
+
const standard = report.standard !== 'no';
|
|
190
|
+
// If standard results are to be reported:
|
|
191
|
+
if (standard) {
|
|
192
|
+
// Initialize the standard result.
|
|
193
|
+
result.standardResult = getStandardResult();
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
// Start the QualWeb core engine, which launches a Playwright browser.
|
|
197
|
+
await qualWeb.start(clusterOptions);
|
|
198
|
+
}
|
|
199
|
+
// If the start fails:
|
|
200
|
+
catch(error) {
|
|
201
|
+
return {
|
|
202
|
+
data: {
|
|
203
|
+
prevented: true,
|
|
204
|
+
error: `Core engine start failed (${(error as Error).message})`
|
|
205
|
+
},
|
|
206
|
+
result
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Otherwise, i.e. if the start succeeds, specify the invariant test options.
|
|
210
|
+
const qualWebOptions: QualWebOptions = {
|
|
211
|
+
log: {
|
|
212
|
+
console: false,
|
|
213
|
+
file: false
|
|
214
|
+
},
|
|
215
|
+
crawlOptions: {
|
|
216
|
+
maxDepth: 0,
|
|
217
|
+
maxUrls: 1,
|
|
218
|
+
timeout: timeLimit * 1000,
|
|
219
|
+
maxParallelCrawls: 1,
|
|
220
|
+
logging: true
|
|
221
|
+
},
|
|
222
|
+
execute: {
|
|
223
|
+
counter: true
|
|
224
|
+
},
|
|
225
|
+
modules: []
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
// Provide the page content, including the data-xpath attributes.
|
|
229
|
+
qualWebOptions.html = await page.content();
|
|
230
|
+
// Specify which rules to test for, adding a custom execute property for report processing.
|
|
231
|
+
const actSpec = rules ? rules.find(typeRules => typeRules.startsWith('act:')) : null;
|
|
232
|
+
const wcagSpec = rules ? rules.find(typeRules => typeRules.startsWith('wcag:')) : null;
|
|
233
|
+
const bestSpec = rules ? rules.find(typeRules => typeRules.startsWith('best:')) : null;
|
|
234
|
+
if (actSpec) {
|
|
235
|
+
if (actSpec === 'act:') {
|
|
236
|
+
qualWebOptions.execute.act = false;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const actRules = actSpec.slice(4).split(',').map(num => `QW-ACT-R${num}`);
|
|
240
|
+
qualWebOptions['act-rules'] = {rules: actRules};
|
|
241
|
+
qualWebOptions.modules.push(actRulesModule);
|
|
242
|
+
qualWebOptions.execute.act = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
qualWebOptions['act-rules'] = {
|
|
247
|
+
levels: ['A', 'AA', 'AAA'],
|
|
248
|
+
principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
|
|
249
|
+
};
|
|
250
|
+
qualWebOptions.modules.push(actRulesModule);
|
|
251
|
+
qualWebOptions.execute.act = true;
|
|
252
|
+
}
|
|
253
|
+
if (wcagSpec) {
|
|
254
|
+
if (wcagSpec === 'wcag:') {
|
|
255
|
+
qualWebOptions.execute.wcag = false;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const wcagTechniques = wcagSpec.slice(5).split(',').map(num => `QW-WCAG-T${num}`);
|
|
259
|
+
qualWebOptions['wcag-techniques'] = {techniques: wcagTechniques};
|
|
260
|
+
qualWebOptions.modules.push(wcagModule);
|
|
261
|
+
qualWebOptions.execute.wcag = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
qualWebOptions['wcag-techniques'] = {
|
|
266
|
+
levels: ['A', 'AA', 'AAA'],
|
|
267
|
+
principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
|
|
268
|
+
};
|
|
269
|
+
qualWebOptions.modules.push(wcagModule);
|
|
270
|
+
qualWebOptions.execute.wcag = true;
|
|
271
|
+
}
|
|
272
|
+
if (bestSpec) {
|
|
273
|
+
if (bestSpec === 'best:') {
|
|
274
|
+
qualWebOptions.execute.bp = false;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const bestPractices = bestSpec.slice(5).split(',').map(num => `QW-BP${num}`);
|
|
278
|
+
qualWebOptions['best-practices'] = {bestPractices};
|
|
279
|
+
qualWebOptions.modules.push(bpModule);
|
|
280
|
+
qualWebOptions.execute.bp = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
qualWebOptions['best-practices'] = {};
|
|
285
|
+
qualWebOptions.modules.push(bpModule);
|
|
286
|
+
qualWebOptions.execute.bp = true;
|
|
287
|
+
}
|
|
288
|
+
let qwReport;
|
|
289
|
+
try {
|
|
290
|
+
// Get the report.
|
|
291
|
+
qwReport = await qualWeb.evaluate(qualWebOptions);
|
|
292
|
+
}
|
|
293
|
+
catch(error) {
|
|
294
|
+
return {
|
|
295
|
+
data: {
|
|
296
|
+
prevented: true,
|
|
297
|
+
error: `qualWeb evaluation failed (${(error as Error).message})`
|
|
298
|
+
},
|
|
299
|
+
result
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Add the report to the result.
|
|
303
|
+
result.nativeResult = qwReport.customHtml;
|
|
304
|
+
const {nativeResult, standardResult} = result;
|
|
305
|
+
// If the report contains, as it should, a copy of the DOM:
|
|
306
|
+
if (nativeResult?.system?.page?.dom) {
|
|
307
|
+
// Delete the copy for parsimony.
|
|
308
|
+
delete nativeResult.system.page.dom;
|
|
309
|
+
const {modules} = nativeResult;
|
|
310
|
+
// If the report contains, as it should, a modules property:
|
|
311
|
+
if (modules) {
|
|
312
|
+
// For each test section in it:
|
|
313
|
+
for (const section of ['act-rules', 'wcag-techniques', 'best-practices'] as QwSection[]) {
|
|
314
|
+
// If testing in the section was specified:
|
|
315
|
+
if (qualWebOptions[section]) {
|
|
316
|
+
// If the section exists:
|
|
317
|
+
if (modules[section]) {
|
|
318
|
+
const {assertions} = modules[section];
|
|
319
|
+
// If it contains assertions (test results):
|
|
320
|
+
if (assertions) {
|
|
321
|
+
const ruleIDs = Object.keys(assertions);
|
|
322
|
+
// For each rule:
|
|
323
|
+
for (const ruleID of ruleIDs) {
|
|
324
|
+
const ruleAssertions = assertions[ruleID];
|
|
325
|
+
const {metadata} = ruleAssertions;
|
|
326
|
+
// If there were any warnings or failures:
|
|
327
|
+
if (metadata?.warning || metadata?.failed) {
|
|
328
|
+
// Delete nonviolations from the results.
|
|
329
|
+
ruleAssertions.results = ruleAssertions.results.filter(
|
|
330
|
+
raResult => raResult.verdict !== 'passed'
|
|
331
|
+
);
|
|
332
|
+
// For each test result:
|
|
333
|
+
for (const raResult of ruleAssertions.results) {
|
|
334
|
+
const {elements, verdict} = raResult;
|
|
335
|
+
// If any violations are reported:
|
|
336
|
+
if (elements?.length) {
|
|
337
|
+
// For each violating element:
|
|
338
|
+
for (const element of elements) {
|
|
339
|
+
// Limit the size of its reported excerpt.
|
|
340
|
+
if ((element.htmlCode?.length as number) > 2000) {
|
|
341
|
+
element.htmlCode = `${element.htmlCode!.slice(0, 2000)} …`;
|
|
342
|
+
}
|
|
343
|
+
// If standard results are to be reported:
|
|
344
|
+
if (standard) {
|
|
345
|
+
const what = `[${verdict}] ${raResult.description}`;
|
|
346
|
+
const xPath = getAttributeXPath(element.htmlCode);
|
|
347
|
+
// Add an instance to the standard result.
|
|
348
|
+
addInstance(standardResult, {
|
|
349
|
+
ruleID,
|
|
350
|
+
what,
|
|
351
|
+
ordinalSeverity: ordinalSeverities[section][verdict],
|
|
352
|
+
outcome: verdict === 'warning' ? 'cantTell' : 'failed',
|
|
353
|
+
catalogIndex: getXPathCatalogIndex(report, xPath)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// Otherwise, i.e. if there were no warnings or failures:
|
|
361
|
+
else {
|
|
362
|
+
// Delete the rule.
|
|
363
|
+
delete assertions[ruleID];
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
// Otherwise, i.e. if it contains no assertions:
|
|
368
|
+
else {
|
|
369
|
+
// Report this.
|
|
370
|
+
data.prevented = true;
|
|
371
|
+
data.error = 'No assertions';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Otherwise, i.e. if the section is missing:
|
|
375
|
+
else {
|
|
376
|
+
// Report this.
|
|
377
|
+
data.prevented = true;
|
|
378
|
+
data.error = `No ${section} section`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Otherwise, i.e. if the report does not contain a modules property:
|
|
384
|
+
else {
|
|
385
|
+
// Report this.
|
|
386
|
+
data.prevented = true;
|
|
387
|
+
data.error = 'No modules';
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Otherwise, i.e. if the report does not contain a copy of the DOM:
|
|
391
|
+
else {
|
|
392
|
+
// Report this.
|
|
393
|
+
data.prevented = true;
|
|
394
|
+
data.error = 'No DOM';
|
|
395
|
+
}
|
|
396
|
+
// Stop the QualWeb core engine.
|
|
397
|
+
await qualWeb.stop();
|
|
398
|
+
// Test whether the result is an object.
|
|
399
|
+
try {
|
|
400
|
+
JSON.stringify(result);
|
|
401
|
+
}
|
|
402
|
+
catch(error) {
|
|
403
|
+
data.prevented = true;
|
|
404
|
+
data.error = `QualWeb result cannot be made JSON (${(error as Error).message})`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch(error) {
|
|
408
|
+
data.prevented = true;
|
|
409
|
+
data.error = `QualWeb failed (${(error as Error).message})`;
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
data,
|
|
413
|
+
result
|
|
414
|
+
};
|
|
415
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { Report, StandardResult } from '../types';
|
|
3
|
+
type SureSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
|
|
4
|
+
interface SureFinding {
|
|
5
|
+
ruleID: string;
|
|
6
|
+
what: string;
|
|
7
|
+
wcag: string;
|
|
8
|
+
severity: SureSeverity;
|
|
9
|
+
uncertainty: string;
|
|
10
|
+
needed: string;
|
|
11
|
+
html: string;
|
|
12
|
+
cssPath: string;
|
|
13
|
+
xPath: string;
|
|
14
|
+
}
|
|
15
|
+
interface SureNativeResult {
|
|
16
|
+
engine?: Record<string, unknown>;
|
|
17
|
+
violations?: SureFinding[];
|
|
18
|
+
incomplete?: SureFinding[];
|
|
19
|
+
passRuleCount?: number;
|
|
20
|
+
inapplicableRuleCount?: number;
|
|
21
|
+
manualRuleCount?: number;
|
|
22
|
+
manualOccurrenceCount?: number;
|
|
23
|
+
prevented?: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const reporter: (page: Page, report: Report, actIndex: number) => Promise<{
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
result: {
|
|
29
|
+
nativeResult: SureNativeResult;
|
|
30
|
+
standardResult: StandardResult;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
export {};
|