testaro 4.9.0 → 4.10.2

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/tests/hover.js CHANGED
@@ -2,16 +2,22 @@
2
2
  hover
3
3
  This test reports unexpected effects of hovering. The effects include elements that are made
4
4
  visible, elements whose opacities are changed, elements with ancestors whose opacities are
5
- changed, and elements that cannot be hovered over. Only Playwright-visible elements that have
6
- 'A', 'BUTTON', and 'LI' tag names or have 'onmouseenter' or 'onmouseover' attributes are
7
- considered as hovering targets. The elements considered when the effects of hovering are
5
+ changed, and elements that cannot be hovered over. Only Playwright-visible elements in the
6
+ DOM that have 'A', 'BUTTON', and 'LI' tag names or have 'onmouseenter' or 'onmouseover' attributes
7
+ are considered as hovering targets. The elements considered when the effects of hovering are
8
8
  examined are the descendants of the grandparent of the element hovered over if that element
9
9
  has the tag name 'A' or 'BUTTON' or otherwise the descendants of the element. The only
10
10
  elements counted as being made visible by hovering are those with tag names 'A', 'BUTTON',
11
11
  'INPUT', and 'SPAN', and those with 'role="menuitem"' attributes. The test waits 700 ms after
12
- each hover in case of delayed effects. Despite this delay, the test can make the execution time
13
- practical by randomly sampling targets instead of hovering over all of them. When sampling is
14
- performed, the results may vary from one execution to another.
12
+ each hover in case of delayed effects. Despite this delay, the test can make the execution
13
+ time practical by randomly sampling targets instead of hovering over all of them. When
14
+ sampling is performed, the results may vary from one execution to another. An element is
15
+ reported as unhoverable when it fails the Playwright actionability checks for hovering, i.e.
16
+ when it fails to be attached to the DOM, visible, stable (not or no longer animating), and
17
+ able to receive events. All target candidates satisfy the first two conditions, so only the
18
+ last two might fail. Playwright defines the ability to receive events as being the target of
19
+ an action on the location where the center of the element is, rather than some other element
20
+ with a higher zIndex value in the same location being the target.
15
21
  */
16
22
 
17
23
  // CONSTANTS
@@ -6,145 +6,148 @@
6
6
  particular style properties, listed in the 'mainStyles' and 'headingStyles' arrays.
7
7
  */
8
8
  exports.reporter = async (page, withItems) => {
9
- // Get an object with arrays of list and adjacent links as properties.
9
+ // Get an object with arrays of list links and adjacent links as properties.
10
10
  const linkTypes = await require('../procs/linksByType').linksByType(page);
11
11
  return await page.$eval('body', (body, args) => {
12
- const withItems = args[0];
13
- const linkTypes = args[1];
12
+ const linkTypes = args[0];
13
+ const withItems = args[1];
14
14
  // Identify the settable style properties to be compared for all tag names.
15
15
  const mainStyles = [
16
- 'borderStyle',
17
- 'borderWidth',
18
16
  'fontStyle',
19
17
  'fontWeight',
18
+ 'opacity',
19
+ 'textDecorationLine',
20
+ 'textDecorationStyle',
21
+ 'textDecorationThickness'
22
+ ];
23
+ // Identify those only for buttons.
24
+ const buttonStyles = [
25
+ 'borderStyle',
26
+ 'borderWidth',
27
+ 'height',
20
28
  'lineHeight',
21
29
  'maxHeight',
22
30
  'maxWidth',
23
31
  'minHeight',
24
32
  'minWidth',
25
- 'opacity',
26
33
  'outlineOffset',
27
34
  'outlineStyle',
28
- 'outlineWidth',
29
- 'textDecorationLine',
30
- 'textDecorationStyle',
31
- 'textDecorationThickness'
35
+ 'outlineWidth'
32
36
  ];
33
37
  // Identify those for headings.
34
38
  const headingStyles = [
39
+ 'color',
35
40
  'fontSize'
36
41
  ];
42
+ // Identify those for list links.
43
+ const listLinkStyles = [
44
+ 'color',
45
+ 'fontSize',
46
+ 'lineHeight'
47
+ ];
37
48
  // Initialize the data to be returned.
38
49
  const data = {
39
50
  mainStyles,
51
+ buttonStyles,
40
52
  headingStyles,
53
+ listLinkStyles,
41
54
  totals: {}
42
55
  };
43
56
  if (withItems) {
44
57
  data.items = {};
45
58
  }
46
- // Identify the heading tag names to be analyzed.
47
- const headingNames = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
48
- // Identify the other nonlink tag names to be analyzed.
49
- const otherNames = ['button'];
50
- // Initialize an object of elements to be analyzed.
51
- const elementClasses = {
52
- headings: {},
53
- other: {
54
- aAdjacent: linkTypes.adjacent,
55
- aList: linkTypes.list
59
+ // Initialize an object of elements to be analyzed, including links.
60
+ const elements = {
61
+ buttons: [],
62
+ headings: {
63
+ h1: [],
64
+ h2: [],
65
+ h3: [],
66
+ h4: [],
67
+ h5: [],
68
+ h6: []
69
+ },
70
+ links: {
71
+ adjacent: linkTypes.adjacent,
72
+ list: linkTypes.list
56
73
  }
57
74
  };
75
+ // Identify the heading tag names to be analyzed.
76
+ const headingNames = Object.keys(elements.headings);
77
+ // Add the buttons to the object.
78
+ elements.buttons = Array.from(body.querySelectorAll('button, input[type=button]'));
58
79
  // For each heading tag name:
59
80
  headingNames.forEach(tagName => {
60
81
  // Add its elements to the object.
61
- elementClasses.headings[tagName] = Array.from(body.getElementsByTagName(tagName));
62
- });
63
- // For each other tag name:
64
- otherNames.forEach(tagName => {
65
- // Add its elements to the object.
66
- elementClasses.other[tagName] = Array.from(body.getElementsByTagName(tagName));
82
+ elements.headings[tagName] = Array.from(body.getElementsByTagName(tagName));
67
83
  });
68
- // For each element superclass:
69
- ['headings', 'other'].forEach(superClass => {
70
- // For each class in the superclass:
71
- Object.keys(elementClasses[superClass]).forEach(tagName => {
72
- const elements = elementClasses[superClass][tagName];
73
- const elementCount = elements.length;
74
- // If there are any:
75
- if (elementCount) {
76
- const styleProps = {};
77
- const styleTexts = {};
84
+ // Tabulates the distribution of style properties for elements of a type.
85
+ const tallyStyles = (typeName, elements, typeStyles, withItems) => {
86
+ // If there are any elements:
87
+ const elementCount = elements.length;
88
+ if (elementCount) {
89
+ const styleProps = {};
90
+ const styleTexts = {};
91
+ // For each element:
92
+ elements.forEach(element => {
93
+ // Get its values on the style properties to be compared.
94
+ const styleDec = window.getComputedStyle(element);
95
+ const style = {};
96
+ const styles = mainStyles.concat(typeStyles);
97
+ styles.forEach(styleName => {
98
+ style[styleName] = styleDec[styleName];
99
+ });
100
+ // Get a text representation of the style, limited to those properties.
101
+ const styleText = JSON.stringify(style);
102
+ // Increment the total of elements with that style.
103
+ styleTexts[styleText] = ++styleTexts[styleText] || 1;
104
+ // If details are to be reported:
78
105
  if (withItems) {
79
- if (! data.items[tagName]) {
80
- data.items[tagName] = {};
81
- }
82
- }
83
- // For each of them:
84
- elements.forEach(element => {
85
- // Get its values on the style properties to be compared.
86
- const styleDec = window.getComputedStyle(element);
87
- const style = {};
88
- // Identify the styles to be compared.
89
- const styles = mainStyles;
90
- if (superClass === 'headings') {
91
- styles.push(...headingStyles);
106
+ // Add the element’s values and text to the style details.
107
+ if (! styleProps[typeName]) {
108
+ styleProps[typeName] = {};
92
109
  }
110
+ const elementText = element.textContent.trim().replace(/\s/g, ' ');
111
+ // For each style property being compared:
93
112
  styles.forEach(styleName => {
94
- style[styleName] = styleDec[styleName];
113
+ if (! styleProps[typeName][styleName]) {
114
+ styleProps[typeName][styleName] = {};
115
+ }
116
+ if (! styleProps[typeName][styleName][style[styleName]]) {
117
+ styleProps[typeName][styleName][style[styleName]] = [];
118
+ }
119
+ styleProps[typeName][styleName][style[styleName]].push(elementText);
95
120
  });
96
- // Get a text representation of the style.
97
- const styleText = JSON.stringify(style);
98
- // Increment the total of elements with that style declaration.
99
- styleTexts[styleText] = ++styleTexts[styleText] || 1;
100
- // If details are required:
101
- if (withItems) {
102
- // For each style property:
103
- styles.forEach(styleName => {
104
- const styleValue = style[styleName];
105
- // Increment the total of elements with the same value on it as the element.
106
- if (styleProps[styleName]) {
107
- styleProps[styleName][styleValue] = ++styleProps[styleName][styleValue] || 1;
108
- }
109
- else {
110
- styleProps[styleName] = {[styleValue]: 1};
111
- }
112
- });
113
- }
114
- });
115
- // Add the total to the result.
116
- data.totals[tagName] = {total: elementCount};
117
- const styleCounts = Object.values(styleTexts);
118
- // If the elements in the element class differ in style:
119
- if (styleCounts.length > 1) {
120
- // Add the distribution of its style counts to the result.
121
- data.totals[tagName].subtotals = styleCounts.sort((a, b) => b - a);
122
121
  }
123
- // If details are required:
122
+ });
123
+ // Add the total to the result.
124
+ data.totals[typeName] = {total: elementCount};
125
+ const styleCounts = Object.values(styleTexts);
126
+ // If the elements of the type differ in style:
127
+ if (styleCounts.length > 1) {
128
+ // Add the distribution of its style counts to the result.
129
+ data.totals[typeName].subtotals = styleCounts.sort((a, b) => b - a);
130
+ // If details are to be reported:
124
131
  if (withItems) {
125
- // For each style property:
126
- Object.keys(styleProps).forEach(styleProp => {
127
- // Ignore it if all the elements have the same value.
128
- if (Object.keys(styleProps[styleProp]).length === 1) {
129
- delete styleProps[styleProp];
130
- }
131
- // Otherwise:
132
- else {
133
- if (! data.items[tagName][styleProp]) {
134
- data.items[tagName][styleProp] = {};
135
- }
136
- // Sort the values in order of decreasing count.
137
- const sortedEntries = Object.entries(styleProps[styleProp]).sort((a, b) => b[1] - a[1]);
138
- sortedEntries.forEach(entry => {
139
- const propData = data.items[tagName][styleProp];
140
- propData[entry[0]] = (propData[entry[0]] || 0) + entry[1];
141
- });
132
+ // Delete the data on uniform style properties.
133
+ Object.keys(styleProps[typeName]).forEach(styleName => {
134
+ if (Object.keys(styleProps[typeName][styleName]).length === 1) {
135
+ delete styleProps[typeName][styleName];
142
136
  }
143
137
  });
138
+ // Add the element values and texts to the result.
139
+ data.items[typeName] = styleProps[typeName];
144
140
  }
145
141
  }
146
- });
142
+ }
143
+ };
144
+ // Report the style-property distributions for the element types.
145
+ tallyStyles('button', elements.buttons, buttonStyles, withItems);
146
+ tallyStyles('adjacentLink', elements.links.adjacent, [], withItems);
147
+ tallyStyles('listLink', elements.links.list, listLinkStyles, withItems);
148
+ headingNames.forEach(headingName => {
149
+ tallyStyles(headingName, elements.headings[headingName], headingStyles, withItems);
147
150
  });
148
151
  return {result: data};
149
- }, [withItems, linkTypes]);
152
+ }, [linkTypes, withItems]);
150
153
  };
package/tests/wave.js CHANGED
@@ -4,6 +4,7 @@
4
4
  specifies a WAVE report type: 1, 2, 3, or 4. The larger the number, the more detailed (and
5
5
  expensive) the report.
6
6
  */
7
+ const fs = require('fs/promises');
7
8
  const https = require('https');
8
9
  exports.reporter = async (page, reportType) => {
9
10
  const waveKey = process.env.WAVE_KEY;
@@ -20,20 +21,33 @@ exports.reporter = async (page, reportType) => {
20
21
  response.on('data', chunk => {
21
22
  report += chunk;
22
23
  });
23
- // When the data arrive, return them as an object.
24
- response.on('end', () => {
24
+ // When the data arrive:
25
+ response.on('end', async () => {
25
26
  try {
27
+ // Delete unnecessary properties.
26
28
  const result = JSON.parse(report);
27
29
  const {categories} = result;
28
30
  delete categories.feature;
29
31
  delete categories.structure;
30
32
  delete categories.aria;
33
+ // Add WCAG information from the WAVE documentation.
34
+ const waveDocJSON = await fs.readFile('procs/wavedoc.json');
35
+ const waveDoc = JSON.parse(waveDocJSON);
36
+ Object.keys(categories).forEach(categoryName => {
37
+ const category = categories[categoryName];
38
+ const {items} = category;
39
+ Object.keys(items).forEach(issueName => {
40
+ const issueDoc = waveDoc.find((issue => issue.name === issueName));
41
+ const {guidelines} = issueDoc;
42
+ items[issueName].wcag = guidelines;
43
+ });
44
+ })
31
45
  return resolve(result);
32
46
  }
33
47
  catch (error) {
34
48
  return resolve({
35
49
  prevented: true,
36
- error: 'WAVE did not return JSON.',
50
+ error: error.message,
37
51
  report
38
52
  });
39
53
  }
@@ -0,0 +1,62 @@
1
+ // app.js
2
+ // Validator for Testaro tests.
3
+
4
+ const fs = require('fs').promises;
5
+ const {handleRequest} = require(`${__dirname}/../../run`);
6
+ const validateTests = async () => {
7
+ const totals = {
8
+ attempts: 0,
9
+ successes: 0
10
+ };
11
+ const scriptFileNames = await fs.readdir(`${__dirname}/../tests/scripts`);
12
+ for (const scriptFileName of scriptFileNames.filter(fileName => fileName === 'styleDiff.json')) {
13
+ const rawScriptJSON = await fs
14
+ .readFile(`${__dirname}/../tests/scripts/${scriptFileName}`, 'utf8');
15
+ const scriptJSON = rawScriptJSON
16
+ .replace(/__targets__/g, `file://${__dirname}/../tests/targets`);
17
+ const script = JSON.parse(scriptJSON);
18
+ const report = {script};
19
+ report.log = [];
20
+ report.acts = [];
21
+ await handleRequest(report);
22
+ const {log, acts} = report;
23
+ if (log.length === 2 && log[1].event === 'endTime' && /^\d{4}-.+$/.test(log[1].value)) {
24
+ console.log('Success: Log has been correctly populated');
25
+ }
26
+ else {
27
+ console.log('Failure: Log empty or invalid');
28
+ console.log(JSON.stringify(log, null, 2));
29
+ }
30
+ if (
31
+ acts.length === script.commands.length
32
+ && acts.every(
33
+ act => act.type && act.type === 'test'
34
+ ? act.result && act.result.failureCount !== undefined
35
+ : true
36
+ )
37
+ ) {
38
+ totals.attempts++;
39
+ totals.successes++;
40
+ console.log('Success: Reports have been correctly populated');
41
+ if (acts.every(
42
+ act => act.type === 'test' ? act.result.failureCount === 0 : true
43
+ )) {
44
+ totals.attempts++;
45
+ totals.successes++;
46
+ console.log('Success: No failures');
47
+ }
48
+ else {
49
+ totals.attempts++;
50
+ console.log('Failure: At least one test has at least one failure');
51
+ console.log(JSON.stringify(acts, null, 2));
52
+ }
53
+ }
54
+ else {
55
+ totals.attempts++;
56
+ console.log('Failure: Reports empty or invalid');
57
+ console.log(JSON.stringify(acts, null, 2));
58
+ }
59
+ }
60
+ console.log(`Grand totals: attempts ${totals.attempts}, successes ${totals.successes}`);
61
+ };
62
+ validateTests();
@@ -15,16 +15,16 @@
15
15
  {
16
16
  "type": "test",
17
17
  "which": "styleDiff",
18
+ "withItems": true,
18
19
  "what": "styleDiff",
19
- "withItems": false,
20
20
  "expect": [
21
- ["totals.aAdjacent.total", "=", 2],
22
- ["totals.aList.total", "=", 2],
21
+ ["totals.adjacentLink.total", "=", 2],
22
+ ["totals.listLink.total", "=", 2],
23
23
  ["totals.button.total", "=", 2],
24
24
  ["totals.h1.total", "=", 1],
25
25
  ["totals.h2.total", "=", 4],
26
- ["totals.aAdjacent.subtotals"],
27
- ["totals.aList.subtotals"],
26
+ ["totals.adjacentLink.subtotals"],
27
+ ["totals.listLink.subtotals"],
28
28
  ["totals.button.subtotals"],
29
29
  ["totals.h1.subtotals"],
30
30
  ["totals.h2.subtotals"]
@@ -38,23 +38,24 @@
38
38
  {
39
39
  "type": "test",
40
40
  "which": "styleDiff",
41
+ "withItems": true,
41
42
  "what": "styleDiff",
42
- "withItems": false,
43
43
  "expect": [
44
- ["totals.aAdjacent.total", "=", 2],
45
- ["totals.aList.total", "=", 2],
44
+ ["totals.adjacentLink.total", "=", 2],
45
+ ["totals.listLink.total", "=", 2],
46
46
  ["totals.button.total", "=", 2],
47
47
  ["totals.h1.total", "=", 1],
48
48
  ["totals.h2.total", "=", 4],
49
- ["totals.aAdjacent.subtotals.0", "=", 1],
50
- ["totals.aAdjacent.subtotals.1", "=", 1],
51
- ["totals.aList.subtotals.0", "=", 1],
52
- ["totals.aList.subtotals.1", "=", 1],
49
+ ["totals.adjacentLink.subtotals.0", "=", 1],
50
+ ["totals.adjacentLink.subtotals.1", "=", 1],
51
+ ["totals.listLink.subtotals.0", "=", 1],
52
+ ["totals.listLink.subtotals.1", "=", 1],
53
53
  ["totals.button.subtotals.0", "=", 1],
54
54
  ["totals.button.subtotals.1", "=", 1],
55
55
  ["totals.h1.subtotals"],
56
56
  ["totals.h2.subtotals.0", "=", 3],
57
- ["totals.h2.subtotals.1", "=", 1]
57
+ ["totals.h2.subtotals.1", "=", 1],
58
+ ["items.adjacentLink.textDecorationStyle.double.0", "=", "French information"]
58
59
  ]
59
60
  }
60
61
  ]
@@ -19,17 +19,17 @@
19
19
  <main>
20
20
  <h1>Page with inconsistent styles</h1>
21
21
  <h2>Inline links</h2>
22
- <p>This paragraph contains inline links to <a href="https://en.wikipedia.org">English information</a> and <a style="text-decoration: underline; text-decoration-style: double" href="https://fr.wikipedia.org">French information</a>. They have different styles.</p>
22
+ <p>This paragraph contains adjacent links to <a href="https://en.wikipedia.org">English information</a> and <a style="text-decoration-style: double" href="https://fr.wikipedia.org">French information</a>. They have different styles.</p>
23
23
  <h2>Block links</h2>
24
- <p>The following links are not inline. They have a different styles.</p>
24
+ <p>The following links are list links. They have different styles.</p>
25
25
  <ul>
26
26
  <li><a style="font-weight: 700" href="https://sp.wikipedia.org">Spanish information</a></li>
27
27
  <li><a href="https://eo.wikipedia.org">Esperanto information</a></li>
28
28
  </ul>
29
29
  <h2>Buttons</h2>
30
- <p>This paragraph contains two buttons with a different custom styles. <button type="button">Wikipedia</button> <button type="button" style="border-width: 3px">Wiktionary</button></p>
30
+ <p>This paragraph contains two buttons with different custom styles. <button type="button">Wikipedia</button> <button type="button" style="border-width: 3px">Wiktionary</button></p>
31
31
  <h2 class="italic">Headings</h2>
32
- <p>This page contains 4 <code>h2</code> headings. This one has a deviant style.</p>
32
+ <p>This page contains 4 <code>h2</code> headings. This last one has a deviant style.</p>
33
33
  </main>
34
34
  </body>
35
35
  </html>
@@ -20,9 +20,9 @@
20
20
  <main>
21
21
  <h1>Page with consistent styles</h1>
22
22
  <h2>Inline links</h2>
23
- <p>This paragraph contains inline links to <a href="https://en.wikipedia.org">English information</a> and <a href="https://fr.wikipedia.org">French information</a>. They both have a default style.</p>
23
+ <p>This paragraph contains adjacent links to <a href="https://en.wikipedia.org">English information</a> and <a href="https://fr.wikipedia.org">French information</a>. They both have a default style.</p>
24
24
  <h2>Block links</h2>
25
- <p>The following links are not inline. They have a uniform custom underline.</p>
25
+ <p>The following links are list links. They have a uniform custom underline.</p>
26
26
  <ul class="dotted">
27
27
  <li><a href="https://sp.wikipedia.org">Spanish information</a></li>
28
28
  <li><a href="https://eo.wikipedia.org">Esperanto information</a></li>