testaro 65.0.4 → 65.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": "65.0.4",
3
+ "version": "65.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": {
@@ -393,66 +393,70 @@ const convert = (toolName, data, result, standardResult) => {
393
393
  // alfa
394
394
  else if (toolName === 'alfa' && result.totals) {
395
395
  result.items.forEach(item => {
396
- const {codeLines} = item.target;
396
+ const {outcome, rule, violator} = item;
397
+ const {ruleID, ruleSummary} = rule;
398
+ const {codeLines, path, pathID, tagName, text} = violator;
397
399
  const code = Array.isArray(codeLines) ? codeLines.join(' ') : '';
398
400
  const identifiers = getIdentifiers(code);
399
- const tagNameArray = item.target
400
- && item.target.path
401
- && item.target.path.match(/^.*\/([a-z]+)\[\d+\]/);
401
+ const tagNameArray = path && path.match(/^.*\/([a-z]+)\[\d+\]/);
402
402
  if (tagNameArray && tagNameArray.length === 2) {
403
403
  identifiers[0] = tagNameArray[1].toUpperCase();
404
404
  }
405
- const {rule, target} = item;
406
405
  let instance;
407
- if (item.verdict === 'failed') {
406
+ if (outcome === 'failed') {
408
407
  instance = {
409
- ruleID: rule.ruleID,
410
- what: rule.ruleSummary,
408
+ ruleID,
409
+ what: ruleSummary,
411
410
  ordinalSeverity: 3,
412
- tagName: identifiers[0],
411
+ tagName: tagName || identifiers[0],
413
412
  id: identifiers[1],
414
413
  location: {
415
414
  doc: 'dom',
416
415
  type: 'xpath',
417
- spec: target.path
416
+ spec: path
418
417
  },
419
418
  excerpt: cap(code),
419
+ text,
420
420
  boxID: '',
421
- pathID: ''
421
+ pathID
422
422
  };
423
423
  standardResult.instances.push(instance);
424
424
  }
425
425
  else if (item.verdict === 'cantTell') {
426
- if (['r66', 'r69'].includes(rule.ruleID)) {
426
+ if (['r66', 'r69'].includes(ruleID)) {
427
427
  instance = {
428
428
  ruleID: 'cantTellTextContrast',
429
- what: `cannot test for rule ${rule.ruleID}: ${rule.ruleSummary}`,
429
+ what: `cannot test for rule ${ruleID}: ${ruleSummary}`,
430
430
  ordinalSeverity: 0,
431
- tagName: identifiers[0],
431
+ tagName: tagName || identifiers[0],
432
432
  id: identifiers[1],
433
433
  location: {
434
434
  doc: 'dom',
435
435
  type: 'xpath',
436
- spec: target.path
436
+ spec: path
437
437
  },
438
- excerpt: cap(code)
438
+ excerpt: cap(code),
439
+ text,
440
+ boxID: '',
441
+ pathID
439
442
  };
440
443
  }
441
444
  else {
442
445
  instance = {
443
446
  ruleID: 'cantTell',
444
- what: `cannot test for rule ${rule.ruleID}: ${rule.ruleSummary}`,
447
+ what: `cannot test for rule ${ruleID}: ${ruleSummary}`,
445
448
  ordinalSeverity: 0,
446
- tagName: identifiers[0],
449
+ tagName: tagName || identifiers[0],
447
450
  id: identifiers[1],
448
451
  location: {
449
452
  doc: 'dom',
450
453
  type: 'xpath',
451
- spec: target.path
454
+ spec: path
452
455
  },
453
456
  excerpt: cap(code),
457
+ text,
454
458
  boxID: '',
455
- pathID: ''
459
+ pathID
456
460
  };
457
461
  }
458
462
  standardResult.instances.push(instance);
package/run.js CHANGED
@@ -1765,7 +1765,7 @@ const doActs = async (report, opts = {}) => {
1765
1765
  .replace(/ data-testaro-id="[^" ]* /g, ' ');
1766
1766
  }
1767
1767
  pathID = instance.pathID;
1768
- // If the instance has no text property:
1768
+ // If the instance has no or an empty text property:
1769
1769
  if (! instance.text) {
1770
1770
  const {excerpt} = instance;
1771
1771
  // If the instance has a markup-free non-empty excerpt:
package/tests/alfa.js CHANGED
@@ -17,12 +17,13 @@
17
17
 
18
18
  let alfaRules = require('@siteimprove/alfa-rules').default;
19
19
  const {Audit} = require('@siteimprove/alfa-act');
20
+ const {getNormalizedXPath} = require('../procs/identify');
20
21
  const {Playwright} = require('@siteimprove/alfa-playwright');
21
22
 
22
23
  // FUNCTIONS
23
24
 
24
25
  // Simplifies the spacing of a string.
25
- const tidy = string => string.replace(/\n/g, ' ').replace(/\s+/g, ' ');
26
+ const tidy = string => string.replace(/\s+/g, ' ');
26
27
 
27
28
  // Conducts and reports the alfa tests.
28
29
  exports.reporter = async (page, report, actIndex) => {
@@ -47,35 +48,53 @@ exports.reporter = async (page, report, actIndex) => {
47
48
  const doc = await page.evaluateHandle('document');
48
49
  const alfaPage = await Playwright.toPage(doc);
49
50
  const audit = Audit.of(alfaPage, alfaRules);
50
- // Get the test outcomes.
51
- const outcomes = Array.from(await audit.evaluate());
52
- // For each outcome:
53
- outcomes.forEach((outcome, index) => {
54
- const {target} = outcome;
55
- // If the target exists and is not a collection:
56
- if (target && ! target._members) {
57
- // Convert the outcome to an object.
58
- const outcomeJ = outcome.toJSON();
59
- // Get the verdict.
60
- const verdict = outcomeJ.outcome;
61
- // If the verdict is a failure or warning:
62
- if (verdict !== 'passed') {
63
- // Add to the result.
64
- const {expectations, rule} = outcomeJ;
51
+ // Get the evaluation.
52
+ const evaluation = Array.from(await audit.evaluate());
53
+ // For each of its components:
54
+ for (const index in evaluation) {
55
+ const component = evaluation[index];
56
+ const violatorClass = component.target;
57
+ // If it has a non-collection violator:
58
+ if (violatorClass && ! violatorClass._members) {
59
+ // Get the path.
60
+ const path = violatorClass.path();
61
+ // Get the normalized path, omitting any final text() selector.
62
+ const pathID = getNormalizedXPath(path.replace(/\/text\(\).*$/, ''));
63
+ // Get the code lines of the violator.
64
+ const codeLines = violatorClass.toString().split('\n');
65
+ // Convert the component to a finding object.
66
+ const finding = component.toJSON();
67
+ const {expectations, outcome, rule} = finding;
68
+ // If the outcome of the finding is a failure or warning:
69
+ if (outcome !== 'passed') {
70
+ let text = '';
71
+ // Get a locator for the violator.
72
+ const violatorLoc = page.locator(`xpath=${pathID}`);
73
+ try {
74
+ // Get the inner text of the violator.
75
+ text = await violatorLoc.innerText({timeout: 50});
76
+ }
77
+ catch(error) {}
65
78
  const {tags, uri, requirements} = rule;
66
79
  const ruleID = uri.replace(/^.+-/, '');
67
80
  let ruleSummary = tidy(expectations?.[0]?.[1]?.error?.message || '');
68
- const targetJ = outcomeJ.target;
69
- const codeLines = target.toString().split('\n');
81
+ const violator = finding.target;
82
+ const {name, type} = violator;
70
83
  if (codeLines[0] === '#document') {
71
84
  codeLines.splice(2, codeLines.length - 3, '...');
72
85
  }
73
86
  else if (codeLines[0].startsWith('<html')) {
74
87
  codeLines.splice(1, codeLines.length - 2, '...');
75
88
  }
76
- const outcomeData = {
89
+ let tagName = name?.toUpperCase();
90
+ if (pathID && (tagName?.startsWith('TEXT') || ! tagName)) {
91
+ const standardTagName = pathID.split('/').pop().replace(/\[.+/, '').toUpperCase() || '';
92
+ tagName = standardTagName;
93
+ }
94
+ // Get data on the finding.
95
+ const findingData = {
77
96
  index,
78
- verdict,
97
+ outcome,
79
98
  rule: {
80
99
  ruleID,
81
100
  ruleSummary,
@@ -83,46 +102,49 @@ exports.reporter = async (page, report, actIndex) => {
83
102
  uri,
84
103
  requirements
85
104
  },
86
- target: {
87
- type: targetJ.type,
88
- tagName: targetJ.name || '',
89
- path: target.path(),
105
+ violator: {
106
+ type,
107
+ name,
108
+ tagName,
109
+ path,
90
110
  codeLines: codeLines.map(
91
111
  line => line.length > 300 ? `${line.slice(0, 300)}...` : line
92
- )
112
+ ),
113
+ text,
114
+ pathID
93
115
  }
94
116
  };
95
117
  // If the rule summary is missing:
96
- if (outcomeData.rule.ruleSummary === '') {
118
+ if (findingData.rule.ruleSummary === '') {
97
119
  // If a first requirement title exists:
98
- const {requirements} = outcomeData.rule;
120
+ const {requirements} = findingData.rule;
99
121
  if (requirements && requirements.length && requirements[0].title) {
100
122
  // Make it the rule summary.
101
- outcomeData.rule.ruleSummary = requirements[0].title;
123
+ findingData.rule.ruleSummary = requirements[0].title;
102
124
  }
103
125
  }
104
126
  const etcTags = [];
105
127
  tags.forEach(tag => {
106
128
  if (tag.type === 'scope') {
107
- outcomeData.rule.scope = tag.scope;
129
+ findingData.rule.scope = tag.scope;
108
130
  }
109
131
  else {
110
132
  etcTags.push(tag);
111
133
  }
112
134
  });
113
135
  if (etcTags.length) {
114
- outcomeData.etcTags = etcTags;
136
+ findingData.etcTags = etcTags;
115
137
  }
116
- if (outcomeData.verdict === 'failed') {
138
+ if (findingData.outcome === 'failed') {
117
139
  result.totals.failures++;
118
140
  }
119
- else if (outcomeData.verdict === 'cantTell') {
141
+ else if (findingData.outcome === 'cantTell') {
120
142
  result.totals.warnings++;
121
143
  }
122
- result.items.push(outcomeData);
144
+ result.items.push(findingData);
123
145
  }
124
146
  }
125
- });
147
+ };
126
148
  }
127
149
  catch(error) {
128
150
  console.log(`ERROR: Navigation to URL timed out (${error})`);