testaro 64.9.1 → 64.9.3

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": "64.9.1",
3
+ "version": "64.9.3",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -103,16 +103,15 @@ exports.getLocatorData = async loc => {
103
103
  };
104
104
  // Returns location data from the extract of a standard instance.
105
105
  exports.getLocationData = async (page, excerpt) => {
106
- const testaroIDArray = excerpt.match(/data-testaro-id="(\d+)#"/);
107
- // If the excerpt contains a Testaro identifier:
106
+ const testaroIDArray = excerpt.match(/data-testaro-id="(\d+)#([^"]*)"/);
107
+ // If the extract contains a Testaro identifier:
108
108
  if (testaroIDArray) {
109
- const testaroID = testaroIDArray[1];
110
- // Return location data for the element.
109
+ const testaroID = `${testaroIDArray[1]}#${testaroIDArray[2]}`;
111
110
  return await page.evaluate(testaroID => {
112
- const element = document.querySelector(`[data-testaro-id="${testaroID}#"]`);
111
+ const element = document.querySelector(`[data-testaro-id="${testaroID}"]`);
113
112
  // If any element has that identifier:
114
113
  if (element) {
115
- // Get box and path IDs for the element.
114
+ // Get a box ID for the element.
116
115
  const box = {};
117
116
  let boxID = '';
118
117
  const boundingBox = element.getBoundingClientRect() || {};
@@ -124,15 +123,32 @@ exports.getLocationData = async (page, excerpt) => {
124
123
  if (typeof box.x === 'number') {
125
124
  boxID = Object.values(box).join(':');
126
125
  }
127
- const pathID = window.getXPath(element) || '';
128
- // Return them.
126
+ // Get a path ID for the element.
127
+ let pathID = testaroID.replace(/^.*?#/, '');
128
+ if (! pathID) {
129
+ pathID = window.getXPath(element);
130
+ }
131
+ // Return the box and path IDs.
129
132
  return {
130
133
  boxID,
131
134
  pathID
132
135
  };
133
136
  }
134
- // Otherwise, i.e. if no element has it, return empty location data.
135
- return {};
137
+ // Otherwise, if no element has it but the identifier includes an XPath:
138
+ else if (testaroIDArray[2]) {
139
+ // Return an empty box ID and that XPath as a path ID.
140
+ return {
141
+ notInDOM: true,
142
+ boxID: '',
143
+ pathID: testaroIDArray[2]
144
+ };
145
+ }
146
+ // Otherwise, return empty location properties.
147
+ return {
148
+ notInDOM: true,
149
+ boxID: '',
150
+ pathID: ''
151
+ };
136
152
  }, testaroID);
137
153
  }
138
154
  // Otherwise, i.e. if the extract contains no Testaro identifier:
@@ -42,8 +42,8 @@ const getIdentifiers = code => {
42
42
  const tagNameArray = startTagData[1].match(/^[A-Za-z0-9]+/);
43
43
  const tagName = tagNameArray ? tagNameArray[0].toUpperCase() : '';
44
44
  // Get the value of the id attribute, if any.
45
- const idData = startTagData[1].match(/ id="([^"]+)"/);
46
- const id = idData ? idData[1] : '';
45
+ const identifierData = startTagData[1].match(/ id="([^"]+)"/);
46
+ const id = identifierData ? identifierData[1] : '';
47
47
  // Return the tag name and the value of the id attribute, if any.
48
48
  return [tagName, id];
49
49
  }
@@ -175,7 +175,9 @@ const doAxe = (result, standardResult, certainty) => {
175
175
  type: 'selector',
176
176
  spec: node.target && node.target.length ? node.target[0] : ''
177
177
  },
178
- excerpt: cap(node.html)
178
+ excerpt: cap(node.html),
179
+ boxID: '',
180
+ pathID: ''
179
181
  };
180
182
  standardResult.instances.push(instance);
181
183
  });
@@ -311,7 +313,9 @@ const doQualWeb = (result, standardResult, ruleClassName) => {
311
313
  type: 'selector',
312
314
  spec: element.pointer
313
315
  },
314
- excerpt: cap(htmlCode)
316
+ excerpt: cap(htmlCode),
317
+ boxID: element.locationData?.boxID || '',
318
+ pathID: element.locationData?.pathID || ''
315
319
  };
316
320
  standardResult.instances.push(instance);
317
321
  });
@@ -365,7 +369,9 @@ const doWAVE = (result, standardResult, categoryName) => {
365
369
  type: 'selector',
366
370
  spec: violationFacts[0] || 'html'
367
371
  },
368
- excerpt: violationFacts[1]
372
+ excerpt: violationFacts[1],
373
+ boxID: '',
374
+ pathID: ''
369
375
  };
370
376
  // Add it to the standard result.
371
377
  standardResult.instances.push(instance);
@@ -406,7 +412,9 @@ const convert = (toolName, data, result, standardResult) => {
406
412
  type: 'xpath',
407
413
  spec: target.path
408
414
  },
409
- excerpt: cap(code)
415
+ excerpt: cap(code),
416
+ boxID: '',
417
+ pathID: ''
410
418
  };
411
419
  standardResult.instances.push(instance);
412
420
  }
@@ -438,7 +446,9 @@ const convert = (toolName, data, result, standardResult) => {
438
446
  type: 'xpath',
439
447
  spec: target.path
440
448
  },
441
- excerpt: cap(code)
449
+ excerpt: cap(code),
450
+ boxID: '',
451
+ pathID: ''
442
452
  };
443
453
  }
444
454
  standardResult.instances.push(instance);
@@ -588,7 +598,9 @@ const convert = (toolName, data, result, standardResult) => {
588
598
  type: 'xpath',
589
599
  spec: item.path.dom
590
600
  },
591
- excerpt: cap(item.snippet)
601
+ excerpt: cap(item.snippet),
602
+ boxID: '',
603
+ pathID: ''
592
604
  };
593
605
  standardResult.instances.push(instance);
594
606
  });
@@ -653,7 +665,9 @@ const convert = (toolName, data, result, standardResult) => {
653
665
  tagName: '',
654
666
  id: '',
655
667
  location: '',
656
- excerpt: ''
668
+ excerpt: '',
669
+ boxID: '',
670
+ pathID: ''
657
671
  });
658
672
  }
659
673
  }
package/procs/testaro.js CHANGED
@@ -227,10 +227,13 @@ exports.getVisibleCountChange = async (
227
227
  };
228
228
  // Annotates every element on a page with a unique identifier.
229
229
  exports.addTestaroIDs = async page => {
230
+ // Wait for the page to be fully loaded.
231
+ await page.waitForLoadState('networkidle');
230
232
  await page.evaluate(() => {
231
233
  let serialID = 0;
232
234
  for (const element of Array.from(document.querySelectorAll('*'))) {
233
- element.setAttribute('data-testaro-id', `${serialID++}#`);
235
+ const xPath = window.getXPath(element);
236
+ element.setAttribute('data-testaro-id', `${serialID++}#${xPath}`);
234
237
  }
235
238
  });
236
239
  };
package/run.js CHANGED
@@ -374,7 +374,6 @@ const launch = exports.launch = async (
374
374
  // If the page emits a message:
375
375
  page.on('console', msg => {
376
376
  const msgText = msg.text();
377
- let indentedMsg = '';
378
377
  // If debugging is on:
379
378
  if (debug) {
380
379
  // Log the start of the message on the console.
@@ -413,8 +412,8 @@ const launch = exports.launch = async (
413
412
  get: () => ['en-US', 'en']
414
413
  });
415
414
  });
416
- const needsXPath = act.type === 'test'
417
- && ['testaro', 'htmlcs', 'nuVal', 'nuVnu', 'wax'].includes(act.which);
415
+ const xPathNeeders = ['testaro', 'htmlcs', 'nuVal', 'nuVnu', 'qualWeb', 'wax'];
416
+ const needsXPath = act.type === 'test' && xPathNeeders.includes(act.which);
418
417
  // If the launch is for a test act that requires XPaths:
419
418
  if (needsXPath) {
420
419
  // Add a script to the page to add a window method to get the XPath of an element.
@@ -437,7 +436,7 @@ const launch = exports.launch = async (
437
436
  // Otherwise, get its parent node.
438
437
  const parent = element.parentNode;
439
438
  // If (abnormally) the parent node is not an element:
440
- if (!parent || parent.nodeType !== Node.ELEMENT_NODE) {
439
+ if (! parent || parent.nodeType !== Node.ELEMENT_NODE) {
441
440
  // Prepend the element (not the parent) to the segment array.
442
441
  segments.unshift(tag);
443
442
  // Stop traversing, leaving the segment array partial.
@@ -917,7 +916,7 @@ const doActs = async (report, opts = {}) => {
917
916
  child.on('close', code => {
918
917
  if (! closed) {
919
918
  closed = true;
920
- resolve(code);
919
+ resolve(`Page closed with code ${code}`);
921
920
  }
922
921
  });
923
922
  });
@@ -1648,16 +1647,19 @@ const doActs = async (report, opts = {}) => {
1648
1647
  standardize(act);
1649
1648
  // For each of its standard instances:
1650
1649
  for (const instance of act.standardResult.instances) {
1651
- const elementID = await identify(instance, page);
1652
- // If a box ID is missing:
1653
- if (! instance.boxID) {
1654
- // Add one.
1655
- instance.boxID = elementID ? elementID.boxID : '';
1656
- }
1657
- // If a path ID is missing or different:
1658
- if (instance.pathID !== elementID.pathID) {
1659
- // Add a box ID and a path ID to each of its standard instances if missing.
1660
- instance.pathID = elementID.pathID;
1650
+ // If the instance does not have both a box ID and a path ID:
1651
+ if (! (instance.boxID && instance.pathID)) {
1652
+ const elementID = await identify(instance, page);
1653
+ // If it has no box ID but the element has a bounding box:
1654
+ if (elementID.boxID && ! instance.boxID) {
1655
+ // Add a box ID.
1656
+ instance.boxID = elementID.boxID;
1657
+ }
1658
+ // If it has no path ID but the element has one:
1659
+ if (elementID.pathID && ! instance.pathID) {
1660
+ // Add a path ID.
1661
+ instance.pathID = elementID.pathID;
1662
+ }
1661
1663
  }
1662
1664
  };
1663
1665
  // If the original-format result is not to be included in the report:
@@ -1677,9 +1679,12 @@ const doActs = async (report, opts = {}) => {
1677
1679
  console.log('Standardization completed');
1678
1680
  const {acts} = report;
1679
1681
  const idData = {};
1682
+ // For each act:
1680
1683
  for (const act of acts) {
1684
+ // If it is a test act:
1681
1685
  if (act.type === 'test') {
1682
1686
  const {which} = act;
1687
+ // Initialize an idData property for the tool if necessary.
1683
1688
  idData[which] ??= {
1684
1689
  instanceCount: 0,
1685
1690
  boxIDCount: 0,
@@ -1690,18 +1695,26 @@ const doActs = async (report, opts = {}) => {
1690
1695
  const actIDData = idData[which];
1691
1696
  const {standardResult} = act;
1692
1697
  const {instances} = standardResult;
1698
+ // For each standard instance in the act:
1693
1699
  for (const instance of instances) {
1694
1700
  const {boxID, pathID} = instance;
1701
+ // Increment the instance count.
1695
1702
  actIDData.instanceCount++;
1703
+ // If the instance has a box ID:
1696
1704
  if (boxID) {
1705
+ // Increment the box ID count.
1697
1706
  actIDData.boxIDCount++;
1698
1707
  }
1708
+ // If the instance has a path ID:
1699
1709
  if (pathID) {
1710
+ // Increment the path ID count.
1700
1711
  actIDData.pathIDCount++;
1701
1712
  }
1702
1713
  }
1703
1714
  const {instanceCount, boxIDCount, pathIDCount} = actIDData;
1715
+ // If there are any instances:
1704
1716
  if (instanceCount) {
1717
+ // Add the box ID and path ID percentages to the iData property.
1705
1718
  actIDData.boxIDPercent = Math.round(100 * boxIDCount / instanceCount);
1706
1719
  actIDData.pathIDPercent = Math.round(100 * pathIDCount / instanceCount);
1707
1720
  }
package/tests/nuVnu.js CHANGED
@@ -33,7 +33,7 @@ const tmpDir = os.tmpdir();
33
33
 
34
34
  // Conducts and reports the Nu Html Checker tests.
35
35
  exports.reporter = async (page, report, actIndex) => {
36
- // Get the vuVal act, if it exists.
36
+ // Get the nuVal act, if it exists.
37
37
  const nuValAct = report.acts.find(act => act.type === 'test' && act.which === 'nuVal');
38
38
  // If it does not exist or it exists but was prevented:
39
39
  if (! nuValAct || nuValAct.data?.prevented) {
@@ -70,6 +70,10 @@ exports.reporter = async (page, report, actIndex) => {
70
70
  await fs.unlink(pagePath);
71
71
  // Postprocess the result.
72
72
  result = await curate(page, data, nuData, rules);
73
+ return {
74
+ data,
75
+ result
76
+ };
73
77
  }
74
78
  // Otherwise, i.e. if the content was not obtained:
75
79
  else {
@@ -77,10 +81,6 @@ exports.reporter = async (page, report, actIndex) => {
77
81
  data.prevented = true;
78
82
  data.error = 'Content not obtained';
79
83
  }
80
- return {
81
- data,
82
- result
83
- };
84
84
  }
85
85
  // Otherwise, i.e. if the nuVal act exists and succeeded:
86
86
  else {
package/tests/qualWeb.js CHANGED
@@ -19,6 +19,8 @@ const {QualWeb} = require('@qualweb/core');
19
19
  const {ACTRules} = require('@qualweb/act-rules');
20
20
  const {WCAGTechniques} = require('@qualweb/wcag-techniques');
21
21
  const {BestPractices} = require('@qualweb/best-practices');
22
+ const {addTestaroIDs} = require('../procs/testaro');
23
+ const {getLocationData} = require('../procs/getLocatorData');
22
24
 
23
25
  // CONSTANTS
24
26
 
@@ -43,6 +45,8 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
43
45
  timeout: timeLimit * 1000,
44
46
  monitor: false
45
47
  };
48
+ // Annotate all elements on the page with unique identifiers.
49
+ await addTestaroIDs(page);
46
50
  try {
47
51
  // Start the QualWeb core engine.
48
52
  await qualWeb.start(clusterOptions, {
@@ -177,7 +181,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
177
181
  if (assertions) {
178
182
  const ruleIDs = Object.keys(assertions);
179
183
  // For each rule:
180
- ruleIDs.forEach(ruleID => {
184
+ for (const ruleID of ruleIDs) {
181
185
  const ruleAssertions = assertions[ruleID];
182
186
  const {metadata} = ruleAssertions;
183
187
  // If result data exist for the rule:
@@ -190,6 +194,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
190
194
  // Otherwise, i.e. if there was at least 1 warning or failure:
191
195
  else {
192
196
  if (ruleAssertions.results) {
197
+ // Delete nonviolations from the results.
193
198
  ruleAssertions.results = ruleAssertions.results.filter(
194
199
  raResult => raResult.verdict !== 'passed'
195
200
  );
@@ -198,17 +203,23 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
198
203
  }
199
204
  // Shorten long HTML codes of elements.
200
205
  const {results} = ruleAssertions;
201
- results.forEach(raResult => {
206
+ // For each test result:
207
+ for (const raResult of results) {
202
208
  const {elements} = raResult;
209
+ // If any violations are reported:
203
210
  if (elements && elements.length) {
204
- elements.forEach(element => {
211
+ // For each violating element:
212
+ for (const element of elements) {
213
+ // Add location data from its excerpt to the element data.
214
+ element.locationData = await getLocationData(page, element.htmlCode);
215
+ // Limit the size of its reported excerpt.
205
216
  if (element.htmlCode && element.htmlCode.length > 700) {
206
217
  element.htmlCode = `${element.htmlCode.slice(0, 700)} …`;
207
218
  }
208
- });
219
+ };
209
220
  }
210
- });
211
- });
221
+ };
222
+ };
212
223
  }
213
224
  else {
214
225
  data.prevented = true;
package/tests/wax.js CHANGED
@@ -60,7 +60,7 @@ exports.reporter = async (page, report, actIndex) => {
60
60
  }
61
61
  // Otherwise, i.e. if it is a successful report:
62
62
  else {
63
- // Add location data to its excerpts.
63
+ // Add location data to its reported violations.
64
64
  for (const violation of actReport) {
65
65
  const {element} = violation;
66
66
  const elementLocation = await getLocationData(page, element);