testaro 4.5.1 → 4.6.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/README.md CHANGED
@@ -23,7 +23,7 @@ Testaro includes some of its own accessibility tests. In addition, it performs t
23
23
  - [alfa](https://alfa.siteimprove.com/) (Siteimprove alfa)
24
24
  - [Automated Accessibility Testing Tool](https://www.npmjs.com/package/aatt) (Paypal AATT, running HTML CodeSniffer)
25
25
  - [axe-playwright](https://www.npmjs.com/package/axe-playwright) (Deque Axe-core)
26
- - [Tenon](https://tenon.io/documentation/what-tenon-tests.php)
26
+ - [Tenon](https://tenon.io/documentation/what-tenon-tests.php) (Level Access)
27
27
  - [WAVE API](https://wave.webaim.org/api/) (WebAIM WAVE)
28
28
 
29
29
  As of this version, the counts of tests in the packages referenced above were:
@@ -560,6 +560,8 @@ The `tests` executor makes use of the scripts in the `validation/tests/scripts`
560
560
 
561
561
  You can define additional Testaro commands and functionality. Contributions are welcome.
562
562
 
563
+ Please report any issues, including feature requests, at the [repository](https://github.com/jrpool/testaro/issues).
564
+
563
565
  ## Accessibility principles
564
566
 
565
567
  The rationales motivating the Testaro-defined tests can be found in comments within the files of those tests, in the `tests` directory. Unavoidably, each test is opinionated. Testaro itself, however, can accommodate other tests representing different opinions. Testaro is intended to be neutral with respect to questions such as the criteria for accessibility, the severities of accessibility issues, whether accessibility is binary or graded, and the distinction between usability and accessibility.
@@ -594,53 +596,23 @@ The files in the `temp` directory are presumed ephemeral and are not tracked by
594
596
 
595
597
  ## Related packages
596
598
 
597
- [Testilo](https://www.npmjs.com/package/testilo) is an application that facilitates the use of Testaro.
599
+ [Testilo](https://www.npmjs.com/package/testilo) is an application that:
600
+ - produces scores and adds them to the JSON report files of Testaro
601
+ - produces human-oriented HTML digests from scored reports
602
+ - produces human-oriented HTML reports comparing the scores of hosts
598
603
 
599
604
  Testaro is derived from [Autotest](https://github.com/jrpool/autotest).
600
605
 
601
606
  Testaro omits some functionalities of Autotest, such as:
602
607
  - tests producing results intended to be human-inspected
603
- - scoring
608
+ - scoring (performed now by Testilo)
604
609
  - file operations for score aggregation, report revision, and HTML reports
605
610
  - a web user interface
606
611
 
607
612
  ## Origin
608
613
 
609
- Work on the custom tests in this package began in 2017, and work on the multi-package federation that Testaro implements began in early 2018. These two aspects were combined into the [Autotest](https://github.com/jrpool/autotest) package in early 2021 and into this more single-purpose package, Testaro, in January 2022.
614
+ Work on the custom tests in this package began in 2017, and work on the multi-package federation that Testaro implements began in early 2018. These two aspects were combined into the [Autotest](https://github.com/jrpool/autotest) package in early 2021 and into the more single-purpose packages, Testaro and Testilo, in January 2022.
610
615
 
611
616
  ## Etymology
612
617
 
613
618
  “Testaro” means “collection of tests” in Esperanto.
614
-
615
- ## Future work
616
-
617
- ### Improvements
618
-
619
- Further development is contemplated, is taking place, or is welcomed, on:
620
- - addition of Tenon to the set of packages
621
- - links with href="#"
622
- - links and buttons styled non-distinguishably
623
- - first focused element not first focusable element in DOM
624
- - never-visible skip links
625
- - buttons with no text content
626
- - modal dialogs
627
- - autocomplete attributes
628
- - inclusion of other test packages, such as:
629
- - FAE (https://github.com/opena11y/evaluation-library)
630
-
631
- ## Corrections
632
-
633
- Issues found or reported with the current version that need diagnosis and correction include:
634
-
635
- ### hover
636
-
637
- There seem to be a couple of problems with the hover test:
638
- - The score for unhoverability is documented as 2 times the count of unhoverables, but is reported as 1 time that count.
639
- - The list of unhoverables in the report is empty.
640
- Observed after inquiry by Tobias Christian Jensen of Siteimprove on 2022-05-09.
641
-
642
- ### axe
643
-
644
- Configuration to include best practices and experimental tests.
645
-
646
- Investigation of tags, including wcag2a, wcag2aa, wcag21a, wcag21aa, best-practice, wcag***, ACT, cat.*.
package/commands.js CHANGED
@@ -146,7 +146,8 @@ exports.commands = {
146
146
  aatt: [
147
147
  'Perform an AATT test with HTML CodeSniffer',
148
148
  {
149
- waitLong: [false, 'boolean', '', 'whether to wait 20 instead of 10 seconds for the result']
149
+ waitLong: [false, 'boolean', '', 'whether to wait 12 instead of 6 seconds for a result'],
150
+ tryLimit: [false, 'number', '', 'times to try the test before giving up; default 4']
150
151
  }
151
152
  ],
152
153
  axe: [
@@ -178,6 +179,7 @@ exports.commands = {
178
179
  hover: [
179
180
  'Perform a hover test',
180
181
  {
182
+ sampleSize: [false, 'number', '', 'number of triggers to sample, if fewer than all'],
181
183
  withItems: [true, 'boolean']
182
184
  }
183
185
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "4.5.1",
3
+ "version": "4.6.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -900,7 +900,7 @@ const doActs = async (report, actIndex, page) => {
900
900
  // Otherwise, if the act is a move:
901
901
  else if (moves[act.type]) {
902
902
  const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
903
- // Wait 10 seconds until the element to perform the move on is identified.
903
+ // Try up to 5 times, every 2 seconds, to identify the element to perform the move on.
904
904
  let matchResult = {success: false};
905
905
  let tries = 0;
906
906
  while (tries++ < 5 && ! matchResult.success) {
package/tests/aatt.js CHANGED
@@ -7,31 +7,59 @@
7
7
  const {evaluate} = require('aatt');
8
8
 
9
9
  // FUNCTIONS
10
- exports.reporter = async (page, waitLong) => {
11
- // Set the limit in seconds on the wait for the result.
12
- const timeLimit = waitLong ? 20 : 10;
13
- // Get the HTML of the document body.
14
- const source = await page.content();
15
- // Return the result of a test with the HTML CodeSniffer WCAG 2.1 AA ruleset as a string.
16
- const report = evaluate({
17
- source,
18
- output: 'json',
19
- engine: 'htmlcs',
20
- level: 'WCAG2AA'
21
- });
22
- // Wait for it until the time limit expires.
23
- let timeoutID;
24
- const wait = new Promise(resolve => {
25
- timeoutID = setTimeout(() => {
26
- resolve('');
27
- }, 1000 * timeLimit);
28
- });
29
- const resultIfFast = await Promise.race([report, wait]);
30
- // If it arrived within the time limit:
31
- if (resultIfFast) {
32
- clearTimeout(timeoutID);
33
- // Remove the non-JSON prefix and (if any) suffix from the string.
34
- const reportJSON = resultIfFast.replace(/^.+?Object]\s+|\s+done\s*$/sg, '');
10
+ // Recursively test a page with HTML CodeSniffer for WCAC 2.1 AAA.
11
+ const retest = async (page, waitLong, triesLeft) => {
12
+ // If the limit on tries has not been exhausted:
13
+ if (triesLeft) {
14
+ // Set the limit in seconds on the wait for the result.
15
+ const timeLimit = waitLong ? 12 : 6;
16
+ // Get the HTML of the document body.
17
+ const source = await page.content();
18
+ // Return the result of a test with the HTML CodeSniffer WCAG 2.1 AA ruleset as a string.
19
+ const report = evaluate({
20
+ source,
21
+ output: 'json',
22
+ engine: 'htmlcs',
23
+ level: 'WCAG2AAA'
24
+ });
25
+ // Wait for it until the time limit expires.
26
+ let timeoutID;
27
+ const wait = new Promise(resolve => {
28
+ timeoutID = setTimeout(() => {
29
+ resolve('');
30
+ }, 1000 * timeLimit);
31
+ });
32
+ const result = await Promise.race([report, wait]);
33
+ // If it arrived within the time limit:
34
+ if (result) {
35
+ clearTimeout(timeoutID);
36
+ // Return the result as JSON.
37
+ const reportJSON = result.replace(/^.+?Object.?\s+|\s+done\s*$/sg, '');
38
+ return {
39
+ triesLeft,
40
+ reportJSON
41
+ };
42
+ }
43
+ else {
44
+ console.log(`ERROR: Test aatt timed out at ${timeLimit} seconds; tries left: ${triesLeft - 1}`);
45
+ return retest(page, waitLong, --triesLeft);
46
+ }
47
+ }
48
+ // Otherwise, i.e. if the limit on tries has been exhausted:
49
+ else {
50
+ // Return this.
51
+ return {
52
+ triesLeft,
53
+ reportJSON: ''
54
+ };
55
+ }
56
+ };
57
+ exports.reporter = async (page, waitLong, tryLimit = 4) => {
58
+ // Try the test up to the limit on tries.
59
+ const result = await retest(page, waitLong, tryLimit);
60
+ const {triesLeft, reportJSON} = result;
61
+ // If any try succeeded:
62
+ if (reportJSON) {
35
63
  try {
36
64
  // Convert the JSON string to an array.
37
65
  const issueArray = JSON.parse(reportJSON);
@@ -49,10 +77,18 @@ exports.reporter = async (page, waitLong) => {
49
77
  .join('+');
50
78
  }
51
79
  });
52
- return {result: nonNotices};
80
+ return {
81
+ result: {
82
+ report: nonNotices,
83
+ triesLeft
84
+ }
85
+ };
53
86
  }
54
87
  catch (error) {
55
88
  console.log(`ERROR processing AATT report (${error.message})`);
89
+ console.log(
90
+ `JSON report starts with ${reportJSON.slice(0, 50)} and ends with ${reportJSON.slice(-50)}`
91
+ );
56
92
  return {
57
93
  result: {
58
94
  prevented: true,
@@ -61,14 +97,15 @@ exports.reporter = async (page, waitLong) => {
61
97
  };
62
98
  }
63
99
  }
64
- // Otherwise, i.e. if the result did not arrive within the time limit:
100
+ // Otherwise, i.e. if the limit on tries was exhausted:
65
101
  else {
66
102
  // Report the failure.
67
- console.log('ERROR: getting report took too long');
103
+ const error = 'ERROR: Getting AATT report took too long';
104
+ console.log(error);
68
105
  return {
69
106
  result: {
70
107
  prevented: true,
71
- error: 'ERROR: getting AATT report took too long'
108
+ error
72
109
  }
73
110
  };
74
111
  }
package/tests/hover.js CHANGED
@@ -9,9 +9,9 @@
9
9
  has the tag name 'A' or 'BUTTON' or otherwise the descendants of the element. The only
10
10
  elements counted as being made visible by hovering are those with tag names 'A', 'BUTTON',
11
11
  'INPUT', and 'SPAN', and those with 'role="menuitem"' attributes. The test waits 700 ms after
12
- each hover in case of delayed effects. Despite this delay, the test makes the execution time
13
- practical by randomly sampling targets instead of hovering over all of them. Therefore, the
14
- results may vary from one execution to another.
12
+ each hover in case of delayed effects. Despite this delay, the test can make the execution time
13
+ practical by randomly sampling targets instead of hovering over all of them. When sampling is
14
+ performed, the results may vary from one execution to another.
15
15
  */
16
16
 
17
17
  // CONSTANTS
@@ -53,14 +53,14 @@ const getSample = (population, sampleSize) => {
53
53
  return [];
54
54
  }
55
55
  };
56
+ // Returns the text of an element.
57
+ const textOf = async (element, limit) => {
58
+ let text = await element.textContent();
59
+ text = text.trim() || await element.innerHTML();
60
+ return text.trim().replace(/\s*/sg, '').slice(0, limit);
61
+ };
56
62
  // Recursively finds and reports triggers and targets.
57
63
  const find = async (withItems, page, triggers) => {
58
- if (withItems) {
59
- data.items = {
60
- triggers: [],
61
- unhoverables: []
62
- };
63
- }
64
64
  // If any potential disclosure triggers remain:
65
65
  if (triggers.length) {
66
66
  // Identify the first of them.
@@ -181,12 +181,6 @@ const find = async (withItems, page, triggers) => {
181
181
  }
182
182
  catch (error) {
183
183
  console.log('ERROR hovering');
184
- // Returns the text of an element.
185
- const textOf = async (element, limit) => {
186
- let text = await element.textContent();
187
- text = text.trim() || await element.innerHTML();
188
- return text.trim().replace(/\s*/sg, '').slice(0, limit);
189
- };
190
184
  data.totals.unhoverables++;
191
185
  if (withItems) {
192
186
  data.items.unhoverables.push({
@@ -201,7 +195,16 @@ const find = async (withItems, page, triggers) => {
201
195
  await find(withItems, page, triggers.slice(1));
202
196
  }
203
197
  };
204
- exports.reporter = async (page, withItems) => {
198
+ // Performs hover test and reports results.
199
+ exports.reporter = async (page, sampleSize = Infinity, withItems) => {
200
+ // If details are to be reported:
201
+ if (withItems) {
202
+ // Add properties for details to the initialized result.
203
+ data.items = {
204
+ triggers: [],
205
+ unhoverables: []
206
+ };
207
+ }
205
208
  // Identify the triggers.
206
209
  const selectors = [
207
210
  'body a:visible',
@@ -217,8 +220,7 @@ exports.reporter = async (page, withItems) => {
217
220
  });
218
221
  // If they number more than the sample size limit, sample them.
219
222
  const triggerCount = triggers.length;
220
- const sampleSize = 15;
221
- const triggerSample = triggerCount > sampleSize ? getSample(triggers, 15) : triggers;
223
+ const triggerSample = triggerCount > sampleSize ? getSample(triggers, sampleSize) : triggers;
222
224
  // Find and document the hover-triggered disclosures.
223
225
  await find(withItems, page, triggerSample);
224
226
  // If the triggers were sampled: