testaro 67.0.0 → 67.1.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": "67.0.0",
3
+ "version": "67.1.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": {
@@ -0,0 +1,117 @@
1
+ /*
2
+ © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+
4
+ Licensed under the MIT License. See LICENSE file at the project root or
5
+ https://opensource.org/license/mit/ for details.
6
+
7
+ SPDX-License-Identifier: MIT
8
+ */
9
+
10
+ // Returns properties of an element with an excerpt when Testaro identifiers have been added.
11
+ exports.getElementData = async (page, excerpt) => {
12
+ // Initialize the properties.
13
+ let data = {
14
+ tagName: '',
15
+ id: '',
16
+ text: '',
17
+ notInDOM: true,
18
+ boxID: '',
19
+ pathID: '',
20
+ originalExcerpt: excerpt,
21
+ };
22
+ let elementProperties = {};
23
+ const testaroIDArray = excerpt.match(/data-testaro-id="(\d+)#([^"]*)"/);
24
+ // If the excerpt contains a Testaro identifier:
25
+ if (testaroIDArray) {
26
+ // Remove the identifier.
27
+ originalExcerpt = excerpt.replace(/ data-testaro-id="\d+#[^" ]+"?/, '');
28
+ elementProperties = await page.evaluate(testaroIDArray => {
29
+ let tagName = '';
30
+ let id = '';
31
+ const text = [];
32
+ let notInDOM = false;
33
+ let boxID = '';
34
+ let pathID = '';
35
+ const testaroID = `${testaroIDArray[1]}#${testaroIDArray[2]}`;
36
+ const element = document.querySelector(`[data-testaro-id="${testaroID}"]`);
37
+ // If any element has the identifier:
38
+ if (element) {
39
+ // Get properties of the element.
40
+ ({tagName, id} = element);
41
+ const segments = element
42
+ .innerText
43
+ ?.trim()
44
+ .split(/[\t\n]+/)
45
+ .filter(segment => segment.length);
46
+ if (segments?.length) {
47
+ if (segments.length > 1) {
48
+ text.push(segments[0], segments[segments.length - 1]);
49
+ }
50
+ else {
51
+ text.push(segments[0]);
52
+ }
53
+ }
54
+ const boundingBox = element.getBoundingClientRect() ?? {};
55
+ const box = {};
56
+ if (boundingBox.x) {
57
+ ['x', 'y', 'width', 'height'].forEach(coordinate => {
58
+ box[coordinate] = Math.round(boundingBox[coordinate]);
59
+ });
60
+ }
61
+ if (typeof box.x === 'number') {
62
+ boxID = Object.values(box).join(':');
63
+ }
64
+ // Get a path ID from the identifier or, if necessary, the element.
65
+ pathID = testaroIDArray[2];
66
+ if (! pathID) {
67
+ pathID = window.getXPath(element);
68
+ }
69
+ }
70
+ // Otherwise, i.e. if no element has the identifier:
71
+ else {
72
+ // Report this.
73
+ notInDOM = true;
74
+ }
75
+ // Report the properties.
76
+ return {
77
+ tagName,
78
+ id,
79
+ text,
80
+ notInDOM,
81
+ boxID,
82
+ pathID
83
+ };
84
+ }, testaroIDArray);
85
+ // Populate the data with any properties obtained from the element.
86
+ Object.assign(data, elementProperties);
87
+ }
88
+ // Otherwise, i.e. if the excerpt contains no Testaro identifier:
89
+ else {
90
+ // Get properties of the element that are gettable from the excerpt.
91
+ const tagNameArray = excerpt.match(/<([-a-z]+)/);
92
+ data.tagName = tagNameArray?.[1] ?? '';
93
+ const idArray = excerpt.match(/ id="([^"]*)"/);
94
+ data.id = idArray?.[1] ?? '';
95
+ }
96
+ // Return the properties.
97
+ return data;
98
+ };
99
+ // Returns a tag name and the value of an id attribute from a substring of HTML code.
100
+ exports.getIdentifiers = exports.getIdentifiers = code => {
101
+ // Normalize the code.
102
+ code = code.replace(/\s+/g, ' ').replace(/\\"/g, '"');
103
+ // Get the first start tag of an element, if any.
104
+ const startTagData = code.match(/^.*?<([a-zA-][^>]*)/);
105
+ // If there is any:
106
+ if (startTagData) {
107
+ // Get the tag name.
108
+ const tagNameArray = startTagData[1].match(/^[A-Za-z0-9]+/);
109
+ const tagName = tagNameArray ? tagNameArray[0].toUpperCase() : '';
110
+ // Get the value of the id attribute, if any.
111
+ const identifierData = startTagData[1].match(/ id="([^"]+)"/);
112
+ const id = identifierData ? identifierData[1] : '';
113
+ // Return the tag name and the value of the id attribute, if any.
114
+ return [tagName, id];
115
+ }
116
+ return ['', ''];
117
+ };
@@ -101,92 +101,3 @@ exports.getLocatorData = async loc => {
101
101
  return null;
102
102
  }
103
103
  };
104
- // Returns properties of an element with an excerpt when Testaro identifiers have been added.
105
- exports.getElementData = async (page, excerpt) => {
106
- // Initialize the properties.
107
- let data = {
108
- tagName: '',
109
- id: '',
110
- text: '',
111
- notInDOM: true,
112
- boxID: '',
113
- pathID: '',
114
- originalExcerpt: excerpt,
115
- };
116
- let elementProperties = {};
117
- const testaroIDArray = excerpt.match(/data-testaro-id="(\d+)#([^"]*)"/);
118
- // If the excerpt contains a Testaro identifier:
119
- if (testaroIDArray) {
120
- // Remove the identifier.
121
- originalExcerpt = excerpt.replace(/ data-testaro-id="\d+#[^" ]+"?/, '');
122
- elementProperties = await page.evaluate(testaroIDArray => {
123
- let tagName = '';
124
- let id = '';
125
- const text = [];
126
- let notInDOM = false;
127
- let boxID = '';
128
- let pathID = '';
129
- const testaroID = `${testaroIDArray[1]}#${testaroIDArray[2]}`;
130
- const element = document.querySelector(`[data-testaro-id="${testaroID}"]`);
131
- // If any element has the identifier:
132
- if (element) {
133
- // Get properties of the element.
134
- ({tagName, id} = element);
135
- const segments = element
136
- .innerText
137
- ?.trim()
138
- .split(/[\t\n]+/)
139
- .filter(segment => segment.length);
140
- if (segments?.length) {
141
- if (segments.length > 1) {
142
- text.push(segments[0], segments[segments.length - 1]);
143
- }
144
- else {
145
- text.push(segments[0]);
146
- }
147
- }
148
- const boundingBox = element.getBoundingClientRect() ?? {};
149
- const box = {};
150
- if (boundingBox.x) {
151
- ['x', 'y', 'width', 'height'].forEach(coordinate => {
152
- box[coordinate] = Math.round(boundingBox[coordinate]);
153
- });
154
- }
155
- if (typeof box.x === 'number') {
156
- boxID = Object.values(box).join(':');
157
- }
158
- // Get a path ID from the identifier or, if necessary, the element.
159
- pathID = testaroIDArray[2];
160
- if (! pathID) {
161
- pathID = window.getXPath(element);
162
- }
163
- }
164
- // Otherwise, i.e. if no element has the identifier:
165
- else {
166
- // Report this.
167
- notInDOM = true;
168
- }
169
- // Report the properties.
170
- return {
171
- tagName,
172
- id,
173
- text,
174
- notInDOM,
175
- boxID,
176
- pathID
177
- };
178
- }, testaroIDArray);
179
- // Populate the data with any properties obtained from the element.
180
- Object.assign(data, elementProperties);
181
- }
182
- // Otherwise, i.e. if the excerpt contains no Testaro identifier:
183
- else {
184
- // Get properties of the element that are gettable from the excerpt.
185
- const tagNameArray = excerpt.match(/<([-a-z]+)/);
186
- data.tagName = tagNameArray?.[1] ?? '';
187
- const idArray = excerpt.match(/ id="([^"]*)"/);
188
- data.id = idArray?.[1] ?? '';
189
- }
190
- // Return the properties.
191
- return data;
192
- };
package/procs/nu.js CHANGED
@@ -17,7 +17,7 @@
17
17
  // Module to add Testaro IDs to elements.
18
18
  const {addTestaroIDs} = require('./testaro');
19
19
  // Module to get location data from an element.
20
- const {getElementData} = require('./getLocatorData');
20
+ const {getElementData} = require('./getElementData');
21
21
  // Module to get the document source.
22
22
  const {getSource} = require('./getSource');
23
23
 
@@ -40,44 +40,6 @@ const getIdentifiers = exports.getIdentifiers = code => {
40
40
  }
41
41
  return ['', ''];
42
42
  };
43
- // Converts issue instances at an axe certainty level.
44
- const doAxe = (result, standardResult, certainty) => {
45
- if (result.details && result.details[certainty]) {
46
- result.details[certainty].forEach(rule => {
47
- rule.nodes.forEach(node => {
48
- const whatSet = new Set([
49
- rule.help,
50
- ... node.any.map(anyItem => anyItem.message),
51
- ... node.all.map(allItem => allItem.message)
52
- ]);
53
- const severityWeights = {
54
- minor: 0,
55
- moderate: 0,
56
- serious: 1,
57
- critical: 1
58
- };
59
- const ordinalSeverity = severityWeights[node.impact] + (certainty === 'violations' ? 2 : 0);
60
- const identifiers = getIdentifiers(node.html);
61
- const instance = {
62
- ruleID: rule.id,
63
- what: Array.from(whatSet.values()).join('; '),
64
- ordinalSeverity,
65
- tagName: identifiers[0],
66
- id: identifiers[1],
67
- location: {
68
- doc: 'dom',
69
- type: 'selector',
70
- spec: node.target && node.target.length ? node.target[0] : ''
71
- },
72
- excerpt: cap(node.html),
73
- boxID: '',
74
- pathID: ''
75
- };
76
- standardResult.instances.push(instance);
77
- });
78
- });
79
- }
80
- };
81
43
  // Converts issue instances at an htmlcs severity level.
82
44
  const doHTMLCS = (result, standardResult, severity) => {
83
45
  if (result[severity]) {
@@ -282,23 +244,13 @@ const convert = (toolName, data, result, standardResult) => {
282
244
  standardResult.prevented = true;
283
245
  }
284
246
  // alfa, aslint
285
- else if (['alfa', 'aslint'].includes(toolName) && result.standardResult) {
247
+ else if (['alfa', 'aslint', 'axe'].includes(toolName) && result.standardResult) {
286
248
  // Move the results to standard locations.
287
249
  Object.assign(result, result.nativeResult);
288
250
  Object.assign(standardResult, result.standardResult);
289
251
  delete result.nativeResult;
290
252
  delete result.standardResult;
291
253
  }
292
- // axe
293
- else if (
294
- toolName === 'axe'
295
- && result
296
- && result.totals
297
- && (result.totals.rulesWarned || result.totals.rulesViolated)
298
- ) {
299
- doAxe(result, standardResult, 'incomplete');
300
- doAxe(result, standardResult, 'violations');
301
- }
302
254
  // ed11y
303
255
  else if (
304
256
  toolName === 'ed11y'
package/run.js CHANGED
@@ -1794,11 +1794,11 @@ const doActs = async (report, opts = {}) => {
1794
1794
  excerpt && ! ['<', '>', '=', '#'].some(markupChar => excerpt.includes(markupChar))
1795
1795
  ) {
1796
1796
  // Add the excerpt (up to any ellipsis) to the text property.
1797
- instance.text = excerpt.split(/ … | *\.\.\./)[0];
1797
+ instance.text = [excerpt.split(/ … | *\.\.\./)[0]];
1798
1798
  }
1799
1799
  // Otherwise, i.e. if it has no markup-free excerpt but has a non-empty path ID:
1800
1800
  else if (pathID) {
1801
- // Initialize a text string.
1801
+ // Initialize a text property.
1802
1802
  let text = '';
1803
1803
  // Get the element if it has text content.
1804
1804
  const elementLoc = page.locator(`xpath=${pathID}`, {hasText: /.+/});
@@ -1812,17 +1812,18 @@ const doActs = async (report, opts = {}) => {
1812
1812
  elementClone
1813
1813
  .querySelectorAll('noscript')
1814
1814
  .forEach(noscript => noscript.remove());
1815
- return elementClone.textContent;
1815
+ return elementClone.innerText;
1816
1816
  });
1817
1817
  }
1818
1818
  // Otherwise, i.e. if it contains no noscript element:
1819
1819
  else {
1820
1820
  // Change the text string to the text content of the element.
1821
- text = await elementLoc.textContent();
1821
+ text = await elementLoc.innerText();
1822
1822
  }
1823
1823
  }
1824
1824
  // Add the text string, truncated if necessary, to the instance.
1825
- instance.text = text.trim().replace(/\s+/g, ' ').slice(0, 300);
1825
+ const textArray = [text.trim().replace(/\s+/g, ' ').slice(0, 300)];
1826
+ instance.text = textArray.filter(segment => segment.length);
1826
1827
  }
1827
1828
  }
1828
1829
  };
package/tests/aslint.js CHANGED
@@ -22,7 +22,7 @@ const {cap, tidy} = require('../procs/job');
22
22
  // Module to handle files.
23
23
  const fs = require('fs/promises');
24
24
  // Function to get location data with a Testaro identifier.
25
- const {getElementData} = require('../procs/getLocatorData');
25
+ const {getElementData} = require('../procs/getElementData');
26
26
  // Function to normalize an XPath.
27
27
  const {getNormalizedXPath} = require('../procs/identify');
28
28
 
package/tests/axe.js CHANGED
@@ -17,11 +17,11 @@
17
17
  The detailLevel argument specifies how many result categories are to be included in the
18
18
  details. 0 = none; 1 = violations; 2 = violations and incomplete; 3 = violations, incomplete,
19
19
  and passes; 4 = violations, incomplete, passes, and inapplicable. Regardless of the value of this
20
- argument, Axe-core is instructed to report all nodes with violation or incomplete results, but only
21
- 1 node per rule found to be passed or inapplicable. Therefore, from the results of this test it
22
- is possible to count the rules passed and the inapplicable rules, but not the nodes for which each
23
- rule is passed or inapplicable. To count those nodes, one would need to revise the 'resultTypes'
24
- property of the 'axeOptions' object.
20
+ argument, Axe-core is instructed to report all nodes with violation or incomplete results, but
21
+ only 1 node per rule found to be passed or inapplicable. Therefore, from the results of this test
22
+ it is possible to count the rules passed and the inapplicable rules, but not the nodes for which
23
+ each rule is passed or inapplicable. To count those nodes, one would need to revise the
24
+ 'resultTypes' property of the 'axeOptions' object.
25
25
 
26
26
  The report of this test shows rule totals by result category and, within the violation and
27
27
  incomplete categories, node totals by severity. It does not show rule or node totals by test
@@ -32,17 +32,43 @@
32
32
  // IMPORTS
33
33
 
34
34
  const axePlaywright = require('axe-playwright');
35
+ // Module to simplify strings.
36
+ const {cap} = require('../procs/job');
37
+ const {getIdentifiers} = require('../procs/getElementData');
35
38
  const {injectAxe} = axePlaywright;
36
39
 
40
+ // CONSTANTS
41
+
42
+ const severityWeights = {
43
+ minor: 0,
44
+ moderate: 0,
45
+ serious: 1,
46
+ critical: 1
47
+ };
48
+
37
49
  // FUNCTIONS
38
50
 
39
51
  // Conducts and reports the Axe tests.
40
- exports.reporter = async (page, report, actIndex, timeLimit) => {
52
+ exports.reporter = async (page, report, actIndex) => {
41
53
  const act = report.acts[actIndex];
42
54
  const {detailLevel, rules} = act;
43
55
  // Initialize the act report.
44
56
  let data = {};
45
- let result = {};
57
+ const result = {
58
+ nativeResult: {},
59
+ standardResult: {}
60
+ };
61
+ const standard = report.standard !== 'no';
62
+ // If standard results are to be reported:
63
+ if (standard) {
64
+ // Initialize the standard result.
65
+ result.standardResult = {
66
+ prevented: false,
67
+ totals: [0, 0, 0, 0],
68
+ instances: []
69
+ };
70
+ }
71
+ const {nativeResult, standardResult} = result;
46
72
  // Inject axe-core into the page.
47
73
  await injectAxe(page)
48
74
  .catch(error => {
@@ -66,8 +92,8 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
66
92
  const {inapplicable, passes, incomplete, violations} = axeReport;
67
93
  // If the test succeeded:
68
94
  if (violations) {
69
- // Initialize the result.
70
- result.totals = {
95
+ // Initialize the native result.
96
+ nativeResult.totals = {
71
97
  rulesNA: 0,
72
98
  rulesPassed: 0,
73
99
  rulesWarned: 0,
@@ -85,9 +111,9 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
85
111
  critical: 0
86
112
  }
87
113
  };
88
- result.details = axeReport;
89
- // Populate the totals.
90
- const {totals} = result;
114
+ nativeResult.details = axeReport;
115
+ // Populate the native-result totals.
116
+ const {totals} = nativeResult;
91
117
  totals.rulesNA = inapplicable.length;
92
118
  totals.rulesPassed = passes.length;
93
119
  incomplete.forEach(rule => {
@@ -108,12 +134,50 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
108
134
  irrelevants.forEach(irrelevant => {
109
135
  delete axeReport[irrelevant];
110
136
  });
137
+ // If standard results are to be reported and there are any violations:
138
+ if (standard && (totals.rulesViolated || totals.rulesWarned)) {
139
+ ['incomplete', 'violations'].forEach(certainty => {
140
+ if (nativeResult?.details?.[certainty]) {
141
+ nativeResult.details[certainty].forEach(rule => {
142
+ rule.nodes.forEach(node => {
143
+ const whatSet = new Set([
144
+ rule.help,
145
+ ... node.any.map(anyItem => anyItem.message),
146
+ ... node.all.map(allItem => allItem.message)
147
+ ]);
148
+ const ordinalSeverity = severityWeights[node.impact]
149
+ + (certainty === 'violations' ? 2 : 0);
150
+ const identifiers = getIdentifiers(node.html);
151
+ const instance = {
152
+ ruleID: rule.id,
153
+ what: Array.from(whatSet.values()).join('; '),
154
+ ordinalSeverity,
155
+ tagName: identifiers[0],
156
+ id: identifiers[1],
157
+ location: {
158
+ doc: 'dom',
159
+ type: 'selector',
160
+ spec: node.target && node.target.length ? node.target[0] : ''
161
+ },
162
+ excerpt: cap(node.html),
163
+ boxID: '',
164
+ pathID: ''
165
+ };
166
+ standardResult.instances.push(instance);
167
+ });
168
+ });
169
+ }
170
+ });
171
+ }
111
172
  }
112
173
  // Otherwise, i.e. if the test failed:
113
174
  else {
114
175
  // Report this.
115
176
  data.prevented = true;
116
177
  data.error = 'ERROR: Act failed';
178
+ if (standard) {
179
+ standardResult.prevented = true;
180
+ }
117
181
  }
118
182
  }
119
183
  // Return the result.
package/tests/htmlcs.js CHANGED
@@ -18,7 +18,7 @@
18
18
  // Module to add and use unique element IDs.
19
19
  const {addTestaroIDs} = require('../procs/testaro');
20
20
  // Module to get location data from an element.
21
- const {getElementData} = require('../procs/getLocatorData');
21
+ const {getElementData} = require('../procs/getElementData');
22
22
  // Module to handle files.
23
23
  const fs = require('fs/promises');
24
24
 
package/tests/qualWeb.js CHANGED
@@ -20,7 +20,7 @@ const {ACTRules} = require('@qualweb/act-rules');
20
20
  const {WCAGTechniques} = require('@qualweb/wcag-techniques');
21
21
  const {BestPractices} = require('@qualweb/best-practices');
22
22
  const {addTestaroIDs} = require('../procs/testaro');
23
- const {getElementData} = require('../procs/getLocatorData');
23
+ const {getElementData} = require('../procs/getElementData');
24
24
 
25
25
  // CONSTANTS
26
26
 
package/tests/wax.js CHANGED
@@ -18,7 +18,7 @@
18
18
  // Function to add and use unique element IDs.
19
19
  const {addTestaroIDs} = require('../procs/testaro');
20
20
  // Function to get location data from an element.
21
- const {getElementData} = require('../procs/getLocatorData');
21
+ const {getElementData} = require('../procs/getElementData');
22
22
  // Modules to run WAX.
23
23
  const runWax = require('@wally-ax/wax-dev');
24
24
  const waxDev = {runWax};