testaro 60.16.2 → 60.17.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "60.16.2",
3
+ "version": "60.17.0",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/testaro.js CHANGED
@@ -169,12 +169,13 @@ exports.doTest = async (
169
169
  const candidates = document.body.querySelectorAll(candidateSelector);
170
170
  let violationCount = 0;
171
171
  const instances = [];
172
- // Get a violation function.
172
+ // Get a function that returns a violation description, if any, for the candidate.
173
173
  const getBadWhat = eval(`(${getBadWhatString})`);
174
174
  // For each candidate:
175
175
  for (const candidate of candidates) {
176
+ // Get the violation description, if any.
176
177
  const violationWhat = await getBadWhat(candidate);
177
- // If it violates the rule:
178
+ // If the candidate violates the rule:
178
179
  if (violationWhat) {
179
180
  // Increment the violation count.
180
181
  violationCount++;
package/testaro/hr.js ADDED
@@ -0,0 +1,32 @@
1
+ /*
2
+ © 2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+
11
+ /*
12
+ hr
13
+ This test reports the use of hr elements.
14
+ */
15
+
16
+ // IMPORTS
17
+
18
+ const {doTest} = require('../procs/testaro');
19
+
20
+ // FUNCTIONS
21
+
22
+ // Runs the test and returns the result.
23
+ exports.reporter = async (page, withItems) => {
24
+ const getBadWhat = element => {
25
+ // Return a violation description.
26
+ return `hr element is used for vertical segmentation`;
27
+ }
28
+ const whats = 'HR elements are used for vertical segmentation';
29
+ return await doTest(
30
+ page, withItems, 'hr', 'hr', whats, 0, 'HR', getBadWhat.toString()
31
+ );
32
+ };
@@ -0,0 +1,31 @@
1
+ /*
2
+ © 2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+
11
+ /*
12
+ linkExt
13
+ This test reports links with target=_blank attributes.
14
+ */
15
+
16
+ // IMPORTS
17
+
18
+ const {doTest} = require('../procs/testaro');
19
+
20
+ // FUNCTIONS
21
+
22
+ exports.reporter = async (page, withItems) => {
23
+ const getBadWhat = element => {
24
+ // Return a violation description.
25
+ return `Link has a target=_blank attribute`;
26
+ };
27
+ const whats = 'Links have target=_blank attributes';
28
+ return await doTest(
29
+ page, withItems, 'linkExt', 'a[target=_blank]', whats, 0, 'A', getBadWhat.toString()
30
+ );
31
+ };
@@ -0,0 +1,43 @@
1
+ /*
2
+ © 2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+
11
+ /*
12
+ linkOldAtt
13
+ This test reports links with deprecated attributes.
14
+ */
15
+
16
+ // IMPORTS
17
+
18
+ const {doTest} = require('../procs/testaro');
19
+
20
+ // FUNCTIONS
21
+
22
+ exports.reporter = async (page, withItems) => {
23
+ const getBadWhat = element => {
24
+ const attNames = element.getAttributeNames();
25
+ const allBadAttNames = ['charset', 'coords', 'name', 'rev', 'shape'];
26
+ const elementBadAttNames = allBadAttNames.filter(att => attNames.includes(att));
27
+ // If the element has 1 deprecated attribute:
28
+ if (elementBadAttNames.length === 1) {
29
+ // Return a violation description.
30
+ return `${elementBadAttNames[0]} attribute is deprecated`;
31
+ }
32
+ // Otherwise, if the element has 2 or more deprecated attributes:
33
+ if (elementBadAttNames.length > 1) {
34
+ // Return a violation description.
35
+ return `Element has deprecated attributes: ${elementBadAttNames.join(', ')}`;
36
+ }
37
+ };
38
+ const selector = 'a[charset], a[coords], a[name], a[rev], a[shape]';
39
+ const whats = 'Links have deprecated attributes';
40
+ return await doTest(
41
+ page, withItems, 'linkOldAtt', selector, whats, 1, 'A', getBadWhat.toString()
42
+ );
43
+ };
@@ -0,0 +1,37 @@
1
+ /*
2
+ © 2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+ /*
11
+ linkTo
12
+ This test reports links without href attributes.
13
+ */
14
+ // IMPORTS
15
+
16
+ const {doTest} = require('../procs/testaro');
17
+
18
+ // FUNCTIONS
19
+
20
+ exports.reporter = async (page, withItems) => {
21
+ const getBadWhat = element => {
22
+ const isVisible = element.checkVisibility({
23
+ contentVisibilityAuto: true,
24
+ opacityProperty: true,
25
+ visibilityProperty: true
26
+ });
27
+ // If the element is visible:
28
+ if (isVisible) {
29
+ // Return a violation description.
30
+ return `Element has no href attribute`;
31
+ }
32
+ };
33
+ const whats = 'Links are missing href attributes';
34
+ return await doTest(
35
+ page, withItems, 'linkTo', 'a:not([href]', whats, 2, 'A', getBadWhat.toString()
36
+ );
37
+ };
@@ -0,0 +1,54 @@
1
+ /*
2
+ © 2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+ /*
11
+ pseudoP
12
+ This test reports 2 or more sequential br elements without intervening text content. They may be inferior substitutes for p elements.
13
+ */
14
+
15
+ // IMPORTS
16
+
17
+ const {doTest} = require('../procs/testaro');
18
+
19
+ // FUNCTIONS
20
+
21
+ exports.reporter = async (page, withItems) => {
22
+ const getBadWhat = element => {
23
+ // Get the node before the element node.
24
+ const previousNode = element.previousSibling;
25
+ let isBad = false;
26
+ // If it is a br element:
27
+ if (previousNode && previousNode.nodeType === Node.ELEMENT_NODE && previousNode.tagName === 'BR') {
28
+ // Classify the element as a violator.
29
+ isBad = true;
30
+ }
31
+ // Otherwise, if it is a text node:
32
+ else if (previousNode && previousNode.nodeType === Node.TEXT_NODE) {
33
+ // If the text node contains only whitespace:
34
+ if (previousNode.textContent.trim() === '') {
35
+ // Get the node before the text node.
36
+ const beforeText = previousNode.previousSibling;
37
+ // If that node is a br element:
38
+ if (beforeText && beforeText.nodeType === Node.ELEMENT_NODE && beforeText.tagName === 'BR') {
39
+ // Classify the element as a violator.
40
+ isBad = true;
41
+ }
42
+ }
43
+ }
44
+ // If the element is a violator:
45
+ if (isBad) {
46
+ // Return a violation description.
47
+ return `Element follows another br element, possibly constituting a pseudo-paragraph`;
48
+ }
49
+ };
50
+ const whats = 'br elements follow other br elements, possibly constituting pseudo-paragraphs';
51
+ return await doTest(
52
+ page, withItems, 'pseudoP', 'body br', whats, 0, 'BR', getBadWhat.toString()
53
+ );
54
+ };
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  © 2025 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Juan S. Casado.
3
+ © 2025 Jonathan Robert Pool.
4
4
 
5
5
  Licensed under the MIT License. See LICENSE file at the project root or
6
6
  https://opensource.org/license/mit/ for details.
@@ -0,0 +1,34 @@
1
+ /*
2
+ © 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+
11
+ /*
12
+ titledEl
13
+ This test reports suspicious use of title attributes.
14
+ */
15
+
16
+ // IMPORTS
17
+
18
+ const {doTest} = require('../procs/testaro');
19
+
20
+ // FUNCTIONS
21
+
22
+ // Runs the test and returns the result.
23
+ exports.reporter = async (page, withItems) => {
24
+ const getBadWhat = element => {
25
+ const elementType = element.tagName.toLowerCase();
26
+ // Return a violation description.
27
+ return `Likely ineffective title attribute is used on the ${elementType} element`;
28
+ }
29
+ const selector = '[title]:not(iframe, link, style)';
30
+ const whats = 'title attributes are used on elements they are likely ineffective on';
31
+ return await doTest(
32
+ page, withItems, 'titledEl', selector, whats, 0, null, getBadWhat.toString()
33
+ );
34
+ };
package/tests/testaro.js CHANGED
@@ -547,158 +547,136 @@ exports.reporter = async (page, report, actIndex) => {
547
547
  };
548
548
  browser.on('disconnected', disconnectHandler);
549
549
  }
550
- // Initialize an argument array for reporter or jsonTest.
550
+ // Initialize an argument array for the reporter.
551
551
  const ruleArgs = [page, withItems];
552
- const ruleFileNames = await fs.readdir(`${__dirname}/../testaro`);
553
- const isJS = ruleFileNames.includes(`${ruleID}.js`);
554
- const isJSON = ruleFileNames.includes(`${ruleID}.json`);
555
- // If the rule is defined with JavaScript or JSON but not both:
556
- if ((isJS || isJSON) && ! (isJS && isJSON)) {
557
- // If with JavaScript and it has extra arguments:
558
- if (isJS && argRules && argRules.includes(ruleID)) {
559
- // Add them to the argument array.
560
- ruleArgs.push(... args[ruleID]);
561
- }
562
- result[ruleID] ??= {};
563
- const ruleResult = result[ruleID];
564
- const {what} = rule;
565
- ruleResult.what = what || '';
566
- const startTime = Date.now();
567
- let timeout;
568
- let testRetries = 2;
569
- let testSuccess = false;
570
- while (testRetries > 0 && ! testSuccess) {
571
- try {
572
- // Apply a time limit to the test.
573
- const timeLimit = 1000 * timeoutMultiplier * rule.timeOut;
574
- // If the time limit expires during the test:
575
- const timer = new Promise(resolve => {
576
- timeout = setTimeout(() => {
577
- // Add data about the test, including its prevention, to the result.
578
- const endTime = Date.now();
579
- testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
580
- data.rulePreventions.push(ruleID);
581
- data.rulePreventionMessages[ruleID] = 'Timeout';
582
- ruleResult.totals = [0, 0, 0, 0];
583
- ruleResult.standardInstances = [];
584
- console.log(`ERROR: Test of testaro rule ${ruleID} timed out`);
585
- resolve({timedOut: true});
586
- }, timeLimit);
587
- });
588
- // Perform the test, subject to the time limit.
589
- const ruleReport = isJS
590
- ? require(`../testaro/${ruleID}`).reporter(... ruleArgs)
591
- : jsonTest(ruleID, ruleArgs);
592
- // Get the test result or a timeout result.
593
- const ruleOrTimeoutReport = await Promise.race([timer, ruleReport]);
594
- // If the test was completed:
595
- if (! ruleOrTimeoutReport.timedOut) {
596
- // Add data from the test to the result.
552
+ // If the rule has extra arguments:
553
+ if (argRules && argRules.includes(ruleID)) {
554
+ // Add them to the argument array.
555
+ ruleArgs.push(... args[ruleID]);
556
+ }
557
+ result[ruleID] ??= {};
558
+ const ruleResult = result[ruleID];
559
+ const {what} = rule;
560
+ ruleResult.what = what || '';
561
+ const startTime = Date.now();
562
+ let timeout;
563
+ let testRetries = 2;
564
+ let testSuccess = false;
565
+ // Until all permitted retries are exhausted or the test succeeds:
566
+ while (testRetries > 0 && ! testSuccess) {
567
+ try {
568
+ // Apply a time limit to the test.
569
+ const timeLimit = 1000 * timeoutMultiplier * rule.timeOut;
570
+ // If the time limit expires during the test:
571
+ const timer = new Promise(resolve => {
572
+ timeout = setTimeout(() => {
573
+ // Add data about the test, including its prevention, to the result.
597
574
  const endTime = Date.now();
598
- testTimes.push([ruleID, Math.round((endTime - startTime) / 1000)]);
599
- Object.keys(ruleOrTimeoutReport).forEach(key => {
600
- ruleResult[key] = ruleOrTimeoutReport[key];
601
- });
602
- // If the test was prevented:
603
- if (ruleResult.data?.prevented && ruleResult.data.error) {
604
- // Add this to the result.
605
- data.rulePreventions.push(ruleID);
606
- data.rulePreventionMessages[ruleID] = ruleResult.data.error;
607
- }
608
- // If the result includes totals:
609
- if (ruleResult.totals) {
610
- // Round them.
611
- ruleResult.totals = ruleResult.totals.map(total => Math.round(total));
612
- }
613
- // Prevent a retry of the test.
614
- testSuccess = true;
615
- // If testing is to stop after a failure and the page failed the test:
616
- if (stopOnFail && ruleResult.totals && ruleResult.totals.some(total => total)) {
617
- // Stop testing.
618
- break;
619
- }
620
- }
621
- // Otherwise, i.e. if the test timed out:
622
- else {
623
- // Report this.
575
+ testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
624
576
  data.rulePreventions.push(ruleID);
625
577
  data.rulePreventionMessages[ruleID] = 'Timeout';
626
- // Stop retrying the test.
578
+ ruleResult.totals = [0, 0, 0, 0];
579
+ ruleResult.standardInstances = [];
580
+ console.log(`ERROR: Test of testaro rule ${ruleID} timed out`);
581
+ resolve({timedOut: true});
582
+ }, timeLimit);
583
+ });
584
+ // Perform the test, subject to the time limit.
585
+ const ruleReport = require(`../testaro/${ruleID}`).reporter(... ruleArgs);
586
+ // Get the test result or a timeout result.
587
+ const ruleOrTimeoutReport = await Promise.race([timer, ruleReport]);
588
+ // If the test was completed:
589
+ if (! ruleOrTimeoutReport.timedOut) {
590
+ // Add data from the test to the result.
591
+ const endTime = Date.now();
592
+ testTimes.push([ruleID, Math.round((endTime - startTime) / 1000)]);
593
+ Object.keys(ruleOrTimeoutReport).forEach(key => {
594
+ ruleResult[key] = ruleOrTimeoutReport[key];
595
+ });
596
+ // If the test was prevented:
597
+ if (ruleResult.data?.prevented && ruleResult.data.error) {
598
+ // Add this to the result.
599
+ data.rulePreventions.push(ruleID);
600
+ data.rulePreventionMessages[ruleID] = ruleResult.data.error;
601
+ }
602
+ // If the result includes totals:
603
+ if (ruleResult.totals) {
604
+ // Round them.
605
+ ruleResult.totals = ruleResult.totals.map(total => Math.round(total));
606
+ }
607
+ // Prevent a retry of the test.
608
+ testSuccess = true;
609
+ // If testing is to stop after a failure and the page failed the test:
610
+ if (stopOnFail && ruleResult.totals && ruleResult.totals.some(total => total)) {
611
+ // Stop testing.
627
612
  break;
628
613
  }
629
614
  }
630
- // If an error is thrown by the test:
631
- catch(error) {
632
- const isPageClosed = ['closed', 'Protocol error', 'Target page'].some(phrase =>
633
- error.message.includes(phrase)
615
+ // Otherwise, i.e. if the test timed out:
616
+ else {
617
+ // Report this.
618
+ data.rulePreventions.push(ruleID);
619
+ data.rulePreventionMessages[ruleID] = 'Timeout';
620
+ // Stop retrying the test.
621
+ break;
622
+ }
623
+ }
624
+ // If an error is thrown by the test:
625
+ catch(error) {
626
+ const isPageClosed = ['closed', 'Protocol error', 'Target page'].some(phrase =>
627
+ error.message.includes(phrase)
628
+ );
629
+ // If the page has closed and there are retries left:
630
+ if (isPageClosed && testRetries) {
631
+ // Report this and decrement the allowed retry count.
632
+ console.log(
633
+ `WARNING: Retry ${3 - testRetries--} of test ${ruleID} starting after page closed`
634
634
  );
635
- // If the page has closed and there are retries left:
636
- if (isPageClosed && testRetries) {
637
- // Report this and decrement the allowed retry count.
638
- console.log(
639
- `WARNING: Retry ${3 - testRetries--} of test ${ruleID} starting after page closed`
640
- );
641
- await wait(2000);
642
- // Replace the browser and the page in the run module and navigate to the target.
643
- await launch(
644
- report,
645
- actIndex,
646
- headEmulation,
647
- report.browserID,
648
- url
649
- );
650
- page = require('../run').page;
651
- // If the page replacement failed:
652
- if (! page) {
653
- // Report this.
654
- console.log(`ERROR: Browser relaunch to retry test ${ruleID} failed`);
655
- data.rulePreventions.push(ruleID);
656
- data.rulePreventionMessages[ruleID] = 'Retry failure due to browser relaunch failure';
657
- // Stop retrying the test.
658
- break;
659
- }
660
- // Update the rule arguments with the current page.
661
- ruleArgs[0] = page;
662
- }
663
- // Otherwise, i.e. if the page is open or it is closed but no retries are left:
664
- else {
665
- // Treat the test as prevented.
635
+ await wait(2000);
636
+ // Replace the browser and the page in the run module and navigate to the target.
637
+ await launch(
638
+ report,
639
+ actIndex,
640
+ headEmulation,
641
+ report.browserID,
642
+ url
643
+ );
644
+ page = require('../run').page;
645
+ // If the page replacement failed:
646
+ if (! page) {
647
+ // Report this.
648
+ console.log(`ERROR: Browser relaunch to retry test ${ruleID} failed`);
666
649
  data.rulePreventions.push(ruleID);
667
- data.rulePreventionMessages[ruleID] = error.message;
668
- console.log(`ERROR: Test of testaro rule ${ruleID} prevented (${error.message})`);
669
- // Do not retry the test even if retries are left.
650
+ data.rulePreventionMessages[ruleID] = 'Retry failure due to browser relaunch failure';
651
+ // Stop retrying the test.
670
652
  break;
671
653
  }
654
+ // Update the rule arguments with the current page.
655
+ ruleArgs[0] = page;
672
656
  }
673
- finally {
674
- // Clear the timeout.
675
- clearTimeout(timeout);
657
+ // Otherwise, i.e. if the page is open or it is closed but no retries are left:
658
+ else {
659
+ // Treat the test as prevented.
660
+ data.rulePreventions.push(ruleID);
661
+ data.rulePreventionMessages[ruleID] = error.message;
662
+ console.log(`ERROR: Test of testaro rule ${ruleID} prevented (${error.message})`);
663
+ // Do not retry the test even if retries are left.
664
+ break;
676
665
  }
677
666
  }
678
- // Clear the error listeners.
679
- if (page && ! page.isClosed() && crashHandler) {
680
- page.off('crash', crashHandler);
681
- crashHandler = null;
682
- }
683
- if (browser && disconnectHandler) {
684
- browser.off('disconnected', disconnectHandler);
685
- disconnectHandler = null;
667
+ finally {
668
+ // Clear the timeout.
669
+ clearTimeout(timeout);
686
670
  }
687
671
  }
688
- // Otherwise, i.e. if the rule is undefined or doubly defined:
689
- else {
690
- // Report this.
691
- data.rulesInvalid.push(rule);
692
- console.log(`ERROR: Rule ${rule.id} not validly defined`);
693
- // Clear the crash listener.
694
- if (page && ! page.isClosed() && crashHandler) {
695
- page.off('crash', crashHandler);
696
- crashHandler = null;
697
- }
698
- if (browser && disconnectHandler) {
699
- browser.off('disconnected', disconnectHandler);
700
- disconnectHandler = null;
701
- }
672
+ // Clear the error listeners.
673
+ if (page && ! page.isClosed() && crashHandler) {
674
+ page.off('crash', crashHandler);
675
+ crashHandler = null;
676
+ }
677
+ if (browser && disconnectHandler) {
678
+ browser.off('disconnected', disconnectHandler);
679
+ disconnectHandler = null;
702
680
  }
703
681
  // Force a garbage collection.
704
682
  try {
package/testaro/hr.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "hr",
3
- "selector": "body hr",
4
- "complaints": {
5
- "instance": "Element instead of styling is used for vertical segmentation",
6
- "summary": "Elements instead of styling are used for vertical segmentation"
7
- },
8
- "ordinalSeverity": 0,
9
- "summaryTagName": "HR"
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "linkExt",
3
- "selector": "a[target=_blank]",
4
- "complaints": {
5
- "instance": "Link has a target=_blank attribute",
6
- "summary": "Links have target=_blank attributes"
7
- },
8
- "ordinalSeverity": 0,
9
- "summaryTagName": "A"
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "linkOldAtt",
3
- "selector": "a[charset], a[coords], a[name], a[rev], a[shape]",
4
- "complaints": {
5
- "instance": "Element has a deprecated attribute",
6
- "summary": "Links have deprecated attributes"
7
- },
8
- "ordinalSeverity": 1,
9
- "summaryTagName": "A"
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "linkTo",
3
- "selector": "a:not([href]):visible",
4
- "complaints": {
5
- "instance": "Element has no href attribute",
6
- "summary": "Links are missing href attributes"
7
- },
8
- "ordinalSeverity": 2,
9
- "summaryTagName": "A"
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "pseudoP",
3
- "selector": "body br + br",
4
- "complaints": {
5
- "instance": "br element follows another br element, likely acting as a pseudo-paragraph",
6
- "summary": "br elements follow other br elements, likely acting as pseudo-paragraphs"
7
- },
8
- "ordinalSeverity": 0,
9
- "summaryTagName": "BR"
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ruleID": "titledEl",
3
- "selector": "[title]:not(button, iframe, input, link, select, textarea)",
4
- "complaints": {
5
- "instance": "Ineligible element has a title attribute",
6
- "summary": "Ineligible elements have title attributes"
7
- },
8
- "ordinalSeverity": 2,
9
- "summaryTagName": ""
10
- }