testaro 13.0.2 → 14.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.
Files changed (100) hide show
  1. package/README.md +190 -94
  2. package/actSpecs.js +17 -158
  3. package/package.json +1 -1
  4. package/run.js +31 -31
  5. package/standardize.js +338 -0
  6. package/{tests → testaro}/allHidden.js +36 -1
  7. package/{tests → testaro}/attVal.js +36 -2
  8. package/{tests → testaro}/autocomplete.js +34 -1
  9. package/{tests → testaro}/bulk.js +15 -1
  10. package/{tests → testaro}/docType.js +15 -1
  11. package/{tests → testaro}/dupAtt.js +34 -1
  12. package/{tests → testaro}/elements.js +9 -3
  13. package/testaro/embAc.js +78 -0
  14. package/{tests → testaro}/filter.js +35 -1
  15. package/{tests → testaro}/focAll.js +20 -3
  16. package/{tests → testaro}/focInd.js +45 -3
  17. package/{tests → testaro}/focOp.js +61 -2
  18. package/{tests → testaro}/focVis.js +35 -1
  19. package/{tests → testaro}/hover.js +67 -5
  20. package/{tests → testaro}/labClash.js +54 -4
  21. package/{tests → testaro}/linkTo.js +33 -1
  22. package/{tests → testaro}/linkUl.js +43 -5
  23. package/{tests → testaro}/menuNav.js +42 -1
  24. package/{tests → testaro}/miniText.js +32 -1
  25. package/{tests → testaro}/motion.js +27 -4
  26. package/{tests → testaro}/nonTable.js +32 -1
  27. package/{tests → testaro}/radioSet.js +33 -2
  28. package/{tests → testaro}/role.js +38 -1
  29. package/{tests → testaro}/styleDiff.js +43 -2
  30. package/{tests → testaro}/tabNav.js +42 -1
  31. package/{tests → testaro}/textNodes.js +6 -2
  32. package/{tests → testaro}/title.js +8 -4
  33. package/{tests → testaro}/titledEl.js +32 -1
  34. package/{tests → testaro}/zIndex.js +40 -2
  35. package/tests/alfa.js +72 -75
  36. package/tests/continuum.js +6 -2
  37. package/tests/ibm.js +14 -42
  38. package/tests/testaro.js +73 -0
  39. package/validation/tests/jobs/allHidden.json +877 -174
  40. package/validation/tests/jobs/attVal.json +57 -19
  41. package/validation/tests/jobs/autocomplete.json +34 -8
  42. package/validation/tests/jobs/bulk.json +33 -7
  43. package/validation/tests/jobs/docType.json +23 -5
  44. package/validation/tests/jobs/dupAtt.json +47 -8
  45. package/validation/tests/jobs/elements.json +231 -70
  46. package/validation/tests/jobs/embAc.json +70 -15
  47. package/validation/tests/jobs/filter.json +56 -12
  48. package/validation/tests/jobs/focAll.json +64 -16
  49. package/validation/tests/jobs/focInd.json +107 -23
  50. package/validation/tests/jobs/focOp.json +93 -21
  51. package/validation/tests/jobs/focVis.json +16 -4
  52. package/validation/tests/jobs/hover.json +246 -56
  53. package/validation/tests/jobs/labClash.json +43 -11
  54. package/validation/tests/jobs/linkTo.json +16 -4
  55. package/validation/tests/jobs/linkUl.json +79 -19
  56. package/validation/tests/jobs/menuNav.json +313 -65
  57. package/validation/tests/jobs/miniText.json +21 -5
  58. package/validation/tests/jobs/motion.json +81 -23
  59. package/validation/tests/jobs/nonTable.json +26 -6
  60. package/validation/tests/jobs/radioSet.json +43 -11
  61. package/validation/tests/jobs/role.json +93 -19
  62. package/validation/tests/jobs/styleDiff.json +124 -28
  63. package/validation/tests/jobs/tabNav.json +313 -65
  64. package/validation/tests/jobs/textNodes.json +190 -49
  65. package/validation/tests/jobs/title.json +23 -5
  66. package/validation/tests/jobs/titledEl.json +26 -6
  67. package/validation/tests/jobs/zIndex.json +28 -8
  68. package/validation/tests/old/allHidden.json +314 -0
  69. package/validation/tests/old/attVal.json +60 -0
  70. package/validation/tests/old/autocomplete.json +51 -0
  71. package/validation/tests/old/bulk.json +48 -0
  72. package/validation/tests/old/docType.json +46 -0
  73. package/validation/tests/old/dupAtt.json +51 -0
  74. package/validation/tests/old/elements.json +140 -0
  75. package/validation/tests/old/embAc.json +54 -0
  76. package/validation/tests/old/filter.json +55 -0
  77. package/validation/tests/old/focAll.json +68 -0
  78. package/validation/tests/old/focInd.json +69 -0
  79. package/validation/tests/old/focOp.json +62 -0
  80. package/validation/tests/old/focVis.json +35 -0
  81. package/validation/tests/old/hover.json +118 -0
  82. package/validation/tests/old/labClash.json +52 -0
  83. package/validation/tests/old/linkTo.json +35 -0
  84. package/validation/tests/old/linkUl.json +71 -0
  85. package/validation/tests/old/menuNav.json +106 -0
  86. package/validation/tests/old/miniText.json +36 -0
  87. package/validation/tests/old/motion.json +62 -0
  88. package/validation/tests/old/nonTable.json +37 -0
  89. package/validation/tests/old/radioSet.json +52 -0
  90. package/validation/tests/old/role.json +60 -0
  91. package/validation/tests/old/styleDiff.json +71 -0
  92. package/validation/tests/old/tabNav.json +106 -0
  93. package/validation/tests/old/temp.js +28 -0
  94. package/validation/tests/old/textNodes.json +98 -0
  95. package/validation/tests/old/title.json +46 -0
  96. package/validation/tests/old/titledEl.json +37 -0
  97. package/validation/tests/old/zIndex.json +49 -0
  98. package/validation/tests/targets/attVal/good.html +1 -1
  99. package/validation/tests/targets/elements/index.html +1 -0
  100. package/tests/embAc.js +0 -36
@@ -63,7 +63,7 @@ const shootAll = async (page, delay, interval, count, toDo, buffers) => {
63
63
  // Returns a number rounded to 2 decimal digits.
64
64
  const round = (num, precision) => Number.parseFloat(num.toPrecision(precision));
65
65
  // Reports motion in a page.
66
- exports.reporter = async (page, delay, interval, count) => {
66
+ exports.reporter = async (page, withItems, delay = 2500, interval = 2500, count = 5) => {
67
67
  // Make screenshots and get their image buffers.
68
68
  const shots = await shootAll(page, delay, interval, count, count, []);
69
69
  // If the shooting succeeded:
@@ -99,7 +99,7 @@ exports.reporter = async (page, delay, interval, count) => {
99
99
  );
100
100
  // Return the result.
101
101
  return {
102
- result: {
102
+ data: {
103
103
  bytes,
104
104
  localRatios,
105
105
  meanLocalRatio,
@@ -109,14 +109,37 @@ exports.reporter = async (page, delay, interval, count) => {
109
109
  meanPixelChange,
110
110
  maxPixelChange,
111
111
  changeFrequency
112
- }
112
+ },
113
+ totals: [
114
+ 0,
115
+ 0,
116
+ 2 * (meanLocalRatio - 1)
117
+ + maxLocalRatio - 1
118
+ + globalRatio - 1
119
+ + meanPixelChange / 10000
120
+ + maxPixelChange / 25000
121
+ + 3 * changeFrequency
122
+ || 0,
123
+ 0
124
+ ],
125
+ standardInstances: [{
126
+ issueID: 'motion',
127
+ what: 'Content moves or changes without user request',
128
+ ordinalSeverity: 2,
129
+ location: {
130
+ doc: '',
131
+ type: '',
132
+ spec: ''
133
+ },
134
+ excerpt: ''
135
+ }]
113
136
  };
114
137
  }
115
138
  // Otherwise, i.e. if the shooting failed:
116
139
  else {
117
140
  // Return failure.
118
141
  return {
119
- result: {
142
+ data: {
120
143
  prevented: true,
121
144
  error: 'ERROR: screenshots failed'
122
145
  }
@@ -60,8 +60,39 @@ exports.reporter = async (page, withItems) => {
60
60
  const data = {
61
61
  total: badTableTexts.length
62
62
  };
63
+ const standardInstances = [];
63
64
  if (withItems) {
64
65
  data.items = badTableTexts;
66
+ data.items.forEach(text => {
67
+ standardInstances.push({
68
+ issueID: 'nonTable',
69
+ what: 'Table is misused to arrange content',
70
+ ordinalSeverity: 0,
71
+ location: {
72
+ doc: '',
73
+ type: '',
74
+ spec: ''
75
+ },
76
+ excerpt: text
77
+ });
78
+ });
79
+ }
80
+ else if (data.total) {
81
+ standardInstances.push({
82
+ issueID: 'nonTable',
83
+ what: 'Tables are misused to arrange content',
84
+ ordinalSeverity: 2,
85
+ location: {
86
+ doc: '',
87
+ type: '',
88
+ spec: ''
89
+ },
90
+ excerpt: ''
91
+ });
65
92
  }
66
- return {result: data};
93
+ return {
94
+ data,
95
+ totals: [0, 0, data.total, 0],
96
+ standardInstances
97
+ };
67
98
  };
@@ -75,18 +75,49 @@ exports.reporter = async (page, withItems) => {
75
75
  totals.inSet = setRadios.length;
76
76
  totals.percent = totals.total ? Math.floor(100 * totals.inSet / totals.total) : 'N.A.';
77
77
  // If itemization is required:
78
+ const standardInstances = [];
78
79
  if (withItems) {
79
80
  // Add it to the results.
80
81
  const nonSetRadios = allRadios.filter(radio => ! setRadios.includes(radio));
81
82
  const items = data.items;
82
83
  items.inSet = setRadios.map(radio => textOf(radio));
83
84
  items.notInSet = nonSetRadios.map(radio => textOf(radio));
85
+ items.notInSet.forEach(text => {
86
+ standardInstances.push({
87
+ issueID: 'radioSet',
88
+ what: 'Radio button and others with its name are not grouped in their own fieldset with a legend',
89
+ ordinalSeverity: 0,
90
+ location: {
91
+ doc: '',
92
+ type: '',
93
+ spec: ''
94
+ },
95
+ excerpt: text
96
+ });
97
+ });
84
98
  }
85
- return {result: data};
99
+ else if (totals.total - totals.inSet > 0) {
100
+ standardInstances.push({
101
+ issueID: 'radioSet',
102
+ what: 'Radio buttons are not validly grouped in fieldsets with legends',
103
+ ordinalSeverity: 1,
104
+ location: {
105
+ doc: '',
106
+ type: '',
107
+ spec: ''
108
+ },
109
+ excerpt: ''
110
+ });
111
+ }
112
+ return {
113
+ data,
114
+ totals: [0, totals.total - totals.inSet, 0, 0],
115
+ standardInstances
116
+ };
86
117
  }
87
118
  else {
88
119
  return {
89
- result: {
120
+ data: {
90
121
  prevented: true,
91
122
  error: 'ERROR identifying homogeneous field sets'
92
123
  }
@@ -478,6 +478,43 @@ exports.reporter = async page => await page.$eval('body', body => {
478
478
  }
479
479
  }
480
480
  });
481
+ const standardInstances = [];
482
+ Object.keys(data.tagNames).forEach(tagName => {
483
+ Object.keys(data.tagNames[tagName]).forEach(role => {
484
+ let count = data.tagNames[tagName][role].redundant;
485
+ if (count) {
486
+ standardInstances.push({
487
+ issueID: 'role',
488
+ what: `Elements ${tagName} have redundant explicit role ${role} (count: ${count})`,
489
+ ordinalSeverity: 1,
490
+ location: {
491
+ doc: '',
492
+ type: '',
493
+ spec: ''
494
+ },
495
+ excerpt: ''
496
+ });
497
+ }
498
+ count = data.tagNames[tagName][role].bad;
499
+ if (count) {
500
+ standardInstances.push({
501
+ issueID: 'role',
502
+ what: `Elements ${tagName} have invalid or native-replaceable explicit role ${role} (count: ${count})`,
503
+ ordinalSeverity: 3,
504
+ location: {
505
+ doc: '',
506
+ type: '',
507
+ spec: ''
508
+ },
509
+ excerpt: ''
510
+ });
511
+ }
512
+ });
513
+ });
481
514
  // Return the result.
482
- return {result: data};
515
+ return {
516
+ data,
517
+ totals: [0, data.redundantRoleElements, 0, data.badRoleElements],
518
+ standardInstances
519
+ };
483
520
  });
@@ -107,7 +107,9 @@ exports.reporter = async (page, withItems) => {
107
107
  if (! styleProps[typeName]) {
108
108
  styleProps[typeName] = {};
109
109
  }
110
- const elementText = element.textContent.trim().replace(/\s/g, ' ');
110
+ const elementText = (element.textContent.trim() || element.outerHTML.trim())
111
+ .replace(/\s+/g, ' ')
112
+ .slice(0, 100);
111
113
  // For each style property being compared:
112
114
  styles.forEach(styleName => {
113
115
  if (! styleProps[typeName][styleName]) {
@@ -116,6 +118,7 @@ exports.reporter = async (page, withItems) => {
116
118
  if (! styleProps[typeName][styleName][style[styleName]]) {
117
119
  styleProps[typeName][styleName][style[styleName]] = [];
118
120
  }
121
+ // Add the element text to the style details.
119
122
  styleProps[typeName][styleName][style[styleName]].push(elementText);
120
123
  });
121
124
  }
@@ -148,6 +151,44 @@ exports.reporter = async (page, withItems) => {
148
151
  headingNames.forEach(headingName => {
149
152
  tallyStyles(headingName, elements.headings[headingName], headingStyles, withItems);
150
153
  });
151
- return {result: data};
154
+ // Report the standandardized data.
155
+ const totals = [0, 0, 0, 0];
156
+ const standardInstances = [];
157
+ const elementData = {
158
+ adjacentLink: [0, 'In-line links'],
159
+ listLink: [1, 'Links in columns'],
160
+ button: [2, 'Buttons'],
161
+ h1: [3, 'Level-1 headings'],
162
+ h2: [3, 'Level-2 headings'],
163
+ h3: [3, 'Level-3 headings'],
164
+ h4: [3, 'Level-4 headings'],
165
+ h5: [3, 'Level-5 headings'],
166
+ h6: [3, 'Level-6 headings'],
167
+ };
168
+ Object.keys(elementData).forEach(elementName => {
169
+ const elementTotal = data.totals[elementName];
170
+ if (elementTotal && elementTotal.subtotals) {
171
+ const currentData = elementData[elementName];
172
+ const severity = currentData[0];
173
+ const elementSubtotals = elementTotal.subtotals;
174
+ totals[severity] += elementSubtotals.length - 1;
175
+ standardInstances.push({
176
+ issueID: 'styleDiff',
177
+ what: `${currentData[1]} have ${elementSubtotals.length} different styles`,
178
+ ordinalSeverity: severity,
179
+ location: {
180
+ doc: '',
181
+ type: '',
182
+ spec: ''
183
+ },
184
+ excerpt: ''
185
+ });
186
+ }
187
+ });
188
+ return {
189
+ data,
190
+ totals,
191
+ standardInstances
192
+ };
152
193
  }, [linkTypes, withItems]);
153
194
  };
@@ -324,5 +324,46 @@ exports.reporter = async (page, withItems) => {
324
324
  // FUNCTION DEFINITIONS END
325
325
  await testTabLists(tabLists);
326
326
  }
327
- return {result: data};
327
+ const totals = data.totals ? [
328
+ data.totals.navigations.all.incorrect,
329
+ data.totals.tabElements.incorrect,
330
+ data.totals.tabLists.incorrect,
331
+ 0
332
+ ] : [];
333
+ const standardInstances = [];
334
+ if (data.tabElements && data.tabElements.incorrect) {
335
+ data.tabElements.incorrect.forEach(item => {
336
+ standardInstances.push({
337
+ issueID: 'tabNav',
338
+ what: `Element ${item.tagName} has a tab role but has nonstandard navigation`,
339
+ ordinalSeverity: 1,
340
+ location: {
341
+ doc: '',
342
+ type: '',
343
+ spec: ''
344
+ },
345
+ excerpt: `${item.tagName}: ${item.text}`
346
+ });
347
+ });
348
+ }
349
+ else if (data.totals.navigations.all.incorrect) {
350
+ standardInstances.push({
351
+ issueID: 'tabNav',
352
+ what: 'Tablists have nonstandard navigation',
353
+ ordinalSeverity: 2,
354
+ location: {
355
+ doc: '',
356
+ type: '',
357
+ spec: ''
358
+ },
359
+ excerpt: ''
360
+ });
361
+ }
362
+ // Reload the page.
363
+ await page.reload({timeout: 15000});
364
+ return {
365
+ data,
366
+ totals,
367
+ standardInstances
368
+ };
328
369
  };
@@ -6,7 +6,7 @@
6
6
  1-3. Count of ancestry levels to provide data on (1 = text node, 2 = also parent,
7
7
  3 = also grandparent)
8
8
  */
9
- exports.reporter = async (page, detailLevel, text = '') => {
9
+ exports.reporter = async (page, withItems, detailLevel, text = '') => {
10
10
  let data = {};
11
11
  // Get the data on the text nodes.
12
12
  try {
@@ -136,5 +136,9 @@ exports.reporter = async (page, detailLevel, text = '') => {
136
136
  };
137
137
  }
138
138
  // Return the result.
139
- return {result: data};
139
+ return {
140
+ data,
141
+ totals: [],
142
+ standardInstances: []
143
+ };
140
144
  };
@@ -4,8 +4,12 @@
4
4
  */
5
5
  exports.reporter = async page => {
6
6
  const title = await page.title();
7
- return {result: {
8
- success: true,
9
- title
10
- }};
7
+ return {
8
+ data: {
9
+ success: true,
10
+ title
11
+ },
12
+ totals: [],
13
+ standardInstances: []
14
+ };
11
15
  };
@@ -24,8 +24,39 @@ exports.reporter = async (page, withItems) => {
24
24
  const data = {
25
25
  total: badTitleElements.length
26
26
  };
27
+ const standardInstances = [];
27
28
  if (withItems) {
28
29
  data.items = badTitleElements;
30
+ badTitleElements.forEach(element => {
31
+ standardInstances.push({
32
+ issueID: 'titledEl',
33
+ what: `Element ${element.tagName} has a title attribute`,
34
+ ordinalSeverity: 2,
35
+ location: {
36
+ doc: '',
37
+ type: '',
38
+ spec: ''
39
+ },
40
+ excerpt: `${element.tagName} (${element.text}): ${element.title}`
41
+ });
42
+ });
29
43
  }
30
- return {result: data};
44
+ else if (data.total) {
45
+ standardInstances.push({
46
+ issueID: 'titledEl',
47
+ what: 'Ineligible elements have title attributes',
48
+ ordinalSeverity: 2,
49
+ location: {
50
+ doc: '',
51
+ type: '',
52
+ spec: ''
53
+ },
54
+ excerpt: ''
55
+ });
56
+ }
57
+ return {
58
+ data,
59
+ totals: [0, 0, data.total, 0],
60
+ standardInstances
61
+ };
31
62
  };
@@ -33,7 +33,10 @@ exports.reporter = async (page, withItems) => {
33
33
  data.items.push({
34
34
  tagName,
35
35
  id: element.id || '',
36
- text: element.textContent.trim().replace(/\s{2,}/g, ' ').slice(0, 100)
36
+ text:
37
+ (element.textContent.trim() || element.outerHTML.trim())
38
+ .replace(/\s+/g, ' ')
39
+ .slice(0, 100)
37
40
  });
38
41
  }
39
42
  };
@@ -45,5 +48,40 @@ exports.reporter = async (page, withItems) => {
45
48
  });
46
49
  return data;
47
50
  }, withItems);
48
- return {result: data};
51
+ const standardInstances = [];
52
+ if (data.items) {
53
+ data.items.forEach(item => {
54
+ const itemID = item.id ? ` (ID ${item.id})` : '';
55
+ const which = `${item.tagName}${itemID}`;
56
+ standardInstances.push({
57
+ issueID: 'zIndex',
58
+ what: `Element ${item.tagName} has a non-default Z index`,
59
+ ordinalSeverity: 0,
60
+ location: {
61
+ doc: '',
62
+ type: '',
63
+ spec: ''
64
+ },
65
+ excerpt: `${which}: ${item.text}`
66
+ });
67
+ });
68
+ }
69
+ else if (data.totals.total) {
70
+ standardInstances.push({
71
+ issueID: 'zIndex',
72
+ what: 'Elements have non-default Z indexes',
73
+ ordinalSeverity: 0,
74
+ location: {
75
+ doc: '',
76
+ type: '',
77
+ spec: ''
78
+ },
79
+ excerpt: ''
80
+ });
81
+ }
82
+ return {
83
+ data,
84
+ totals: [data.totals.total, 0, 0, 0],
85
+ standardInstances
86
+ };
49
87
  };
package/tests/alfa.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // IMPORTS
7
7
 
8
8
  const {Audit} = require('@siteimprove/alfa-act');
9
- const {Scraper} = require('@siteimprove/alfa-scraper');
9
+ const {Playwright} = require('@siteimprove/alfa-playwright');
10
10
  let alfaRules = require('@siteimprove/alfa-rules').default;
11
11
 
12
12
  // FUNCTIONS
@@ -25,8 +25,16 @@ exports.reporter = async (page, rules) => {
25
25
  const msgText = msg.text();
26
26
  console.log(msgText);
27
27
  });
28
+ // Initialize the result.
29
+ let data = {
30
+ totals: {
31
+ failures: 0,
32
+ warnings: 0
33
+ },
34
+ items: []
35
+ };
28
36
  try {
29
- const response = await rulePage.goto('https://alfa.siteimprove.com/rules', {timeout: 10000});
37
+ const response = await rulePage.goto('https://alfa.siteimprove.com/rules', {timeout: 15000});
30
38
  let ruleData = {};
31
39
  if (response.status() === 200) {
32
40
  // Compile data on the rule IDs and summaries.
@@ -49,90 +57,79 @@ exports.reporter = async (page, rules) => {
49
57
  });
50
58
  await rulePage.close();
51
59
  }
52
- // Initialize the result.
53
- const data = {
54
- totals: {
55
- failures: 0,
56
- warnings: 0
57
- },
58
- items: []
59
- };
60
- await Scraper.with(async scraper => {
61
- // Get the page content.
62
- for (const input of await scraper.scrape(page.url())) {
63
- // Test it with the specified rules.
64
- const audit = Audit.of(input, alfaRules);
65
- const outcomes = Array.from(await audit.evaluate());
66
- // For each failure or warning:
67
- outcomes.forEach((outcome, index) => {
68
- const {target} = outcome;
69
- if (target && ! target._members) {
70
- const outcomeJ = outcome.toJSON();
71
- const verdict = outcomeJ.outcome;
72
- if (verdict !== 'passed') {
73
- // Add to the result.
74
- const {rule} = outcomeJ;
75
- const {tags, uri, requirements} = rule;
76
- const ruleID = uri.replace(/^.+-/, '');
77
- const ruleSummary = ruleData[ruleID] || '';
78
- const targetJ = outcomeJ.target;
79
- const codeLines = target.toString().split('\n');
80
- if (codeLines[0] === '#document') {
81
- codeLines.splice(2, codeLines.length - 3, '...');
82
- }
83
- else if (codeLines[0].startsWith('<html')) {
84
- codeLines.splice(1, codeLines.length - 2, '...');
85
- }
86
- const outcomeData = {
87
- index,
88
- verdict,
89
- rule: {
90
- ruleID,
91
- ruleSummary,
92
- scope: '',
93
- uri,
94
- requirements
95
- },
96
- target: {
97
- type: targetJ.type,
98
- tagName: targetJ.name || '',
99
- path: target.path(),
100
- codeLines: codeLines.map(line => line.length > 300 ? `${line.slice(0, 300)}...` : line)
101
- }
102
- };
103
- const etcTags = [];
104
- tags.forEach(tag => {
105
- if (tag.type === 'scope') {
106
- outcomeData.rule.scope = tag.scope;
107
- }
108
- else {
109
- etcTags.push(tag);
110
- }
111
- });
112
- if (etcTags.length) {
113
- outcomeData.etcTags = etcTags;
114
- }
115
- if (outcomeData.verdict === 'failed') {
116
- data.totals.failures++;
117
- }
118
- else if (outcomeData.verdict === 'cantTell') {
119
- data.totals.warnings++;
120
- }
121
- data.items.push(outcomeData);
60
+ // Test the page content with the specified rules.
61
+ const doc = await page.evaluateHandle('document');
62
+ const alfaPage = await Playwright.toPage(doc);
63
+ const audit = Audit.of(alfaPage, alfaRules);
64
+ const outcomes = Array.from(await audit.evaluate());
65
+ // For each failure or warning:
66
+ outcomes.forEach((outcome, index) => {
67
+ const {target} = outcome;
68
+ if (target && ! target._members) {
69
+ const outcomeJ = outcome.toJSON();
70
+ const verdict = outcomeJ.outcome;
71
+ if (verdict !== 'passed') {
72
+ // Add to the result.
73
+ const {rule} = outcomeJ;
74
+ const {tags, uri, requirements} = rule;
75
+ const ruleID = uri.replace(/^.+-/, '');
76
+ const ruleSummary = ruleData[ruleID] || '';
77
+ const targetJ = outcomeJ.target;
78
+ const codeLines = target.toString().split('\n');
79
+ if (codeLines[0] === '#document') {
80
+ codeLines.splice(2, codeLines.length - 3, '...');
81
+ }
82
+ else if (codeLines[0].startsWith('<html')) {
83
+ codeLines.splice(1, codeLines.length - 2, '...');
84
+ }
85
+ const outcomeData = {
86
+ index,
87
+ verdict,
88
+ rule: {
89
+ ruleID,
90
+ ruleSummary,
91
+ scope: '',
92
+ uri,
93
+ requirements
94
+ },
95
+ target: {
96
+ type: targetJ.type,
97
+ tagName: targetJ.name || '',
98
+ path: target.path(),
99
+ codeLines: codeLines.map(line => line.length > 300 ? `${line.slice(0, 300)}...` : line)
100
+ }
101
+ };
102
+ const etcTags = [];
103
+ tags.forEach(tag => {
104
+ if (tag.type === 'scope') {
105
+ outcomeData.rule.scope = tag.scope;
122
106
  }
107
+ else {
108
+ etcTags.push(tag);
109
+ }
110
+ });
111
+ if (etcTags.length) {
112
+ outcomeData.etcTags = etcTags;
123
113
  }
124
- });
114
+ if (outcomeData.verdict === 'failed') {
115
+ data.totals.failures++;
116
+ }
117
+ else if (outcomeData.verdict === 'cantTell') {
118
+ data.totals.warnings++;
119
+ }
120
+ data.items.push(outcomeData);
121
+ }
125
122
  }
126
123
  });
127
- return {result: data};
128
124
  }
129
125
  catch(error) {
130
126
  console.log(`ERROR: navigation to URL timed out (${error})`);
131
- return {
127
+ data = {
132
128
  result: {
133
129
  prevented: true,
134
130
  error: 'ERROR: navigation to URL timed out'
135
131
  }
136
132
  };
137
133
  }
134
+ return {result: data};
138
135
  };
@@ -43,12 +43,16 @@ exports.reporter = async (page, rules) => {
43
43
  }, 30000);
44
44
  });
45
45
  const bigResult = await Promise.race([bigResultPromise, deadlinePromise]);
46
+ // If the result compilation succeeded:
46
47
  if (Array.isArray(bigResult)) {
48
+ // Return a compact version of it, removing useless and invariant properties.
47
49
  return bigResult.map(bigItem => {
48
50
  const item = bigItem._rawEngineJsonObject;
51
+ delete item.testResult;
52
+ delete item.fixType;
49
53
  delete item.fingerprint.encoding;
50
- if (item.element.length > 200) {
51
- item.element = `${item.element.slice(0, 100)} ... ${item.element.slice(-100)}`;
54
+ if (item.element.length > 240) {
55
+ item.element = `${item.element.slice(0, 200)} ... ${item.element.slice(-200)}`;
52
56
  }
53
57
  return item;
54
58
  });