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 +1 -1
- package/procs/getLocatorData.js +26 -10
- package/procs/standardize.js +23 -9
- package/procs/testaro.js +4 -1
- package/run.js +28 -15
- package/tests/nuVnu.js +5 -5
- package/tests/qualWeb.js +17 -6
- package/tests/wax.js +1 -1
package/package.json
CHANGED
package/procs/getLocatorData.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
128
|
-
|
|
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,
|
|
135
|
-
|
|
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:
|
package/procs/standardize.js
CHANGED
|
@@ -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
|
|
46
|
-
const id =
|
|
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
|
-
|
|
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
|
|
417
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
//
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
//
|
|
1660
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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);
|