testaro 2.1.3 → 2.2.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.
Files changed (65) hide show
  1. package/README.md +18 -9
  2. package/package.json +1 -1
  3. package/tests/hover.js +203 -165
  4. package/tests/radioSet.js +1 -1
  5. package/validation/executors/appBatch.js +87 -0
  6. package/validation/executors/appNoBatch.js +67 -0
  7. package/validation/executors/tests.js +53 -0
  8. package/validation/{scripts/test → tests/scripts}/bulk.json +2 -2
  9. package/validation/{scripts/test → tests/scripts}/embAc.json +2 -2
  10. package/validation/{scripts/test → tests/scripts}/focAll.json +3 -3
  11. package/validation/{scripts/test → tests/scripts}/focInd.json +2 -2
  12. package/validation/{scripts/test → tests/scripts}/focOp.json +2 -2
  13. package/validation/{scripts/test → tests/scripts}/hover.json +2 -2
  14. package/validation/{scripts/test → tests/scripts}/labClash.json +2 -2
  15. package/validation/{scripts/test → tests/scripts}/linkUl.json +3 -3
  16. package/validation/{scripts/test → tests/scripts}/menuNav.json +2 -2
  17. package/validation/{scripts/test → tests/scripts}/motion.json +2 -2
  18. package/validation/{scripts/test → tests/scripts}/radioSet.json +2 -2
  19. package/validation/{scripts/test → tests/scripts}/role.json +2 -2
  20. package/validation/{scripts/test → tests/scripts}/styleDiff.json +2 -2
  21. package/validation/{scripts/test → tests/scripts}/tabNav.json +2 -2
  22. package/validation/{scripts/test → tests/scripts}/zIndex.json +2 -2
  23. package/validation/{targets → tests/targets}/bulk/bad.html +0 -0
  24. package/validation/{targets → tests/targets}/bulk/good.html +0 -0
  25. package/validation/{targets → tests/targets}/embAc/bad.html +0 -0
  26. package/validation/{targets → tests/targets}/embAc/good.html +0 -0
  27. package/validation/{targets → tests/targets}/focAll/good.html +0 -0
  28. package/validation/{targets → tests/targets}/focAll/less.html +0 -0
  29. package/validation/{targets → tests/targets}/focAll/more.html +0 -0
  30. package/validation/{targets → tests/targets}/focInd/bad.html +0 -0
  31. package/validation/{targets → tests/targets}/focInd/good.html +0 -0
  32. package/validation/{targets → tests/targets}/focOp/bad.html +0 -0
  33. package/validation/{targets → tests/targets}/focOp/good.html +0 -0
  34. package/validation/{targets → tests/targets}/hover/bad.html +0 -0
  35. package/validation/{targets → tests/targets}/hover/good.html +0 -0
  36. package/validation/{targets → tests/targets}/labClash/bad.html +0 -0
  37. package/validation/{targets → tests/targets}/labClash/good.html +0 -0
  38. package/validation/{targets → tests/targets}/linkUl/bad.html +0 -0
  39. package/validation/{targets → tests/targets}/linkUl/good.html +0 -0
  40. package/validation/{targets → tests/targets}/linkUl/na.html +0 -0
  41. package/validation/{targets → tests/targets}/menuNav/bad.html +0 -0
  42. package/validation/{targets → tests/targets}/menuNav/bad.js +0 -0
  43. package/validation/{targets → tests/targets}/menuNav/good.html +0 -0
  44. package/validation/{targets → tests/targets}/menuNav/good.js +0 -0
  45. package/validation/{targets → tests/targets}/menuNav/style.css +0 -0
  46. package/validation/{targets → tests/targets}/motion/bad.css +0 -0
  47. package/validation/{targets → tests/targets}/motion/bad.html +0 -0
  48. package/validation/{targets → tests/targets}/motion/good.html +0 -0
  49. package/validation/{targets → tests/targets}/radioSet/bad.html +0 -0
  50. package/validation/{targets → tests/targets}/radioSet/good.html +0 -0
  51. package/validation/{targets → tests/targets}/role/bad.html +0 -0
  52. package/validation/{targets → tests/targets}/role/good.html +0 -0
  53. package/validation/{targets → tests/targets}/styleDiff/bad.html +0 -0
  54. package/validation/{targets → tests/targets}/styleDiff/good.html +0 -0
  55. package/validation/{targets → tests/targets}/tabNav/bad.html +0 -0
  56. package/validation/{targets → tests/targets}/tabNav/bad.js +0 -0
  57. package/validation/{targets → tests/targets}/tabNav/good.html +0 -0
  58. package/validation/{targets → tests/targets}/tabNav/good.js +0 -0
  59. package/validation/{targets → tests/targets}/tabNav/goodMoz.js +0 -0
  60. package/validation/{targets → tests/targets}/tabNav/style.css +0 -0
  61. package/validation/{targets → tests/targets}/zIndex/bad.html +0 -0
  62. package/validation/{targets → tests/targets}/zIndex/good.html +0 -0
  63. package/validation/batches/sample.json +0 -13
  64. package/validation/executors/sample.js +0 -11
  65. package/validation/scripts/app/sample.json +0 -21
package/README.md CHANGED
@@ -8,9 +8,9 @@ Testaro is a collection of web accessibility tests.
8
8
 
9
9
  The purpose of Testaro is to provide programmatic access to over 600 accessibility tests defined in several test packages and in Testaro itself.
10
10
 
11
- Calling Testaro requires telling it which operations (including tests) to perform, which URLs to perform them on, and where to write its reports.
11
+ Running Testaro requires telling it which operations (including tests) to perform and which URLs to perform them on, and giving Testaro an object to put its output into.
12
12
 
13
- Testaro outputs progress messages and a list of reports to the standard output. It writes the reports in JSON format.
13
+ Testaro outputs progress messages to the standard output. It populates the object with log information and test reports.
14
14
 
15
15
  ## System requirements
16
16
 
@@ -47,7 +47,7 @@ The main directories containing code files are:
47
47
  - package root: main code files
48
48
  - `tests`: files containing the code defining particular tests
49
49
  - `procs`: shared procedures
50
- - `validation`: code and artifacts for the validation of tests
50
+ - `validation`: code and artifacts for the validation of Testaro
51
51
 
52
52
  ## Installation
53
53
 
@@ -256,12 +256,6 @@ An example of a **Testaro-defined** test is:
256
256
 
257
257
  In this case, Testaro runs the `motion` test with the specified parameters.
258
258
 
259
- ###### Validation
260
-
261
- For each of the non-package tests defined in Testaro, a validating script and some files tested by it are also provided. They are in the `validation` directory. A module that you can use to execute those scripts is also there, named `validator.js`.
262
-
263
- For example, to execute the `focOp.json` validation script, you can enter `node validation/validator focOp`. The `validator.js` module outputs the report to the console.
264
-
265
259
  ##### Scoring
266
260
 
267
261
  An example of a **scoring** command is:
@@ -383,6 +377,8 @@ The second item in each array, if there are 3 items in the array, is an operator
383
377
  - `>`: greater than
384
378
  - `!`: unequal to
385
379
 
380
+ A typical use for an `expect` property is checking the correctness of a Testaro test. Thus, the validation scripts in the `validation/tests/scripts` directory all contain `test` commands with `expect` properties. See the “Validation” section below.
381
+
386
382
  ## Batches
387
383
 
388
384
  There are two ways to use a script to give instructions to Testaro:
@@ -442,6 +438,19 @@ If a `wave` test is included in the script, an environment variable named `TESTA
442
438
 
443
439
  Before executing a Testaro script, you can optionally also set the environment variables `TESTARO_DEBUG` (to `'true'` or anything else) and/or `TESTARO_WAITS` (to a non-negative integer). The effects of these variables are described in the `index.js` file.
444
440
 
441
+ ## Validation
442
+
443
+ Three _executors_ for Testaro validation are located in the `validation` directory. An executor is a commonJS JavaScript module that runs Testaro and reports whether the results are correct.
444
+
445
+ The executors are:
446
+ - `appNoBatch.js`: Reports whether Testaro runs correctly with a no-batch script.
447
+ - `appBatch.js`: Reports whether Testaro runs correctly with a script and a batch.
448
+ - `tests.js`: Runs Testaro with each custom test and reports whether the results are correct.
449
+
450
+ To execute any executor `xyz.js`, call it with the statement `node validation/executors/xyz`. The results will appear in the standard output.
451
+
452
+ The `tests.js` executor makes use of the scripts in the `validation/tests/scripts` directory, and they, in turn, run tests on HTML files in the `validation/tests/targets` directory.
453
+
445
454
  ## Contribution
446
455
 
447
456
  You can define additional Testaro commands and functionality. Contributions are welcome.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "2.1.3",
3
+ "version": "2.2.1",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/tests/hover.js CHANGED
@@ -8,188 +8,226 @@
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
- 'INPUT', and 'SPAN', and those with 'role="menuitem"' attributes.
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 makes the execution time
13
+ practical by randomly sampling targets instead of hovering over all of them. Therefore, the
14
+ results may vary from one execution to another.
12
15
  */
13
- exports.reporter = async (page, withItems) => {
14
- // Initialize a counter.
15
- let elementsChecked = 0;
16
- // Identify the elements that are likely to trigger disclosures on hover.
17
- const triggers = await page.$$(
18
- 'body a:visible, body button:visible, body li:visible, body [onmouseenter]:visible, body [onmouseover]:visible'
19
- )
20
- .catch(error => {
21
- console.log(`ERROR getting hover triggers (${error.message})`);
22
- return [];
23
- });
24
- // Identify the selectors of active elements likely to be disclosed by a hover.
25
- const targetSelectors = ['a', 'button', 'input', '[role=menuitem]', 'span']
26
- .map(selector => `${selector}:visible`)
27
- .join(', ');
28
- // Initialize the result.
29
- const data = {
30
- totals: {
31
- triggers: 0,
32
- madeVisible: 0,
33
- opacityChanged: 0,
34
- opacityAffected: 0,
35
- unhoverables: 0
16
+
17
+ // CONSTANTS
18
+
19
+ // Selectors of active elements likely to be disclosed by a hover.
20
+ const targetSelectors = ['a', 'button', 'input', '[role=menuitem]', 'span']
21
+ .map(selector => `${selector}:visible`)
22
+ .join(', ');
23
+ // Initialize the result.
24
+ const data = {
25
+ totals: {
26
+ triggers: 0,
27
+ madeVisible: 0,
28
+ opacityChanged: 0,
29
+ opacityAffected: 0,
30
+ unhoverables: 0
31
+ }
32
+ };
33
+
34
+ // VARIABLES
35
+
36
+ // Counter.
37
+ let elementsChecked = 0;
38
+
39
+ // FUNCTIONS
40
+
41
+ // Samples a population and returns the sample.
42
+ const getSample = (population, sampleSize) => {
43
+ const popSize = population.length;
44
+ if (sampleSize > 0 && sampleSize < popSize) {
45
+ const sample = new Set();
46
+ while (sample.size < sampleSize) {
47
+ const index = Math.floor(popSize * Math.random());
48
+ sample.add(population[index]);
36
49
  }
37
- };
50
+ return Array.from(sample);
51
+ }
52
+ else {
53
+ return [];
54
+ }
55
+ };
56
+ // Recursively finds and reports triggers and targets.
57
+ const find = async (withItems, page, triggers) => {
38
58
  if (withItems) {
39
59
  data.items = {
40
60
  triggers: [],
41
61
  unhoverables: []
42
62
  };
43
63
  }
44
- let triggerTag = '';
45
- // FUNCTION DEFINITION START
46
- // Recursively finds and reports triggers and targets.
47
- const find = async triggers => {
48
- // If any potential disclosure triggers remain:
49
- if (triggers.length) {
50
- // Identify the first of them.
51
- const firstTrigger = triggers[0];
52
- const tagNameJSHandle = await firstTrigger.getProperty('tagName')
53
- .catch(error => {
54
- console.log(`ERROR getting trigger tag name (${error.message})`);
55
- return '';
56
- });
57
- if (tagNameJSHandle) {
58
- const tagName = await tagNameJSHandle.jsonValue();
59
- // Identify the root of a subtree likely to contain disclosed elements.
60
- let root = firstTrigger;
61
- if (['A', 'BUTTON'].includes(tagName)) {
62
- const rootJSHandle = await page.evaluateHandle(
63
- firstTrigger => {
64
- const parent = firstTrigger.parentElement;
65
- if (parent) {
66
- return parent.parentElement || parent;
67
- }
68
- else {
69
- return firstTrigger;
70
- }
71
- },
72
- firstTrigger
73
- );
74
- root = rootJSHandle.asElement();
75
- }
76
- // Identify the visible active descendants of the root before the hover.
77
- const preVisibles = await root.$$(targetSelectors);
78
- // Identify all the descendants of the root.
79
- const descendants = await root.$$('*');
80
- // Identify their opacities before the hover.
81
- const preOpacities = await page.evaluate(
82
- elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
83
- );
84
- try {
85
- // Hover over the potential trigger.
86
- await firstTrigger.hover({timeout: 700});
87
- // Identify whether it controls other elements.
88
- const isController = await page.evaluate(
89
- element => element.ariaHasPopup || element.hasAttribute('aria-controls'), firstTrigger
90
- );
91
- // Wait for any delayed and/or slowed hover reaction if likely.
92
- await page.waitForTimeout(
93
- elementsChecked++ < 10 || tagName !== triggerTag || isController ? 1200 : 200
94
- );
95
- await root.waitForElementState('stable');
96
- // Identify the visible active descendants of the root during the hover.
97
- const postVisibles = await root.$$(targetSelectors);
98
- // Identify the opacities of the descendants of the root during the hover.
99
- const postOpacities = await page.evaluate(
100
- elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
101
- );
102
- // Identify the elements with opacity changes.
103
- const opacityTargets = descendants
104
- .filter((descendant, index) => postOpacities[index] !== preOpacities[index]);
105
- // Count them and their descendants.
106
- const opacityAffected = opacityTargets.length
107
- ? await page.evaluate(elements => elements.reduce(
108
- (total, current) => total + 1 + current.querySelectorAll('*').length, 0
109
- ), opacityTargets)
110
- : 0;
111
- // If hovering disclosed any element or changed any opacity:
112
- if (postVisibles.length > preVisibles.length || opacityAffected) {
113
- // Preserve the lengthened reaction wait, if any, for the next 5 tries.
114
- if (elementsChecked < 11) {
115
- elementsChecked = 5;
64
+ // If any potential disclosure triggers remain:
65
+ if (triggers.length) {
66
+ // Identify the first of them.
67
+ const firstTrigger = triggers[0];
68
+ const tagNameJSHandle = await firstTrigger.getProperty('tagName')
69
+ .catch(error => {
70
+ console.log(`ERROR getting trigger tag name (${error.message})`);
71
+ return '';
72
+ });
73
+ if (tagNameJSHandle) {
74
+ const tagName = await tagNameJSHandle.jsonValue();
75
+ // Identify the root of a subtree likely to contain disclosed elements.
76
+ let root = firstTrigger;
77
+ if (['A', 'BUTTON'].includes(tagName)) {
78
+ const rootJSHandle = await page.evaluateHandle(
79
+ firstTrigger => {
80
+ const parent = firstTrigger.parentElement;
81
+ if (parent) {
82
+ return parent.parentElement || parent;
116
83
  }
117
- // Hover over the upper-left corner of the page, to undo any hover reactions.
118
- await page.hover('body', {
119
- position: {
120
- x: 0,
121
- y: 0
122
- }
123
- });
124
- // Wait for any delayed and/or slowed hover reaction.
125
- await page.waitForTimeout(200);
126
- await root.waitForElementState('stable');
127
- // Increment the counts of triggers and targets.
128
- data.totals.triggers++;
129
- const madeVisible = Math.max(0, postVisibles.length - preVisibles.length);
130
- data.totals.madeVisible += madeVisible;
131
- data.totals.opacityChanged += opacityTargets.length;
132
- data.totals.opacityAffected += opacityAffected;
133
- // If details are to be reported:
134
- if (withItems) {
135
- // Report them.
136
- const triggerDataJSHandle = await page.evaluateHandle(args => {
137
- // Returns the text of an element.
138
- const textOf = (element, limit) => {
139
- const text = element.textContent.trim() || element.outerHTML.trim();
140
- return text.replace(/\s{2,}/sg, ' ').slice(0, limit);
141
- };
142
- const trigger = args[0];
143
- const preVisibles = args[1];
144
- const postVisibles = args[2];
145
- const madeVisible = postVisibles
146
- .filter(el => ! preVisibles.includes(el))
147
- .map(el => ({
148
- tagName: el.tagName,
149
- text: textOf(el, 50)
150
- }));
151
- const opacityChanged = args[3].map(el => ({
152
- tagName: el.tagName,
153
- text: textOf(el, 50)
154
- }));
155
- return {
156
- tagName: trigger.tagName,
157
- id: trigger.id || '',
158
- text: textOf(trigger, 50),
159
- madeVisible,
160
- opacityChanged
161
- };
162
- }, [firstTrigger, preVisibles, postVisibles, opacityTargets]);
163
- const triggerData = await triggerDataJSHandle.jsonValue();
164
- data.items.triggers.push(triggerData);
84
+ else {
85
+ return firstTrigger;
165
86
  }
87
+ },
88
+ firstTrigger
89
+ );
90
+ root = rootJSHandle.asElement();
91
+ }
92
+ // Identify the visible active descendants of the root before the hover.
93
+ const preVisibles = await root.$$(targetSelectors);
94
+ // Identify all the descendants of the root.
95
+ const descendants = await root.$$('*');
96
+ // Identify their opacities before the hover.
97
+ const preOpacities = await page.evaluate(
98
+ elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
99
+ );
100
+ try {
101
+ // Hover over the potential trigger.
102
+ await firstTrigger.hover({timeout: 700});
103
+ // Identify whether it is coded as controlling other elements.
104
+ const isController = await page.evaluate(
105
+ element => element.ariaHasPopup || element.hasAttribute('aria-controls'), firstTrigger
106
+ );
107
+ // Wait for any delayed and/or slowed hover reaction, longer if coded as a controller.
108
+ await page.waitForTimeout(isController ? 1200 : 600);
109
+ await root.waitForElementState('stable');
110
+ // Identify the visible active descendants of the root during the hover.
111
+ const postVisibles = await root.$$(targetSelectors);
112
+ // Identify the opacities of the descendants of the root during the hover.
113
+ const postOpacities = await page.evaluate(
114
+ elements => elements.map(el => window.getComputedStyle(el).opacity), descendants
115
+ );
116
+ // Identify the elements with opacity changes.
117
+ const opacityTargets = descendants
118
+ .filter((descendant, index) => postOpacities[index] !== preOpacities[index]);
119
+ // Count them and their descendants.
120
+ const opacityAffected = opacityTargets.length
121
+ ? await page.evaluate(elements => elements.reduce(
122
+ (total, current) => total + 1 + current.querySelectorAll('*').length, 0
123
+ ), opacityTargets)
124
+ : 0;
125
+ // If hovering disclosed any element or changed any opacity:
126
+ if (postVisibles.length > preVisibles.length || opacityAffected) {
127
+ // Preserve the lengthened reaction wait, if any, for the next 5 tries.
128
+ if (elementsChecked < 11) {
129
+ elementsChecked = 5;
166
130
  }
167
- }
168
- catch (error) {
169
- console.log('ERROR hovering');
170
- // Returns the text of an element.
171
- const textOf = async (element, limit) => {
172
- let text = await element.textContent();
173
- text = text.trim() || await element.innerHTML();
174
- return text.trim().replace(/\s*/sg, '').slice(0, limit);
175
- };
176
- data.totals.unhoverables++;
131
+ // Hover over the upper-left corner of the page, to undo any hover reactions.
132
+ await page.hover('body', {
133
+ position: {
134
+ x: 0,
135
+ y: 0
136
+ }
137
+ });
138
+ // Wait for any delayed and/or slowed hover reaction.
139
+ await page.waitForTimeout(200);
140
+ await root.waitForElementState('stable');
141
+ // Increment the counts of triggers and targets.
142
+ data.totals.triggers++;
143
+ const madeVisible = Math.max(0, postVisibles.length - preVisibles.length);
144
+ data.totals.madeVisible += madeVisible;
145
+ data.totals.opacityChanged += opacityTargets.length;
146
+ data.totals.opacityAffected += opacityAffected;
147
+ // If details are to be reported:
177
148
  if (withItems) {
178
- data.items.unhoverables.push({
179
- tagName: tagName,
180
- id: firstTrigger.id || '',
181
- text: await textOf(firstTrigger, 50)
182
- });
149
+ // Report them.
150
+ const triggerDataJSHandle = await page.evaluateHandle(args => {
151
+ // Returns the text of an element.
152
+ const textOf = (element, limit) => {
153
+ const text = element.textContent.trim() || element.outerHTML.trim();
154
+ return text.replace(/\s{2,}/sg, ' ').slice(0, limit);
155
+ };
156
+ const trigger = args[0];
157
+ const preVisibles = args[1];
158
+ const postVisibles = args[2];
159
+ const madeVisible = postVisibles
160
+ .filter(el => ! preVisibles.includes(el))
161
+ .map(el => ({
162
+ tagName: el.tagName,
163
+ text: textOf(el, 50)
164
+ }));
165
+ const opacityChanged = args[3].map(el => ({
166
+ tagName: el.tagName,
167
+ text: textOf(el, 50)
168
+ }));
169
+ return {
170
+ tagName: trigger.tagName,
171
+ id: trigger.id || '',
172
+ text: textOf(trigger, 50),
173
+ madeVisible,
174
+ opacityChanged
175
+ };
176
+ }, [firstTrigger, preVisibles, postVisibles, opacityTargets]);
177
+ const triggerData = await triggerDataJSHandle.jsonValue();
178
+ data.items.triggers.push(triggerData);
183
179
  }
184
180
  }
185
- triggerTag = tagName;
186
181
  }
187
- // Process the remaining potential triggers.
188
- await find(triggers.slice(1));
182
+ catch (error) {
183
+ console.log('ERROR hovering');
184
+ // Returns the text of an element.
185
+ const textOf = async (element, limit) => {
186
+ let text = await element.textContent();
187
+ text = text.trim() || await element.innerHTML();
188
+ return text.trim().replace(/\s*/sg, '').slice(0, limit);
189
+ };
190
+ data.totals.unhoverables++;
191
+ if (withItems) {
192
+ data.items.unhoverables.push({
193
+ tagName: tagName,
194
+ id: firstTrigger.id || '',
195
+ text: await textOf(firstTrigger, 50)
196
+ });
197
+ }
198
+ }
189
199
  }
190
- };
200
+ // Process the remaining potential triggers.
201
+ await find(withItems, page, triggers.slice(1));
202
+ }
203
+ };
204
+ exports.reporter = async (page, withItems) => {
205
+ // Identify the triggers.
206
+ const selectors = [
207
+ 'body a:visible',
208
+ 'body button:visible',
209
+ 'body li:visible, body [onmouseenter]:visible',
210
+ 'body [onmouseover]:visible'
211
+ ];
212
+ const triggers = await page.$$(selectors.join(', '))
213
+ .catch(error => {
214
+ console.log(`ERROR getting hover triggers (${error.message})`);
215
+ return [];
216
+ });
217
+ // If they number more than the sample size limit, sample them.
218
+ const triggerCount = triggers.length;
219
+ const sampleSize = 15;
220
+ const triggerSample = triggerCount > sampleSize ? getSample(triggers, 15) : triggers;
191
221
  // Find and document the hover-triggered disclosures.
192
- await find(triggers);
222
+ await find(withItems, page, triggerSample);
223
+ // If the triggers were sampled:
224
+ if (triggerCount > sampleSize) {
225
+ // Change the totals to population estimates.
226
+ const multiplier = triggerCount / sampleSize;
227
+ Object.keys(data.totals).forEach(key => {
228
+ data.totals[key] = Math.round(multiplier * data.totals[key]);
229
+ });
230
+ }
193
231
  // Return the result.
194
232
  return {result: data};
195
233
  };
package/tests/radioSet.js CHANGED
@@ -12,7 +12,7 @@ exports.reporter = async (page, withItems) => {
12
12
  // If itemization is required:
13
13
  if (withItems) {
14
14
  // Add the body of the textOf function as a string to the array.
15
- const textOfBody = await fs.readFile(`${__dirname}../procs/test/textOf.txt`, 'utf8');
15
+ const textOfBody = await fs.readFile(`${__dirname}/../procs/test/textOf.txt`, 'utf8');
16
16
  args.push(textOfBody);
17
17
  }
18
18
  // Get the result data.
@@ -0,0 +1,87 @@
1
+ // app.js
2
+ // Validator for Testaro application with a script and a batch.
3
+
4
+ const options = {
5
+ script: {
6
+ what: 'Sample Testaro executor with 1 test',
7
+ strict: true,
8
+ commands: [
9
+ {
10
+ type: 'launch',
11
+ which: 'chromium',
12
+ what: 'Chromium browser'
13
+ },
14
+ {
15
+ type: 'url',
16
+ which: 'https://*',
17
+ what: 'URL to be replaced with URLs in the batch'
18
+ },
19
+ {
20
+ type: 'test',
21
+ which: 'bulk',
22
+ what: 'bulk'
23
+ },
24
+ {
25
+ type: 'test',
26
+ which: 'noSuchTest',
27
+ what: 'test that does not exist'
28
+ }
29
+ ]
30
+ },
31
+ batch: {
32
+ what: 'Two websites',
33
+ hosts: [
34
+ {
35
+ which: 'https://www.w3.org/',
36
+ what: 'W3C'
37
+ },
38
+ {
39
+ which: 'https://www.wikimedia.org/',
40
+ what: 'Wikimedia'
41
+ }
42
+ ]
43
+ },
44
+ log: [],
45
+ reports: []
46
+ };
47
+ const {handleRequest} = require(`${__dirname}/../../index`);
48
+ const isValidReport = (reports, index) => {
49
+ const isValid = reports[index].acts
50
+ && reports[index].acts.length === 4
51
+ && reports[index].acts[2].result
52
+ && reports[index].acts[2].result.visibleElements
53
+ && typeof reports[index].acts[2].result.visibleElements === 'number'
54
+ && reports[index].acts[3].result
55
+ && typeof reports[index].acts[3].result === 'string'
56
+ && reports[index].acts[3].result.startsWith('ERROR');
57
+ return isValid;
58
+ };
59
+ handleRequest(options)
60
+ .then(
61
+ () => {
62
+ const {log, reports} = options;
63
+ if (
64
+ log.length === 4
65
+ && log[1].event === 'timeStamp'
66
+ && /^[a-z0-9]+$/.test(log[1].value)
67
+ && log[2].event === 'batchSize'
68
+ && log[2].value === 2
69
+ ) {
70
+ console.log('Success: Log has been correctly populated');
71
+ }
72
+ else {
73
+ console.log('Failure: Log empty or invalid');
74
+ console.log(JSON.stringify(log, null, 2));
75
+ }
76
+ if (reports.length === 2 && isValidReport(reports, 0) && isValidReport(reports, 1)) {
77
+ console.log('Success: Reports have been correctly populated');
78
+ }
79
+ else {
80
+ console.log('Failure: Reports empty or invalid');
81
+ console.log(JSON.stringify(reports, null, 2));
82
+ }
83
+ },
84
+ rejection => {
85
+ console.log(`Failure: ${rejection}`);
86
+ }
87
+ );
@@ -0,0 +1,67 @@
1
+ // app.js
2
+ // Validator for Testaro application with a no-batch script.
3
+
4
+ const options = {
5
+ script: {
6
+ what: 'Sample Testaro executor with 1 test',
7
+ strict: true,
8
+ commands: [
9
+ {
10
+ type: 'launch',
11
+ which: 'chromium',
12
+ what: 'Chromium browser'
13
+ },
14
+ {
15
+ type: 'url',
16
+ which: 'https://example.com/',
17
+ what: 'simple page, replaced if there is a batch'
18
+ },
19
+ {
20
+ type: 'test',
21
+ which: 'bulk',
22
+ what: 'bulk'
23
+ },
24
+ {
25
+ type: 'test',
26
+ which: 'noSuchTest',
27
+ what: 'test that does not exist'
28
+ }
29
+ ]
30
+ },
31
+ log: [],
32
+ reports: []
33
+ };
34
+ const {handleRequest} = require(`${__dirname}/../../index`);
35
+ handleRequest(options)
36
+ .then(
37
+ () => {
38
+ const {log, reports} = options;
39
+ if (log.length === 3 && log[1].event === 'timeStamp' && /^[a-z0-9]+$/.test(log[1].value)) {
40
+ console.log('Success: Log has been correctly populated');
41
+ }
42
+ else {
43
+ console.log('Failure: Log empty or invalid');
44
+ console.log(JSON.stringify(log, null, 2));
45
+ }
46
+ if (
47
+ reports.length === 1
48
+ && reports[0].acts
49
+ && reports[0].acts.length === 4
50
+ && reports[0].acts[2].result
51
+ && reports[0].acts[2].result.visibleElements
52
+ && typeof reports[0].acts[2].result.visibleElements === 'number'
53
+ && reports[0].acts[3].result
54
+ && typeof reports[0].acts[3].result === 'string'
55
+ && reports[0].acts[3].result.startsWith('ERROR')
56
+ ) {
57
+ console.log('Success: Reports have been correctly populated');
58
+ }
59
+ else {
60
+ console.log('Failure: Reports empty or invalid');
61
+ console.log(JSON.stringify(reports, null, 2));
62
+ }
63
+ },
64
+ rejection => {
65
+ console.log(`Failure: ${rejection}`);
66
+ }
67
+ );
@@ -0,0 +1,53 @@
1
+ // app.js
2
+ // Validator for Testaro tests.
3
+
4
+ const fs = require('fs').promises;
5
+ const {handleRequest} = require(`${__dirname}/../../index`);
6
+ const validateTests = async () => {
7
+ const scriptFileNames = await fs.readdir(`${__dirname}/../tests/scripts`);
8
+ for (const scriptFileName of scriptFileNames) {
9
+ const rawScriptJSON = await fs
10
+ .readFile(`${__dirname}/../tests/scripts/${scriptFileName}`, 'utf8');
11
+ const scriptJSON = rawScriptJSON
12
+ .replace(/__targets__/g, `file://${__dirname}/../tests/targets`);
13
+ const script = JSON.parse(scriptJSON);
14
+ const options = {script};
15
+ options.log = [];
16
+ options.reports = [];
17
+ await handleRequest(options);
18
+ const {log, reports} = options;
19
+ if (log.length === 3 && log[1].event === 'timeStamp' && /^[a-z0-9]+$/.test(log[1].value)) {
20
+ console.log('Success: Log has been correctly populated');
21
+ }
22
+ else {
23
+ console.log('Failure: Log empty or invalid');
24
+ console.log(JSON.stringify(log, null, 2));
25
+ }
26
+ if (
27
+ reports.length === 1
28
+ && reports[0].acts
29
+ && reports[0].acts.length === script.commands.length
30
+ && reports[0].acts.every(
31
+ act => act.type && act.type === 'test'
32
+ ? act.result && act.result.failureCount !== undefined
33
+ : true
34
+ )
35
+ ) {
36
+ console.log('Success: Reports have been correctly populated');
37
+ if (reports[0].acts.every(
38
+ act => act.type === 'test' ? act.result.failureCount === 0 : true
39
+ )) {
40
+ console.log('Success: No failures');
41
+ }
42
+ else {
43
+ console.log('Failure: At least one test has at least one failure');
44
+ console.log(JSON.stringify(reports, null, 2));
45
+ }
46
+ }
47
+ else {
48
+ console.log('Failure: Reports empty or invalid');
49
+ console.log(JSON.stringify(reports, null, 2));
50
+ }
51
+ }
52
+ };
53
+ validateTests();
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/bulk/good.html",
12
+ "which": "__targets__/bulk/good.html",
13
13
  "what": "small page"
14
14
  },
15
15
  {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  {
25
25
  "type": "url",
26
- "which": "file://__dirname/validation/targets/bulk/bad.html",
26
+ "which": "__targets__/bulk/bad.html",
27
27
  "what": "large page"
28
28
  },
29
29
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/embAc/good.html",
12
+ "which": "__targets__/embAc/good.html",
13
13
  "what": "page without embedding in links or buttons"
14
14
  },
15
15
  {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  {
28
28
  "type": "url",
29
- "which": "file://__dirname/validation/targets/embAc/bad.html",
29
+ "which": "__targets__/embAc/bad.html",
30
30
  "what": "page with embeddings in links and buttons"
31
31
  },
32
32
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/focAll/good.html",
12
+ "which": "__targets__/focAll/good.html",
13
13
  "what": "fully Tab-focusable page"
14
14
  },
15
15
  {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "type": "url",
28
- "which": "file://__dirname/validation/targets/focAll/less.html",
28
+ "which": "__targets__/focAll/less.html",
29
29
  "what": "partly Tab-focusable page"
30
30
  },
31
31
  {
@@ -41,7 +41,7 @@
41
41
  },
42
42
  {
43
43
  "type": "url",
44
- "which": "file://__dirname/validation/targets/focAll/more.html",
44
+ "which": "__targets__/focAll/more.html",
45
45
  "what": "page with added Tab-focusability"
46
46
  },
47
47
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/focInd/good.html",
12
+ "which": "__targets__/focInd/good.html",
13
13
  "what": "page with outline focus indication"
14
14
  },
15
15
  {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  {
32
32
  "type": "url",
33
- "which": "file://__dirname/validation/targets/focInd/bad.html",
33
+ "which": "__targets__/focInd/bad.html",
34
34
  "what": "page with mixed focus indication"
35
35
  },
36
36
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/focOp/good.html",
12
+ "which": "__targets__/focOp/good.html",
13
13
  "what": "page with standard focusability and operability"
14
14
  },
15
15
  {
@@ -29,7 +29,7 @@
29
29
  },
30
30
  {
31
31
  "type": "url",
32
- "which": "file://__dirname/validation/targets/focOp/bad.html",
32
+ "which": "__targets__/focOp/bad.html",
33
33
  "what": "page with deviant focusability and operability"
34
34
  },
35
35
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/hover/good.html",
12
+ "which": "__targets__/hover/good.html",
13
13
  "what": "page with standard hover behavior"
14
14
  },
15
15
  {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  {
29
29
  "type": "url",
30
- "which": "file://__dirname/validation/targets/hover/bad.html",
30
+ "which": "__targets__/hover/bad.html",
31
31
  "what": "page with deviant hover behavior"
32
32
  },
33
33
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/labClash/good.html",
12
+ "which": "__targets__/labClash/good.html",
13
13
  "what": "page with standard labeling"
14
14
  },
15
15
  {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "type": "url",
28
- "which": "file://__dirname/validation/targets/labClash/bad.html",
28
+ "which": "__targets__/labClash/bad.html",
29
29
  "what": "page with deviant labeling"
30
30
  },
31
31
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/linkUl/good.html",
12
+ "which": "__targets__/linkUl/good.html",
13
13
  "what": "page with underlined inline links"
14
14
  },
15
15
  {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  {
28
28
  "type": "url",
29
- "which": "file://__dirname/validation/targets/linkUl/bad.html",
29
+ "which": "__targets__/linkUl/bad.html",
30
30
  "what": "page without underlined inline links"
31
31
  },
32
32
  {
@@ -43,7 +43,7 @@
43
43
  },
44
44
  {
45
45
  "type": "url",
46
- "which": "file://__dirname/validation/targets/linkUl/na.html",
46
+ "which": "__targets__/linkUl/na.html",
47
47
  "what": "page without inline links"
48
48
  },
49
49
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/menuNav/good.html",
12
+ "which": "__targets__/menuNav/good.html",
13
13
  "what": "page with standard menu navigation"
14
14
  },
15
15
  {
@@ -52,7 +52,7 @@
52
52
  },
53
53
  {
54
54
  "type": "url",
55
- "which": "file://__dirname/validation/targets/menuNav/bad.html",
55
+ "which": "__targets__/menuNav/bad.html",
56
56
  "what": "page with deviant menu navigation"
57
57
  },
58
58
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/motion/good.html",
12
+ "which": "__targets__/motion/good.html",
13
13
  "what": "page without motion"
14
14
  },
15
15
  {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  {
32
32
  "type": "url",
33
- "which": "file://__dirname/validation/targets/motion/bad.html",
33
+ "which": "__targets__/motion/bad.html",
34
34
  "what": "page with motion"
35
35
  },
36
36
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/radioSet/good.html",
12
+ "which": "__targets__/radioSet/good.html",
13
13
  "what": "page with standard radio-button grouping"
14
14
  },
15
15
  {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "type": "url",
28
- "which": "file://__dirname/validation/targets/radioSet/bad.html",
28
+ "which": "__targets__/radioSet/bad.html",
29
29
  "what": "page with deviant radio-button grouping"
30
30
  },
31
31
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/role/good.html",
12
+ "which": "__targets__/role/good.html",
13
13
  "what": "page with standard roles"
14
14
  },
15
15
  {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  {
25
25
  "type": "url",
26
- "which": "file://__dirname/validation/targets/role/bad.html",
26
+ "which": "__targets__/role/bad.html",
27
27
  "what": "page with deviant roles"
28
28
  },
29
29
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/styleDiff/good.html",
12
+ "which": "__targets__/styleDiff/good.html",
13
13
  "what": "page with consistent styles"
14
14
  },
15
15
  {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  {
34
34
  "type": "url",
35
- "which": "file://__dirname/validation/targets/styleDiff/bad.html",
35
+ "which": "__targets__/styleDiff/bad.html",
36
36
  "what": "page with consistent styles"
37
37
  },
38
38
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/tabNav/good.html",
12
+ "which": "__targets__/tabNav/good.html",
13
13
  "what": "page with standard menu navigation"
14
14
  },
15
15
  {
@@ -52,7 +52,7 @@
52
52
  },
53
53
  {
54
54
  "type": "url",
55
- "which": "file://__dirname/validation/targets/tabNav/bad.html",
55
+ "which": "__targets__/tabNav/bad.html",
56
56
  "what": "page with deviant menu navigation"
57
57
  },
58
58
  {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "type": "url",
12
- "which": "file://__dirname/validation/targets/zIndex/good.html",
12
+ "which": "__targets__/zIndex/good.html",
13
13
  "what": "page with no explicit z-index attributes"
14
14
  },
15
15
  {
@@ -23,7 +23,7 @@
23
23
  },
24
24
  {
25
25
  "type": "url",
26
- "which": "file://__dirname/validation/targets/zIndex/bad.html",
26
+ "which": "__targets__/zIndex/bad.html",
27
27
  "what": "page with explicit z-index attributes roles"
28
28
  },
29
29
  {
@@ -1,13 +0,0 @@
1
- {
2
- "what": "Sample batch",
3
- "hosts": [
4
- {
5
- "which": "https://www.w3.org/",
6
- "what": "W3C"
7
- },
8
- {
9
- "which": "https://www.wikimedia.org/",
10
- "what": "Wikimedia"
11
- }
12
- ]
13
- }
@@ -1,11 +0,0 @@
1
- // sample.js
2
- // Sample executor for Testaro.
3
-
4
- const options = {
5
- reports: `${__dirname}/../../reports`,
6
- // To use the sample batch, uncomment the following line.
7
- batches: `${__dirname}/../../batches/sample.json`,
8
- script: `${__dirname}/../../scripts/app/sample.json`
9
- };
10
- const {handleRequest} = require('../../index');
11
- handleRequest(options);
@@ -1,21 +0,0 @@
1
- {
2
- "what": "Sample Testaro executor with 1 test",
3
- "strict": true,
4
- "commands": [
5
- {
6
- "type": "launch",
7
- "which": "chromium",
8
- "what": "Chromium browser"
9
- },
10
- {
11
- "type": "url",
12
- "which": "https://example.com/",
13
- "what": "simple page, replaced if there is a batch"
14
- },
15
- {
16
- "type": "test",
17
- "which": "bulk",
18
- "what": "bulk"
19
- }
20
- ]
21
- }