testaro 60.18.2 → 61.0.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/AGENTS.md +1 -1
- package/README.md +5 -5
- package/{VALIDATION_README.md → VALIDATION.md} +1 -1
- package/actSpecs.js +1 -1
- package/package.json +3 -2
- package/procs/job.js +2 -1
- package/procs/standardize.js +48 -0
- package/run.js +1 -1
- package/tests/nuVal.js +2 -7
- package/tests/nuVnu.js +131 -0
package/AGENTS.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
- **Main dirs**: `/tests` (tool test definitions), `/procs` (shared procedures), `/validation` (test validators), root (core modules)
|
|
15
15
|
- **Key files**: `run.js` (main executor), `actSpecs.js` (act specifications), `call.js` (CLI entry), `tests/testaro.js` (Testaro tool rules)
|
|
16
|
-
- **Tools**: Integrates
|
|
16
|
+
- **Tools**: Integrates 12 accessibility tools (Axe, Alfa, IBM Checker, QualWeb, ASLint, WAVE, WallyAX, Ed11y, HTML CodeSniffer, Nu Validator API, Nu Html Checker, Testaro)
|
|
17
17
|
- **Data flow**: Jobs (JSON) → run.js → tool tests → reports (with standardized results)
|
|
18
18
|
- **Env vars**: Required for WallyAX (`WAX_KEY`), WAVE (`WAVE_KEY`); optional `DEBUG`, `WAITS`, `JOBDIR`, `REPORTDIR`, `AGENT`
|
|
19
19
|
|
package/README.md
CHANGED
|
@@ -342,9 +342,9 @@ That means that a test act (i.e. an act with a `type` property having the value
|
|
|
342
342
|
|
|
343
343
|
If a particular test act either must have or may have any other properties, those properties are specified in the `tools` property in `actSpecs.js`.
|
|
344
344
|
|
|
345
|
-
When you include a `rules` property, you limit the tests of the tool that are performed or reported. For some tools (`alfa`, `axe`, `htmlcs`, `qualWeb`, `testaro`, and `wax`), only the specified tests are performed. Other tools (`aslint`, `ed11y`, `ibm`, `nuVal`, and `wave`) do not allow such a limitation, so, for those tools, all tests are performed but results are reported from only the specified tests.
|
|
345
|
+
When you include a `rules` property, you limit the tests of the tool that are performed or reported. For some tools (`alfa`, `axe`, `htmlcs`, `qualWeb`, `testaro`, and `wax`), only the specified tests are performed. Other tools (`aslint`, `ed11y`, `ibm`, `nuVal`, `nuVnu`, and `wave`) do not allow such a limitation, so, for those tools, all tests are performed but results are reported from only the specified tests.
|
|
346
346
|
|
|
347
|
-
The `nuVal`, `qualWeb`, and `testaro` tools require specific formats for the `rules` property. Those formats are described below in the sections about those tools.
|
|
347
|
+
The `nuVal`, `nuVnu`, `qualWeb`, and `testaro` tools require specific formats for the `rules` property. Those formats are described below in the sections about those tools.
|
|
348
348
|
|
|
349
349
|
##### Examples of test acts
|
|
350
350
|
|
|
@@ -490,9 +490,9 @@ Experimentation indicates that the `ibm` tool emits untrappable errors for some
|
|
|
490
490
|
|
|
491
491
|
#### Nu Html Checker
|
|
492
492
|
|
|
493
|
-
The `nuVal`
|
|
493
|
+
The `nuVal` and `nuVnu` tools perform the tests of the Nu Html Checker.
|
|
494
494
|
|
|
495
|
-
Its `rules` argument is **not** an array of rule IDs, but instead is an array of rule _specifications_. A rule specification for `nuVal` is a string with the format `=ruleID` or `~ruleID`. The `=` prefix indicates that the rule ID is invariable. The `~` prefix indicates that the rule ID is variable, in which case the `ruleID` part of the specification is a matching regular expression, rather than the exact text of a message. This `rules` format arises from the fact that `nuVal`
|
|
495
|
+
Its `rules` argument is **not** an array of rule IDs, but instead is an array of rule _specifications_. A rule specification for `nuVal` or `nuVnu` is a string with the format `=ruleID` or `~ruleID`. The `=` prefix indicates that the rule ID is invariable. The `~` prefix indicates that the rule ID is variable, in which case the `ruleID` part of the specification is a matching regular expression, rather than the exact text of a message. This `rules` format arises from the fact that `nuVal` and `nuVnu` generate customized messages and do not accompany them with rule identifiers.
|
|
496
496
|
|
|
497
497
|
#### QualWeb
|
|
498
498
|
|
|
@@ -771,7 +771,7 @@ This standard format reflects some judgments. For example:
|
|
|
771
771
|
|
|
772
772
|
- The `ordinalSeverity` property of an instance involves interpretation. Tools may report severity, certainty, priority, or some combination of those. They may use ordinal or metric quantifications. If they quantify ordinally, their scales may have more or fewer than 4 ranks. Testaro coerces each tool’s severity, certainty, and/or priority classification into a 4-rank ordinal classification. This classification is deemed to express the most common pattern among the tools.
|
|
773
773
|
- The `tagName` property of an instance may not always be obvious, because in some cases the rule being tested for requires a relationship among more than one element (e.g., “An X element may not have a Y element as its parent”).
|
|
774
|
-
- The `ruleID` property of an instance is a matching rule if the tool issues a message but no rule identifier for each instance. The `nuVal`
|
|
774
|
+
- The `ruleID` property of an instance is a matching rule if the tool issues a message but no rule identifier for each instance. The `nuVal` and `nuVnu` tools do this. In this case, Testaro is classifying the messages into rules.
|
|
775
775
|
- The `ruleID` property of an instance may reclassify tool rules. For example, if a tool rule covers multiple situations that are dissimilar, that rule may be split into multiple rules with distinct `ruleID` properties.
|
|
776
776
|
|
|
777
777
|
You are not dependent on the judgments incorporated into the standard format, because Testaro can give you the original reports from the tools as the `result` property of a `test` act.
|
package/actSpecs.js
CHANGED
|
@@ -133,7 +133,7 @@ exports.actSpecs = {
|
|
|
133
133
|
{
|
|
134
134
|
which: [true, 'string', 'isTest', 'tool name'],
|
|
135
135
|
launch: [false, 'object', '', 'new target, browserID, and/or what, if any'],
|
|
136
|
-
rules: [false, 'array', 'areStrings', 'rule IDs or (for testaro or
|
|
136
|
+
rules: [false, 'array', 'areStrings', 'rule IDs or (for testaro, nuVal, or nuVnu) specifications, if not all']
|
|
137
137
|
}
|
|
138
138
|
],
|
|
139
139
|
text: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testaro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "61.0.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": {
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"playwright-dompath": "*",
|
|
49
49
|
"playwright-extra": "*",
|
|
50
50
|
"playwright-extra-plugin-stealth": "*",
|
|
51
|
-
"puppeteer-extra-plugin-stealth": "*"
|
|
51
|
+
"puppeteer-extra-plugin-stealth": "*",
|
|
52
|
+
"vnu-jar": "*"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"dom-accessibility-api": "*",
|
package/procs/job.js
CHANGED
|
@@ -31,7 +31,8 @@ const tools = exports.tools = {
|
|
|
31
31
|
ed11y: 'Editoria11y',
|
|
32
32
|
htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
|
|
33
33
|
ibm: 'IBM Accessibility Checker',
|
|
34
|
-
nuVal: 'Nu Html Checker',
|
|
34
|
+
nuVal: 'Nu Html Checker API',
|
|
35
|
+
nuVnu: 'Nu Html Checker',
|
|
35
36
|
qualWeb: 'QualWeb',
|
|
36
37
|
testaro: 'Testaro',
|
|
37
38
|
wax: 'WallyAX',
|
package/procs/standardize.js
CHANGED
|
@@ -255,6 +255,45 @@ const doNuVal = (result, standardResult, docType) => {
|
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
257
|
};
|
|
258
|
+
// Converts issue instances from a nuVnu document type.
|
|
259
|
+
const doNuVnu = (result, standardResult, docType) => {
|
|
260
|
+
const items = result[docType] && result[docType].messages;
|
|
261
|
+
if (items && items.length) {
|
|
262
|
+
items.forEach(item => {
|
|
263
|
+
const identifiers = getIdentifiers(item.extract);
|
|
264
|
+
if (! identifiers[0] && item.message) {
|
|
265
|
+
const tagNameLCArray = item.message.match(
|
|
266
|
+
/^Element ([^ ]+)|^An (img) element| (meta|script) element| element (script)| tag (script)/
|
|
267
|
+
);
|
|
268
|
+
if (tagNameLCArray && tagNameLCArray[1]) {
|
|
269
|
+
identifiers[0] = tagNameLCArray[1].toUpperCase();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Include the message twice. A scoring procedure may replace the ruleID with a pattern.
|
|
273
|
+
const instance = {
|
|
274
|
+
ruleID: item.message,
|
|
275
|
+
what: item.message,
|
|
276
|
+
ordinalSeverity: -1,
|
|
277
|
+
tagName: identifiers[0],
|
|
278
|
+
id: identifiers[1],
|
|
279
|
+
location: {
|
|
280
|
+
doc: docType === 'pageContent' ? 'dom' : 'source',
|
|
281
|
+
type: 'line',
|
|
282
|
+
spec: item && item.lastLine && item.lastLine.toString() || ''
|
|
283
|
+
},
|
|
284
|
+
excerpt: cap(item.extract)
|
|
285
|
+
};
|
|
286
|
+
const {type, subType} = item;
|
|
287
|
+
if (type === 'info' && subType === 'warning') {
|
|
288
|
+
instance.ordinalSeverity = 0;
|
|
289
|
+
}
|
|
290
|
+
else if (type === 'error') {
|
|
291
|
+
instance.ordinalSeverity = subType === 'fatal' ? 3 : 2;
|
|
292
|
+
}
|
|
293
|
+
standardResult.instances.push(instance);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
258
297
|
// Converts instances of a qualWeb rule class.
|
|
259
298
|
const doQualWeb = (result, standardResult, ruleClassName) => {
|
|
260
299
|
if (result.modules && result.modules[ruleClassName]) {
|
|
@@ -568,6 +607,15 @@ const convert = (toolName, data, result, standardResult) => {
|
|
|
568
607
|
doNuVal(result, standardResult, 'rawPage');
|
|
569
608
|
}
|
|
570
609
|
}
|
|
610
|
+
// nuVnu
|
|
611
|
+
else if (toolName === 'nuVnu' && (result.pageContent || result.rawPage)) {
|
|
612
|
+
if (result.pageContent) {
|
|
613
|
+
doNuVnu(result, standardResult, 'pageContent');
|
|
614
|
+
}
|
|
615
|
+
if (result.rawPage) {
|
|
616
|
+
doNuVnu(result, standardResult, 'rawPage');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
571
619
|
// qualWeb
|
|
572
620
|
else if (
|
|
573
621
|
toolName === 'qualWeb'
|
package/run.js
CHANGED
|
@@ -83,7 +83,7 @@ const timeLimits = {
|
|
|
83
83
|
alfa: 20,
|
|
84
84
|
ed11y: 30,
|
|
85
85
|
ibm: 30,
|
|
86
|
-
testaro:
|
|
86
|
+
testaro: 150 + Math.round(6 * waits / 1000)
|
|
87
87
|
};
|
|
88
88
|
// Timeout multiplier.
|
|
89
89
|
const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1;
|
package/tests/nuVal.js
CHANGED
|
@@ -9,13 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
nuVal
|
|
12
|
-
This tool subjects a page and its source to the Nu Html Checker, thereby testing scripted
|
|
13
|
-
|
|
14
|
-
The API erratically replaces left and right double quotation marks with invalid UTF-8, which
|
|
15
|
-
appears as 2 or 3 successive instances of the replacement character (U+fffd). Therefore, this
|
|
16
|
-
test removes all such quotation marks and the replacement character. That causes
|
|
17
|
-
'Bad value “” for' to become 'Bad value for'. Since the corruption of quotation marks is
|
|
18
|
-
erratic, no better solution is known.
|
|
12
|
+
This tool subjects a page and its source to the Nu Html Checker, thereby testing scripted content found only in the loaded page and erroneous content before the browser corrects it. The API erratically replaces left and right double quotation marks with invalid UTF-8, which appears as 2 or 3 successive instances of the replacement character (U+fffd). Therefore, this test removes all such quotation marks and the replacement character. That causes 'Bad value “” for' to become 'Bad value for'. Since the corruption of quotation marks is erratic, no better solution is known.
|
|
13
|
+
This tool is the API version of the Nu Html Checker. It is an alternative to the vnu tool, which uses the same validator as an installed dependency. Each tool has advantages and disadvantages. The main advantage of nuVal is that it does not require the Testaro host to provide a Java virtual machine. The main disadvantage of nuVal is that it returns 502 status errors when the content being uploaded is larger than about 80,000 bytes. The main advantage of the vnu tool is that it can evaluate pages larger than about 80,000 bytes and pages reachable from the host that Testaro runs on even if not reachable from the public Internet.
|
|
19
14
|
*/
|
|
20
15
|
|
|
21
16
|
// IMPORTS
|
package/tests/nuVnu.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2022–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool.
|
|
4
|
+
|
|
5
|
+
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
|
+
https://opensource.org/license/mit/ for details.
|
|
7
|
+
|
|
8
|
+
SPDX-License-Identifier: MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
This tool subjects a page and its source to the Nu Html Checker, thereby testing scripted content found only in the loaded page and erroneous content before the browser corrects it. The API erratically replaces left and right double quotation marks with invalid UTF-8, which appears as 2 or 3 successive instances of the replacement character (U+fffd). Therefore, this test removes all such quotation marks and the replacement character. That causes 'Bad value “” for' to become 'Bad value for'. Since the corruption of quotation marks is erratic, no better solution is known.
|
|
13
|
+
This tool is the installed version of the Nu Html Checker. It is an alternative to the nuVal tool, which uses the same validator as a web service of the World Wide Web Consortium (W3C). Each tool has advantages and disadvantages. The main advantage of the nuVnu tool is that it can evaluate pages larger than about 80,000 bytes and pages reachable from the host that Testaro runs on even if not reachable from the public Internet. The main advantage of nuVal is that it does not require the Testaro host to provide a Java virtual machine.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// IMPORTS
|
|
17
|
+
|
|
18
|
+
// Module to perform file operations.
|
|
19
|
+
const fs = require('fs/promises');
|
|
20
|
+
// Module to define the operating-system temporary-file directory.
|
|
21
|
+
const os = require('os');
|
|
22
|
+
// Module to run tests.
|
|
23
|
+
const {vnu} = require('vnu-jar');
|
|
24
|
+
// Module to get the document source.
|
|
25
|
+
const {getSource} = require('../procs/getSource');
|
|
26
|
+
|
|
27
|
+
// CONSTANTS
|
|
28
|
+
|
|
29
|
+
const tmpDir = os.tmpdir();
|
|
30
|
+
|
|
31
|
+
// FUNCTIONS
|
|
32
|
+
|
|
33
|
+
// Conducts and reports the Nu Html Checker tests.
|
|
34
|
+
exports.reporter = async (page, report, actIndex) => {
|
|
35
|
+
const act = report.acts[actIndex];
|
|
36
|
+
const {rules} = act;
|
|
37
|
+
// Get the browser-parsed page.
|
|
38
|
+
const pageContent = await page.content();
|
|
39
|
+
const pagePath = `${tmpDir}/nuVnu-page-${report.id}.html`;
|
|
40
|
+
// Save it.
|
|
41
|
+
await fs.writeFile(pagePath, pageContent);
|
|
42
|
+
// Get the source.
|
|
43
|
+
const sourceData = await getSource(page);
|
|
44
|
+
const data = {
|
|
45
|
+
docTypes: {
|
|
46
|
+
pageContent: {},
|
|
47
|
+
rawPage: {}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const result = {};
|
|
51
|
+
// If the source was not obtained:
|
|
52
|
+
if (sourceData.prevented) {
|
|
53
|
+
// Report this.
|
|
54
|
+
data.prevented = true;
|
|
55
|
+
data.error = sourceData.error;
|
|
56
|
+
}
|
|
57
|
+
// Otherwise, i.e. if it was obtained:
|
|
58
|
+
else {
|
|
59
|
+
const sourcePath = `${tmpDir}/nuVnu-source-${report.id}.html`;
|
|
60
|
+
// Save the source.
|
|
61
|
+
await fs.writeFile(sourcePath, sourceData.source);
|
|
62
|
+
const pageTypes = [['pageContent', pagePath], ['rawPage', sourcePath]];
|
|
63
|
+
// For each page type:
|
|
64
|
+
for (const page of pageTypes) {
|
|
65
|
+
let nuResult;
|
|
66
|
+
try {
|
|
67
|
+
// Get Nu Html Checker output on it.
|
|
68
|
+
const nuOutput = await vnu.check(['--format', 'json', '--stdout', page[1]]);
|
|
69
|
+
// Consider the JSON output to be the result.
|
|
70
|
+
nuResult = nuOutput;
|
|
71
|
+
}
|
|
72
|
+
// If any error was thrown:
|
|
73
|
+
catch (error) {
|
|
74
|
+
let errorMessage = error.message;
|
|
75
|
+
// If it was due to an incompatible Java version:
|
|
76
|
+
if (errorMessage.includes('Unsupported major.minor version')) {
|
|
77
|
+
// Revise the error messageand report this.
|
|
78
|
+
errorMessage = `Installed version of Java is incompatible. Details: ${errorMessage}`;
|
|
79
|
+
data.docTypes[page[0]].prevented = true;
|
|
80
|
+
data.docTypes[page[0]].error = errorMessage;
|
|
81
|
+
}
|
|
82
|
+
// Otherwise, i.e. if it was not due to an incompatible Java version:
|
|
83
|
+
else {
|
|
84
|
+
try {
|
|
85
|
+
// Treat the output as a JSON result reporting rule violations.
|
|
86
|
+
nuResult = JSON.parse(error.message);
|
|
87
|
+
// But, if parsing it as JSON fails:
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Report this.
|
|
90
|
+
data.docTypes[page[0]].prevented = true;
|
|
91
|
+
data.docTypes[page[0]].error = `Error getting result (${error.message.slice(0, 300)});`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Delete the temporary file.
|
|
96
|
+
await fs.unlink(page[1]);
|
|
97
|
+
// If a result with or without reported violations was obtained:
|
|
98
|
+
if (nuResult) {
|
|
99
|
+
// Delete left and right quotation marks and their erratic invalid replacements.
|
|
100
|
+
const nuResultClean = JSON.parse(JSON.stringify(nuResult).replace(/[\u{fffd}“”]/ug, ''));
|
|
101
|
+
result[page[0]] = nuResultClean;
|
|
102
|
+
// If there is a report and rules were specified:
|
|
103
|
+
if (! result[page[0]].error && rules && Array.isArray(rules) && rules.length) {
|
|
104
|
+
// Remove all messages except those specified.
|
|
105
|
+
result[page[0]].messages = result[page[0]].messages.filter(message => rules.some(rule => {
|
|
106
|
+
if (rule[0] === '=') {
|
|
107
|
+
return message.message === rule.slice(1);
|
|
108
|
+
}
|
|
109
|
+
else if (rule[0] === '~') {
|
|
110
|
+
return new RegExp(rule.slice(1)).test(message.message);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(`ERROR: Invalid nuVal rule ${rule}`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// If both page types prevented testing:
|
|
121
|
+
if (pageTypes.every(pageType => data.docTypes[pageType[0]].prevented)) {
|
|
122
|
+
// Report this.
|
|
123
|
+
data.prevented = true;
|
|
124
|
+
data.error = 'Both doc types prevented';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
data,
|
|
129
|
+
result
|
|
130
|
+
};
|
|
131
|
+
};
|