testaro 26.5.0 → 27.0.1

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/CONTRIBUTING.md CHANGED
@@ -82,15 +82,16 @@ You can copy and revise any of the existing JSON files in the `testaro` director
82
82
  - Assign an integer from 0 through 3 to `ordinalSeverity`.
83
83
  - If all instances of violations of the rule necessarily involve elements of the same type, assign its tag name (such as `"BUTTON"`) to `summaryTagName`.
84
84
 
85
+ ### Simplifiable rules
86
+
87
+ More complex Testaro rules are implemented in JavaScript. Some rules are _simplifiable_. These can be implemented with JavaScript modules like the one for the `allSlanted` rule. To implement such a rule, you can copy an existing module and replace the 7 values of the `ruleData` object. The significant decisions here are about the values of the `selector` and `pruner` properties.
88
+
89
+ The `selector` value is a CSS selector that identifies candidate elements for violation reporting. What makes this rule simplifiable, instead of simple, is that these elements may or may not be determined to violate the rule. Each of the elements identified by the selector must be further analyzed by the pruner. The pruner takes a Playwright locator as its argument and returns `true` if it finds that the element located by the locator violates the rule, or `false` if not.
90
+
91
+ The `isDestructive` property should be set to `true` if your pruner modifies the page. Any pruner that calls the `isOperable()` function from the `operable` module does so.
92
+
85
93
  ### Complex rules
86
94
 
87
- More complex Testaro rules are implemented in JavaScript. You can use any of the existing JavaScript rules, or `data/template.js` file, as an example to begin with. Examples include:
88
- - `allSlanted`, `distortion`, `filter`, `lineHeight`, `miniText`, `zIndex`: violations based on element styles.
89
- - `focOp`, `opFoc`, `targetSize`: violations based on attributes
90
- - `linkTitle`: violations based on attributes and text content.
91
- - `linkAmb`, `pseudoP`, `radioSet`: violations based on relations among elements and text.
92
- - `hover`, `tabNav`: violations based on performing actions and observing page behavior.
93
- - `role`: violations based on data about standards.
94
- - `docType`, `title`: violations based on page properties.
95
+ Even more complex Testaro rules require analysis that cannot fit into the simple or simplifiable category. You can begin with existing JavaScript rules, or the `data/template.js` file, as an example.
95
96
 
96
- Some utility functions in modules in the `procs` directory are available for support of new rules.
97
+ Some utility functions in modules in the `procs` directory are available for support of new rules. Among these modules are `testaro` (used in many tests), `isInlineLink`, `operable`, and `visChange`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "26.5.0",
3
+ "version": "27.0.1",
4
4
  "description": "Run 650 web accessibility tests from 8 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/testaro.js CHANGED
@@ -14,7 +14,7 @@ const sampleMax = 100;
14
14
  // ########## FUNCTIONS
15
15
 
16
16
  // Initializes locators and a result.
17
- exports.init = async (page, locAllSelector, options = {}) => {
17
+ const init = async (page, locAllSelector, options = {}) => {
18
18
  // Get locators for the specified elements.
19
19
  const locPop = page.locator(locAllSelector, options);
20
20
  const locPops = await locPop.all();
@@ -40,7 +40,7 @@ exports.init = async (page, locAllSelector, options = {}) => {
40
40
  };
41
41
 
42
42
  // Populates a result.
43
- exports.report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName = '') => {
43
+ const report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName = '') => {
44
44
  const {locs, result} = all;
45
45
  const {data, totals, standardInstances} = result;
46
46
  // For each instance locator:
@@ -92,3 +92,41 @@ exports.report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName
92
92
  // Return the result.
93
93
  return result;
94
94
  };
95
+ // Performs a simplifiable test.
96
+ const simplify = async (page, withItems, ruleData) => {
97
+ const {
98
+ ruleID, selector, pruner, isDestructive, complaints, ordinalSeverity, summaryTagName
99
+ } = ruleData;
100
+ // Initialize the locators and result.
101
+ const all = await init(page, selector);
102
+ // For each locator:
103
+ for (const loc of all.allLocs) {
104
+ // Get whether its element violates the rule.
105
+ const isBad = await pruner(loc);
106
+ // If it does:
107
+ if (isBad) {
108
+ // Add the locator to the array of violators.
109
+ all.locs.push(loc);
110
+ }
111
+ }
112
+ // Populate and return the result.
113
+ const whats = [
114
+ complaints.instance,
115
+ complaints.summary
116
+ ];
117
+ const testReport = await report(withItems, all, ruleID, whats, ordinalSeverity, summaryTagName);
118
+ // If the pruner modifies the page:
119
+ if (isDestructive) {
120
+ // Reload the page.
121
+ try {
122
+ await page.reload({timeout: 15000});
123
+ }
124
+ catch(error) {
125
+ console.log('ERROR: page reload timed out');
126
+ }
127
+ }
128
+ return testReport;
129
+ };
130
+ exports.init = init;
131
+ exports.report = report;
132
+ exports.simplify = simplify;
@@ -8,18 +8,17 @@
8
8
  // ########## IMPORTS
9
9
 
10
10
  // Module to perform common operations.
11
- const {init, report} = require('../procs/testaro');
11
+ const {simplify} = require('../procs/testaro');
12
12
 
13
13
  // ########## FUNCTIONS
14
14
 
15
15
  // Runs the test and returns the result.
16
16
  exports.reporter = async (page, withItems) => {
17
- // Initialize the locators and result.
18
- const all = await init(page, 'body *:not(style, script, svg)');
19
- // For each locator:
20
- for (const loc of all.allLocs) {
21
- // Get whether its element violates the rule.
22
- const isBad = await loc.evaluate(el => {
17
+ // Specify the rule.
18
+ const ruleData = {
19
+ ruleID: 'allCaps',
20
+ selector: 'body *:not(style, script, svg)',
21
+ pruner: async loc => await loc.evaluate(el => {
23
22
  const elText = Array
24
23
  .from(el.childNodes)
25
24
  .filter(node => node.nodeType === Node.TEXT_NODE)
@@ -37,14 +36,15 @@ exports.reporter = async (page, withItems) => {
37
36
  const transformStyle = elStyleDec.textTransform;
38
37
  return transformStyle === 'uppercase' && elText.length > 7;
39
38
  }
40
- });
41
- // If it does:
42
- if (isBad) {
43
- // Add the locator to the array of violators.
44
- all.locs.push(loc);
45
- }
46
- }
47
- // Populate and return the result.
48
- const whats = ['Element contains all-capital text', 'Elements contain all-capital text'];
49
- return await report(withItems, all, 'allCaps', whats, 0);
39
+ }),
40
+ isDestructive: false,
41
+ complaints: {
42
+ instance: 'Element contains all-capital text',
43
+ summary: 'Elements contain all-capital text'
44
+ },
45
+ ordinalSeverity: 0,
46
+ summaryTagName: ''
47
+ };
48
+ // Run the test and return the result.
49
+ return await simplify(page, withItems, ruleData);
50
50
  };
@@ -8,32 +8,29 @@
8
8
  // ########## IMPORTS
9
9
 
10
10
  // Module to perform common operations.
11
- const {init, report} = require('../procs/testaro');
11
+ const {simplify} = require('../procs/testaro');
12
12
 
13
13
  // ########## FUNCTIONS
14
14
 
15
15
  // Runs the test and returns the result.
16
16
  exports.reporter = async (page, withItems) => {
17
- // Initialize the locators and result.
18
- const all = await init(page, 'body *:not(style, script, svg)');
19
- // For each locator:
20
- for (const loc of all.allLocs) {
21
- // Get whether its element violates the rule.
22
- const isBad = await loc.evaluate(el => {
17
+ // Specify the rule.
18
+ const ruleData = {
19
+ ruleID: 'allSlanted',
20
+ selector: 'body *:not(style, script, svg)',
21
+ pruner: async loc => await loc.evaluate(el => {
23
22
  const elStyleDec = window.getComputedStyle(el);
24
23
  const elText = el.textContent;
25
24
  return ['italic', 'oblique'].includes(elStyleDec.fontStyle) && elText.length > 39;
26
- });
27
- // If it does:
28
- if (isBad) {
29
- // Add the locator to the array of violators.
30
- all.locs.push(loc);
31
- }
32
- }
33
- // Populate and return the result.
34
- const whats = [
35
- 'Element contains all-italic or all-oblique text',
36
- 'Elements contain all-italic or all-oblique text'
37
- ];
38
- return await report(withItems, all, 'allSlanted', whats, 0);
25
+ }),
26
+ isDestructive: false,
27
+ complaints: {
28
+ instance: 'Element contains all-italic or all-oblique text',
29
+ summary: 'Elements contain all-italic or all-oblique text'
30
+ },
31
+ ordinalSeverity: 0,
32
+ summaryTagName: ''
33
+ };
34
+ // Run the test and return the result.
35
+ return await simplify(page, withItems, ruleData);
39
36
  };
@@ -8,27 +8,30 @@
8
8
  // ########## IMPORTS
9
9
 
10
10
  // Module to perform common operations.
11
- const {init, report} = require('../procs/testaro');
11
+ const {simplify} = require('../procs/testaro');
12
12
 
13
13
  // ########## FUNCTIONS
14
14
 
15
15
  // Runs the test and returns the result.
16
16
  exports.reporter = async (page, withItems) => {
17
- // Initialize the locators and result.
18
- const all = await init(page, 'body *');
19
- // Get those that have distorting transform styles.
20
- for (const loc of all.allLocs) {
21
- const isDistorted = await loc.evaluate(el => {
17
+ // Specify the rule.
18
+ const ruleData = {
19
+ ruleID: 'distortion',
20
+ selector: 'body *',
21
+ pruner: async loc => await loc.evaluate(el => {
22
22
  const styleDec = window.getComputedStyle(el);
23
23
  const {transform} = styleDec;
24
24
  return transform
25
25
  && ['matrix', 'perspective', 'rotate', 'scale', 'skew'].some(key => transform.includes(key));
26
- });
27
- if (isDistorted) {
28
- all.locs.push(loc);
29
- }
30
- }
31
- // Populate the result.
32
- const whats = ['Element distorts its text', 'Elements distort their texts'];
33
- return await report(withItems, all, 'distortion', whats, 1);
26
+ }),
27
+ isDestructive: false,
28
+ complaints: {
29
+ instance: 'Element distorts its text',
30
+ summary: 'Elements distort their texts'
31
+ },
32
+ ordinalSeverity: 1,
33
+ summaryTagName: ''
34
+ };
35
+ // Run the test and return the result.
36
+ return await simplify(page, withItems, ruleData);
34
37
  };
package/testaro/filter.js CHANGED
@@ -9,28 +9,28 @@
9
9
  // ########## IMPORTS
10
10
 
11
11
  // Module to perform common operations.
12
- const {init, report} = require('../procs/testaro');
12
+ const {simplify} = require('../procs/testaro');
13
13
 
14
14
  // ########## FUNCTIONS
15
15
 
16
16
  // Runs the test and returns the result.
17
17
  exports.reporter = async (page, withItems) => {
18
- // Initialize the locators and result.
19
- const all = await init(page, 'body *');
20
- // For each locator:
21
- for (const loc of all.allLocs) {
22
- // Get whether its element violates the rule.
23
- const isBad = await loc.evaluate(el => {
18
+ // Specify the rule.
19
+ const ruleData = {
20
+ ruleID: 'filter',
21
+ selector: 'body *',
22
+ pruner: async loc => await loc.evaluate(el => {
24
23
  const styleDec = window.getComputedStyle(el);
25
24
  return styleDec.filter !== 'none';
26
- });
27
- // If it does:
28
- if (isBad) {
29
- // Add the locator to the array of violators.
30
- all.locs.push(loc);
31
- }
32
- }
33
- // Populate and return the result.
34
- const whats = ['Element has a filter style', 'Elements have filter styles'];
35
- return await report(withItems, all, 'filter', whats, 2);
25
+ }),
26
+ isDestructive: false,
27
+ complaints: {
28
+ instance: 'Element has a filter style',
29
+ summary: 'Elements have filter styles'
30
+ },
31
+ ordinalSeverity: 2,
32
+ summaryTagName: ''
33
+ };
34
+ // Run the test and return the result.
35
+ return await simplify(page, withItems, ruleData);
36
36
  };
@@ -4,8 +4,10 @@
4
4
  This test reports links with title attributes whose values the link text contains.
5
5
  */
6
6
 
7
+ // ########## IMPORTS
8
+
7
9
  // Module to perform common operations.
8
- const {init, report} = require('../procs/testaro');
10
+ const {simplify} = require('../procs/testaro');
9
11
  // Module to get locator data.
10
12
  const {getLocatorData} = require('../procs/getLocatorData');
11
13
 
@@ -13,24 +15,23 @@ const {getLocatorData} = require('../procs/getLocatorData');
13
15
 
14
16
  // Runs the test and returns the result.
15
17
  exports.reporter = async (page, withItems) => {
16
- // Initialize the locators and result.
17
- const all = await init(page, 'a[title]');
18
- // For each locator:
19
- for (const loc of all.allLocs) {
20
- // Get whether its element violates the rule.
21
- const elData = await getLocatorData(loc);
22
- const title = await loc.getAttribute('title');
23
- const isBad = elData.excerpt.toLowerCase().includes(title.toLowerCase());
24
- // If it does:
25
- if (isBad) {
26
- // Add the locator to the array of violators.
27
- all.locs.push(loc);
28
- }
29
- }
30
- // Populate and return the result.
31
- const whats = [
32
- 'Link has a title attribute that repeats link text content',
33
- 'Links have title attributes that repeat link text contents'
34
- ];
35
- return await report(withItems, all, 'linkTitle', whats, 0);
18
+ // Specify the rule.
19
+ const ruleData = {
20
+ ruleID: 'linkTitle',
21
+ selector: 'a[title]',
22
+ pruner: async loc => {
23
+ const elData = await getLocatorData(loc);
24
+ const title = await loc.getAttribute('title');
25
+ return elData.excerpt.toLowerCase().includes(title.toLowerCase());
26
+ },
27
+ isDestructive: false,
28
+ complaints: {
29
+ instance: 'Link has a title attribute that repeats link text content',
30
+ summary: 'Links have title attributes that repeat link text contents'
31
+ },
32
+ ordinalSeverity: 0,
33
+ summaryTagName: 'A'
34
+ };
35
+ // Run the test and return the result.
36
+ return await simplify(page, withItems, ruleData);
36
37
  };
package/testaro/linkUl.js CHANGED
@@ -8,8 +8,10 @@
8
8
  merely to discover which passages are links.
9
9
  */
10
10
 
11
+ // ########## IMPORTS
12
+
11
13
  // Module to perform common operations.
12
- const {init, report} = require('../procs/testaro');
14
+ const {simplify} = require('../procs/testaro');
13
15
  // Module to classify links.
14
16
  const {isInlineLink} = require('../procs/isInlineLink');
15
17
 
@@ -17,27 +19,30 @@ const {isInlineLink} = require('../procs/isInlineLink');
17
19
 
18
20
  // Runs the test and returns the result.
19
21
  exports.reporter = async (page, withItems) => {
20
- // Initialize the locators and result.
21
- const all = await init(page, 'a');
22
- // For each locator:
23
- for (const loc of all.allLocs) {
24
- // Get whether its element is underlined.
25
- const isUnderlined = await loc.evaluate(el => {
26
- const styleDec = window.getComputedStyle(el);
27
- return styleDec.textDecorationLine === 'underline';
28
- });
29
- // If it is not:
30
- if (! isUnderlined) {
31
- // Get whether it is inline.
32
- const isInline = await isInlineLink(loc);
33
- // If it is:
34
- if (isInline) {
35
- // Add the locator to the array of violators.
36
- all.locs.push(loc);
22
+ // Specify the rule.
23
+ const ruleData = {
24
+ ruleID: 'linkUl',
25
+ selector: 'a',
26
+ pruner: async loc => {
27
+ // Get whether each link is underlined.
28
+ const isUnderlined = await loc.evaluate(el => {
29
+ const styleDec = window.getComputedStyle(el);
30
+ return styleDec.textDecorationLine === 'underline';
31
+ });
32
+ // If it is not:
33
+ if (! isUnderlined) {
34
+ // Return whether it is a violator.
35
+ return await isInlineLink(loc);
37
36
  }
38
- }
39
- }
40
- // Populate and return the result.
41
- const whats = ['Link is inline but has no underline', 'Inline links are missing underlines'];
42
- return await report(withItems, all, 'linkUl', whats, 1);
37
+ },
38
+ isDestructive: false,
39
+ complaints: {
40
+ instance: 'Link is inline but has no underline',
41
+ summary: 'Inline links are missing underlines'
42
+ },
43
+ ordinalSeverity: 1,
44
+ summaryTagName: 'A'
45
+ };
46
+ // Run the test and return the result.
47
+ return await simplify(page, withItems, ruleData);
43
48
  };
@@ -4,19 +4,20 @@
4
4
  This test reports tables used for layout.
5
5
  */
6
6
 
7
+ // ########## IMPORTS
8
+
7
9
  // Module to perform common operations.
8
- const {init, report} = require('../procs/testaro');
10
+ const {simplify} = require('../procs/testaro');
9
11
 
10
12
  // ########## FUNCTIONS
11
13
 
12
14
  // Runs the test and returns the result.
13
15
  exports.reporter = async (page, withItems) => {
14
- // Initialize the locators and result.
15
- const all = await init(page, 'table');
16
- // For each locator:
17
- for (const loc of all.allLocs) {
18
- // Get whether its element violates the rule.
19
- const isBad = await loc.evaluate(el => {
16
+ // Specify the rule.
17
+ const ruleData = {
18
+ ruleID: 'nonTable',
19
+ selector: 'table',
20
+ pruner: async loc => await loc.evaluate(el => {
20
21
  const role = el.getAttribute('role');
21
22
  // If it contains another table:
22
23
  if (el.querySelector('table')) {
@@ -54,14 +55,15 @@ exports.reporter = async (page, withItems) => {
54
55
  // Return misuse.
55
56
  return true;
56
57
  }
57
- });
58
- // If it does:
59
- if (isBad) {
60
- // Add the locator to the array of violators.
61
- all.locs.push(loc);
62
- }
63
- }
64
- // Populate and return the result.
65
- const whats = ['Table is misused to arrange content', 'Tables are misused to arrange content'];
66
- return await report(withItems, all, 'nonTable', whats, 2, 'TABLE');
58
+ }),
59
+ isDestructive: false,
60
+ complaints: {
61
+ instance: 'Table is misused to arrange content',
62
+ summary: 'Tables are misused to arrange content'
63
+ },
64
+ ordinalSeverity: 2,
65
+ summaryTagName: 'TABLE'
66
+ };
67
+ // Run the test and return the result.
68
+ return await simplify(page, withItems, ruleData);
67
69
  };
@@ -66,16 +66,6 @@
66
66
  "=",
67
67
  "the World Wide Web"
68
68
  ],
69
- [
70
- "standardResult.instances.1.excerpt",
71
- "=",
72
- "Never"
73
- ],
74
- [
75
- "standardResult.instances.2.excerpt",
76
- "=",
77
- "accessible"
78
- ],
79
69
  [
80
70
  "standardResult.instances.1.tagName",
81
71
  "=",
@@ -112,6 +112,11 @@
112
112
  "standardResult.instances.0.what",
113
113
  "i",
114
114
  "Links have"
115
+ ],
116
+ [
117
+ "standardResult.instances.0.tagName",
118
+ "=",
119
+ "A"
115
120
  ]
116
121
  ],
117
122
  "rules": [
@@ -170,6 +170,63 @@
170
170
  "y",
171
171
  "linkUl"
172
172
  ]
173
+ },
174
+ {
175
+ "type": "test",
176
+ "which": "testaro",
177
+ "withItems": false,
178
+ "stopOnFail": true,
179
+ "expect": [
180
+ [
181
+ "standardResult.totals.1",
182
+ "=",
183
+ 6
184
+ ],
185
+ [
186
+ "standardResult.totals.2",
187
+ "=",
188
+ 0
189
+ ],
190
+ [
191
+ "standardResult.instances.length",
192
+ "=",
193
+ 1
194
+ ],
195
+ [
196
+ "standardResult.instances.0.ruleID",
197
+ "=",
198
+ "linkUl"
199
+ ],
200
+ [
201
+ "standardResult.instances.0.what",
202
+ "i",
203
+ "missing"
204
+ ],
205
+ [
206
+ "standardResult.instances.0.ordinalSeverity",
207
+ "=",
208
+ 1
209
+ ],
210
+ [
211
+ "standardResult.instances.0.count",
212
+ "=",
213
+ 6
214
+ ],
215
+ [
216
+ "standardResult.instances.0.tagName",
217
+ "=",
218
+ "A"
219
+ ],
220
+ [
221
+ "standardResult.instances.0.location.doc",
222
+ "=",
223
+ ""
224
+ ]
225
+ ],
226
+ "rules": [
227
+ "y",
228
+ "linkUl"
229
+ ]
173
230
  }
174
231
  ],
175
232
  "sources": {