testaro 27.0.0 → 28.0.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/CONTRIBUTING.md +3 -1
- package/README.md +14 -3
- package/package.json +2 -1
- package/procs/aslint.js +83 -0
- package/procs/nav.js +56 -14
- package/procs/standardize.js +36 -0
- package/run.js +35 -22
- package/tests/aslint.js +83 -0
- package/tests/axe.js +4 -0
- package/tests/qualWeb.js +6 -0
- package/aslint/LICENSE +0 -362
- package/aslint/README.md +0 -260
- package/aslint/app/rules/abstract-rule.ts +0 -83
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.documentation.md +0 -36
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.test.ts +0 -113
- package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.ts +0 -103
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.documentation.md +0 -34
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.test.ts +0 -82
- package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.ts +0 -44
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.documentation.md +0 -40
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.test.ts +0 -48
- package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts +0 -37
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.test.ts +0 -50
- package/aslint/app/rules/aslint/links-language-destination/links-language-destination.ts +0 -70
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.test.ts +0 -55
- package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.ts +0 -83
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.test.ts +0 -12
- package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts +0 -73
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.test.ts +0 -12
- package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.ts +0 -87
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.test.ts +0 -48
- package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.ts +0 -40
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.ts +0 -66
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.ts +0 -134
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.test.ts +0 -12
- package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.ts +0 -90
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.test.ts +0 -12
- package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.ts +0 -48
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.test.ts +0 -12
- package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.ts +0 -148
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.test.ts +0 -12
- package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.ts +0 -66
- package/aslint/app/rules/aslint/outline-zero/outline-zero.test.ts +0 -12
- package/aslint/app/rules/aslint/outline-zero/outline-zero.ts +0 -85
- package/aslint/app/rules/aslint/overlay/overlay.test.ts +0 -122
- package/aslint/app/rules/aslint/overlay/overlay.ts +0 -141
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.documentation.md +0 -49
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.test.ts +0 -91
- package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.ts +0 -62
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.documentation.md +0 -44
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.test.ts +0 -111
- package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.ts +0 -120
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.documentation.md +0 -45
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.test.ts +0 -63
- package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.ts +0 -44
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.documentation.md +0 -55
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.test.ts +0 -80
- package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.ts +0 -58
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.documentation.md +0 -48
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.test.ts +0 -52
- package/aslint/app/rules/aslint/redundant/flash-content/flash-content.ts +0 -32
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.documentation.md +0 -44
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.test.ts +0 -12
- package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.ts +0 -83
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.documentation.md +0 -46
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.test.ts +0 -46
- package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.ts +0 -36
- package/aslint/app/rules/aslint/role-application/role-application.test.ts +0 -48
- package/aslint/app/rules/aslint/role-application/role-application.ts +0 -38
- package/aslint/app/rules/aslint/rtl-content/rtl-content.test.ts +0 -12
- package/aslint/app/rules/aslint/rtl-content/rtl-content.ts +0 -75
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.test.ts +0 -12
- package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.ts +0 -48
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.test.ts +0 -73
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.ts +0 -34
- package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden.documentation.md +0 -32
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.docmentation.md +0 -48
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.test.ts +0 -67
- package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.ts +0 -63
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts +0 -12
- package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.ts +0 -63
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.documentation.md +0 -65
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.test.ts +0 -53
- package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.ts +0 -47
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.documentation.md +0 -57
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.test.ts +0 -52
- package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.ts +0 -63
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.documentation.md +0 -39
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.test.ts +0 -66
- package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.ts +0 -37
- package/aslint/utils/aria.test.ts +0 -12
- package/aslint/utils/aria.ts +0 -120
- package/aslint/utils/async.test.ts +0 -25
- package/aslint/utils/async.ts +0 -22
- package/aslint/utils/common.test.ts +0 -239
- package/aslint/utils/common.ts +0 -168
- package/aslint/utils/console.test.ts +0 -85
- package/aslint/utils/console.ts +0 -89
- package/aslint/utils/css.test.ts +0 -153
- package/aslint/utils/css.ts +0 -191
- package/aslint/utils/dom.test.ts +0 -627
- package/aslint/utils/dom.ts +0 -1051
- package/aslint/utils/env.test.ts +0 -14
- package/aslint/utils/env.ts +0 -8
- package/aslint/utils/func.test.ts +0 -160
- package/aslint/utils/func.ts +0 -70
- package/aslint/utils/global.test.ts +0 -12
- package/aslint/utils/global.ts +0 -25
- package/aslint/utils/object.test.ts +0 -524
- package/aslint/utils/object.ts +0 -278
- package/aslint/utils/report.test.ts +0 -56
- package/aslint/utils/report.ts +0 -36
- package/aslint/utils/text.test.ts +0 -270
- package/aslint/utils/text.ts +0 -165
- package/aslint/utils/time.test.ts +0 -43
- package/aslint/utils/time.ts +0 -33
package/CONTRIBUTING.md
CHANGED
|
@@ -10,7 +10,9 @@ Testaro can benefit from contributions of various types, such as:
|
|
|
10
10
|
|
|
11
11
|
## Adding tools
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Tools that may merit consideration include:
|
|
14
|
+
- Arc (by TPGi). Costs $0.05 per page tested, and requires each user to have an account with the tool maker and to allow the tool maker to charge a charge account for payment.
|
|
15
|
+
- Aslint (by Essential Accessibility). Apparently requires using a bundle file that is in the repository but is also listed in the `.gitignore` file and is missing from the published package, so would need to be physically included in the Testaro code rather than referenced as a dependency.
|
|
14
16
|
|
|
15
17
|
## Improving execution speed
|
|
16
18
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ The purposes of Testaro are to:
|
|
|
10
10
|
- provide programmatic access to accessibility tests defined by several tools
|
|
11
11
|
- facilitate the integration of the reports of the tools into a unified report
|
|
12
12
|
|
|
13
|
+
Testaro is described in [Testaro: Efficient Ensemble Testing for Web Accessibility](https://arxiv.org/abs/2309.10167).
|
|
14
|
+
|
|
13
15
|
The need for multi-tool integration, and its costs, are discussed in [Accessibility Metatesting: Comparing Nine Testing Tools](https://arxiv.org/abs/2304.07591).
|
|
14
16
|
|
|
15
17
|
Testaro launches and controls web browsers, performing operations, conducting tests, and recording results.
|
|
@@ -43,6 +45,7 @@ Testaro uses:
|
|
|
43
45
|
Testaro performs tests of these tools:
|
|
44
46
|
- [accessibility-checker](https://www.npmjs.com/package/accessibility-checker) (IBM)
|
|
45
47
|
- [alfa](https://alfa.siteimprove.com/) (Siteimprove)
|
|
48
|
+
- [aslint](https://www.npmjs.com/package/@essentialaccessibility/aslint) (eSSENTIAL Accessibility)
|
|
46
49
|
- [axe-playwright](https://www.npmjs.com/package/axe-playwright) (Deque)
|
|
47
50
|
- [HTML CodeSniffer](https://www.npmjs.com/package/html_codesniffer) (Squiz Labs)
|
|
48
51
|
- [Nu Html Checker](https://github.com/validator/validator) (World Wide Web Consortium)
|
|
@@ -50,11 +53,11 @@ Testaro performs tests of these tools:
|
|
|
50
53
|
- [Testaro](https://www.npmjs.com/package/testaro) (CVS Health)
|
|
51
54
|
- [WAVE API](https://wave.webaim.org/api/) (WebAIM)
|
|
52
55
|
|
|
53
|
-
Some of the tests of Testaro are designed to act as approximate alternatives to tests of vulnerable, restricted, or no longer available tools.
|
|
56
|
+
Some of the tests of Testaro are designed to act as approximate alternatives to tests of vulnerable, restricted, or no longer available tools. In all such cases the Testaro rules are independently designed and implemented, without reference to the code of the tests that inspired them.
|
|
54
57
|
|
|
55
58
|
## Rules
|
|
56
59
|
|
|
57
|
-
Each tool accessed with Testaro defines _rules_ and tests _targets_ for compliance with its rules. In total, the
|
|
60
|
+
Each tool accessed with Testaro defines _rules_ and tests _targets_ for compliance with its rules. In total, the nine tools define about 760 rules. Some of the tools are under active development, and their rule counts change over time.
|
|
58
61
|
|
|
59
62
|
When you ask Testaro to run tests of a tool, you may specify a subset of the rules of that tool, and the report will give you the results of only the tests for those rules. These tools will perform only those tests:
|
|
60
63
|
- `alfa`
|
|
@@ -68,6 +71,8 @@ These tools always perform a fixed set of tests, and Testaro disregards irreleva
|
|
|
68
71
|
- `nuVal`
|
|
69
72
|
- `wave`
|
|
70
73
|
|
|
74
|
+
The `aslint` tool does not yet allow rule specification.
|
|
75
|
+
|
|
71
76
|
## Job data
|
|
72
77
|
|
|
73
78
|
A report produced by Testaro discloses:
|
|
@@ -392,6 +397,8 @@ When you include a `rules` property, you limit the tests of the tool that are pe
|
|
|
392
397
|
|
|
393
398
|
The `nuVal`, `qualWeb`, and `testaro` tools require specific formats for the `rules` property. Those formats are described below in the sections about those tools.
|
|
394
399
|
|
|
400
|
+
The `aslint` tool does not yet allow rule specification.
|
|
401
|
+
|
|
395
402
|
###### Examples
|
|
396
403
|
|
|
397
404
|
An example of a `test` act is:
|
|
@@ -468,6 +475,10 @@ A typical use for an `expect` property is checking the correctness of a Testaro
|
|
|
468
475
|
|
|
469
476
|
When a `test` act has an `expect` property, the result for that act has an `expectations` property reporting whether the expectations were satisfied. The value of `expectations` is an array of objects, one object per expectation. Each object includes a `property` property identifying the expectation, and a `passed` property with `true` or `false` value reporting whether the expectation was satisfied. If applicable, it also has other properties identifying what was expected and what was actually reported.
|
|
470
477
|
|
|
478
|
+
###### ASLint
|
|
479
|
+
|
|
480
|
+
The `aslint` tool makes use of the [`aslint-testaro` fork](https://www.npmjs.com/package/aslint-testaro) of the [`aslint` repository](https://github.com/essentialaccessibility/aslint), which, unlike the published `aslint` package, contains the `aslint.bundle.js` file.
|
|
481
|
+
|
|
471
482
|
###### HTML CodeSniffer
|
|
472
483
|
|
|
473
484
|
The `htmlcs` tool makes use of the `htmlcs/HTMLCS.js` file. That file was created, and can be recreated if necessary, as follows:
|
|
@@ -664,7 +675,7 @@ The validity criterion named in item 2 may be any of these:
|
|
|
664
675
|
- `'isBrowserType'`: is `'chromium'`, `'firefox'`, or `'webkit'`
|
|
665
676
|
- `'isFocusable'`: is `'a'`, `'button'`, `'input'`, `'select'`, or `'option'`
|
|
666
677
|
- `'isState'`: is `'loaded'` or `'idle'`
|
|
667
|
-
- `'isTest'`: is the name of a
|
|
678
|
+
- `'isTest'`: is the name of a tool
|
|
668
679
|
- `'isWaitable'`: is `'url'`, `'title'`, or `'body'`
|
|
669
680
|
- `'areStrings'`: is an array of strings
|
|
670
681
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testaro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "28.0.0",
|
|
4
4
|
"description": "Run 650 web accessibility tests from 8 tools and get a standardized report",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@siteimprove/alfa-rules": "*",
|
|
34
34
|
"@siteimprove/alfa-scraper": "*",
|
|
35
35
|
"accessibility-checker": "*",
|
|
36
|
+
"aslint-testaro": "*",
|
|
36
37
|
"axe-playwright": "*",
|
|
37
38
|
"dotenv": "*",
|
|
38
39
|
"node-fetch": "<3.0.0",
|
package/procs/aslint.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
aslint
|
|
3
|
+
Injects and captures results from the ASLint bundle.
|
|
4
|
+
*/
|
|
5
|
+
const options = {
|
|
6
|
+
asyncRunner: true,
|
|
7
|
+
context: document.documentElement,
|
|
8
|
+
includeElementReference: true,
|
|
9
|
+
reportFormat: {JSON: true},
|
|
10
|
+
watchDomChanges: false
|
|
11
|
+
};
|
|
12
|
+
const bundleEl = document.getElementById('aslintBundle');
|
|
13
|
+
window
|
|
14
|
+
.aslint
|
|
15
|
+
.config(options)
|
|
16
|
+
.addListener('onValidatorStarted', function () {
|
|
17
|
+
console.log('@ Validator started');
|
|
18
|
+
})
|
|
19
|
+
.addListener('onValidatorComplete', function (error, report) {
|
|
20
|
+
console.log('@ Validator Complete');
|
|
21
|
+
})
|
|
22
|
+
.addFilter('onBeforeRuleReport', function (report) {
|
|
23
|
+
return report;
|
|
24
|
+
})
|
|
25
|
+
.setRule('turn-me-off', {isSelectedForScanning: false})
|
|
26
|
+
.run()
|
|
27
|
+
.then(function (result) {
|
|
28
|
+
const resultEl = document.createElement('pre');
|
|
29
|
+
resultEl.id = 'aslintResult';
|
|
30
|
+
if (result.rules) {
|
|
31
|
+
const ruleIDs = Object.keys(result.rules);
|
|
32
|
+
ruleIDs.forEach(ruleID => {
|
|
33
|
+
const {results} = result.rules[ruleID];
|
|
34
|
+
if (results && results.length) {
|
|
35
|
+
results.forEach(ruleResult => {
|
|
36
|
+
delete ruleResult.data;
|
|
37
|
+
delete ruleResult.element.reference;
|
|
38
|
+
delete ruleResult.message.expected;
|
|
39
|
+
delete ruleResult.skipReason;
|
|
40
|
+
ruleResult.element.html = ruleResult
|
|
41
|
+
.element
|
|
42
|
+
.html
|
|
43
|
+
.replace(/</g, '<')
|
|
44
|
+
.replace(/>/g, '>')
|
|
45
|
+
.replace(/"/g, '\"')
|
|
46
|
+
.replace(/=/g, '=')
|
|
47
|
+
.replace(///g, '/')
|
|
48
|
+
.replace(/'/g, '\'')
|
|
49
|
+
.replace(/&(?:amp;)*/g, '&')
|
|
50
|
+
.replace(/%3A/g, ':')
|
|
51
|
+
.replace(/%2F/g, '/');
|
|
52
|
+
ruleResult.message.actual.description = ruleResult
|
|
53
|
+
.message
|
|
54
|
+
.actual
|
|
55
|
+
.description
|
|
56
|
+
.replace(/</g, '<')
|
|
57
|
+
.replace(/>/g, '>')
|
|
58
|
+
.replace(/"/g, '\"')
|
|
59
|
+
.replace(/=/g, '=')
|
|
60
|
+
.replace(///g, '/')
|
|
61
|
+
.replace(/&(?:amp;)*/g, '&')
|
|
62
|
+
.replace(/%2F/g, '/');
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
ruleIDs.forEach(ruleID => {
|
|
67
|
+
try {
|
|
68
|
+
JSON.stringify(result.rules[ruleID]);
|
|
69
|
+
}
|
|
70
|
+
catch(error) {
|
|
71
|
+
console.log(`ERROR: Rule ${ruleID} result not stringifiable so its results deleted`);
|
|
72
|
+
delete result.rules[ruleID].results;
|
|
73
|
+
result.rules[ruleID].success = false;
|
|
74
|
+
result.rules[ruleID].error = 'Result property not stringifiable so deleted';
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
resultEl.textContent = JSON.stringify(result, null, 2);
|
|
79
|
+
document.body.insertAdjacentElement('beforeend', resultEl);
|
|
80
|
+
})
|
|
81
|
+
.catch(error => {
|
|
82
|
+
console.error('[ASLint error]', error);
|
|
83
|
+
});
|
package/procs/nav.js
CHANGED
|
@@ -37,6 +37,28 @@ let browser;
|
|
|
37
37
|
|
|
38
38
|
// Returns a string with any final slash removed.
|
|
39
39
|
const deSlash = string => string.endsWith('/') ? string.slice(0, -1) : string;
|
|
40
|
+
// Gets the script nonce from a response.
|
|
41
|
+
const getNonce = async response => {
|
|
42
|
+
let nonce = '';
|
|
43
|
+
// If the response includes a content security policy:
|
|
44
|
+
const headers = await response.allHeaders();
|
|
45
|
+
const cspWithQuotes = headers && headers['content-security-policy'];
|
|
46
|
+
if (cspWithQuotes) {
|
|
47
|
+
// If it requires scripts to have a nonce:
|
|
48
|
+
const csp = cspWithQuotes.replace(/'/g, '');
|
|
49
|
+
const directives = csp.split(/ *; */).map(directive => directive.split(/ +/));
|
|
50
|
+
const scriptDirective = directives.find(dir => dir[0] === 'script-src');
|
|
51
|
+
if (scriptDirective) {
|
|
52
|
+
const nonceSpec = scriptDirective.find(valPart => valPart.startsWith('nonce-'));
|
|
53
|
+
if (nonceSpec) {
|
|
54
|
+
// Return the nonce.
|
|
55
|
+
nonce = nonceSpec.replace(/^nonce-/, '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Return the nonce, if any.
|
|
60
|
+
return nonce;
|
|
61
|
+
};
|
|
40
62
|
// Visits a URL and returns the response of the server.
|
|
41
63
|
const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
42
64
|
// If the URL is a file path:
|
|
@@ -68,8 +90,11 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
68
90
|
else {
|
|
69
91
|
// Press the Escape key to dismiss any modal dialog.
|
|
70
92
|
await page.keyboard.press('Escape');
|
|
71
|
-
// Return the
|
|
72
|
-
return
|
|
93
|
+
// Return the result of the navigation.
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
response
|
|
97
|
+
};
|
|
73
98
|
}
|
|
74
99
|
}
|
|
75
100
|
// Otherwise, i.e. if the response status was abnormal:
|
|
@@ -78,6 +103,7 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
78
103
|
console.log(`ERROR: Visit to ${url} got status ${httpStatus}`);
|
|
79
104
|
report.jobData.visitRejectionCount++;
|
|
80
105
|
return {
|
|
106
|
+
success: false,
|
|
81
107
|
error: 'badStatus'
|
|
82
108
|
};
|
|
83
109
|
}
|
|
@@ -85,6 +111,7 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
85
111
|
catch(error) {
|
|
86
112
|
console.log(`ERROR visiting ${url} (${error.message.slice(0, 200)})`);
|
|
87
113
|
return {
|
|
114
|
+
success: false,
|
|
88
115
|
error: 'noVisit'
|
|
89
116
|
};
|
|
90
117
|
}
|
|
@@ -127,9 +154,12 @@ const launch = async (report, typeName, url, debug, waits, isLowMotion = false)
|
|
|
127
154
|
// If the launch failed:
|
|
128
155
|
.catch(async error => {
|
|
129
156
|
healthy = false;
|
|
130
|
-
console.log(`ERROR launching browser (${
|
|
157
|
+
console.log(`ERROR launching browser (${error.message.slice(0, 200)})`);
|
|
131
158
|
// Return this.
|
|
132
|
-
return
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: 'Browser launch failed'
|
|
162
|
+
};
|
|
133
163
|
});
|
|
134
164
|
// Open a context (i.e. browser tab), with reduced motion if specified.
|
|
135
165
|
const options = {reduceMotion: isLowMotion ? 'reduce' : 'no-preference'};
|
|
@@ -187,36 +217,48 @@ const launch = async (report, typeName, url, debug, waits, isLowMotion = false)
|
|
|
187
217
|
await currentPage.waitForLoadState('domcontentloaded', {timeout: 5000});
|
|
188
218
|
// Navigate to the specified URL.
|
|
189
219
|
const navResult = await goTo(report, currentPage, url, 15000, 'domcontentloaded');
|
|
190
|
-
// If the navigation
|
|
191
|
-
if (navResult.
|
|
192
|
-
// Return this.
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
// Otherwise, i.e. if it succeeded:
|
|
196
|
-
else if (! navResult.error) {
|
|
220
|
+
// If the navigation succeeded:
|
|
221
|
+
if (navResult.success) {
|
|
197
222
|
// Update the name of the current browser type and store it in the page.
|
|
198
223
|
currentPage.browserTypeName = typeName;
|
|
199
|
-
// Return the browser context and the page.
|
|
224
|
+
// Return the response, the browser context, and the page.
|
|
200
225
|
return {
|
|
226
|
+
success: true,
|
|
227
|
+
response: navResult.response,
|
|
201
228
|
browserContext,
|
|
202
229
|
currentPage
|
|
203
230
|
};
|
|
204
231
|
}
|
|
232
|
+
// If the navigation failed:
|
|
233
|
+
if (navResult.error) {
|
|
234
|
+
// Return this.
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
error: 'Navigation failed'
|
|
238
|
+
};
|
|
239
|
+
}
|
|
205
240
|
}
|
|
206
241
|
// If it fails to become stable by the deadline:
|
|
207
242
|
catch(error) {
|
|
208
243
|
// Return this.
|
|
209
244
|
console.log(`ERROR: Blank page load in new tab timed out (${error.message})`);
|
|
210
|
-
return
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
error: 'Blank page load in new tab timed out'
|
|
248
|
+
};
|
|
211
249
|
}
|
|
212
250
|
}
|
|
213
251
|
// Otherwise, i.e. if it does not exist:
|
|
214
252
|
else {
|
|
215
253
|
// Return this.
|
|
216
254
|
console.log(`ERROR: Browser of type ${typeName} could not be launched`);
|
|
217
|
-
return
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: `${typeName} browser launch failed`
|
|
258
|
+
};
|
|
218
259
|
}
|
|
219
260
|
};
|
|
220
261
|
exports.browserClose = browserClose;
|
|
262
|
+
exports.getNonce = getNonce;
|
|
221
263
|
exports.goTo = goTo;
|
|
222
264
|
exports.launch = launch;
|
package/procs/standardize.js
CHANGED
|
@@ -259,6 +259,42 @@ const convert = (toolName, result, standardResult) => {
|
|
|
259
259
|
standardResult.instances.push(instance);
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
|
+
// aslint
|
|
263
|
+
else if (toolName === 'aslint' && result.summary && result.summary.byIssueType) {
|
|
264
|
+
standardResult.totals = [
|
|
265
|
+
result.summary.byIssueType.warning, 0, 0, result.summary.byIssueType.error
|
|
266
|
+
];
|
|
267
|
+
Object.keys(result.rules).forEach(ruleID => {
|
|
268
|
+
const ruleResults = result.rules[ruleID].results;
|
|
269
|
+
if (ruleResults && ruleResults.length) {
|
|
270
|
+
ruleResults.forEach(ruleResult => {
|
|
271
|
+
const {issueType} = result.rules[ruleID];
|
|
272
|
+
const xpath = ruleResult.element && ruleResult.element.xpath || '';
|
|
273
|
+
const tagName = xpath && xpath.replace(/^.*\//, '').replace(/[^-\w].*$/, '').toUpperCase()
|
|
274
|
+
|| '';
|
|
275
|
+
const excerpt = ruleResult.element && ruleResult.element.html || '';
|
|
276
|
+
const idDraft = excerpt && excerpt.replace(/^[^[>]+id="/, 'id=').replace(/".*$/, '');
|
|
277
|
+
const id = idDraft && idDraft.length > 3 && idDraft.startsWith('id=')
|
|
278
|
+
? idDraft.slice(3)
|
|
279
|
+
: '';
|
|
280
|
+
const instance = {
|
|
281
|
+
ruleID,
|
|
282
|
+
what: ruleResult.message.actual.description,
|
|
283
|
+
ordinalSeverity: ['warning', 0, 0, 'error'].indexOf(issueType),
|
|
284
|
+
tagName,
|
|
285
|
+
id,
|
|
286
|
+
location: {
|
|
287
|
+
doc: 'dom',
|
|
288
|
+
type: 'xpath',
|
|
289
|
+
spec: xpath
|
|
290
|
+
},
|
|
291
|
+
excerpt
|
|
292
|
+
};
|
|
293
|
+
standardResult.instances.push(instance);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
262
298
|
// axe
|
|
263
299
|
else if (
|
|
264
300
|
toolName === 'axe'
|
package/run.js
CHANGED
|
@@ -10,7 +10,7 @@ require('dotenv').config();
|
|
|
10
10
|
// Requirements for acts.
|
|
11
11
|
const {actSpecs} = require('./actSpecs');
|
|
12
12
|
// Navigation.
|
|
13
|
-
const {browserClose, goTo, launch} = require('./procs/nav');
|
|
13
|
+
const {browserClose, getNonce, goTo, launch} = require('./procs/nav');
|
|
14
14
|
// Module to standardize report formats.
|
|
15
15
|
const {standardize} = require('./procs/standardize');
|
|
16
16
|
|
|
@@ -36,6 +36,7 @@ const moves = {
|
|
|
36
36
|
// Names and descriptions of tools.
|
|
37
37
|
const tests = {
|
|
38
38
|
alfa: 'alfa',
|
|
39
|
+
aslint: 'ASLint',
|
|
39
40
|
axe: 'Axe',
|
|
40
41
|
htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
|
|
41
42
|
ibm: 'IBM Accessibility Checker',
|
|
@@ -515,17 +516,21 @@ const doActs = async (report, actIndex, page) => {
|
|
|
515
516
|
// Otherwise, if the act is a launch:
|
|
516
517
|
else if (act.type === 'launch') {
|
|
517
518
|
// Launch the specified browser and navigate to the specified URL.
|
|
518
|
-
const
|
|
519
|
+
const launchResult = await launch(
|
|
519
520
|
report, act.which, act.url, debug, waits, act.lowMotion ? 'reduce' : 'no-preference'
|
|
520
521
|
);
|
|
521
522
|
// If the launch and navigation succeeded:
|
|
522
|
-
if (
|
|
523
|
+
if (launchResult && launchResult.success) {
|
|
523
524
|
// Save the browser data.
|
|
524
|
-
|
|
525
|
-
currentPage = browserData.currentPage;
|
|
525
|
+
const {response, currentPage} = launchResult;
|
|
526
526
|
page = currentPage;
|
|
527
527
|
// Add the actual URL to the act.
|
|
528
528
|
act.actualURL = page.url();
|
|
529
|
+
// Add any nonce to the Act.
|
|
530
|
+
const scriptNonce = await getNonce(response);
|
|
531
|
+
if (scriptNonce) {
|
|
532
|
+
report.jobData.lastScriptNonce = scriptNonce;
|
|
533
|
+
}
|
|
529
534
|
}
|
|
530
535
|
// Otherwise, i.e. if the launch or navigation failed:
|
|
531
536
|
else {
|
|
@@ -541,26 +546,32 @@ const doActs = async (report, actIndex, page) => {
|
|
|
541
546
|
const resolved = act.which.replace('__dirname', __dirname);
|
|
542
547
|
requestedURL = resolved;
|
|
543
548
|
// Visit it and wait until the DOM is loaded.
|
|
544
|
-
const
|
|
545
|
-
// If the visit
|
|
546
|
-
if (
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
else {
|
|
553
|
-
// If a prohibited redirection occurred:
|
|
554
|
-
if (response.exception === 'badRedirection') {
|
|
555
|
-
// Report this and quit.
|
|
556
|
-
addError(act, 'badRedirection', 'ERROR: Navigation illicitly redirected');
|
|
557
|
-
await abortActs();
|
|
549
|
+
const navResult = await goTo(report, page, requestedURL, 15000, 'domcontentloaded');
|
|
550
|
+
// If the visit succeeded:
|
|
551
|
+
if (navResult.success) {
|
|
552
|
+
// Add the script nonce, if any, to the act.
|
|
553
|
+
const {response} = navResult;
|
|
554
|
+
const scriptNonce = getNonce(response);
|
|
555
|
+
if (scriptNonce) {
|
|
556
|
+
report.jobData.lastScriptNonce = scriptNonce;
|
|
558
557
|
}
|
|
559
558
|
// Add the resulting URL to the act.
|
|
560
559
|
if (! act.result) {
|
|
561
560
|
act.result = {};
|
|
562
561
|
}
|
|
563
562
|
act.result.url = page.url();
|
|
563
|
+
// If a prohibited redirection occurred:
|
|
564
|
+
if (response.exception === 'badRedirection') {
|
|
565
|
+
// Report this and quit.
|
|
566
|
+
addError(act, 'badRedirection', 'ERROR: Navigation illicitly redirected');
|
|
567
|
+
await abortActs();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// If the visit failed:
|
|
571
|
+
if (response.error) {
|
|
572
|
+
// Report this and quit.
|
|
573
|
+
addError(act, 'failure', 'ERROR: Visits failed');
|
|
574
|
+
await abortActs();
|
|
564
575
|
}
|
|
565
576
|
}
|
|
566
577
|
// Otherwise, if the act is a wait for text:
|
|
@@ -684,7 +695,10 @@ const doActs = async (report, actIndex, page) => {
|
|
|
684
695
|
// Add a description of the test to the act.
|
|
685
696
|
act.what = tests[act.which];
|
|
686
697
|
// Initialize the options argument.
|
|
687
|
-
const options = {
|
|
698
|
+
const options = {
|
|
699
|
+
act,
|
|
700
|
+
scriptNonce: report.jobData.lastScriptNonce || ''
|
|
701
|
+
};
|
|
688
702
|
// Add any specified arguments to it.
|
|
689
703
|
Object.keys(act).forEach(key => {
|
|
690
704
|
if (! ['type', 'which'].includes(key)) {
|
|
@@ -700,8 +714,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
700
714
|
};
|
|
701
715
|
// Perform the specified tests of the tool and get a report.
|
|
702
716
|
try {
|
|
703
|
-
|
|
704
|
-
toolReport = await require(`./tests/${act.which}`).reporter(... args);
|
|
717
|
+
toolReport = await require(`./tests/${act.which}`).reporter(page, options);
|
|
705
718
|
toolReport.result.success = true;
|
|
706
719
|
}
|
|
707
720
|
// If the testing failed:
|
package/tests/aslint.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
aslint
|
|
3
|
+
This test implements the ASLint ruleset for accessibility.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
// Module to handle files.
|
|
9
|
+
const fs = require('fs/promises');
|
|
10
|
+
|
|
11
|
+
// FUNCTIONS
|
|
12
|
+
|
|
13
|
+
// Conducts and reports an ASLint test.
|
|
14
|
+
exports.reporter = async (page, options) => {
|
|
15
|
+
// Initialize the report.
|
|
16
|
+
let data = {};
|
|
17
|
+
// Get the ASLint runner and bundle scripts.
|
|
18
|
+
const aslintRunner = await fs.readFile(`${__dirname}/../procs/aslint.js`, 'utf8');
|
|
19
|
+
const aslintBundle = await fs.readFile(
|
|
20
|
+
`${__dirname}/../node_modules/aslint-testaro/aslint.bundle.js`, 'utf8'
|
|
21
|
+
);
|
|
22
|
+
// Get the nonce, if any.
|
|
23
|
+
const {scriptNonce} = options;
|
|
24
|
+
// Inject the ASLint bundle and runner into the page.
|
|
25
|
+
await page.evaluate(args => {
|
|
26
|
+
const {scriptNonce, aslintBundle, aslintRunner} = args;
|
|
27
|
+
// Bundle.
|
|
28
|
+
const bundleEl = document.createElement('script');
|
|
29
|
+
bundleEl.id = 'aslintBundle';
|
|
30
|
+
if (scriptNonce) {
|
|
31
|
+
bundleEl.nonce = scriptNonce;
|
|
32
|
+
console.log(`Added nonce ${scriptNonce} to bundle`);
|
|
33
|
+
}
|
|
34
|
+
bundleEl.textContent = aslintBundle;
|
|
35
|
+
document.head.insertAdjacentElement('beforeend', bundleEl);
|
|
36
|
+
// Runner.
|
|
37
|
+
const runnerEl = document.createElement('script');
|
|
38
|
+
if (scriptNonce) {
|
|
39
|
+
runnerEl.nonce = scriptNonce;
|
|
40
|
+
console.log(`Added nonce ${scriptNonce} to runner`);
|
|
41
|
+
}
|
|
42
|
+
runnerEl.textContent = aslintRunner;
|
|
43
|
+
document.body.insertAdjacentElement('beforeend', runnerEl);
|
|
44
|
+
}, {scriptNonce, aslintBundle, aslintRunner})
|
|
45
|
+
.catch(error => {
|
|
46
|
+
console.log(`ERROR: ASLint injection failed (${error.message.slice(0, 400)})`);
|
|
47
|
+
data.prevented = true;
|
|
48
|
+
data.error = 'ERROR: ASLint injection failed';
|
|
49
|
+
});
|
|
50
|
+
// If the injection succeeded:
|
|
51
|
+
if (! data.prevented) {
|
|
52
|
+
// Wait for the test results.
|
|
53
|
+
const reportLoc = page.locator('#aslintResult');
|
|
54
|
+
await reportLoc.waitFor({
|
|
55
|
+
state: 'attached',
|
|
56
|
+
timeout: 10000
|
|
57
|
+
});
|
|
58
|
+
// Get them.
|
|
59
|
+
const report = await reportLoc.textContent();
|
|
60
|
+
// Populate the tool report.
|
|
61
|
+
data = JSON.parse(report);
|
|
62
|
+
// Delete irrelevant properties from the tool report details.
|
|
63
|
+
if (data.rules) {
|
|
64
|
+
Object.keys(data.rules).forEach(ruleID => {
|
|
65
|
+
if (['passed', 'skipped'].includes(data.rules[ruleID].status.type)) {
|
|
66
|
+
delete data.rules[ruleID];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Return the result.
|
|
72
|
+
try {
|
|
73
|
+
JSON.stringify(data);
|
|
74
|
+
}
|
|
75
|
+
catch(error) {
|
|
76
|
+
console.log(`ERROR: ASLint result cannot be made JSON (${error.message.slice(0, 200)})`);
|
|
77
|
+
data = {
|
|
78
|
+
prevented: true,
|
|
79
|
+
error: `ERROR: ASLint result cannot be made JSON (${error.message.slice(0, 200)})`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {result: data};
|
|
83
|
+
};
|
package/tests/axe.js
CHANGED
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
category (“tag”), such as 'wcag21aaa'. Scoring can consider test categories by getting the value
|
|
19
19
|
of the 'tags' property of each rule.
|
|
20
20
|
*/
|
|
21
|
+
|
|
21
22
|
// IMPORTS
|
|
23
|
+
|
|
22
24
|
const {injectAxe, getAxeResults} = require('axe-playwright');
|
|
25
|
+
|
|
23
26
|
// FUNCTIONS
|
|
27
|
+
|
|
24
28
|
// Conducts and reports an Axe test.
|
|
25
29
|
exports.reporter = async (page, options) => {
|
|
26
30
|
const {detailLevel, rules} = options;
|
package/tests/qualWeb.js
CHANGED
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
qualWeb
|
|
3
3
|
This test implements the QualWeb ruleset for accessibility.
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
// IMPORTS
|
|
7
|
+
|
|
6
8
|
const {QualWeb} = require('@qualweb/core');
|
|
9
|
+
|
|
7
10
|
// CONSTANTS
|
|
11
|
+
|
|
8
12
|
const qualWeb = new QualWeb({});
|
|
9
13
|
const clusterOptions = {
|
|
10
14
|
timeout: 25 * 1000
|
|
11
15
|
};
|
|
16
|
+
|
|
12
17
|
// FUNCTIONS
|
|
18
|
+
|
|
13
19
|
// Conducts and reports a QualWeb test.
|
|
14
20
|
exports.reporter = async (page, options) => {
|
|
15
21
|
const {withNewContent, rules} = options;
|