testaro 14.6.2 → 14.7.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
@@ -65,7 +65,7 @@ As of this version, the counts of tests of the tools referenced above were:
65
65
  - Testaro: 29
66
66
  - total: 1356
67
67
 
68
- Of the 29 Testaro tests, 26 are evaluative (they discover accessibility issues), and the other 3 (`elements`, `textNodes`, and `title`) are informative (they report conditions specified by the user).
68
+ Of the 29 Testaro tests, 26 are evaluative (they discover accessibility problems), and the other 3 (`elements`, `textNodes`, and `title`) are informative (they report conditions specified by the user).
69
69
 
70
70
  ## Quasi-tests
71
71
 
@@ -195,21 +195,21 @@ While each tool produces a _tool report_ of the results of its tests, Testaro al
195
195
 
196
196
  ##### Tool formats
197
197
 
198
- The tools listed above as dependencies write their tool reports in various formats. They differ in how they organize multiple instances of the same issue, how they classify issue severity and certainty, how they point to the locations of issue instances, how they name issues, etc.
199
-
200
- Integrating reports from 10 different tools is a complex task, as analyzed in [“Accessibility Metatesting”](https://arxiv.org/abs/2304.07591). The diversity of their reporting formats makes the task more complex than it would otherwise be.
198
+ The tools listed above as dependencies write their tool reports in various formats. They differ in how they organize multiple instances of the same problem, how they classify severity and certainty, how they point to the locations of problems, how they name problems, etc.
201
199
 
202
200
  ##### Standard format
203
201
 
204
202
  Testaro helps overcome this format diversity by offering to represent the main facts in the report of each tool in a single standardized format.
205
203
 
204
+ In the conceptual scheme underlying the format standardization of Testaro, each tool has its own set of _rules_, where a rule is an algorithm for evaluating a target and determining whether instances of some kind of problem exist in it. With standardization, Testaro reports, in a uniform way, the outcomes from the application of rules by tools to a target.
205
+
206
206
  If the `STANDARD` environment variable has the value `also` (which it has by default) or `only`, Testaro converts some data in each tool report to a standard format. That permits you to ignore the format idiosyncrasies of the tools. If `STANDARD` has the value `also`, the job report includes both formats. If the value is `only`, the job report includes only the standard format. If the value is `no`, the job report includes only the original format of each tool.
207
207
 
208
208
  The standard format of each tool report has two properties:
209
- - `totals`: an array of 4 integers, representing the counts of issue instances classified by the tool into 4 ordinal degrees of severity. For example, `[2, 13, 0, 5]` would mean that the tool reported 2 instances at the lowest severity, 13 at the next-lowest, none at the third-lowest, and 5 at the highest.
209
+ - `totals`: an array of 4 integers, representing the counts of problem instances classified by the tool into 4 ordinal degrees of severity. For example, `[2, 13, 0, 5]` would mean that the tool reported 2 instances at the lowest severity, 13 at the next-lowest, none at the third-lowest, and 5 at the highest.
210
210
  - `instances`: an array of objects describing facts about issue instances reported by the tool. This object has these properties, some of which have empty strings as values when the tool does not provide values:
211
- - `issueID`: a code identifying the issue
212
- - `what`: a description of the issue
211
+ - `ruleID`: a code identifying a rule
212
+ - `what`: a description of the rule
213
213
  - `count` (optional): the count of instances if this instance represents multiple instances
214
214
  - `ordinalSeverity`: how the tool ranks the severity of the instance, on a 4-point ordinal scale
215
215
  - `tagName`: upper-case tagName of the affected element
@@ -227,7 +227,7 @@ standardResult: {
227
227
  totals: [2, 0, 17, 0],
228
228
  instances: [
229
229
  {
230
- issueID: 'rule01',
230
+ ruleID: 'rule01',
231
231
  what: 'Button type invalid',
232
232
  ordinalSeverity: 0,
233
233
  tagName: 'BUTTON'
@@ -240,7 +240,7 @@ standardResult: {
240
240
  excerpt: '<button type="link"></button>'
241
241
  },
242
242
  {
243
- issueID: 'rule01',
243
+ ruleID: 'rule01',
244
244
  what: 'Button type invalid',
245
245
  ordinalSeverity: 1,
246
246
  tagName: 'BUTTON',
@@ -253,7 +253,7 @@ standardResult: {
253
253
  excerpt: '<button type="important">Submit</button>'
254
254
  },
255
255
  {
256
- issueID: 'rule02',
256
+ ruleID: 'rule02',
257
257
  what: 'Links have empty href attributes',
258
258
  count: 17,
259
259
  ordinalSeverity: 3,
@@ -272,7 +272,7 @@ standardResult: {
272
272
 
273
273
  If a tool has the option to be used without itemization and is being so used, the `instances` array may be empty.
274
274
 
275
- The standard format is not opinionated about issue classifications. It treats an issue ID from any tool as an identifier of what that tool considers to be the issue. Useful reporting from multi-tool testing still requires issue classification. If tool `A` identifies an issue as `alt-incomplete` and tool `B` identifies an issue as `image_alt_meaningless`, Testaro does not decide whether those are really the same issue or different issues. That decision belongs to whoever consumes Testaro reports. The standardization of tool reports by Testaro eliminates some of the drudgery in issue classification, but not any of the judgment required for issue classification.
275
+ This standard format is not opinionated about issue classifications. A rule ID identifies something deemed to be an issue by a tool. Useful reporting from multi-tool testing still requires the classification of tool **rules** into **issues**. If tool `A` has `alt-incomplete` as a rule ID and tool `B` has `image_alt_stub` as a rule ID, Testaro does not decide whether those are really the same issue or different issues. That decision belongs to you. The standardization of tool reports by Testaro eliminates some of the drudgery in issue classification, but not any of the judgment required for issue classification.
276
276
 
277
277
  ### Acts
278
278
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "14.6.2",
3
+ "version": "14.7.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/standardize.js CHANGED
@@ -22,10 +22,15 @@ const cap = rawString => {
22
22
  const getIdentifiers = code => {
23
23
  let tagName = '';
24
24
  let id = '';
25
+ // If the substring includes a tag of an element:
25
26
  if (code && typeof code === 'string' && code.length && /<.+/s.test(code)) {
26
- const startTag = code.replace(/^[^<]*<|>.*/sg, '').trim();
27
+ // Get the first start tag in the substring.
28
+ const startTag = code.replace(/^.*?<(?!\/)/s, '').replace(/>.*$/s, '').trim();
29
+ // If it exists:
27
30
  if (startTag && startTag.length) {
28
- tagName = startTag.replace(/\s.+$/s, '').toUpperCase();
31
+ // Get its tag name, upper-cased.
32
+ tagName = startTag.replace(/\s.*$/s, '').toUpperCase();
33
+ // Get the value of the id attribute of the start tag, if any.
29
34
  const idArray = startTag.match(/\sid="([^"<>]+)"/);
30
35
  if (idArray && idArray.length === 2) {
31
36
  id = idArray[1];
@@ -53,7 +58,7 @@ const doAxe = (result, standardResult, certainty) => {
53
58
  const ordinalSeverity = severityWeights[node.impact] + (certainty === 'violations' ? 2 : 0);
54
59
  const identifiers = getIdentifiers(node.html);
55
60
  const instance = {
56
- issueID: rule.id,
61
+ ruleID: rule.id,
57
62
  what: Array.from(whatSet.values()).join('; '),
58
63
  ordinalSeverity,
59
64
  tagName: identifiers[0],
@@ -79,7 +84,7 @@ const doHTMLCS = (result, standardResult, severity) => {
79
84
  ruleData[what].forEach(item => {
80
85
  const {tagName, id, code} = item;
81
86
  const instance = {
82
- issueID: ruleID,
87
+ ruleID,
83
88
  what,
84
89
  ordinalSeverity: ['Warning', '', '', 'Error'].indexOf(severity),
85
90
  tagName: tagName.toUpperCase(),
@@ -113,7 +118,7 @@ const doNuVal = (result, standardResult, docType) => {
113
118
  }
114
119
  // Include the message twice, because in scoring it is likely to be replaced by a pattern.
115
120
  const instance = {
116
- issueID: item.message,
121
+ ruleID: item.message,
117
122
  what: item.message,
118
123
  ordinalSeverity: -1,
119
124
  tagName: identifiers[0],
@@ -154,14 +159,14 @@ const doQualWeb = (result, standardResult, ruleClassName) => {
154
159
  failed: 3
155
160
  }
156
161
  };
157
- Object.keys(ruleClass.assertions).forEach(rule => {
158
- const ruleResult = ruleClass.assertions[rule];
162
+ Object.keys(ruleClass.assertions).forEach(ruleID => {
163
+ const ruleResult = ruleClass.assertions[ruleID];
159
164
  ruleResult.results.forEach(item => {
160
165
  item.elements.forEach(element => {
161
166
  const {htmlCode} = element;
162
167
  const identifiers = getIdentifiers(htmlCode);
163
168
  const instance = {
164
- issueID: rule,
169
+ ruleID,
165
170
  what: ruleResult.description,
166
171
  ordinalSeverity: severities[ruleClassName][item.verdict],
167
172
  tagName: identifiers[0],
@@ -185,8 +190,8 @@ const doWAVE = (result, standardResult, categoryName) => {
185
190
  if (result.categories && result.categories[categoryName]) {
186
191
  const category = result.categories[categoryName];
187
192
  const ordinalSeverity = categoryName === 'alert' ? 0 : 3;
188
- Object.keys(category.items).forEach(rule => {
189
- category.items[rule].selectors.forEach(selector => {
193
+ Object.keys(category.items).forEach(ruleID => {
194
+ category.items[ruleID].selectors.forEach(selector => {
190
195
  let tagName = '';
191
196
  let id = '';
192
197
  if (typeof selector === 'string') {
@@ -201,8 +206,8 @@ const doWAVE = (result, standardResult, categoryName) => {
201
206
  }
202
207
  }
203
208
  const instance = {
204
- issueID: rule,
205
- what: category.items[rule].description,
209
+ ruleID,
210
+ what: category.items[ruleID].description,
206
211
  ordinalSeverity,
207
212
  tagName,
208
213
  id,
@@ -234,16 +239,17 @@ const convert = (toolName, result, standardResult) => {
234
239
  tagName = tagNameArray[1];
235
240
  }
236
241
  }
242
+ const {rule, target} = item;
237
243
  const instance = {
238
- issueID: item.rule.ruleID,
239
- what: item.rule.ruleSummary,
244
+ ruleID: rule.ruleID,
245
+ what: rule.ruleSummary,
240
246
  ordinalSeverity: ['cantTell', '', '', 'failed'].indexOf(item.verdict),
241
247
  tagName: tagName.toUpperCase() || identifiers[0],
242
248
  id: identifiers[1],
243
249
  location: {
244
250
  doc: 'dom',
245
251
  type: 'xpath',
246
- spec: item.target.path
252
+ spec: target.path
247
253
  },
248
254
  excerpt: cap(code)
249
255
  };
@@ -281,7 +287,7 @@ const convert = (toolName, result, standardResult) => {
281
287
  }
282
288
  }
283
289
  const instance = {
284
- issueID: item.engineTestId.toString(),
290
+ ruleID: item.engineTestId.toString(),
285
291
  what: item.attributeDetail,
286
292
  ordinalSeverity: 3,
287
293
  tagName,
@@ -320,7 +326,7 @@ const convert = (toolName, result, standardResult) => {
320
326
  }
321
327
  }
322
328
  const instance = {
323
- issueID: item.ruleId,
329
+ ruleID: item.ruleId,
324
330
  what: item.message,
325
331
  ordinalSeverity: ['', 'recommendation', '', 'violation'].indexOf(item.level),
326
332
  tagName: identifiers[0],
@@ -385,7 +391,7 @@ const convert = (toolName, result, standardResult) => {
385
391
  }
386
392
  }
387
393
  const instance = {
388
- issueID: item.tID ? item.tID.toString() : '',
394
+ ruleID: item.tID ? item.tID.toString() : '',
389
395
  what: item.errorTitle || '',
390
396
  ordinalSeverity: Math.min(
391
397
  3, Math.max(0, Math.round((item.certainty || 0) * (item.priority || 0) / 3333))
package/testaro/focInd.js CHANGED
@@ -27,15 +27,15 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
27
27
  totals: {
28
28
  total: 0,
29
29
  types: {
30
- indicatorMissing: {
30
+ missing: {
31
31
  total: 0,
32
32
  tagNames: {}
33
33
  },
34
- nonOutlinePresent: {
34
+ nonoutline: {
35
35
  total: 0,
36
36
  tagNames: {}
37
37
  },
38
- outlinePresent: {
38
+ outline: {
39
39
  total: 0,
40
40
  meanDelay: 0,
41
41
  tagNames: {}
@@ -45,16 +45,16 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
45
45
  };
46
46
  if (withItems) {
47
47
  data.items = {
48
- indicatorMissing: [],
49
- nonOutlinePresent: [],
50
- outlinePresent: []
48
+ missing: [],
49
+ nonoutline: [],
50
+ outline: []
51
51
  };
52
52
  }
53
53
  // Adds facts about an element to the result.
54
54
  const addElementFacts = (element, status, delay = null) => {
55
55
  const type = data.totals.types[status];
56
56
  type.total++;
57
- if (status === 'outlinePresent') {
57
+ if (status === 'outline') {
58
58
  type.meanDelay = Math.round(((type.total - 1) * type.meanDelay + delay) / type.total);
59
59
  }
60
60
  const tagName = element.tagName;
@@ -71,7 +71,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
71
71
  text: (element.textContent.trim() || element.outerHTML.trim()).replace(/\s+/g, ' ')
72
72
  .slice(0, 100)
73
73
  };
74
- if (status === 'outlinePresent') {
74
+ if (status === 'outline') {
75
75
  elementData.delay = delay;
76
76
  }
77
77
  data.items[status].push(elementData);
@@ -111,7 +111,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
111
111
  // If a non-transparent outline appeared immediately on focus:
112
112
  if (styleDec.outlineWidth !== '0px' && styleDec.outlineColor !== 'rgba(0, 0, 0, 0)') {
113
113
  // Add facts about the element to the result.
114
- addElementFacts(element, 'outlinePresent', 0);
114
+ addElementFacts(element, 'outline', 0);
115
115
  hasOutline = true;
116
116
  }
117
117
  // Otherwise, if a wait for an outline is allowed:
@@ -137,7 +137,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
137
137
  const delay = await outlineDelay;
138
138
  if (delay) {
139
139
  // Add facts about the element to the result.
140
- addElementFacts(element, 'outlinePresent', delay);
140
+ addElementFacts(element, 'outline', delay);
141
141
  hasOutline = true;
142
142
  }
143
143
  }
@@ -158,7 +158,7 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
158
158
  || hasDiffBorder
159
159
  || indicatorStyleNames.slice(5).reduce((any, styleName) => any || diff(styleName), false);
160
160
  // Add the determination to the result.
161
- const status = hasIndicator ? 'nonOutlinePresent' : 'indicatorMissing';
161
+ const status = hasIndicator ? 'nonoutline' : 'missing';
162
162
  addElementFacts(element, status);
163
163
  }
164
164
  }
@@ -166,17 +166,17 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
166
166
  return data;
167
167
  }, [allowedDelay, withItems]);
168
168
  const {types} = data.totals;
169
- const totals = [0, 0, types.nonOutlinePresent.total, types.indicatorMissing.total];
169
+ const totals = [0, 0, types.nonoutline.total, types.missing.total];
170
170
  const standardInstances = [];
171
171
  if (data.items) {
172
- const issueNames = ['nonOutlinePresent', 'indicatorMissing'];
172
+ const issueNames = ['nonoutline', 'missing'];
173
173
  issueNames.forEach(issueName => {
174
174
  data.items[issueName].forEach(item => {
175
- const qualifier = issueName === 'nonOutlinePresent' ? 'a non-outline' : 'no';
175
+ const qualifier = issueName === 'nonoutline' ? 'a non-outline' : 'no';
176
176
  standardInstances.push({
177
177
  issueID: `focInd-${issueName}`,
178
178
  what: `${item.tagName} element has ${qualifier} focus indicator`,
179
- ordinalSeverity: issueName === 'nonOutlinePresent' ? 2 : 3,
179
+ ordinalSeverity: issueName === 'nonoutline' ? 2 : 3,
180
180
  tagName: item.tagName,
181
181
  id: item.id,
182
182
  location: {
@@ -190,11 +190,11 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
190
190
  });
191
191
  }
192
192
  else {
193
- if (types.indicatorMissing.total) {
193
+ if (types.missing.total) {
194
194
  standardInstances.push({
195
195
  issueID: 'focInd-missing',
196
196
  what: 'Elements have missing focus indicators',
197
- count: types.indicatorMissing.total,
197
+ count: types.missing.total,
198
198
  ordinalSeverity: 3,
199
199
  tagName: '',
200
200
  id: '',
@@ -206,11 +206,11 @@ exports.reporter = async (page, withItems, revealAll = false, allowedDelay = 250
206
206
  excerpt: ''
207
207
  });
208
208
  }
209
- if (types.nonOutlinePresent.total) {
209
+ if (types.nonoutline.total) {
210
210
  standardInstances.push({
211
211
  issueID: 'focInd-nonoutline',
212
212
  what: 'Elements have non-outline focus indicators',
213
- count: types.nonOutlinePresent.total,
213
+ count: types.nonoutline.total,
214
214
  ordinalSeverity: 2,
215
215
  tagName: '',
216
216
  id: '',