testaro 61.2.0 → 62.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/actSpecs.js CHANGED
@@ -182,6 +182,18 @@ exports.actSpecs = {
182
182
  withNewContent: [true, 'boolean', '', 'whether to use a URL instead of page content']
183
183
  }
184
184
  ],
185
+ nuVal: [
186
+ 'Perform NuVal tests',
187
+ {
188
+ withSource: [false, 'boolean', '', 'whether to use the page source and not the browser page']
189
+ }
190
+ ],
191
+ nuVnu: [
192
+ 'Perform NuVnu tests',
193
+ {
194
+ withSource: [false, 'boolean', '', 'whether to use the page source and not the browser page']
195
+ }
196
+ ],
185
197
  testaro: [
186
198
  'Perform Testaro tests',
187
199
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "61.2.0",
3
+ "version": "62.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": {
package/procs/nu.js ADDED
@@ -0,0 +1,87 @@
1
+ /*
2
+ © 2025 Jonathan Robert Pool.
3
+
4
+ Licensed under the MIT License. See LICENSE file at the project root or
5
+ https://opensource.org/license/mit/ for details.
6
+
7
+ SPDX-License-Identifier: MIT
8
+ */
9
+
10
+ /*
11
+ nu
12
+ Utilities for Testaro tests nuVal and nuVnu.
13
+ */
14
+
15
+ // ########## IMPORTS
16
+
17
+ // Module to get the document source.
18
+ const {getSource} = require('./getSource');
19
+
20
+ // ########## FUNCTIONS
21
+
22
+ // Gets the content for a nuVal or nuVnu test.
23
+ exports.getContent = async (page, withSource) => {
24
+ const data = {
25
+ withSource,
26
+ testTarget: null
27
+ };
28
+ // If the specified content type was the page source:
29
+ if (withSource) {
30
+ // Get it.
31
+ const sourceData = await getSource(page);
32
+ // If the source was not obtained:
33
+ if (sourceData.prevented) {
34
+ // Report this.
35
+ data.prevented = true;
36
+ data.error = sourceData.error;
37
+ }
38
+ // Otherwise, i.e. if it was obtained:
39
+ else {
40
+ // Add it to the data.
41
+ data.testTarget = sourceData.source;
42
+ }
43
+ }
44
+ // Otherwise, i.e. if the specified content type was the Playwright page content:
45
+ else {
46
+ // Add it to the data.
47
+ data.testTarget = await page.content();
48
+ }
49
+ // Return the data.
50
+ return data;
51
+ };
52
+ // Postprocesses a result from nuVal or nuVnu tests.
53
+ exports.curate = (data, nuData, rules) => {
54
+ // Delete most of the test target from the data.
55
+ data.testTarget = `${data.testTarget.slice(0, 200)}…`;
56
+ let result;
57
+ // If a result was obtained:
58
+ if (nuData) {
59
+ // Delete left and right quotation marks and their erratic invalid replacements.
60
+ const nuDataValid = JSON.stringify(nuData).replace(/[\u{fffd}“”]/ug, '');
61
+ const nuDataClean = JSON.parse(nuDataValid);
62
+ result = nuDataClean;
63
+ }
64
+ // If there is a result and rules were specified:
65
+ if (result && rules && Array.isArray(rules) && rules.length) {
66
+ // Remove all messages except those specified.
67
+ result.messages = result.messages.filter(message => rules.some(rule => {
68
+ if (rule[0] === '=') {
69
+ return message.message === rule.slice(1);
70
+ }
71
+ else if (rule[0] === '~') {
72
+ return new RegExp(rule.slice(1)).test(message.message);
73
+ }
74
+ else {
75
+ console.log(`ERROR: Invalid nuVal rule ${rule}`);
76
+ return false;
77
+ }
78
+ }));
79
+ }
80
+ // Remove messages reporting duplicate blank IDs.
81
+ const badMessages = new Set(['Duplicate ID .', 'The first occurrence of ID was here.']);
82
+ result.messages = result.messages.filter(
83
+ message => ! badMessages.has(message.message)
84
+ );
85
+ // Return the result.
86
+ return result;
87
+ };
@@ -209,9 +209,9 @@ const doHTMLCS = (result, standardResult, severity) => {
209
209
  });
210
210
  }
211
211
  };
212
- // Converts issue instances from a nuVal document type.
213
- const doNuVal = (result, standardResult, docType) => {
214
- const items = result[docType] && result[docType].messages;
212
+ // Converts issue instances from a nuVal or nuVnu result..
213
+ const doNu = (withSource, result, standardResult) => {
214
+ const items = result && result.messages;
215
215
  if (items && items.length) {
216
216
  items.forEach(item => {
217
217
  const {extract, firstColumn, lastColumn, lastLine, message, subType, type} = item;
@@ -236,7 +236,7 @@ const doNuVal = (result, standardResult, docType) => {
236
236
  tagName: identifiers[0],
237
237
  id: identifiers[1],
238
238
  location: {
239
- doc: docType === 'pageContent' ? 'dom' : 'source',
239
+ doc: withSource ? 'source' : 'dom',
240
240
  type: 'code',
241
241
  spec
242
242
  },
@@ -252,48 +252,13 @@ const doNuVal = (result, standardResult, docType) => {
252
252
  });
253
253
  }
254
254
  };
255
- // Converts issue instances from a nuVnu document type.
256
- const doNuVnu = (result, standardResult, docType) => {
257
- const items = result[docType] && result[docType].messages;
258
- if (items && items.length) {
259
- items.forEach(item => {
260
- const {extract, firstColumn, lastColumn, lastLine, message, subType, type} = item;
261
- const identifiers = getIdentifiers(extract);
262
- if (! identifiers[0] && message) {
263
- const tagNameLCArray = message.match(
264
- /^Element ([^ ]+)|^An (img) element| (meta|script) element| element (script)| tag (script)/
265
- );
266
- if (tagNameLCArray && tagNameLCArray[1]) {
267
- identifiers[0] = tagNameLCArray[1].toUpperCase();
268
- }
269
- }
270
- let spec = '';
271
- const locationSegments = [lastLine, firstColumn, lastColumn];
272
- if (locationSegments.every(segment => typeof segment === 'number')) {
273
- spec = locationSegments.join(':');
274
- }
275
- const instance = {
276
- ruleID: message,
277
- what: message,
278
- ordinalSeverity: -1,
279
- tagName: identifiers[0],
280
- id: identifiers[1],
281
- location: {
282
- doc: docType === 'pageContent' ? 'dom' : 'source',
283
- type: 'code',
284
- spec
285
- },
286
- excerpt: cap(extract)
287
- };
288
- if (type === 'info' && subType === 'warning') {
289
- instance.ordinalSeverity = 0;
290
- }
291
- else if (type === 'error') {
292
- instance.ordinalSeverity = subType === 'fatal' ? 3 : 2;
293
- }
294
- standardResult.instances.push(instance);
295
- });
296
- }
255
+ // Converts issue instances from a nuVal result.
256
+ const doNuVal = (withSource, result, standardResult) => {
257
+ doNu(withSource, result, standardResult);
258
+ };
259
+ // Converts issue instances from a nuVnu result.
260
+ const doNuVnu = (withSource, result, standardResult) => {
261
+ doNu(withSource, result, standardResult);
297
262
  };
298
263
  // Converts instances of a qualWeb rule class.
299
264
  const doQualWeb = (result, standardResult, ruleClassName) => {
@@ -600,22 +565,12 @@ const convert = (toolName, data, result, standardResult) => {
600
565
  }
601
566
  }
602
567
  // nuVal
603
- else if (toolName === 'nuVal' && (result.pageContent || result.rawPage)) {
604
- if (result.pageContent) {
605
- doNuVal(result, standardResult, 'pageContent');
606
- }
607
- if (result.rawPage) {
608
- doNuVal(result, standardResult, 'rawPage');
609
- }
568
+ else if (toolName === 'nuVal' && result) {
569
+ doNuVal(data.withSource, result, standardResult);
610
570
  }
611
571
  // nuVnu
612
- else if (toolName === 'nuVnu' && (result.pageContent || result.rawPage)) {
613
- if (result.pageContent) {
614
- doNuVnu(result, standardResult, 'pageContent');
615
- }
616
- if (result.rawPage) {
617
- doNuVnu(result, standardResult, 'rawPage');
618
- }
572
+ else if (toolName === 'nuVnu' && result) {
573
+ doNuVnu(data.withSource, result, standardResult);
619
574
  }
620
575
  // qualWeb
621
576
  else if (
package/tests/nuVal.js CHANGED
@@ -10,42 +10,26 @@
10
10
  /*
11
11
  nuVal
12
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.
13
+ This tool is the API version of the Nu Html Checker. It is an alternative to the nuVnu 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 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.
14
14
  */
15
15
 
16
16
  // IMPORTS
17
17
 
18
- // Module to process files.
19
- const fs = require('fs/promises');
20
- // Module to get the document source.
21
- const {getSource} = require('../procs/getSource');
18
+ // Module to get the content.
19
+ const {curate, getContent} = require('../procs/nu');
22
20
 
23
21
  // FUNCTIONS
24
22
 
25
- // Conducts and reports the Nu Html Checker tests.
23
+ // Conducts and reports the Nu Html Checker API tests.
26
24
  exports.reporter = async (page, report, actIndex) => {
27
25
  const act = report.acts[actIndex];
28
- const {rules} = act;
29
- // Get the browser-parsed page.
30
- const pageContent = await page.content();
31
- // Get the source.
32
- const sourceData = await getSource(page);
33
- const data = {
34
- docTypes: {
35
- pageContent: {},
36
- rawPage: {}
37
- }
38
- };
39
- const result = {};
40
- // If the source was not obtained:
41
- if (sourceData.prevented) {
42
- // Report this.
43
- data.prevented = true;
44
- data.error = sourceData.error;
45
- }
46
- // Otherwise, i.e. if it was obtained:
47
- else {
48
- // Get results from validator.w3.org, a more reliable service than validator.nu.
26
+ const {rules, withSource} = act;
27
+ // Get the content and add it to the data.
28
+ const data = await getContent(page, withSource);
29
+ let result;
30
+ // If it was obtained:
31
+ if (data.testTarget) {
32
+ let nuData;
49
33
  const fetchOptions = {
50
34
  method: 'post',
51
35
  headers: {
@@ -54,70 +38,40 @@ exports.reporter = async (page, report, actIndex) => {
54
38
  }
55
39
  };
56
40
  const nuURL = 'https://validator.w3.org/nu/?parser=html&out=json';
57
- const pageTypes = [['pageContent', pageContent], ['rawPage', sourceData.source]];
58
- // For each page type:
59
- for (const page of pageTypes) {
60
- try {
61
- fetchOptions.body = page[1];
62
- // Get a Nu Html Checker report on it.
63
- const nuResult = await fetch(nuURL, fetchOptions);
64
- // If the request failed:
65
- if (! nuResult.ok) {
66
- // Get the response body as text.
67
- const text = await nuResult.text();
68
- // Add a failure report to the data.
69
- result[page[0]] = {
70
- prevented: true,
71
- error: `HTTP ${nuResult.status}: ${nuResult.statusText}`,
72
- rawBody: text
73
- };
74
- data.docTypes[page[0]] = result[page[0]];
75
- }
76
- // Otherwise, i.e. if it succeeded:
77
- else {
78
- // Get the response body as JSON.
79
- const nuData = await nuResult.json();
80
- // Delete left and right quotation marks and their erratic invalid replacements.
81
- const nuDataClean = JSON.parse(JSON.stringify(nuData).replace(/[\u{fffd}“”]/ug, ''));
82
- result[page[0]] = nuDataClean;
83
- }
84
- // If there is a report and rules were specified:
85
- if (! result[page[0]].error && rules && Array.isArray(rules) && rules.length) {
86
- // Remove all messages except those specified.
87
- result[page[0]].messages = result[page[0]].messages.filter(message => rules.some(rule => {
88
- if (rule[0] === '=') {
89
- return message.message === rule.slice(1);
90
- }
91
- else if (rule[0] === '~') {
92
- return new RegExp(rule.slice(1)).test(message.message);
93
- }
94
- else {
95
- console.log(`ERROR: Invalid nuVal rule ${rule}`);
96
- return false;
97
- }
98
- }));
99
- }
100
- // Remove messages reporting duplicate blank IDs.
101
- const badMessages = new Set(['Duplicate ID .', 'The first occurrence of ID was here.']);
102
- result[page[0]].messages = result[page[0]].messages.filter(
103
- message => ! badMessages.has(message.message)
104
- );
41
+ try {
42
+ fetchOptions.body = data.testTarget;
43
+ // Get a Nu Html Checker report from the W3C validator service.
44
+ nuResponse = await fetch(nuURL, fetchOptions);
45
+ // If the acquisition succeeded:
46
+ if (nuResponse.ok) {
47
+ // Get the response body as JSON.
48
+ nuData = await nuResponse.json();
49
+ }
50
+ // Otherwise, i.e. if the request failed:
51
+ else {
52
+ // Get the response body as text.
53
+ const nuResponseText = await nuResponse.text();
54
+ // Add a failure report to the data.
55
+ data.prevented = true;
56
+ data.error = `HTTP ${nuResponse.status}: ${nuResponse.statusText} (${nuResponseText.slice(0, 200)})`;
105
57
  }
106
- // If an error occurred:
107
- catch (error) {
108
- // Report it.
109
- const message = `ERROR getting results for ${page[0]} (${error.message}; status ${nuResult.status}, body ${JSON.stringify(nuResult?.body, null, 2)}`;
110
- console.log(message);
111
- data.docTypes[page[0]].prevented = true;
112
- data.docTypes[page[0]].error = message;
113
- };
114
- };
115
- // If both page types prevented testing:
116
- if (pageTypes.every(pageType => data.docTypes[pageType[0]].prevented)) {
117
- // Report this.
118
- data.prevented = true;
119
- data.error = 'Both doc types prevented';
120
58
  }
59
+ // If an error occurred:
60
+ catch (error) {
61
+ // Report it.
62
+ const message = `ERROR getting results (${error.message}; status ${nuResult?.status || 'none'} (${JSON.stringify(nuData?.body || 'no body', null, 2)})`;
63
+ console.log(message);
64
+ data.prevented = true;
65
+ data.error = message;
66
+ };
67
+ // Postprocess the response data.
68
+ result = curate(data, nuData, rules);
69
+ }
70
+ // Otherwise, i.e. if the page content was not obtained:
71
+ else {
72
+ // Report this.
73
+ data.prevented = true;
74
+ data.error = 'Content not obtained';
121
75
  }
122
76
  return {
123
77
  data,
package/tests/nuVnu.js CHANGED
@@ -21,8 +21,8 @@ const fs = require('fs/promises');
21
21
  const os = require('os');
22
22
  // Module to run tests.
23
23
  const {vnu} = require('vnu-jar');
24
- // Module to get the document source.
25
- const {getSource} = require('../procs/getSource');
24
+ // Module to get the content.
25
+ const {curate, getContent} = require('../procs/nu');
26
26
 
27
27
  // CONSTANTS
28
28
 
@@ -33,101 +33,54 @@ const tmpDir = os.tmpdir();
33
33
  // Conducts and reports the Nu Html Checker tests.
34
34
  exports.reporter = async (page, report, actIndex) => {
35
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: {}
36
+ const {rules, withSource} = act;
37
+ // Get the content.
38
+ const data = await getContent(page, withSource);
39
+ let result;
40
+ // If it was obtained:
41
+ if (data.testTarget) {
42
+ const pagePath = `${tmpDir}/nuVnu-page-${report.id}.html`;
43
+ // Save it in a temporary file.
44
+ await fs.writeFile(pagePath, data.testTarget);
45
+ let nuData;
46
+ try {
47
+ // Get Nu Html Checker output on it.
48
+ nuData = await vnu.check(['--format', 'json', '--stdout', pagePath]);
48
49
  }
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;
50
+ // If any error was thrown:
51
+ catch (error) {
52
+ let errorMessage = error.message;
53
+ // If it was due to an incompatible Java version:
54
+ if (errorMessage.includes('Unsupported major.minor version')) {
55
+ // Revise the error message and report this.
56
+ errorMessage = `Installed version of Java is incompatible. Details: ${errorMessage}`;
57
+ data.prevented = true;
58
+ data.error = errorMessage;
71
59
  }
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
- }
60
+ // Otherwise, i.e. if it was not due to an incompatible Java version:
61
+ else {
62
+ try {
63
+ // Treat the error message as a JSON result reporting rule violations.
64
+ nuData = JSON.parse(error.message);
93
65
  }
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
- }));
66
+ // But, if parsing it as JSON fails:
67
+ catch (error) {
68
+ // Report this.
69
+ data.prevented = true;
70
+ data.error = `Error getting result (${error.message.slice(0, 300)});`;
117
71
  }
118
- // Remove messages reporting duplicate blank IDs.
119
- const badMessages = new Set(['Duplicate ID .', 'The first occurrence of ID was here.']);
120
- result[page[0]].messages = result[page[0]].messages.filter(
121
- message => ! badMessages.has(message.message)
122
- );
123
72
  }
124
73
  }
125
- // If both page types prevented testing:
126
- if (pageTypes.every(pageType => data.docTypes[pageType[0]].prevented)) {
127
- // Report this.
128
- data.prevented = true;
129
- data.error = 'Both doc types prevented';
130
- }
74
+ // Delete the temporary file.
75
+ await fs.unlink(pagePath);
76
+ // Postprocess the result.
77
+ result = curate(data, nuData, rules);
78
+ }
79
+ // Otherwise, i.e. if the content was not obtained:
80
+ else {
81
+ // Report this.
82
+ data.prevented = true;
83
+ data.error = 'Content not obtained';
131
84
  }
132
85
  return {
133
86
  data,