testaro 14.4.0 → 14.5.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/README.md CHANGED
@@ -348,9 +348,9 @@ This act causes Testaro to alter the `display` and `visibility` style properties
348
348
 
349
349
  ###### Introduction
350
350
 
351
- An act of type `test` performs operations and reports a result. The result may indicate that a page passes or fails requirements. Typically, accessibility tests report successes and failures. But a test in Testaro is defined less restrictively, so it can report any result. As one example, the Testaro `elements` test reports facts about certain elements on a page, without asserting that those facts are successes or failures.
351
+ An act of type `test` performs the tests of a tool and reports a result. The result may indicate that a page passes or fails requirements. Typically, accessibility tests report successes and failures. But a test in Testaro is defined less restrictively, so it can report any result. As one example, the Testaro `elements` test reports facts about certain elements on a page, without asserting that those facts are successes or failures.
352
352
 
353
- The `which` property of a `test` act identifies the operations to perform. The value of `which` is the name of one of the tools, such as `alfa`.
353
+ The `which` property of a `test` act identifies a tool, such as `alfa`.
354
354
 
355
355
  ###### Configuration
356
356
 
@@ -360,20 +360,25 @@ Every test in Testaro must have:
360
360
 
361
361
  The `actSpecs.js` file (described in detail below) contains a specification for any `test` act, namely:
362
362
 
363
- ```json
363
+ ```javascript
364
364
  test: [
365
365
  'Perform a test',
366
366
  {
367
367
  which: [true, 'string', 'isTest', 'test name'],
368
+ rules: [false, 'array', 'areStrings', 'rule IDs or specifications, if not all']
368
369
  what: [false, 'string', 'hasLength', 'comment']
369
370
  }
370
371
  ],
371
372
  ```
372
373
 
373
- That means that a test act (i.e. an act with a `type` property having the value `'test'`) must have a string-valued `which` property naming a tool and may optionally have a string-valued `what` property describing the tool.
374
+ That means that a test act (i.e. an act with a `type` property having the value `'test'`) must have a string-valued `which` property naming a tool and may optionally have an array-valued `rules` property restricting the tests to be reported and/or a string-valued `what` property describing the tool and/or the tests.
374
375
 
375
376
  If a particular test act either must have or may have any other properties, those properties are specified in the `tests` property in `actSpecs.js`.
376
377
 
378
+ When you include a `rules` property, you limit the tests of the tool that are performed or reported. For some tools (`alfa`, `axe`, `continuum`, `htmlcs`, `qualWeb`, and `testaro`), only the specified tests are performed. Other tools (`ibm`, `nuVal`, `tenon`, 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.
379
+
380
+ The `nuVal` and `testaro` tools require specific formats for the `rules` property. Those formats are described below in the sections about those tools.
381
+
377
382
  ###### Examples
378
383
 
379
384
  An example of a `test` act is:
@@ -521,6 +526,12 @@ These changes were proposed as pull requests 1333 and 1334 (https://github.com/I
521
526
 
522
527
  The `ibm` tool is one of two tools (`testaro` is the other) with a `withItems` property. If you set `withItems` to `false`, the result includes the counts of “violations” and “recommendations”, but no information about the rules that gave rise to them.
523
528
 
529
+ ###### Nu Html Checker
530
+
531
+ The `nuVal` tool performs the tests of the Nu Html Checker.
532
+
533
+ 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 `type:substring`, where `type` is replaced with a message type (namely `info` or `error`) and `substring` is replaced with any substring of a message. This `rules` format arises from the fact that `nuVal` generates customized messages and does not accompany them with rule identifiers. Thus, by choosing a type and a substring, you are deciding that any message of that type that includes that substring will be deemed a `nuVal` rule.
534
+
524
535
  ###### QualWeb
525
536
 
526
537
  The `qualWeb` tool performs the ACT rules, WCAG Techniques, and best-practices tests of QualWeb. Only failures and warnings are included in the report. The EARL report of QualWeb is not generated, because it is equivalent to the report of the ACT rules tests.
@@ -571,6 +582,8 @@ If a `tenon` test act is included in a job, environment variables named `TENON_U
571
582
 
572
583
  If you do not specify rules when using the `testaro` tool, Testaro will test for the rules listed in the `evalRules` object of the `tests/testaro.js` file.
573
584
 
585
+ The `rules` property for `testaro` is an array whose first item is either `'y'` or `'n'` and whose remaining items are rule IDs. If `'y'`, then only the specified rules’ tests are performed. If `'n'`, then all the evaluative tests are performed, except for the specified rules.
586
+
574
587
  It has been found that the `motion` test of the `testaro` tool measures motion only when the `webkit` browser type is in use. If you want to use `testaro` with different browser types for different tests, you can include 2 or 3 `testaro` test acts in your job, specifying different browser types and different rules.
575
588
 
576
589
  The `testaro` tool (like the `ibm` tool) has a `withItems` property. If you set it to `false`, the `standardResult` object of `testaro` will contain an `instances` property with summaries that identify issues and instance counts. If you set it to `true`, some of the instances will be itemized.
package/actSpecs.js CHANGED
@@ -122,10 +122,10 @@ exports.actSpecs = {
122
122
  }
123
123
  ],
124
124
  test: [
125
- 'Perform test of a tool',
125
+ 'Perform tests of a tool',
126
126
  {
127
- which: [true, 'string', 'isTest', 'test name'],
128
- what: [false, 'string', 'hasLength', 'comment']
127
+ which: [true, 'string', 'isTest', 'tool name'],
128
+ rules: [false, 'array', 'areStrings', 'rule IDs or specifications, if not all']
129
129
  }
130
130
  ],
131
131
  text: [
@@ -153,34 +153,10 @@ exports.actSpecs = {
153
153
  ]
154
154
  },
155
155
  tests: {
156
- alfa: [
157
- 'Perform alfa tests',
158
- {
159
- rules: [false, 'array', 'areStrings', 'rule names (e.g., r25), if not all']
160
- }
161
- ],
162
156
  axe: [
163
157
  'Perform Axe tests',
164
158
  {
165
- detailLevel: [true, 'number', '', '0 = least, 4 = most'],
166
- rules: [true, 'array', 'areStrings', 'rule names, or empty if all']
167
- }
168
- ],
169
- continuum: [
170
- 'Perform Continuum tests',
171
- {
172
- rules: [false, 'array', 'areNumbers', 'rule numbers (e.g., 25), if not all']
173
- }
174
- ],
175
- htmlcs: [
176
- 'Perform HTML CodeSniffer tests',
177
- {
178
- rules: [
179
- false,
180
- 'array',
181
- 'areStrings',
182
- 'rule names (e.g., Principle1.Guideline1_4.1_4_9), if not all'
183
- ]
159
+ detailLevel: [true, 'number', '', '0 = least, 4 = most']
184
160
  }
185
161
  ],
186
162
  ibm: [
@@ -189,26 +165,13 @@ exports.actSpecs = {
189
165
  withItems: [true, 'boolean', '', 'itemize'],
190
166
  withNewContent: [
191
167
  true, 'boolean', '', 'true: use a URL; false: use page content'
192
- ],
193
- rules: [false, 'array', 'areStrings', 'rule names (e.g., RPT_Elem_UniqueId), if not all']
194
- }
195
- ],
196
- nuVal: [
197
- 'Perform Nu Html Checker tests',
198
- {
199
- messages: [
200
- false,
201
- 'array',
202
- 'areStrings',
203
- 'message specifications (type and start of message, e.g., error:Bad value), if not all'
204
168
  ]
205
169
  }
206
170
  ],
207
171
  qualWeb: [
208
172
  'Perform QualWeb tests',
209
173
  {
210
- withNewContent: [true, 'boolean', '', 'whether to use a URL instead of page content'],
211
- rules: [false, 'array', 'areStrings', 'QualWeb or ACT IDs of ACT rules to include, if not all']
174
+ withNewContent: [true, 'boolean', '', 'whether to use a URL instead of page content']
212
175
  }
213
176
  ],
214
177
  tenon: [
@@ -221,7 +184,6 @@ exports.actSpecs = {
221
184
  'Perform Testaro tests',
222
185
  {
223
186
  withItems: [true, 'boolean', '', 'itemize'],
224
- rules: [false, 'array', 'areStrings', 'IDs of rules to include if array starts with y or exclude if with n, if not all evaluative rules'],
225
187
  args: [false, 'object', 'areArrays', 'extra args (object with rule properties and arrays of argument values as values ({focInd: [false, 250], hover: [-1], motion: [2500, 2500, 5]} by default'],
226
188
  }
227
189
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "14.4.0",
3
+ "version": "14.5.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -36,15 +36,12 @@ const tests = {
36
36
  alfa: 'alfa',
37
37
  axe: 'Axe',
38
38
  continuum: 'Level Access Continuum, community edition',
39
- elements: 'data on specified elements',
40
39
  htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
41
40
  ibm: 'IBM Accessibility Checker',
42
41
  nuVal: 'Nu Html Checker',
43
42
  qualWeb: 'QualWeb',
44
43
  tenon: 'Tenon',
45
44
  testaro: 'Testaro',
46
- textNodes: 'data on specified text nodes',
47
- title: 'page title',
48
45
  wave: 'WAVE',
49
46
  };
50
47
  // Browser types available in PlayWright.
@@ -164,6 +161,7 @@ const hasSubtype = (variable, subtype) => {
164
161
  return isState(variable);
165
162
  }
166
163
  else {
164
+ console.log(`ERROR: ${subtype} not a known subtype`);
167
165
  return false;
168
166
  }
169
167
  }
@@ -977,21 +975,18 @@ const doActs = async (report, actIndex, page) => {
977
975
  tenonData.requestIDs[id] = responseID || '';
978
976
  }
979
977
  }
980
- // Otherwise, if the act is a test:
978
+ // Otherwise, if the act performs the tests of a tool:
981
979
  else if (act.type === 'test') {
982
980
  // Add a description of the test to the act.
983
981
  act.what = tests[act.which];
984
- // Initialize the arguments.
985
- const args = [act.which === 'tenon' ? tenonData : page];
986
- // Identify the additional validator of the test.
987
- const testValidator = actSpecs.tests[act.which];
988
- // If it exists:
989
- if (testValidator) {
990
- // Identify its argument properties.
991
- const argProperties = Object.keys(testValidator[1]);
992
- // Add their values to the arguments.
993
- args.push(...argProperties.map(propName => act[propName]));
994
- }
982
+ // Initialize the options argument.
983
+ const options = {};
984
+ // Add any specified arguments to it.
985
+ Object.keys(act).forEach(key => {
986
+ if (! ['type', 'which'].includes(key)) {
987
+ options[key] = act[key];
988
+ }
989
+ });
995
990
  // Conduct, report, and time the test.
996
991
  const startTime = Date.now();
997
992
  let testReport = {
@@ -1000,7 +995,8 @@ const doActs = async (report, actIndex, page) => {
1000
995
  }
1001
996
  };
1002
997
  try {
1003
- testReport = await require(`./tests/${act.which}`).reporter(...args);
998
+ const args = [act.which === 'tenon' ? tenonData : page, options];
999
+ testReport = await require(`./tests/${act.which}`).reporter(... args);
1004
1000
  const expectations = act.expect;
1005
1001
  // If the test has expectations:
1006
1002
  if (expectations) {
package/standardize.js CHANGED
@@ -18,6 +18,22 @@ const cap = rawString => {
18
18
  return '';
19
19
  }
20
20
  };
21
+ // Returns the tag name and the value of an id attribute from a substring of HTML code.
22
+ const getIdentifiers = code => {
23
+ let tagName = '';
24
+ let id = '';
25
+ if (code && typeof code === 'string' && code.length && /<.+/s.test(code)) {
26
+ const startTag = code.replace(/^[^<]*<|>.*/sg, '').trim();
27
+ if (startTag && startTag.length) {
28
+ tagName = startTag.replace(/\s.+$/s, '').toUpperCase();
29
+ const idArray = startTag.match(/\sid="([^"<>]+)"/);
30
+ if (idArray && idArray.length === 2) {
31
+ id = idArray[1];
32
+ }
33
+ }
34
+ }
35
+ return [tagName, id];
36
+ };
21
37
  // Converts issue instances at an axe certainty level.
22
38
  const doAxe = (result, standardResult, certainty) => {
23
39
  if (result.details && result.details[certainty]) {
@@ -35,23 +51,13 @@ const doAxe = (result, standardResult, certainty) => {
35
51
  critical: 1
36
52
  };
37
53
  const ordinalSeverity = severityWeights[node.impact] + (certainty === 'violations' ? 2 : 0);
38
- const tagName = node.html && node.html.replace(/^<|[ >].*$/sg, '').toUpperCase();
39
- let id = '';
40
- if (node.target && node.target.length && node.target[0].startsWith('#')) {
41
- id = node.target[0].slice(1);
42
- }
43
- else if (node.html) {
44
- const idArray = node.html.match(/\sid="([^"]+)"/);
45
- if (idArray && idArray.length === 2) {
46
- id = idArray[1];
47
- }
48
- }
54
+ const identifiers = getIdentifiers(node.html);
49
55
  const instance = {
50
56
  issueID: rule.id,
51
57
  what: Array.from(whatSet.values()).join('; '),
52
58
  ordinalSeverity,
53
- tagName,
54
- id,
59
+ tagName: identifiers[0],
60
+ id: identifiers[1],
55
61
  location: {
56
62
  doc: 'dom',
57
63
  type: 'selector',
@@ -96,18 +102,13 @@ const doNuVal = (result, standardResult, docType) => {
96
102
  const items = result[docType] && result[docType].messages;
97
103
  if (items && items.length) {
98
104
  items.forEach(item => {
99
- let tagName = '';
100
- let id = '';
101
- if (item.extract) {
102
- const tagNameLCArray = item.extract.match(
105
+ const identifiers = getIdentifiers(item.extract);
106
+ if (! identifiers[0] && item.message) {
107
+ const tagNameLCArray = item.message.match(
103
108
  /^Element ([^ ]+)|^An (img) element| (meta|script) element| element (script)| tag (script)/
104
109
  );
105
110
  if (tagNameLCArray && tagNameLCArray.length > 1) {
106
- tagName = tagNameLCArray[1].toUpperCase();
107
- }
108
- const idArray = item.extract.match(/^.+\sid="([^"]+)"/);
109
- if (idArray && idArray.length === 2) {
110
- id = idArray[1];
111
+ identifiers[0] = tagNameLCArray[1].toUpperCase();
111
112
  }
112
113
  }
113
114
  // Include the message twice, because in scoring it is likely to be replaced by a pattern.
@@ -115,8 +116,8 @@ const doNuVal = (result, standardResult, docType) => {
115
116
  issueID: item.message,
116
117
  what: item.message,
117
118
  ordinalSeverity: -1,
118
- tagName,
119
- id,
119
+ tagName: identifiers[0],
120
+ id: identifiers[1],
120
121
  location: {
121
122
  doc: docType === 'pageContent' ? 'dom' : 'source',
122
123
  type: 'line',
@@ -158,24 +159,13 @@ const doQualWeb = (result, standardResult, ruleClassName) => {
158
159
  ruleResult.results.forEach(item => {
159
160
  item.elements.forEach(element => {
160
161
  const {htmlCode} = element;
161
- let tagName = '';
162
- let id = '';
163
- if (htmlCode) {
164
- const tagNameArray = htmlCode.match(/^<([^ >]+)/);
165
- if (tagNameArray && tagNameArray.length === 2) {
166
- tagName = tagNameArray[1].toUpperCase();
167
- }
168
- const idArray = htmlCode.match(/\sid="([^"]+)"/);
169
- if (idArray && idArray.length === 2) {
170
- id = idArray[1];
171
- }
172
- }
162
+ const identifiers = getIdentifiers(htmlCode);
173
163
  const instance = {
174
164
  issueID: rule,
175
165
  what: ruleResult.description,
176
166
  ordinalSeverity: severities[ruleClassName][item.verdict],
177
- tagName,
178
- id,
167
+ tagName: identifiers[0],
168
+ id: identifiers[1],
179
169
  location: {
180
170
  doc: 'dom',
181
171
  type: 'selector',
@@ -235,26 +225,27 @@ const convert = (toolName, result, standardResult) => {
235
225
  standardResult.totals = [result.totals.warnings, 0, 0, result.totals.failures];
236
226
  result.items.forEach(item => {
237
227
  const {codeLines} = item.target;
238
- let id = '';
239
- if (codeLines && codeLines.length) {
240
- const code = codeLines[0];
241
- const idMatchArray = code.match(/\sid="([^"]+)"/);
242
- if (idMatchArray && idMatchArray.length === 2) {
243
- id = idMatchArray[1];
228
+ const code = Array.isArray(codeLines) ? codeLines.join(' ') : '';
229
+ const identifiers = getIdentifiers(code);
230
+ let tagName = item.target && item.target.tagName || '';
231
+ if (! tagName) {
232
+ const tagNameArray = item.target.path.match(/\/([a-z]+)\[\d+\]\/text\(\)\[\d+\]$/);
233
+ if (tagNameArray && tagNameArray.length === 2) {
234
+ tagName = tagNameArray[1];
244
235
  }
245
236
  }
246
237
  const instance = {
247
238
  issueID: item.rule.ruleID,
248
239
  what: item.rule.ruleSummary,
249
240
  ordinalSeverity: ['cantTell', '', '', 'failed'].indexOf(item.verdict),
250
- tagName: item.target.tagName.toUpperCase(),
251
- id,
241
+ tagName: tagName.toUpperCase() || identifiers[0],
242
+ id: identifiers[1],
252
243
  location: {
253
244
  doc: 'dom',
254
245
  type: 'xpath',
255
246
  spec: item.target.path
256
247
  },
257
- excerpt: Array.isArray(codeLines) ? cap(codeLines.join(' ')) : ''
248
+ excerpt: cap(code)
258
249
  };
259
250
  standardResult.instances.push(instance);
260
251
  });
@@ -321,26 +312,19 @@ const convert = (toolName, result, standardResult) => {
321
312
  else if (toolName === 'ibm' && result.totals) {
322
313
  standardResult.totals = [0, result.totals.recommendation, 0, result.totals.violation];
323
314
  result.items.forEach(item => {
324
- let tagName = '';
325
- let id = '';
326
- if (item.path && item.path.dom) {
315
+ const identifiers = getIdentifiers(item.snippet);
316
+ if (! identifiers[0] && item.path && item.path.dom) {
327
317
  const tagNameArray = item.path.dom.match(/^.+\/([^/[]+)/s);
328
318
  if (tagNameArray && tagNameArray.length === 2) {
329
- tagName = tagNameArray[1];
330
- }
331
- if (item.snippet) {
332
- const idArray = item.snippet.match(/^.+\sid="([^"]+)"/s);
333
- if (idArray && idArray.length === 2) {
334
- id = idArray[1];
335
- }
319
+ identifiers[0] = tagNameArray[1].toUpperCase();
336
320
  }
337
321
  }
338
322
  const instance = {
339
323
  issueID: item.ruleId,
340
324
  what: item.message,
341
325
  ordinalSeverity: ['', 'recommendation', '', 'violation'].indexOf(item.level),
342
- tagName,
343
- id,
326
+ tagName: identifiers[0],
327
+ id: identifiers[1],
344
328
  location: {
345
329
  doc: 'dom',
346
330
  type: 'xpath',
@@ -391,18 +375,13 @@ const convert = (toolName, result, standardResult) => {
391
375
  // tenon
392
376
  else if (toolName === 'tenon' && result.data && result.data.resultSet) {
393
377
  result.data.resultSet.forEach(item => {
394
- let tagName = '';
395
- if (item.xpath) {
378
+ const identifiers = getIdentifiers(
379
+ item.errorSnippet.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
380
+ );
381
+ if (! identifiers[0] && item.xpath) {
396
382
  const tagNameArray = item.xpath.match(/^.+\/([^/[]+)/);
397
383
  if (tagNameArray && tagNameArray.length === 2) {
398
- tagName = tagNameArray[1].toUpperCase();
399
- }
400
- }
401
- let id = '';
402
- if (item.errorSnippet) {
403
- const idArray = item.errorSnippet.match(/^.+\sid="([^"]+)"/);
404
- if (idArray && idArray.length === 2) {
405
- id = idArray[1];
384
+ identifiers[0] = tagNameArray[1].toUpperCase();
406
385
  }
407
386
  }
408
387
  const instance = {
@@ -411,8 +390,8 @@ const convert = (toolName, result, standardResult) => {
411
390
  ordinalSeverity: Math.min(
412
391
  3, Math.max(0, Math.round((item.certainty || 0) * (item.priority || 0) / 3333))
413
392
  ),
414
- tagName,
415
- id,
393
+ tagName: identifiers[0],
394
+ id: identifiers[1],
416
395
  location: {
417
396
  doc: 'dom',
418
397
  type: 'xpath',
package/testaro/embAc.js CHANGED
@@ -32,7 +32,7 @@ exports.reporter = async (page, withItems) => await page.$$eval(
32
32
  }
33
33
  items.push({
34
34
  embeddedElement: bad.tagName,
35
- embeddedID: bad.id,
35
+ embeddedID: bad.id || '',
36
36
  excerpt: compact(container.outerHTML)
37
37
  });
38
38
  }
@@ -49,7 +49,7 @@ exports.reporter = async (page, withItems) => await page.$$eval(
49
49
  what: `${item.embeddedElement} element is embedded in a link or button`,
50
50
  ordinalSeverity: 2,
51
51
  tagName: item.embeddedElement,
52
- id: item.id,
52
+ id: item.embeddedID,
53
53
  location: {
54
54
  doc: '',
55
55
  type: '',
package/tests/alfa.js CHANGED
@@ -12,7 +12,8 @@ let alfaRules = require('@siteimprove/alfa-rules').default;
12
12
  // FUNCTIONS
13
13
 
14
14
  // Conducts and reports an alfa test.
15
- exports.reporter = async (page, rules) => {
15
+ exports.reporter = async (page, options) => {
16
+ const {rules} = options;
16
17
  // If only some rules are to be employed:
17
18
  if (rules && rules.length) {
18
19
  // Remove the other rules.
package/tests/axe.js CHANGED
@@ -22,7 +22,8 @@
22
22
  const {injectAxe, getAxeResults} = require('axe-playwright');
23
23
  // FUNCTIONS
24
24
  // Conducts and reports an Axe test.
25
- exports.reporter = async (page, detailLevel, rules = []) => {
25
+ exports.reporter = async (page, options) => {
26
+ const {detailLevel, rules} = options;
26
27
  // Initialize the report.
27
28
  let data = {};
28
29
  // Inject axe-core into the page.
@@ -38,7 +39,7 @@ exports.reporter = async (page, detailLevel, rules = []) => {
38
39
  const axeOptions = {
39
40
  resultTypes: ['violations', 'incomplete']
40
41
  };
41
- if (rules.length) {
42
+ if (rules && rules.length) {
42
43
  axeOptions.runOnly = rules;
43
44
  }
44
45
  else {
@@ -6,7 +6,8 @@
6
6
 
7
7
  // FUNCTIONS
8
8
  // Runs Continuum on the page.
9
- exports.reporter = async (page, rules) => {
9
+ exports.reporter = async (page, options) => {
10
+ const {rules} = options;
10
11
  let result = {};
11
12
  // Inject the continuum scripts into the page, exposing the continuum object.
12
13
  for (const fileName of ['continuum.conf', 'AccessEngine.community', 'Continuum.community']) {
package/tests/htmlcs.js CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
  // FUNCTIONS
7
7
  // Runs HTML CodeSniffer on the page.
8
- exports.reporter = async (page, rules) => {
8
+ exports.reporter = async (page, options) => {
9
+ const {rules} = options;
9
10
  const result = {};
10
11
  // Add the HTMLCS script to the page.
11
12
  await page.addScriptTag({
package/tests/ibm.js CHANGED
@@ -122,7 +122,8 @@ const doTest = async (content, withItems, timeLimit, rules) => {
122
122
  }
123
123
  };
124
124
  // Returns results of an IBM test.
125
- exports.reporter = async (page, withItems, withNewContent, rules) => {
125
+ exports.reporter = async (page, options) => {
126
+ const {withItems, withNewContent, rules} = options;
126
127
  const contentType = withNewContent ? 'new' : 'existing';
127
128
  console.log(`>>>>>> Content type: ${contentType}`);
128
129
  let result;
package/tests/nuVal.js CHANGED
@@ -18,7 +18,8 @@ const fs = require('fs/promises');
18
18
 
19
19
  // ########## FUNCTIONS
20
20
 
21
- exports.reporter = async (page, messages) => {
21
+ exports.reporter = async (page, options) => {
22
+ const {rules} = options;
22
23
  // Get the browser-parsed page.
23
24
  const pageContent = await page.content();
24
25
  // Get the page source.
@@ -63,12 +64,12 @@ exports.reporter = async (page, messages) => {
63
64
  // Delete left and right quotation marks and their erratic invalid replacements.
64
65
  data[page[0]] = nuDataClean;
65
66
  // If there is a report and restrictions on the report messages were specified:
66
- if (! data[page[0]].error && messages && Array.isArray(messages) && messages.length) {
67
+ if (! data[page[0]].error && rules && Array.isArray(rules) && rules.length) {
67
68
  // Remove all messages except those specified.
68
- const messageSpecs = messages.map(messageSpec => messageSpec.split(':', 2));
69
- data[page[0]].messages = data[page[0]].messages.filter(message => messageSpecs.some(
70
- messageSpec => message.type === messageSpec[0]
71
- && message.message.startsWith(messageSpec[1])
69
+ const ruleSpecs = rules.map(ruleSpec => ruleSpec.split(':', 2));
70
+ data[page[0]].messages = data[page[0]].messages.filter(message => ruleSpecs.some(
71
+ ruleSpec => message.type === ruleSpec[0]
72
+ && message.message.includes(ruleSpec[1])
72
73
  ));
73
74
  }
74
75
  }
package/tests/qualWeb.js CHANGED
@@ -11,11 +11,12 @@ const clusterOptions = {
11
11
  };
12
12
  // FUNCTIONS
13
13
  // Conducts and reports a QualWeb test.
14
- exports.reporter = async (page, withNewContent, rules = null) => {
14
+ exports.reporter = async (page, options) => {
15
+ const {withNewContent, rules} = options;
15
16
  // Initialize the report.
16
17
  // Start the QualWeb core engine.
17
18
  await qualWeb.start(clusterOptions);
18
- // Specify the page.
19
+ // Specify the test options.
19
20
  const qualWebOptions = {
20
21
  log: {
21
22
  console: true
package/tests/tenon.js CHANGED
@@ -5,7 +5,8 @@
5
5
  const https = require('https');
6
6
  // Wait until a time limit in seconds expires.
7
7
  const wait = timeLimit => new Promise(resolve => setTimeout(resolve, 1000 * timeLimit));
8
- exports.reporter = async (tenonData, id) => {
8
+ exports.reporter = async (tenonData, options) => {
9
+ const {id, rules} = options;
9
10
  if (tenonData && tenonData.accessToken && tenonData.requestIDs && tenonData.requestIDs[id]) {
10
11
  // Shared request options.
11
12
  const requestOptions = {
@@ -59,6 +60,19 @@ exports.reporter = async (tenonData, id) => {
59
60
  });
60
61
  resultRequest.end();
61
62
  });
63
+ // If any rules were specified and any test results exist:
64
+ if (
65
+ rules
66
+ && rules.length
67
+ && testResult.data
68
+ && testResult.data.resultSet
69
+ && testResult.data.resultSet.length
70
+ ) {
71
+ // Delete the results of tests of rules not specified.
72
+ const {resultSet} = testResult.data;
73
+ testResult.data.resultSet = resultSet
74
+ .filter(result => ! rules.includes(result.tID.toString()));
75
+ }
62
76
  return testResult;
63
77
  };
64
78
  // Get the test status (not reliable: may say 200 instead of 202).
package/tests/testaro.js CHANGED
@@ -42,31 +42,50 @@ const etcRules = {
42
42
  // FUNCTIONS
43
43
 
44
44
  // Conducts and reports a Testaro test.
45
- exports.reporter = async (
46
- page, withItems, rules = ['y', ... Object.keys(evalRules)], args = null
47
- ) => {
45
+ exports.reporter = async (page, options) => {
46
+ const {withItems, args} = options;
47
+ const argRules = args ? Object.keys(args) : null;
48
+ const rules = options.rules || ['y', ... Object.keys(evalRules)];
48
49
  // Initialize the data.
49
50
  const data = {
50
51
  rules: {}
51
52
  };
52
- // For each rule invoked:
53
- const argRules = args && Object.keys(args);
54
- const realRules = rules[0] === 'y'
55
- ? rules.slice(1)
56
- : Object.keys(evalRules).filter(ruleName => ! rules.slice(1).includes(ruleName));
57
- for (const rule of realRules) {
58
- // Initialize an argument array.
59
- const ruleArgs = [page, withItems];
60
- // If the rule has extra arguments:
61
- if (argRules && argRules.includes(rule)) {
62
- // Add them to the argument array.
63
- ruleArgs.push(... args[rule]);
53
+ // If the rule specification is valid:
54
+ if (
55
+ rules.length > 1
56
+ && ['y', 'n'].includes(rules[0])
57
+ && rules.slice(1).every(rule => evalRules[rule] || etcRules[rule])
58
+ ) {
59
+ // For each rule invoked:
60
+ const realRules = rules[0] === 'y'
61
+ ? rules.slice(1)
62
+ : Object.keys(evalRules).filter(ruleID => ! rules.slice(1).includes(ruleID));
63
+ for (const rule of realRules) {
64
+ // Initialize an argument array.
65
+ const ruleArgs = [page, withItems];
66
+ // If the rule has extra arguments:
67
+ if (argRules && argRules.includes(rule)) {
68
+ // Add them to the argument array.
69
+ ruleArgs.push(... args[rule]);
70
+ }
71
+ // Test the page.
72
+ const what = evalRules[rule] || etcRules[rule];
73
+ if (! data.rules[rule]) {
74
+ data.rules[rule] = {};
75
+ }
76
+ data.rules[rule].what = what;
77
+ console.log(`>>>>>> ${rule} (${what})`);
78
+ const report = await require(`../testaro/${rule}`).reporter(... ruleArgs);
79
+ Object.keys(report).forEach(key => {
80
+ data.rules[rule][key] = report[key];
81
+ });
64
82
  }
65
- // Test the page.
66
- data.rules[rule] = await require(`../testaro/${rule}`).reporter(... ruleArgs);
67
- const what = evalRules[rule] || etcRules[rule];
68
- data.rules[rule].what = what;
69
- console.log(`>>>>>> ${rule} (${what})`);
83
+ }
84
+ // Otherwise, i.e. if the rule specification is invalid:
85
+ else {
86
+ console.log('ERROR: Testaro rule specification invalid');
87
+ data.prevented = true;
88
+ data.error = 'ERROR: Rule specification invalid';
70
89
  }
71
90
  return {result: data};
72
91
  };
package/tests/wave.js CHANGED
@@ -6,7 +6,8 @@
6
6
  */
7
7
  const fs = require('fs/promises');
8
8
  const https = require('https');
9
- exports.reporter = async (page, reportType) => {
9
+ exports.reporter = async (page, options) => {
10
+ const {reportType, rules} = options;
10
11
  const waveKey = process.env.WAVE_KEY;
11
12
  // Get the data from a WAVE test.
12
13
  const data = await new Promise(resolve => {
@@ -30,6 +31,23 @@ exports.reporter = async (page, reportType) => {
30
31
  delete categories.feature;
31
32
  delete categories.structure;
32
33
  delete categories.aria;
34
+ // If rules were specified:
35
+ if (rules && rules.length) {
36
+ // Delete the results of tests for other rules.
37
+ ['error', 'contrast', 'alert'].forEach(category => {
38
+ if (
39
+ categories[category]
40
+ && categories[category].items
41
+ && categories[category].items.length
42
+ ) {
43
+ Object.keys(categories[category].items).forEach(ruleID => {
44
+ if (! rules.includes(ruleID)) {
45
+ delete categories[category].items[ruleID];
46
+ }
47
+ });
48
+ }
49
+ });
50
+ }
33
51
  // Add WCAG information from the WAVE documentation.
34
52
  const waveDocJSON = await fs.readFile('procs/wavedoc.json');
35
53
  const waveDoc = JSON.parse(waveDocJSON);