testaro 60.16.2 → 60.18.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.18.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
@@ -26,122 +26,6 @@ const {xPath} = require('playwright-dompath');
26
26
 
27
27
  // ########## FUNCTIONS
28
28
 
29
- // Initializes violation locators and a result and returns them in an object.
30
- const init = exports.init = async (sampleMax, page, locAllSelector, options = {}) => {
31
- // Get locators for the specified elements.
32
- const locPop = page.locator(locAllSelector, options);
33
- const locPops = await locPop.all();
34
- const populationSize = locPops.length;
35
- const sampleSize = Math.min(sampleMax, populationSize);
36
- const locIndexes = getSample(locPops, sampleSize);
37
- const allLocs = locIndexes.map(index => locPops[index]);
38
- const result = {
39
- data: {
40
- populationSize,
41
- sampleSize,
42
- populationRatio: sampleSize ? populationSize / sampleSize : null
43
- },
44
- totals: [0, 0, 0, 0],
45
- standardInstances: []
46
- };
47
- // Return the result.
48
- return {
49
- allLocs,
50
- locs: [],
51
- result
52
- };
53
- };
54
-
55
- // Populates and returns a result.
56
- const getRuleResult = exports.getRuleResult = async (
57
- withItems, all, ruleID, whats, ordinalSeverity, tagName = ''
58
- ) => {
59
- const {locs, result} = all;
60
- const {data, totals, standardInstances} = result;
61
- // For each violation locator:
62
- for (const locItem of locs) {
63
- // Get data on its element.
64
- let loc, whatParam;
65
- if (Array.isArray(locItem)) {
66
- loc = locItem[0];
67
- whatParam = locItem[1];
68
- }
69
- else {
70
- loc = locItem;
71
- }
72
- const elData = await getLocatorData(loc);
73
- // Increment the totals.
74
- totals[ordinalSeverity] += data.populationRatio;
75
- // If itemization is required:
76
- if (withItems) {
77
- // Get the bounding box of the element.
78
- const {tagName, id, location, excerpt} = elData;
79
- const box = location.type === 'box' ? location.spec : await boxOf(loc);
80
- // Add a standard instance to the result.
81
- standardInstances.push({
82
- ruleID,
83
- what: whatParam ? whats[0].replace('__param__', whatParam) : whats[0],
84
- ordinalSeverity,
85
- tagName,
86
- id,
87
- location,
88
- excerpt,
89
- boxID: boxToString(box),
90
- pathID: tagName === 'HTML' ? '/html' : await xPath(loc)
91
- });
92
- }
93
- }
94
- // If itemization is not required and any instances exist:
95
- if (! withItems && locs.length) {
96
- // Add a summary standard instance to the result.
97
- standardInstances.push({
98
- ruleID,
99
- what: whats[1],
100
- ordinalSeverity,
101
- count: Math.round(totals[ordinalSeverity]),
102
- tagName,
103
- id: '',
104
- location: {
105
- doc: '',
106
- type: '',
107
- spec: ''
108
- },
109
- excerpt: '',
110
- boxID: '',
111
- pathID: ''
112
- });
113
- }
114
- // Return the result.
115
- return result;
116
- };
117
- // Performs a simplifiable test.
118
- exports.simplify = async (page, withItems, ruleData) => {
119
- const {
120
- ruleID, selector, pruner, complaints, ordinalSeverity, summaryTagName
121
- } = ruleData;
122
- // Get an object with initialized violation locators and result as properties.
123
- const all = await init(100, page, selector);
124
- // For each locator:
125
- for (const loc of all.allLocs) {
126
- // Get whether its element violates the rule.
127
- const isBad = await pruner(loc);
128
- // If it does:
129
- if (isBad) {
130
- // Add the locator of the element to the array of violation locators.
131
- all.locs.push(loc);
132
- }
133
- }
134
- // Populate and return the result.
135
- const whats = [
136
- complaints.instance,
137
- complaints.summary
138
- ];
139
- const result = await getRuleResult(
140
- withItems, all, ruleID, whats, ordinalSeverity, summaryTagName
141
- );
142
- // Return the result.
143
- return result;
144
- };
145
29
  // Performs a standard test.
146
30
  exports.doTest = async (
147
31
  page,
@@ -169,12 +53,13 @@ exports.doTest = async (
169
53
  const candidates = document.body.querySelectorAll(candidateSelector);
170
54
  let violationCount = 0;
171
55
  const instances = [];
172
- // Get a violation function.
56
+ // Get a function that returns a violation description, if any, for the candidate.
173
57
  const getBadWhat = eval(`(${getBadWhatString})`);
174
58
  // For each candidate:
175
59
  for (const candidate of candidates) {
60
+ // Get the violation description, if any.
176
61
  const violationWhat = await getBadWhat(candidate);
177
- // If it violates the rule:
62
+ // If the candidate violates the rule:
178
63
  if (violationWhat) {
179
64
  // Increment the violation count.
180
65
  violationCount++;
@@ -14,48 +14,44 @@
14
14
  This test reports elements with native or transformed upper-case text at least 8 characters long. Blocks of upper-case text are difficult to read.
15
15
  */
16
16
 
17
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'allCaps',
29
- selector: 'body *:not(style, script, svg)',
30
- pruner: async loc => await loc.evaluate(el => {
31
- // Get the concatenated and debloated text content of the element and its child text nodes.
32
- const elText = Array
33
- .from(el.childNodes)
34
- .filter(node => node.nodeType === Node.TEXT_NODE)
35
- .map(textNode => textNode.nodeValue)
36
- .join(' ')
37
- .replace(/\s{2,}/g, ' ')
38
- .replace(/-{2,}/g, '-');
39
- // If the element text includes 8 sequential upper-case letters, spaces, or hyphen-minuses:
40
- if (/[- A-Z]{8}/.test(elText)) {
41
- // Report this.
42
- return true;
25
+ const getBadWhat = element => {
26
+ // Get the child text nodes of the element.
27
+ const childTextNodes = Array.from(element.childNodes).filter(
28
+ node => node.nodeType === Node.TEXT_NODE
29
+ );
30
+ // Get the concatenation of their texts that contain 8 or more consecutive letters.
31
+ let longText = childTextNodes
32
+ .map(node => node.nodeValue.trim())
33
+ .filter(text => /[A-Z]{8,}/i.test(text))
34
+ .join(' ');
35
+ // If there is any:
36
+ if (longText) {
37
+ // Get the style declaration of the element.
38
+ const styleDec = window.getComputedStyle(element);
39
+ const {textTransform} = styleDec;
40
+ // If the style declaration transforms the text to upper case:
41
+ if (textTransform === 'uppercase') {
42
+ // Return a violation description.
43
+ return 'Element text is rendered as all-capital';
43
44
  }
44
- // Otherwise:
45
- else {
46
- // Report whether its text is at least 8 characters long and transformed to upper case.
47
- const elStyleDec = window.getComputedStyle(el);
48
- const transformStyle = elStyleDec.textTransform;
49
- return transformStyle === 'uppercase' && elText.length > 7;
45
+ // Otherwise, if the text contains 8 or more consecutive upper-case letters:
46
+ if (/[A-Z]{8,}/.test(longText)) {
47
+ // Return a violation description.
48
+ return 'Element contains all-capital text';
50
49
  }
51
- }),
52
- complaints: {
53
- instance: 'Element contains all-capital text',
54
- summary: 'Elements contain all-capital text'
55
- },
56
- ordinalSeverity: 0,
57
- summaryTagName: ''
50
+ }
58
51
  };
59
- // Run the test and return the result.
60
- return await simplify(page, withItems, ruleData);
52
+ const selector = 'body *:not(style, script, svg)';
53
+ const whats = 'Elements have all-capital text';
54
+ return await doTest(
55
+ page, withItems, 'allCaps', selector, whats, 0, null, getBadWhat.toString()
56
+ );
61
57
  };
@@ -14,31 +14,26 @@
14
14
  This test reports elements with italic or oblique text at least 40 characters long. Blocks of slanted text are difficult to read.
15
15
  */
16
16
 
17
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'allSlanted',
29
- selector: 'body *:not(style, script, svg)',
30
- pruner: async loc => await loc.evaluate(el => {
31
- const elStyleDec = window.getComputedStyle(el);
32
- const elText = el.textContent;
33
- return ['italic', 'oblique'].includes(elStyleDec.fontStyle) && elText.length > 39;
34
- }),
35
- complaints: {
36
- instance: 'Element contains all-italic or all-oblique text',
37
- summary: 'Elements contain all-italic or all-oblique text'
38
- },
39
- ordinalSeverity: 0,
40
- summaryTagName: ''
25
+ const getBadWhat = element => {
26
+ const styleDec = window.getComputedStyle(element);
27
+ const {textContent} = element;
28
+ // If the element contains 40 or more characters of slanted text:
29
+ if (['italic', 'oblique'].includes(styleDec.fontStyle) && textContent.length > 39) {
30
+ // Return a violation description.
31
+ return 'Element contains all-slanted text';
32
+ }
41
33
  };
42
- // Run the test and return the result.
43
- return await simplify(page, withItems, ruleData);
34
+ const selector = 'body *:not(style, script, svg)';
35
+ const whats = 'Elements contain all-slanted text';
36
+ return await doTest(
37
+ page, withItems, 'allSlanted', selector, whats, 0, null, getBadWhat.toString()
38
+ );
44
39
  };
@@ -14,32 +14,30 @@
14
14
  This test reports elements whose transform style properties distort the content. Distortion makes text difficult to read.
15
15
  */
16
16
 
17
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'distortion',
29
- selector: 'body *',
30
- pruner: async loc => await loc.evaluate(el => {
31
- const styleDec = window.getComputedStyle(el);
32
- const {transform} = styleDec;
33
- return transform
34
- && ['matrix', 'perspective', 'rotate', 'scale', 'skew'].some(key => transform.includes(key));
35
- }),
36
- complaints: {
37
- instance: 'Element distorts its text',
38
- summary: 'Elements distort their texts'
39
- },
40
- ordinalSeverity: 1,
41
- summaryTagName: ''
25
+ const getBadWhat = element => {
26
+ const styleDec = window.getComputedStyle(element);
27
+ const {transform} = styleDec;
28
+ const badTransformTypes = ['matrix', 'perspective', 'rotate', 'scale', 'skew'];
29
+ // If the element style transforms the text:
30
+ if (transform) {
31
+ const transformType = badTransformTypes.find(key => transform.includes(key));
32
+ // If the transformation is distortive:
33
+ if (transformType) {
34
+ // Return a violation description.
35
+ return `Element distorts its text with ${transformType} transformation`;
36
+ }
37
+ }
42
38
  };
43
- // Run the test and return the result.
44
- return await simplify(page, withItems, ruleData);
39
+ const whats = 'Elements distort their texts';
40
+ return await doTest(
41
+ page, withItems, 'distortion', 'body *', whats, 0, null, getBadWhat.toString()
42
+ );
45
43
  };
@@ -70,19 +70,18 @@ exports.reporter = async (page, withItems) => {
70
70
  ]);
71
71
  // Initialize the operabilities of the element.
72
72
  const opHow = [];
73
- let hasPointer = false;
74
73
  // If the element is not a label:
75
74
  if (element.tagName !== 'LABEL') {
76
- const styleDec = window.getComputedStyle(element);
77
- hasPointer = styleDec.cursor === 'pointer';
75
+ const liveStyleDec = window.getComputedStyle(element);
78
76
  // If it has a pointer cursor:
79
- if (hasPointer) {
77
+ if (liveStyleDec.cursor === 'pointer') {
80
78
  // Neutralize the cursor style of the parent element of the element.
81
79
  element.parentElement.style.cursor = 'default';
82
- // Get whether, after this, the element still has a pointer cursor.
83
- hasPointer = styleDec.cursor === 'pointer';
84
- // Add this to the operabilities of the element.
85
- opHow.push('pointer cursor');
80
+ // If, after this, the element still has a pointer cursor:
81
+ if (liveStyleDec.cursor === 'pointer') {
82
+ // Add this to the operabilities of the element.
83
+ opHow.push('pointer cursor');
84
+ }
86
85
  }
87
86
  }
88
87
  // If the element has a click event listener:
package/testaro/hover.js CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  /*
12
12
  hover
13
- This test reports unexpected impacts of hovering. The elements that are subjected to hovering (called “triggers”) include all the elements that have attributes associated with control over the visibility of other elements. If hovering over an element results in an increase or decrease in the total count of visible elements in the tree rooted in the grandparent of the trigger, the rule is considered violated.
13
+ This test reports unexpected impacts of hovering. The elements that are subjected to hovering (called “triggers”) include all the elements that have attributes associated with control over the visibility of other elements. If hovering over an element results in an increase or decrease in the total count of visible elements in the tree rooted in the grandparent of the trigger, the rule is considered violated. This test uses the getBasicResult function in order to use Playwright for the most realistic hover simulation.
14
14
  */
15
15
 
16
16
  // IMPORTS
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
+ };
@@ -1,6 +1,7 @@
1
1
  /*
2
2
  © 2025 CVS Health and/or one of its affiliates. All rights reserved.
3
3
  © 2025 Juan S. Casado.
4
+ © 2025 Jonathan Robert Pool
4
5
 
5
6
  Licensed under the MIT License. See LICENSE file at the project root or
6
7
  https://opensource.org/license/mit/ for details.
@@ -11,25 +12,27 @@
11
12
  /*
12
13
  imageLink
13
14
  Clean-room rule.
14
- This test reports anchor elements whose href attributes point to image files.
15
+ This test reports links whose destinations are image files.
15
16
  */
16
17
 
17
- const {simplify} = require('../procs/testaro');
18
+ // IMPORTS
18
19
 
20
+ const {doTest} = require('../procs/testaro');
21
+
22
+ // FUNCTIONS
23
+
24
+ // Runs the test and returns the result.
19
25
  exports.reporter = async (page, withItems) => {
20
- const ruleData = {
21
- ruleID: 'imageLink',
22
- selector: 'a[href]',
23
- pruner: async loc => await loc.evaluate(el => {
24
- const href = el.getAttribute('href') || '';
25
- return /\.(?:png|jpe?g|gif|svg|webp|ico)(?:$|[?#])/i.test(href);
26
- }),
27
- complaints: {
28
- instance: 'Link destination is an image file',
29
- summary: 'Links have image files as their destinations'
30
- },
31
- ordinalSeverity: 0,
32
- summaryTagName: 'A'
26
+ const getBadWhat = element => {
27
+ const href = element.getAttribute('href') || '';
28
+ // If the destination of the element is an image file:
29
+ if (/\.(?:png|jpe?g|gif|svg|webp|ico)(?:$|[?#])/i.test(href)) {
30
+ // Return a violation description.
31
+ return 'Link destination is an image file';
32
+ }
33
33
  };
34
- return await simplify(page, withItems, ruleData);
34
+ const whats = 'Links have image files as their destinations';
35
+ return await doTest(
36
+ page, withItems, 'imageLink', 'a[href]', whats, 0, 'A', getBadWhat.toString()
37
+ );
35
38
  };
@@ -1,6 +1,7 @@
1
1
  /*
2
2
  © 2025 CVS Health and/or one of its affiliates. All rights reserved.
3
3
  © 2025 Juan S. Casado.
4
+ © 2025 Jonathan Robert Pool.
4
5
 
5
6
  Licensed under the MIT License. See LICENSE file at the project root or
6
7
  https://opensource.org/license/mit/ for details.
@@ -14,30 +15,24 @@
14
15
  This test reports legend elements that are not the first children of fieldset elements.
15
16
  */
16
17
 
17
- const {simplify} = require('../procs/testaro');
18
+ // IMPORTS
18
19
 
20
+ const {doTest} = require('../procs/testaro');
21
+
22
+ // FUNCTIONS
23
+
24
+ // Runs the test and returns the result.
19
25
  exports.reporter = async (page, withItems) => {
20
- const ruleData = {
21
- ruleID: 'legendLoc',
22
- selector: 'legend',
23
- pruner: async (loc) => await loc.evaluate(el => {
24
- const parent = el.parentElement;
25
- if (!parent) return true;
26
- if (parent.tagName.toUpperCase() !== 'FIELDSET') return true;
27
- // Check if this legend is the first element child of the fieldset
28
- for (const child of parent.children) {
29
- if (child.nodeType === 1) {
30
- return child !== el; // true if not first child
31
- }
32
- }
33
- return true;
34
- }),
35
- complaints: {
36
- instance: 'Element is not the first child of a fieldset element',
37
- summary: 'legend elements are not the first children of fieldset elements'
38
- },
39
- ordinalSeverity: 3,
40
- summaryTagName: 'LEGEND'
26
+ const getBadWhat = element => {
27
+ const parent = element.parentElement;
28
+ // If the element violates the rule:
29
+ if (! (parent && parent.tagName === 'FIELDSET' && parent.firstElementChild === element)) {
30
+ // Return a violation description.
31
+ return 'Element is not the first child of a fieldset element';
32
+ }
41
33
  };
42
- return await simplify(page, withItems, ruleData);
34
+ const whats = 'Legend elements are not the first children of fieldset elements';
35
+ return await doTest(
36
+ page, withItems, 'legendLoc', 'legend', whats, 3, 'LEGEND', getBadWhat.toString()
37
+ );
43
38
  };
@@ -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
+ };