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.
Files changed (118) hide show
  1. package/CONTRIBUTING.md +3 -1
  2. package/README.md +14 -3
  3. package/package.json +2 -1
  4. package/procs/aslint.js +83 -0
  5. package/procs/nav.js +56 -14
  6. package/procs/standardize.js +36 -0
  7. package/run.js +35 -22
  8. package/tests/aslint.js +83 -0
  9. package/tests/axe.js +4 -0
  10. package/tests/qualWeb.js +6 -0
  11. package/aslint/LICENSE +0 -362
  12. package/aslint/README.md +0 -260
  13. package/aslint/app/rules/abstract-rule.ts +0 -83
  14. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.documentation.md +0 -36
  15. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.test.ts +0 -113
  16. package/aslint/app/rules/aslint/incorrect-technique-for-hiding-content/incorrect-technique-for-hiding-content.ts +0 -103
  17. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.documentation.md +0 -34
  18. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.test.ts +0 -82
  19. package/aslint/app/rules/aslint/invalid-attribute-dir-value/invalid-attribute-dir-value.ts +0 -44
  20. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.documentation.md +0 -40
  21. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.test.ts +0 -48
  22. package/aslint/app/rules/aslint/label-duplicated-content-title/label-duplicated-content-title.ts +0 -37
  23. package/aslint/app/rules/aslint/links-language-destination/links-language-destination.test.ts +0 -50
  24. package/aslint/app/rules/aslint/links-language-destination/links-language-destination.ts +0 -70
  25. package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.test.ts +0 -55
  26. package/aslint/app/rules/aslint/main-element-only-one/main-element-only-one.ts +0 -83
  27. package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.test.ts +0 -12
  28. package/aslint/app/rules/aslint/main-landmark-must-be-top-level/main-landmark-must-be-top-level.ts +0 -73
  29. package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.test.ts +0 -12
  30. package/aslint/app/rules/aslint/minimum-font-size/minimum-font-size.ts +0 -87
  31. package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.test.ts +0 -48
  32. package/aslint/app/rules/aslint/missing-href-on-a/missing-href-on-a.ts +0 -40
  33. package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.test.ts +0 -12
  34. package/aslint/app/rules/aslint/misused-aria-on-focusable-element/misused-aria-on-focusable-element.ts +0 -66
  35. package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.test.ts +0 -12
  36. package/aslint/app/rules/aslint/misused-input-attribute/misused-input-attribute.ts +0 -134
  37. package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.test.ts +0 -12
  38. package/aslint/app/rules/aslint/misused-required-attribute/misused-required-attribute.ts +0 -90
  39. package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.test.ts +0 -12
  40. package/aslint/app/rules/aslint/navigation-landmark-restrictions/navigation-landmark-restrictions.ts +0 -48
  41. package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.test.ts +0 -12
  42. package/aslint/app/rules/aslint/obsolete-html-attributes/obsolete-html-attributes.ts +0 -148
  43. package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.test.ts +0 -12
  44. package/aslint/app/rules/aslint/obsolete-html-elements/obsolete-html-elements.ts +0 -66
  45. package/aslint/app/rules/aslint/outline-zero/outline-zero.test.ts +0 -12
  46. package/aslint/app/rules/aslint/outline-zero/outline-zero.ts +0 -85
  47. package/aslint/app/rules/aslint/overlay/overlay.test.ts +0 -122
  48. package/aslint/app/rules/aslint/overlay/overlay.ts +0 -141
  49. package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.documentation.md +0 -49
  50. package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.test.ts +0 -91
  51. package/aslint/app/rules/aslint/redundant/aria-role-dialog/aria-role-dialog.ts +0 -62
  52. package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.documentation.md +0 -44
  53. package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.test.ts +0 -111
  54. package/aslint/app/rules/aslint/redundant/capital-letters-words/capital-letters-words.ts +0 -120
  55. package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.documentation.md +0 -45
  56. package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.test.ts +0 -63
  57. package/aslint/app/rules/aslint/redundant/contentinfo-landmark-only-one/contentinfo-landmark-only-one.ts +0 -44
  58. package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.documentation.md +0 -55
  59. package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.test.ts +0 -80
  60. package/aslint/app/rules/aslint/redundant/empty-title-attribute/empty-title-attribute.ts +0 -58
  61. package/aslint/app/rules/aslint/redundant/flash-content/flash-content.documentation.md +0 -48
  62. package/aslint/app/rules/aslint/redundant/flash-content/flash-content.test.ts +0 -52
  63. package/aslint/app/rules/aslint/redundant/flash-content/flash-content.ts +0 -32
  64. package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.documentation.md +0 -44
  65. package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.test.ts +0 -12
  66. package/aslint/app/rules/aslint/redundant/font-style-italic/font-style-italic.ts +0 -83
  67. package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.documentation.md +0 -46
  68. package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.test.ts +0 -46
  69. package/aslint/app/rules/aslint/redundant/h1-must-be/h1-must-be.ts +0 -36
  70. package/aslint/app/rules/aslint/role-application/role-application.test.ts +0 -48
  71. package/aslint/app/rules/aslint/role-application/role-application.ts +0 -38
  72. package/aslint/app/rules/aslint/rtl-content/rtl-content.test.ts +0 -12
  73. package/aslint/app/rules/aslint/rtl-content/rtl-content.ts +0 -75
  74. package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.test.ts +0 -12
  75. package/aslint/app/rules/aslint/unclear-uri-on-a/unclear-anchor-uri.ts +0 -48
  76. package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.test.ts +0 -73
  77. package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden-false.ts +0 -34
  78. package/aslint/app/rules/aslint/unimportant/aria-hidden-false/aria-hidden.documentation.md +0 -32
  79. package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.docmentation.md +0 -48
  80. package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.test.ts +0 -67
  81. package/aslint/app/rules/aslint/unimportant/content-editable-missing-attributes/content-editable-missing-attributes.ts +0 -63
  82. package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.test.ts +0 -12
  83. package/aslint/app/rules/aslint/unsupported-role-on-element/unsupported-role-on-element.ts +0 -63
  84. package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.documentation.md +0 -65
  85. package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.test.ts +0 -53
  86. package/aslint/app/rules/aslint/used/elements-not-allowed-in-head/elements-not-allowed-in-head.ts +0 -47
  87. package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.documentation.md +0 -57
  88. package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.test.ts +0 -52
  89. package/aslint/app/rules/aslint/used/headings-sibling-unique/headings-sibling-unique.ts +0 -63
  90. package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.documentation.md +0 -39
  91. package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.test.ts +0 -66
  92. package/aslint/app/rules/aslint/used/horizontal-rule/horizontal-rule.ts +0 -37
  93. package/aslint/utils/aria.test.ts +0 -12
  94. package/aslint/utils/aria.ts +0 -120
  95. package/aslint/utils/async.test.ts +0 -25
  96. package/aslint/utils/async.ts +0 -22
  97. package/aslint/utils/common.test.ts +0 -239
  98. package/aslint/utils/common.ts +0 -168
  99. package/aslint/utils/console.test.ts +0 -85
  100. package/aslint/utils/console.ts +0 -89
  101. package/aslint/utils/css.test.ts +0 -153
  102. package/aslint/utils/css.ts +0 -191
  103. package/aslint/utils/dom.test.ts +0 -627
  104. package/aslint/utils/dom.ts +0 -1051
  105. package/aslint/utils/env.test.ts +0 -14
  106. package/aslint/utils/env.ts +0 -8
  107. package/aslint/utils/func.test.ts +0 -160
  108. package/aslint/utils/func.ts +0 -70
  109. package/aslint/utils/global.test.ts +0 -12
  110. package/aslint/utils/global.ts +0 -25
  111. package/aslint/utils/object.test.ts +0 -524
  112. package/aslint/utils/object.ts +0 -278
  113. package/aslint/utils/report.test.ts +0 -56
  114. package/aslint/utils/report.ts +0 -36
  115. package/aslint/utils/text.test.ts +0 -270
  116. package/aslint/utils/text.ts +0 -165
  117. package/aslint/utils/time.test.ts +0 -43
  118. 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
- To come.
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. One such tool is the [BBC Accessibility Standards Checker](https://github.com/bbc/bbc-a11y). In all such cases the Testaro rules are independently designed and implemented, without reference to the code of the tests that inspired them.
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 eight tools define about 650 rules. Some of the tools are under active development, and their rule counts change over time.
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 test
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": "27.0.0",
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",
@@ -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(/&lt;/g, '<')
44
+ .replace(/&gt;/g, '>')
45
+ .replace(/&quot;/g, '\"')
46
+ .replace(/&#x3D;/g, '=')
47
+ .replace(/&#x2F;/g, '/')
48
+ .replace(/&#39;/g, '\'')
49
+ .replace(/&amp;(?:amp;)*/g, '&')
50
+ .replace(/%3A/g, ':')
51
+ .replace(/%2F/g, '/');
52
+ ruleResult.message.actual.description = ruleResult
53
+ .message
54
+ .actual
55
+ .description
56
+ .replace(/&lt;/g, '<')
57
+ .replace(/&gt;/g, '>')
58
+ .replace(/&quot;/g, '\"')
59
+ .replace(/&#x3D;/g, '=')
60
+ .replace(/&#x2F;/g, '/')
61
+ .replace(/&amp;(?: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 response.
72
- return response;
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 (${errorStart(error)})`);
157
+ console.log(`ERROR launching browser (${error.message.slice(0, 200)})`);
131
158
  // Return this.
132
- return false;
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 failed:
191
- if (navResult.error) {
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 false;
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 false;
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;
@@ -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 browserData = await launch(
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 (browserData) {
523
+ if (launchResult && launchResult.success) {
523
524
  // Save the browser data.
524
- browserContext = browserData.browserContext;
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 response = await goTo(report, page, requestedURL, 15000, 'domcontentloaded');
545
- // If the visit failed:
546
- if (response.error) {
547
- // Report this and quit.
548
- addError(act, 'failure', 'ERROR: Visits failed');
549
- await abortActs();
550
- }
551
- // Otherwise, i.e. if the visit succeeded:
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
- const args = [page, options];
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:
@@ -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;