testaro 60.5.1 → 60.6.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
@@ -140,7 +140,7 @@ All of the tests that Testaro can perform are free of cost, except those perform
140
140
 
141
141
  ## Jobs
142
142
 
143
- A _job_ is an object that specifies what Testaro is to do. As Testaro performs a job, Testaro reports results by adding data to the job and making that enhanced object available as a _report_.
143
+ A _job_ is an object that specifies what Testaro is to do, and how. As Testaro performs a job, Testaro reports results by adding data to the job and making that enhanced object available as a _report_.
144
144
 
145
145
  ### Example of a job
146
146
 
@@ -192,7 +192,13 @@ Here is an example of a job:
192
192
  },
193
193
  {
194
194
  type: 'test',
195
- launch: {},
195
+ launch: {
196
+ browserID: 'chromium',
197
+ target: {
198
+ what: 'Real Estate Management',
199
+ url: 'https://abccorp.com/mgmt/realproperty.html'
200
+ }
201
+ },
196
202
  which: 'qualWeb',
197
203
  withNewContent: false,
198
204
  rules: ['QW-BP25', 'QW-BP26']
@@ -204,7 +210,7 @@ Here is an example of a job:
204
210
 
205
211
  This job tells Testaro to perform two _acts_. One performs one test of the Axe tool wih reporting at detail level 2, and the other performs two tests of the QualWeb tool.
206
212
 
207
- Each act includes a `launch` property with a default value. That instructs Testaro, before performing those tests, to launch a new Webkit browser, open a context (window) with some properties of an iPhone 8 and without a reduced-motion setting, create a page (tab), and navigate to a particular page of the `abccorp.com` website.
213
+ Each act includes a `launch` object property. In the first act it is an empty object, the browser ID and target URL specified by the job are used. In the second act it overrides the job values with per-act values.
208
214
 
209
215
  Job properties:
210
216
 
@@ -518,7 +524,18 @@ The target can be provided to QualWeb either as an existing page or as a URL. Ex
518
524
 
519
525
  The rules that Testaro can test for are implemented in files within the `testaro` directory.
520
526
 
521
- If you do not specify rules when using the `testaro` tool, Testaro will test for its default rules, namely the rules that have `true` values on the `defaultOn` property in the `allRules` array defined in the `tests/testaro.js` file. It will test for these rules in the order in which they appear in the array.
527
+ The Testaro rules are classified by an `allRules` array defined in the `tests/testaro.js` file. Each item in that array is an object with these properties:
528
+
529
+ - `id`: the rule ID.
530
+ - `what`: a description of the rule.
531
+ - `launchRole`: what a test for the rule does with respect to a browser launch:
532
+ - `sharer`: requires a browser and leaves it unchanged so the next test can safely reuse it
533
+ - `waster`: requires a browser and modifies it so the next test cannot safely reuse it
534
+ - `owner`: launches a custom browser itself and closes it at the end of the test
535
+ - `defaultOn`: whether the rule is to be tested for by default.
536
+ - `timeOut`: the maximum time in seconds allowed for a test for the rule.
537
+
538
+ If you do not specify rules when using the `testaro` tool, Testaro will test for its default rules. It will test for these rules in the order in which they appear in the array.
522
539
 
523
540
  The optional `rules` argument for a `testaro` test act is an array whose first item is either `'y'` or `'n'` and whose remaining items are rule IDs. If `'y'`, then only the specified rules’ tests are performed. If `'n'`, then all the default rules are tested for, **except** for the specified rules.
524
541
 
@@ -882,12 +899,17 @@ The arguments and behaviors described above for execution by a module apply here
882
899
 
883
900
  In addition to their uses described above, environment variables can be used by acts of type `test`, as documented in the `actSpecs.js` file.
884
901
 
885
- Before making Testaro run a job, you can optionally also set `DEBUG` (to `'true'` or anything else) and/or `WAITS` (to a non-negative integer). The effects of these variables are described in the `run.js` file.
902
+ Before making Testaro run a job, you can optionally also set `HEADED_BROWSER`, `DEBUG`, and/or `WAITS`. The effects of these variables are:
903
+
904
+ - `HEADED_BROWSER`: whether to run the browser in headed mode instead of the default headless mode
905
+ - `DEBUG`: whether to make logging verbose
906
+ - `WAITS`: the number of milliseconds to wait between actions
886
907
 
887
908
  You may store environment variables in an untracked `.env` file if you wish, and Testaro will recognize them. Here is a template for a `.env` file:
888
909
 
889
910
  ```conf
890
911
  AGENT=agentabc
912
+ HEADED_BROWSER=false
891
913
  DEBUG=false
892
914
  JOBDIR=../testing/jobs
893
915
  NETWATCH_URL_0_JOB=http://localhost:3000/api/assignJob/agentabc
@@ -974,9 +996,17 @@ Tools can become faulty. For example, Alfa stopped reporting any rule violations
974
996
 
975
997
  Testaro would become more reliable if the behavior of its tools were monitored for suspect changes.
976
998
 
999
+ ### Dependency deployment
1000
+
1001
+ The behavior of Testaro as a dependency of an application deployed on a virtual private server has been observed to be vulnerable to slower performance and more frequent test failures than when Testaro is deployed as a stand-alone application on a workstation. The configuration of Testaro has been tuned for mitigation of such behaviors.
1002
+
977
1003
  ### Containerized deployment
978
1004
 
979
- The experimental deployment of Testaro as a dependency in a containerized application has been unsuccessful. Playwnight errors have been thrown that are not thrown when the same application is deployed without containerization.
1005
+ The experimental deployment of Testaro as a dependency in a containerized application has been unsuccessful. Playwright errors have been thrown that are not thrown when the same application is deployed without containerization.
1006
+
1007
+ ### Headless browser fidelity
1008
+
1009
+ Testaro normally performs tests with headless browsers. Some experiments appear to have shown that some test results are inaccurate with headless browsers, but this has not been replicated. The `launch` function in the `run` module accepts a `headEmulation` argument with `'high'` and `'low'` values. Its purpose is to permit optimizations of headless browsers to be turned off (`high`), at some performance cost, when making the browsers behave and appear more similar to headed browsers improves test accuracy. Observation has, however, failed to show any performance cost. Therefore, `'high'` is currently the default value.
980
1010
 
981
1011
  ## Repository exclusions
982
1012
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "60.5.1",
3
+ "version": "60.6.0",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -39,9 +39,8 @@ const os = require('os');
39
39
 
40
40
  // CONSTANTS
41
41
 
42
- // Set DEBUG environment variable to 'true' to add debugging features.
42
+ const headedBrowser = process.env.HEADED_BROWSER === 'true';
43
43
  const debug = process.env.DEBUG === 'true';
44
- // Set WAITS environment variable to a positive number to insert delays (in ms).
45
44
  const waits = Number.parseInt(process.env.WAITS) || 0;
46
45
  const tmpDir = os.tmpdir();
47
46
 
@@ -61,80 +60,85 @@ const doTestAct = async () => {
61
60
  const act = report.acts[actIndex];
62
61
  // Get the tool name.
63
62
  const {which} = act;
64
- // Launch a browser, navigate to the URL, and update the page export of the run module.
65
- await launch(
66
- report,
67
- debug,
68
- waits,
69
- act.launch && act.launch.browserID || report.browserID,
70
- act.launch && act.launch.target && act.launch.target.url || report.target.url
71
- );
72
- // If the launch aborted the job:
73
- if (report.jobData && report.jobData.aborted) {
74
- // Close any existing browser.
75
- await browserClose();
76
- // Save the revised report.
77
- const reportJSON = JSON.stringify(report);
78
- await fs.writeFile(reportPath, reportJSON);
79
- // Report this.
80
- process.send('ERROR: Job aborted');
81
- }
82
- // Otherwise, i.e. if the launch did not abort the job:
83
- else {
84
- // Get the updated page.
85
- const {page} = require('../run');
86
- // If it exists:
87
- if (page) {
88
- try {
89
- // Make the act reporter perform the specified tests of the tool.
90
- const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
91
- // Add the data and result to the act.
92
- act.data = actReport.data;
93
- act.result = actReport.result;
94
- // If the tool reported that the page prevented testing:
95
- if (act.data && act.data.prevented) {
96
- // Add prevention data to the job data.
97
- report.jobData.preventions[which] = act.data.error;
98
- }
99
- // Close any existing browser.
100
- await browserClose();
101
- const reportJSON = JSON.stringify(report);
102
- // Save the revised report.
103
- await fs.writeFile(reportPath, reportJSON);
104
- // Send a completion message.
105
- process.send('Act completed');
106
- }
107
- // If the tool invocation failed:
108
- catch(error) {
109
- // Close any existing browser.
110
- await browserClose();
111
- // Save the revised report.
112
- const reportJSON = JSON.stringify(report);
113
- await fs.writeFile(reportPath, reportJSON);
114
- // Report the failure.
115
- const message = error.message.slice(0, 400);
116
- console.log(`ERROR: Test act ${act.which} failed (${message})`);
117
- process.send('ERROR performing the act');
118
- };
63
+ let page;
64
+ // If the tool is not Testaro:
65
+ if (which !== 'testaro') {
66
+ const browserID = act.launch && act.launch.browserID || report.browserID;
67
+ const targetURL = act.launch && act.launch.target && act.launch.target.url || report.target.url;
68
+ // Launch a browser, navigate to the URL, and update the run-module page export.
69
+ await launch(
70
+ report,
71
+ 'high',
72
+ browserID,
73
+ targetURL
74
+ );
75
+ // If the launch aborted the job:
76
+ if (report.jobData && report.jobData.aborted) {
77
+ // Close any existing browser.
78
+ await browserClose();
79
+ // Save the revised report.
80
+ const reportJSON = JSON.stringify(report);
81
+ await fs.writeFile(reportPath, reportJSON);
82
+ // Report this.
83
+ process.send('ERROR: Job aborted');
119
84
  }
120
- // Otherwise, i.e. if the page does not exist:
85
+ // Otherwise, i.e. if the launch did not abort the job:
121
86
  else {
122
- // Add data to the act.
123
- act.data ??= {};
124
- act.data.prevented = true;
125
- act.data.error = 'No page';
126
- // Add prevention data to the job data.
127
- report.jobData.preventions[which] = act.data.error;
87
+ // Get the updated page.
88
+ page = require('../run').page;
89
+ }
90
+ }
91
+ // If the page exists:
92
+ if (page || which === 'testaro') {
93
+ try {
94
+ // Make the act reporter perform the specified tests of the tool.
95
+ const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
96
+ // Add the data and result to the act.
97
+ act.data = actReport.data;
98
+ act.result = actReport.result;
99
+ // If the tool reported that the page prevented testing:
100
+ if (act.data && act.data.prevented) {
101
+ // Add prevention data to the job data.
102
+ report.jobData.preventions[which] = act.data.error;
103
+ }
128
104
  // Close any existing browser.
129
105
  await browserClose();
130
106
  const reportJSON = JSON.stringify(report);
131
107
  // Save the revised report.
132
108
  await fs.writeFile(reportPath, reportJSON);
133
- // Report this.
134
- const message = 'ERROR: No page';
135
- console.log(message);
136
- process.send(message);
109
+ // Send a completion message.
110
+ process.send('Act completed');
137
111
  }
112
+ // If the tool invocation failed:
113
+ catch(error) {
114
+ // Close any existing browser.
115
+ await browserClose();
116
+ // Save the revised report.
117
+ const reportJSON = JSON.stringify(report);
118
+ await fs.writeFile(reportPath, reportJSON);
119
+ // Report the failure.
120
+ const message = error.message.slice(0, 400);
121
+ console.log(`ERROR: Test act ${act.which} failed (${message})`);
122
+ process.send('ERROR performing the act');
123
+ };
124
+ }
125
+ // Otherwise, i.e. if the page does not exist:
126
+ else {
127
+ // Add data to the act.
128
+ act.data ??= {};
129
+ act.data.prevented = true;
130
+ act.data.error = 'No page';
131
+ // Add prevention data to the job data.
132
+ report.jobData.preventions[which] = act.data.error;
133
+ // Close any existing browser.
134
+ await browserClose();
135
+ const reportJSON = JSON.stringify(report);
136
+ // Save the revised report.
137
+ await fs.writeFile(reportPath, reportJSON);
138
+ // Report this.
139
+ const message = 'ERROR: No page';
140
+ console.log(message);
141
+ process.send(message);
138
142
  }
139
143
  };
140
144
 
package/run.js CHANGED
@@ -58,9 +58,8 @@ const os = require('os');
58
58
 
59
59
  // CONSTANTS
60
60
 
61
- // Set DEBUG environment variable to 'true' to add debugging features.
61
+ const headedBrowser = process.env.HEADED_BROWSER === 'true';
62
62
  const debug = process.env.DEBUG === 'true';
63
- // Set WAITS environment variable to a positive number to insert delays (in ms).
64
63
  const waits = Number.parseInt(process.env.WAITS) || 0;
65
64
  // CSS selectors for targets of moves.
66
65
  const moves = {
@@ -291,7 +290,7 @@ const browserClose = exports.browserClose = async () => {
291
290
  };
292
291
  // Launches a browser and navigates to a URL.
293
292
  const launch = exports.launch = async (
294
- report, debug, waits, tempBrowserID, tempURL, retries = 2
293
+ report, headEmulation, tempBrowserID, tempURL, retries = 2
295
294
  ) => {
296
295
  const act = report.acts[actIndex];
297
296
  const {device} = report;
@@ -306,7 +305,37 @@ const launch = exports.launch = async (
306
305
  const browserType = playwrightBrowsers[browserID];
307
306
  // Close any current browser.
308
307
  await browserClose();
309
- // Define browser options.
308
+ // Define the browser-option args, depending on the browser type and head-emulation level.
309
+ const browserOptionArgs = [];
310
+ if (browserID === 'chromium') {
311
+ browserOptionArgs.push(
312
+ '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled'
313
+ );
314
+ if (headEmulation === 'high') {
315
+ browserOptionArgs.push(
316
+ '--no-sandbox',
317
+ '--disable-setuid-sandbox',
318
+ '--disable-gpu',
319
+ '--disable-software-rasterizer',
320
+ '--force-device-scale-factor=1',
321
+ '--disable-default-apps',
322
+ '--disable-extensions',
323
+ '--disable-sync',
324
+ '--disable-background-timer-throttling',
325
+ '--disable-backgrounding-occluded-windows',
326
+ '--disable-renderer-backgrounding',
327
+ '--disable-background-networking',
328
+ '--force-color-profile=srgb',
329
+ '--disable-features=TranslateUI,VizDisplayCompositor',
330
+ '--disable-ipc-flooding-protection',
331
+ '--disable-logging',
332
+ '--disable-permissions-api',
333
+ '--disable-notifications',
334
+ '--disable-popup-blocking'
335
+ );
336
+ }
337
+ }
338
+ // Define the browser options.
310
339
  const browserOptions = {
311
340
  logger: {
312
341
  isEnabled: () => false,
@@ -316,11 +345,9 @@ const launch = exports.launch = async (
316
345
  }
317
346
  }
318
347
  },
319
- headless: ! debug,
348
+ headless: ! headedBrowser,
320
349
  slowMo: waits || 0,
321
- ...(browserID === 'chromium' && {
322
- args: ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled']
323
- })
350
+ args: browserOptionArgs
324
351
  };
325
352
  try {
326
353
  // Replace the browser with a new one.
@@ -460,7 +487,7 @@ const launch = exports.launch = async (
460
487
  );
461
488
  await wait(1000 * waitSeconds + 100);
462
489
  // Then retry the launch and navigation.
463
- return launch(report, debug, waits, tempBrowserID, tempURL, retries - 1);
490
+ return launch(report, headEmulation, tempBrowserID, tempURL, retries - 1);
464
491
  }
465
492
  // Otherwise, i.e. if no retries remain:
466
493
  else {
@@ -765,8 +792,7 @@ const doActs = async (report, opts = {}) => {
765
792
  // Launch a browser, navigate to a page, and add the result to the act.
766
793
  await launch(
767
794
  report,
768
- debug,
769
- waits,
795
+ 'high',
770
796
  actLaunchSpecs[0],
771
797
  actLaunchSpecs[1]
772
798
  );
@@ -1516,8 +1542,7 @@ const doActs = async (report, opts = {}) => {
1516
1542
  // Replace the browser and navigate to the URL.
1517
1543
  await launch(
1518
1544
  report,
1519
- debug,
1520
- waits,
1545
+ 'high',
1521
1546
  specs[0],
1522
1547
  specs[1]
1523
1548
  );
package/tests/testaro.js CHANGED
@@ -44,410 +44,413 @@ const allRules = [
44
44
  {
45
45
  id: 'shoot0',
46
46
  what: 'first page screenshot',
47
- contaminator: false,
47
+ launchRole: 'owner',
48
48
  timeOut: 5,
49
49
  defaultOn: true
50
50
  },
51
51
  {
52
52
  id: 'adbID',
53
53
  what: 'elements with ambiguous or missing referenced descriptions',
54
- contaminator: false,
54
+ launchRole: 'sharer',
55
55
  timeOut: 5,
56
56
  defaultOn: true
57
57
  },
58
58
  {
59
59
  id: 'allCaps',
60
60
  what: 'leaf elements with entirely upper-case text longer than 7 characters',
61
- contaminator: false,
61
+ launchRole: 'sharer',
62
62
  timeOut: 10,
63
63
  defaultOn: true
64
64
  },
65
65
  {
66
66
  id: 'allHidden',
67
67
  what: 'page that is entirely or mostly hidden',
68
- contaminator: false,
68
+ launchRole: 'sharer',
69
69
  timeOut: 5,
70
70
  defaultOn: true
71
71
  },
72
72
  {
73
73
  id: 'allSlanted',
74
74
  what: 'leaf elements with entirely italic or oblique text longer than 39 characters',
75
- contaminator: false,
75
+ launchRole: 'sharer',
76
76
  timeOut: 5,
77
77
  defaultOn: true
78
78
  },
79
79
  {
80
80
  id: 'altScheme',
81
81
  what: 'img elements with alt attributes having URLs as their entire values',
82
- contaminator: false,
82
+ launchRole: 'sharer',
83
83
  timeOut: 5,
84
84
  defaultOn: true
85
85
  },
86
86
  {
87
87
  id: 'attVal',
88
88
  what: 'elements with attributes having illicit values',
89
- contaminator: false,
89
+ launchRole: 'sharer',
90
90
  timeOut: 5,
91
91
  defaultOn: false
92
92
  },
93
93
  {
94
94
  id: 'dupAtt',
95
95
  what: 'duplicate attribute values',
96
- contaminator: false,
96
+ launchRole: 'sharer',
97
97
  timeOut: 5,
98
98
  defaultOn: true
99
99
  },
100
100
  {
101
101
  id: 'autocomplete',
102
102
  what: 'name and email inputs without autocomplete attributes',
103
- contaminator: false,
103
+ launchRole: 'sharer',
104
104
  timeOut: 5,
105
105
  defaultOn: true
106
106
  },
107
107
  {
108
108
  id: 'bulk',
109
109
  what: 'large count of visible elements',
110
- contaminator: false,
110
+ launchRole: 'sharer',
111
111
  timeOut: 5,
112
112
  defaultOn: true
113
113
  },
114
114
  {
115
115
  id: 'captionLoc',
116
116
  what: 'caption elements that are not first children of table elements',
117
- contaminator: false,
117
+ launchRole: 'sharer',
118
118
  timeOut: 5,
119
119
  defaultOn: true
120
120
  },
121
121
  {
122
122
  id: 'datalistRef',
123
123
  what: 'elements with ambiguous or missing referenced datalist elements',
124
- contaminator: false,
124
+ launchRole: 'sharer',
125
125
  timeOut: 5,
126
126
  defaultOn: true
127
127
  },
128
128
  {
129
129
  id: 'distortion',
130
130
  what: 'distorted text',
131
- contaminator: false,
131
+ launchRole: 'sharer',
132
132
  timeOut: 10,
133
133
  defaultOn: true
134
134
  },
135
135
  {
136
136
  id: 'docType',
137
137
  what: 'document without a doctype property',
138
- contaminator: false,
138
+ launchRole: 'sharer',
139
139
  timeOut: 10,
140
140
  defaultOn: true
141
141
  },
142
142
  {
143
143
  id: 'dupAtt',
144
144
  what: 'elements with duplicate attributes',
145
- contaminator: false,
145
+ launchRole: 'sharer',
146
146
  timeOut: 5,
147
147
  defaultOn: true
148
148
  },
149
149
  {
150
150
  id: 'embAc',
151
151
  what: 'active elements embedded in links or buttons',
152
- contaminator: false,
152
+ launchRole: 'sharer',
153
153
  timeOut: 5,
154
154
  defaultOn: true
155
155
  },
156
156
  {
157
157
  id: 'headEl',
158
158
  what: 'invalid elements within the head',
159
- contaminator: false,
159
+ launchRole: 'sharer',
160
160
  timeOut: 5,
161
161
  defaultOn: true
162
162
  },
163
163
  {
164
164
  id: 'headingAmb',
165
165
  what: 'same-level sibling headings with identical texts',
166
- contaminator: false,
166
+ launchRole: 'sharer',
167
167
  timeOut: 5,
168
168
  defaultOn: true
169
169
  },
170
170
  {
171
171
  id: 'hr',
172
172
  what: 'hr element instead of styles used for vertical segmentation',
173
- contaminator: false,
173
+ launchRole: 'sharer',
174
174
  timeOut: 5,
175
175
  defaultOn: true
176
176
  },
177
177
  {
178
178
  id: 'imageLink',
179
179
  what: 'links with image files as their destinations',
180
- contaminator: false,
180
+ launchRole: 'sharer',
181
181
  timeOut: 5,
182
182
  defaultOn: true
183
183
  },
184
184
  {
185
185
  id: 'labClash',
186
186
  what: 'labeling inconsistencies',
187
- contaminator: false,
187
+ launchRole: 'sharer',
188
188
  timeOut: 10,
189
189
  defaultOn: true
190
190
  },
191
191
  {
192
192
  id: 'legendLoc',
193
193
  what: 'legend elements that are not first children of fieldset elements',
194
- contaminator: false,
194
+ launchRole: 'sharer',
195
195
  timeOut: 5,
196
196
  defaultOn: true
197
197
  },
198
198
  {
199
199
  id: 'lineHeight',
200
200
  what: 'text with a line height less than 1.5 times its font size',
201
- contaminator: false,
201
+ launchRole: 'sharer',
202
202
  timeOut: 10,
203
203
  defaultOn: true
204
204
  },
205
205
  {
206
206
  id: 'linkAmb',
207
207
  what: 'links with identical texts but different destinations',
208
- contaminator: false,
208
+ launchRole: 'sharer',
209
209
  timeOut: 5,
210
210
  defaultOn: true
211
211
  },
212
212
  {
213
213
  id: 'linkExt',
214
214
  what: 'links that automatically open new windows',
215
- contaminator: false,
215
+ launchRole: 'sharer',
216
216
  timeOut: 5,
217
217
  defaultOn: true
218
218
  },
219
219
  {
220
220
  id: 'linkOldAtt',
221
221
  what: 'links with deprecated attributes',
222
- contaminator: false,
222
+ launchRole: 'sharer',
223
223
  timeOut: 5,
224
224
  defaultOn: true
225
225
  },
226
226
  {
227
227
  id: 'linkTitle',
228
228
  what: 'links with title attributes repeating text content',
229
- contaminator: false,
229
+ launchRole: 'sharer',
230
230
  timeOut: 5,
231
231
  defaultOn: true
232
232
  },
233
233
  {
234
234
  id: 'linkTo',
235
235
  what: 'links without destinations',
236
- contaminator: false,
236
+ launchRole: 'sharer',
237
237
  timeOut: 5,
238
238
  defaultOn: true
239
239
  },
240
240
  {
241
241
  id: 'linkUl',
242
242
  what: 'missing underlines on inline links',
243
- contaminator: false,
243
+ launchRole: 'sharer',
244
244
  timeOut: 10,
245
245
  defaultOn: true
246
246
  },
247
247
  {
248
248
  id: 'miniText',
249
249
  what: 'text smaller than 11 pixels',
250
- contaminator: false,
250
+ launchRole: 'sharer',
251
251
  timeOut: 5,
252
252
  defaultOn: true
253
253
  },
254
254
  {
255
255
  id: 'nonTable',
256
256
  what: 'table elements used for layout',
257
- contaminator: false,
257
+ launchRole: 'sharer',
258
258
  timeOut: 5,
259
259
  defaultOn: true
260
260
  },
261
261
  {
262
262
  id: 'optRoleSel',
263
263
  what: 'Non-option elements with option roles that have no aria-selected attributes',
264
- contaminator: false,
264
+ launchRole: 'sharer',
265
265
  timeOut: 5,
266
266
  defaultOn: true
267
267
  },
268
268
  {
269
269
  id: 'phOnly',
270
270
  what: 'input elements with placeholders but no accessible names',
271
- contaminator: false,
271
+ launchRole: 'sharer',
272
272
  timeOut: 5,
273
273
  defaultOn: true
274
274
  },
275
275
  {
276
276
  id: 'pseudoP',
277
277
  what: 'adjacent br elements suspected of nonsemantically simulating p elements',
278
- contaminator: false,
278
+ launchRole: 'sharer',
279
279
  timeOut: 5,
280
280
  defaultOn: true
281
281
  },
282
282
  {
283
283
  id: 'radioSet',
284
284
  what: 'radio buttons not grouped into standard field sets',
285
- contaminator: false,
285
+ launchRole: 'sharer',
286
286
  timeOut: 5,
287
287
  defaultOn: true
288
288
  },
289
289
  {
290
290
  id: 'role',
291
291
  what: 'native-replacing explicit roles',
292
- contaminator: false,
292
+ launchRole: 'sharer',
293
293
  timeOut: 5,
294
294
  defaultOn: true
295
295
  },
296
296
  {
297
297
  id: 'secHeading',
298
298
  what: 'headings that violate the logical level order in their sectioning containers',
299
- contaminator: false,
299
+ launchRole: 'sharer',
300
300
  timeOut: 5,
301
301
  defaultOn: true
302
302
  },
303
303
  {
304
304
  id: 'styleDiff',
305
305
  what: 'style inconsistencies',
306
- contaminator: false,
306
+ launchRole: 'sharer',
307
307
  timeOut: 5,
308
308
  defaultOn: true
309
309
  },
310
310
  {
311
311
  id: 'targetSmall',
312
312
  what: 'buttons, inputs, and non-inline links smaller than 44 pixels wide and high',
313
- contaminator: false,
313
+ launchRole: 'sharer',
314
314
  timeOut: 5,
315
315
  defaultOn: true
316
316
  },
317
317
  {
318
318
  id: 'targetTiny',
319
319
  what: 'buttons, inputs, and non-inline links smaller than 24 pixels wide and high',
320
- contaminator: false,
320
+ launchRole: 'sharer',
321
321
  timeOut: 5,
322
322
  defaultOn: true
323
323
  },
324
324
  {
325
325
  id: 'textSem',
326
326
  what: 'semantically vague elements i, b, and/or small',
327
- contaminator: false,
327
+ launchRole: 'sharer',
328
328
  timeOut: 10,
329
329
  defaultOn: true
330
330
  },
331
331
  {
332
332
  id: 'title',
333
333
  what: 'page title',
334
- contaminator: false,
334
+ launchRole: 'sharer',
335
335
  timeOut: 5,
336
336
  defaultOn: false
337
337
  },
338
338
  {
339
339
  id: 'titledEl',
340
340
  what: 'title attributes on inappropriate elements',
341
- contaminator: false,
341
+ launchRole: 'sharer',
342
342
  timeOut: 5,
343
343
  defaultOn: true
344
344
  },
345
345
  {
346
346
  id: 'zIndex',
347
347
  what: 'non-default Z indexes',
348
- contaminator: false,
348
+ launchRole: 'sharer',
349
349
  timeOut: 5,
350
350
  defaultOn: true
351
351
  },
352
352
  {
353
353
  id: 'shoot1',
354
354
  what: 'second page screenshot',
355
- contaminator: false,
355
+ launchRole: 'owner',
356
356
  timeOut: 5,
357
357
  defaultOn: true
358
358
  },
359
359
  {
360
360
  id: 'motion',
361
361
  what: 'motion without user request, measured across tests',
362
- contaminator: false,
362
+ launchRole: 'sharer',
363
363
  timeOut: 5,
364
364
  defaultOn: true
365
365
  },
366
366
  {
367
367
  id: 'buttonMenu',
368
368
  what: 'nonstandard keyboard navigation between items of button-controlled menus',
369
- contaminator: true,
369
+ launchRole: 'waster',
370
370
  timeOut: 15,
371
371
  defaultOn: true
372
372
  },
373
373
  {
374
374
  id: 'elements',
375
375
  what: 'data on specified elements',
376
- contaminator: true,
376
+ launchRole: 'waster',
377
377
  timeOut: 10,
378
378
  defaultOn: false
379
379
  },
380
380
  {
381
381
  id: 'focAll',
382
382
  what: 'discrepancies between focusable and Tab-focused elements',
383
- contaminator: true,
383
+ launchRole: 'waster',
384
384
  timeOut: 10,
385
385
  defaultOn: true
386
386
  },
387
387
  {
388
388
  id: 'focInd',
389
389
  what: 'missing and nonstandard focus indicators',
390
- contaminator: true,
390
+ launchRole: 'waster',
391
391
  timeOut: 10,
392
392
  defaultOn: true
393
393
  },
394
394
  {
395
395
  id: 'focOp',
396
396
  what: 'Tab-focusable elements that are not operable',
397
- contaminator: true,
397
+ launchRole: 'waster',
398
398
  timeOut: 5,
399
399
  defaultOn: true
400
400
  },
401
401
  {
402
402
  id: 'focVis',
403
403
  what: 'links that are not entirely visible when focused',
404
- contaminator: true,
404
+ launchRole: 'waster',
405
405
  timeOut: 10,
406
406
  defaultOn: true
407
407
  },
408
408
  {
409
409
  id: 'hover',
410
410
  what: 'hover-caused content changes',
411
- contaminator: true,
411
+ launchRole: 'waster',
412
412
  timeOut: 10,
413
413
  defaultOn: true
414
414
  },
415
415
  {
416
416
  id: 'hovInd',
417
417
  what: 'hover indication nonstandard',
418
- contaminator: true,
418
+ launchRole: 'waster',
419
419
  timeOut: 10,
420
420
  defaultOn: true
421
421
  },
422
- {
423
- id: 'motionSolo',
424
- what: 'motion without user request, measured within this test',
425
- contaminator: true,
426
- timeOut: 15,
427
- defaultOn: false
428
- },
429
422
  {
430
423
  id: 'opFoc',
431
424
  what: 'operable elements that are not Tab-focusable',
432
- contaminator: true,
425
+ launchRole: 'waster',
433
426
  timeOut: 10,
434
427
  defaultOn: true
435
428
  },
436
429
  {
437
430
  id: 'tabNav',
438
431
  what: 'nonstandard keyboard navigation between elements with the tab role',
439
- contaminator: true,
432
+ launchRole: 'waster',
440
433
  timeOut: 10,
441
434
  defaultOn: true
442
435
  },
436
+ {
437
+ id: 'motionSolo',
438
+ what: 'motion without user request, measured within this test',
439
+ launchRole: 'waster',
440
+ timeOut: 15,
441
+ defaultOn: false
442
+ },
443
443
  {
444
444
  id: 'textNodes',
445
445
  what: 'data on specified text nodes',
446
- contaminator: true,
446
+ launchRole: 'waster',
447
447
  timeOut: 10,
448
448
  defaultOn: false
449
449
  }
450
450
  ];
451
+ const headedBrowser = process.env.HEADED_BROWSER === 'true';
452
+ const debug = process.env.DEBUG === 'true';
453
+ const waits = Number.parseInt(process.env.WAITS) || 0;
451
454
  const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1;
452
455
 
453
456
  // ERROR HANDLER
@@ -485,11 +488,11 @@ const wait = ms => {
485
488
  };
486
489
  // Conducts and reports Testaro tests.
487
490
  exports.reporter = async (page, report, actIndex) => {
488
- const url = await page.url();
489
491
  const act = report.acts[actIndex];
490
492
  const {args, stopOnFail, withItems} = act;
491
- const launchOptions = act.launch;
492
- const browserID = launchOptions ? launchOptions.browserID || report.browserID : report.browserID;
493
+ const target = act.target || report.target;
494
+ const url = target.url;
495
+ const browserID = act.launch ? act.launch.browserID || report.browserID : report.browserID;
493
496
  const argRules = args ? Object.keys(args) : null;
494
497
  // Get the specification of rules to be tested for.
495
498
  const ruleSpec = act.rules
@@ -528,34 +531,36 @@ exports.reporter = async (page, report, actIndex) => {
528
531
  : allRules.filter(rule => rule.defaultOn && ! allRuleIDs.includes(rule.id));
529
532
  const jobRules = allRules.filter(rule => jobRuleIDs.includes(rule.id));
530
533
  const testTimes = [];
531
- let contaminatorsStarted = false;
532
534
  // For each rule to be tested for:
533
- for (const rule of jobRules) {
535
+ for (const ruleIndexString in jobRules) {
536
+ const ruleIndex = Number.parseInt(ruleIndexString);
537
+ const rule = jobRules[ruleIndex];
534
538
  const ruleID = rule.id;
535
539
  console.log(`Starting rule ${ruleID}`);
536
- const pageClosed = page ? page.isClosed() : true;
537
- const isContaminator = rule.contaminator;
538
- // If it is a contaminator other than the first one or the page has closed:
539
- if (contaminatorsStarted || pageClosed) {
540
- // If the page has closed:
541
- if (pageClosed) {
540
+ // Make the browser emulate headedness in all cases, because performance does not suffer.
541
+ const headEmulation = ruleID.startsWith('shoot') ? 'high' : 'high';
542
+ // Get whether it needs a new browser launched.
543
+ const needsLaunch = ruleIndex
544
+ && jobRules[ruleIndex - 1].launchRole !== 'sharer'
545
+ && rule.launchRole !== 'owner'
546
+ || ! ruleIndex;
547
+ const pageClosed = page && page.isClosed();
548
+ // If it does, or if the page has closed:
549
+ if (needsLaunch || pageClosed) {
550
+ // If the page has closed when it is expected to be open:
551
+ if (pageClosed && ! needsLaunch) {
542
552
  // Report this.
543
553
  console.log(`WARNING: Relaunching browser for test ${rule} after abnormal closure`);
544
554
  }
545
555
  // Replace the browser and the page and navigate to the target.
546
556
  await launch(
547
557
  report,
548
- process.env.DEBUG === 'true',
549
- Number.parseInt(process.env.WAITS) || 0,
558
+ headEmulation,
550
559
  browserID,
551
560
  url
552
561
  );
553
562
  page = require('../run').page;
554
563
  }
555
- // If the rule is a contaminator, ensure that future tests use new browsers.
556
- if (isContaminator) {
557
- contaminatorsStarted = true;
558
- }
559
564
  // Report crashes and disconnections during this test.
560
565
  let crashHandler;
561
566
  let disconnectHandler;
@@ -573,7 +578,7 @@ exports.reporter = async (page, report, actIndex) => {
573
578
  };
574
579
  browser.on('disconnected', disconnectHandler);
575
580
  }
576
- // Initialize an argument array.
581
+ // Initialize an argument array for reporter or jsonTest.
577
582
  const ruleArgs = [page, withItems];
578
583
  const ruleFileNames = await fs.readdir(`${__dirname}/../testaro`);
579
584
  const isJS = ruleFileNames.includes(`${ruleID}.js`);
@@ -658,11 +663,10 @@ exports.reporter = async (page, report, actIndex) => {
658
663
  `WARNING: Retry ${3 - testRetries--} of test ${ruleID} starting after page closed`
659
664
  );
660
665
  await wait(2000);
661
- // Replace the browser and the page and navigate to the target.
666
+ // Replace the browser and the page in the run module and navigate to the target.
662
667
  await launch(
663
668
  report,
664
- process.env.DEBUG === 'true',
665
- Number.parseInt(process.env.WAITS) || 0,
669
+ headEmulation,
666
670
  report.browserID,
667
671
  url
668
672
  );
@@ -21,8 +21,7 @@
21
21
  ]
22
22
  }
23
23
  ],
24
- "sources": {
25
- },
24
+ "sources": {},
26
25
  "standard": "only",
27
26
  "observe": false,
28
27
  "sendReportTo": "http://localhost:3007/api",