testaro 42.0.1 → 43.0.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/README.md CHANGED
@@ -132,11 +132,26 @@ Here is an example of a job:
132
132
  ```javaScript
133
133
  {
134
134
  id: '250110T1200-7f-4',
135
+ what: 'monthly health check',
135
136
  strict: true,
136
137
  isolate: true,
137
138
  standard: 'also',
138
139
  observe: false,
139
- deviceID: 'Kindle Fire HDX',
140
+ device: {
141
+ id: 'iPhone 8',
142
+ windowOptions: {
143
+ reducedMotion: 'no-preference',
144
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1',
145
+ viewport: {
146
+ width: 375,
147
+ height: 667
148
+ },
149
+ deviceScaleFactor: 2,
150
+ isMobile: true,
151
+ hasTouch: true,
152
+ defaultBrowserType: 'webkit'
153
+ }
154
+ },
140
155
  browserID: 'webkit',
141
156
  lowMotion: false,
142
157
  timeLimit: 80,
@@ -176,7 +191,7 @@ Here is an example of a job:
176
191
  ```
177
192
 
178
193
  This job contains three _acts_, telling Testaro to:
179
- 1. Launch a Webkit browser without a reduced-motion setting, open a tab with the properties of a Kindle Fire HDX device, and navigate to `https://abccorp.com/mgmt/realproperty.html`.
194
+ 1. Launch a Webkit browser without a reduced-motion setting, open a window with the properties of an iPhone 8 device, create a page (tab), and navigate to `https://abccorp.com/mgmt/realproperty.html`.
180
195
  1. Perform the test for the `landmark-complementary-is-top-level` rule of the `axe` tool and report the test result with Axe detail level 2.
181
196
  1. Perform the tests for rules `QW-BP25` and `QW-BP26` of the `qualWeb` tool on the existing page.
182
197
 
@@ -186,9 +201,8 @@ Job properties:
186
201
  - `strict`: `true` or `false`, indicating whether _substantive redirections_ should be treated as failures. These are redirections that do more than add or subtract a final slash.
187
202
  - `standard`: `'also'`, `'only'`, or `'no'`, indicating whether rule-violation instances are to be reported in tool-native formats and also in the Testaro standard format, only in the standard format, or only in the tool-native formats.
188
203
  - `observe`: `true` or `false`, indicating whether tool and Testaro-rule invocations are to be reported to the server as they occur, so that the server can update a waiting client.
189
- - `deviceID`: the ID of the device that some browser properties will resemble, unless overridden by a `launch` act. It must be `'default'` or the ID of one of about 125 devices recognized by Playwright, published at `https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json`.
204
+ - `device`: the ID of a device and the properties of each new browser context (window) that will be set for conformity to that device, unless overridden by a `launch` act. It must be `'default'` or the ID of one of [about 125 devices recognized by Playwright](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json).
190
205
  - `browserID`: the ID of the browser to be used, unless overridden by a `launch` act. It must be `'chromium'`, `'firefox'`, or `'webkit`'.
191
- - `lowMotion`: whether the browser is to create tabs with the `reduce-motion` option set to `reduce` instead of `no-preference`.
192
206
  - `timeLimit`: the number of seconds allowed for the execution of the job.
193
207
  - `creationTimeStamp`: a string in `yymmddThhMM` format, describing when the job was created.
194
208
  - `executionTimeStamp`: a string in `yymmddThhMM` format, specifying a date and time before which the job is not to be performed.
@@ -237,7 +251,7 @@ When the texts of multiple elements of the same type will contain the same `whic
237
251
 
238
252
  #### Navigations
239
253
 
240
- An example of a **navigation** is the act of type `launch` above. The launch configuration is inherited from the `deviceID`, `browserID`, `lowMotion`, and `target` properties of the job, except that the act may override any of those properties.
254
+ An example of a **navigation** is the act of type `launch` in the job example above. That `launch` act has only a `type` property. If you want a particular `launch` act to use a different browser type or to navigate to a different target from the job defaults, the `launch` act can have a `browserID` and/or a `target` property.
241
255
 
242
256
  If any act alters the page, you can restore the page to its original state for the next act by inserting a new `launch` act (and, if necessary, additional page-specific acts) between them.
243
257
 
package/actSpecs.js CHANGED
@@ -54,10 +54,8 @@ exports.actSpecs = {
54
54
  launch: [
55
55
  'Launch a Playwright browser',
56
56
  {
57
- url: [false, 'string', 'isURL', 'initial URL to navigate to'],
58
- deviceID: [false, 'string', 'isDeviceID', 'Playwright device ID if not default, e.g. iPhone 6 landscape'],
59
- browserID: [false, 'string', 'isBrowserID', 'chromium, firefox, or webkit if not job default'],
60
- lowMotion: [false, 'boolean', '', 'set reduced-motion option if true'],
57
+ target: [false, 'object', '', 'target different from sources.target of the job'],
58
+ browserID: [false, 'string', 'isBrowserID', 'browser type different from browserID of the job'],
61
59
  what: [false, 'string', 'hasLength', 'comment']
62
60
  }
63
61
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "42.0.1",
3
+ "version": "43.0.2",
4
4
  "description": "Run 1000 web accessibility tests from 10 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/job.js CHANGED
@@ -29,8 +29,8 @@
29
29
 
30
30
  // Requirements for acts.
31
31
  const {actSpecs} = require('../actSpecs');
32
- // Module to validate device IDs.
33
- const {isDeviceID} = require('./device');
32
+ // Data on devices.
33
+ const {devices} = require('playwright');
34
34
  // Module to get dates from time stamps.
35
35
  const {dateOf} = require('./dateOf');
36
36
 
@@ -82,9 +82,6 @@ const hasSubtype = (variable, subtype) => {
82
82
  else if (subtype === 'isURL') {
83
83
  return isURL(variable);
84
84
  }
85
- else if (subtype === 'isDeviceID') {
86
- return isDeviceID(variable);
87
- }
88
85
  else if (subtype === 'isBrowserID') {
89
86
  return isBrowserID(variable);
90
87
  }
@@ -119,7 +116,7 @@ const hasSubtype = (variable, subtype) => {
119
116
  }
120
117
  };
121
118
  // Validates a browser type.
122
- const isBrowserID = type => ['chromium', 'firefox', 'webkit'].includes(type);
119
+ const isBrowserID = exports.isBrowserID = type => ['chromium', 'firefox', 'webkit'].includes(type);
123
120
  // Validates a load state.
124
121
  const isState = string => ['loaded', 'idle'].includes(string);
125
122
  // Validates a URL.
@@ -179,6 +176,8 @@ const isValidAct = exports.isValidAct = act => {
179
176
  return false;
180
177
  }
181
178
  };
179
+ // Returns whether a device ID is valid.
180
+ const isDeviceID = deviceID => deviceID === 'default' || !! devices[deviceID];
182
181
  // Returns blank if a job is valid, or an error message.
183
182
  exports.isValidJob = job => {
184
183
  // If any job was provided:
@@ -190,9 +189,8 @@ exports.isValidJob = job => {
190
189
  isolate,
191
190
  standard,
192
191
  observe,
193
- deviceID,
192
+ device,
194
193
  browserID,
195
- lowMotion,
196
194
  timeLimit,
197
195
  creationTimeStamp,
198
196
  executionTimeStamp,
@@ -217,15 +215,12 @@ exports.isValidJob = job => {
217
215
  if (typeof observe !== 'boolean') {
218
216
  return 'Bad job observe';
219
217
  }
220
- if (! isDeviceID(deviceID)) {
218
+ if (device.id !== 'default' && ! devices[device.id]) {
221
219
  return 'Bad job deviceID';
222
220
  }
223
- if (! ['chromium', 'firefox', 'webkit'].includes(browserID)) {
221
+ if (! isBrowserID(browserID)) {
224
222
  return 'Bad job browserID';
225
223
  }
226
- if (typeof lowMotion !== 'boolean') {
227
- return 'Bad job lowMotion';
228
- }
229
224
  if (typeof timeLimit !== 'number' || timeLimit < 1) {
230
225
  return 'Bad job timeLimit';
231
226
  }
package/run.js CHANGED
@@ -30,15 +30,13 @@
30
30
  // Module to keep secrets.
31
31
  require('dotenv').config();
32
32
  // Module to validate jobs.
33
- const {isValidJob, tools} = require('./procs/job');
33
+ const {isBrowserID, isValidJob, tools} = require('./procs/job');
34
34
  // Module to standardize report formats.
35
35
  const {standardize} = require('./procs/standardize');
36
36
  // Module to identify element bounding boxes.
37
37
  const {identify} = require('./procs/identify');
38
38
  // Module to send a notice to an observer.
39
39
  const {tellServer} = require('./procs/tellServer');
40
- // Module to get device options.
41
- const {getDeviceOptions, isDeviceID} = require('./procs/device');
42
40
 
43
41
  // CONSTANTS
44
42
 
@@ -186,16 +184,15 @@ const browserClose = async () => {
186
184
  }
187
185
  };
188
186
  // Launches a browser, navigates to a URL, and returns browser data.
189
- const launch = async (report, url, debug, waits, deviceID, browserID, lowMotion) => {
190
- // Get the default arguments.
191
- url ??= report.url;
192
- deviceID ??= report.deviceID;
193
- browserID ??= report.browserID;
194
- lowMotion ??= report.lowMotion;
187
+ const launch = async (report, debug, waits, tempBrowserID, tempTarget) => {
188
+ const {browserID, device, target} = report;
189
+ // Get the default arguments if not overridden.
190
+ browserID ??= browserID;
191
+ const {url} = (tempTarget || target);
195
192
  // If the specified browser type exists:
196
- if (! browserID || ['chromium', 'firefox', 'webkit'].includes(browserID)) {
193
+ if (isBrowserID(browserID)) {
197
194
  // Create a browser of the specified or default type.
198
- const browserType = require('playwright')[browserID || report.browserID];
195
+ const browserType = require('playwright')[tempBrowserID || browserID];
199
196
  // Close the current browser, if any.
200
197
  await browserClose();
201
198
  // Define browser options.
@@ -218,122 +215,106 @@ const launch = async (report, url, debug, waits, deviceID, browserID, lowMotion)
218
215
  error: 'Browser launch failed'
219
216
  };
220
217
  });
221
- // Get the device options for a new context.
222
- const deviceOptions = getDeviceOptions(
223
- deviceID || 'default', lowMotion ? 'reduce-motion' : 'no-preference'
224
- );
225
- // If the device is valid:
226
- if (deviceOptions) {
227
- // Open a context (i.e. browser tab), with reduced motion if specified.
228
- const browserContext = await browser.newContext(deviceOptions);
229
- // Prevent default timeouts.
230
- browserContext.setDefaultTimeout(0);
231
- // When a page (i.e. browser tab) is added to the browser context (i.e. browser window):
232
- browserContext.on('page', async page => {
233
- // Ensure the report has a jobData property.
234
- report.jobData ??= {};
235
- report.jobData.logCount ??= 0;
236
- report.jobData.logSize ??= 0;
237
- report.jobData.errorLogCount ??= 0;
238
- report.jobData.browserTabOptions ??= deviceOptions;
239
- // Add any error events to the count of logging errors.
240
- page.on('crash', () => {
241
- report.jobData.errorLogCount++;
242
- console.log('Page crashed');
243
- });
244
- page.on('pageerror', () => {
245
- report.jobData.errorLogCount++;
246
- });
247
- page.on('requestfailed', () => {
248
- report.jobData.errorLogCount++;
249
- });
250
- // If the page emits a message:
251
- page.on('console', msg => {
252
- const msgText = msg.text();
253
- let indentedMsg = '';
254
- // If debugging is on:
255
- if (debug) {
256
- // Log a summary of the message on the console.
257
- const parts = [msgText.slice(0, 75)];
258
- if (msgText.length > 75) {
259
- parts.push(msgText.slice(75, 150));
260
- if (msgText.length > 150) {
261
- const tail = msgText.slice(150).slice(-150);
262
- if (msgText.length > 300) {
263
- parts.push('...');
264
- }
265
- parts.push(tail.slice(0, 75));
266
- if (tail.length > 75) {
267
- parts.push(tail.slice(75));
268
- }
218
+ // Open a context (i.e. browser tab).
219
+ const browserContext = await browser.newContext(device.windowOptions);
220
+ // Prevent default timeouts.
221
+ browserContext.setDefaultTimeout(0);
222
+ // When a page (i.e. browser tab) is added to the browser context (i.e. browser window):
223
+ browserContext.on('page', async page => {
224
+ // Ensure the report has a jobData property.
225
+ report.jobData ??= {};
226
+ const {jobData} = report;
227
+ jobData.logCount ??= 0;
228
+ jobData.logSize ??= 0;
229
+ jobData.errorLogCount ??= 0;
230
+ // Add any error events to the count of logging errors.
231
+ page.on('crash', () => {
232
+ jobData.errorLogCount++;
233
+ console.log('Page crashed');
234
+ });
235
+ page.on('pageerror', () => {
236
+ jobData.errorLogCount++;
237
+ });
238
+ page.on('requestfailed', () => {
239
+ jobData.errorLogCount++;
240
+ });
241
+ // If the page emits a message:
242
+ page.on('console', msg => {
243
+ const msgText = msg.text();
244
+ let indentedMsg = '';
245
+ // If debugging is on:
246
+ if (debug) {
247
+ // Log a summary of the message on the console.
248
+ const parts = [msgText.slice(0, 75)];
249
+ if (msgText.length > 75) {
250
+ parts.push(msgText.slice(75, 150));
251
+ if (msgText.length > 150) {
252
+ const tail = msgText.slice(150).slice(-150);
253
+ if (msgText.length > 300) {
254
+ parts.push('...');
255
+ }
256
+ parts.push(tail.slice(0, 75));
257
+ if (tail.length > 75) {
258
+ parts.push(tail.slice(75));
269
259
  }
270
260
  }
271
- indentedMsg = parts.map(part => ` | ${part}`).join('\n');
272
- console.log(`\n${indentedMsg}`);
273
- }
274
- // Add statistics on the message to the report.
275
- const msgTextLC = msgText.toLowerCase();
276
- const msgLength = msgText.length;
277
- report.jobData.logCount++;
278
- report.jobData.logSize += msgLength;
279
- if (errorWords.some(word => msgTextLC.includes(word))) {
280
- report.jobData.errorLogCount++;
281
- report.jobData.errorLogSize += msgLength;
282
261
  }
283
- const msgLC = msgText.toLowerCase();
284
- if (
285
- msgText.includes('403') && (msgLC.includes('status')
286
- || msgLC.includes('prohibited'))
287
- ) {
288
- report.jobData.prohibitedCount++;
289
- }
290
- });
291
- });
292
- // Open the first page of the context.
293
- const page = await browserContext.newPage();
294
- try {
295
- // Wait until it is stable.
296
- await page.waitForLoadState('domcontentloaded', {timeout: 5000});
297
- // Navigate to the specified URL.
298
- const navResult = await goTo(report, page, url, 15000, 'domcontentloaded');
299
- // If the navigation succeeded:
300
- if (navResult.success) {
301
- // Update the name of the current browser type and store it in the page.
302
- page.browserTypeName = browserID;
303
- // Return the response of the target server, the browser context, and the page.
304
- return {
305
- success: true,
306
- response: navResult.response,
307
- browserContext,
308
- page
309
- };
262
+ indentedMsg = parts.map(part => ` | ${part}`).join('\n');
263
+ console.log(`\n${indentedMsg}`);
310
264
  }
311
- // Otherwise, if the navigation failed:
312
- else {
313
- // Return the error.
314
- return {
315
- success: false,
316
- error: navResult.error
317
- };
265
+ // Add statistics on the message to the report.
266
+ const msgTextLC = msgText.toLowerCase();
267
+ const msgLength = msgText.length;
268
+ jobData.logCount++;
269
+ jobData.logSize += msgLength;
270
+ if (errorWords.some(word => msgTextLC.includes(word))) {
271
+ jobData.errorLogCount++;
272
+ jobData.errorLogSize += msgLength;
273
+ }
274
+ const msgLC = msgText.toLowerCase();
275
+ if (
276
+ msgText.includes('403') && (msgLC.includes('status')
277
+ || msgLC.includes('prohibited'))
278
+ ) {
279
+ jobData.prohibitedCount++;
318
280
  }
281
+ });
282
+ });
283
+ // Open the first page (tab) of the context (window).
284
+ const page = await browserContext.newPage();
285
+ try {
286
+ // Wait until it is stable.
287
+ await page.waitForLoadState('domcontentloaded', {timeout: 5000});
288
+ // Navigate to the specified URL.
289
+ const navResult = await goTo(report, page, url, 15000, 'domcontentloaded');
290
+ // If the navigation succeeded:
291
+ if (navResult.success) {
292
+ // Update the name of the current browser type and store it in the page.
293
+ page.browserID = browserID;
294
+ // Return the response of the target server, the browser context, and the page.
295
+ return {
296
+ success: true,
297
+ response: navResult.response,
298
+ browserContext,
299
+ page
300
+ };
319
301
  }
320
- // If it fails to become stable after load:
321
- catch(error) {
322
- // Return this.
323
- console.log(`ERROR: Blank page load in new tab timed out (${error.message})`);
302
+ // Otherwise, if the navigation failed:
303
+ else {
304
+ // Return the error.
324
305
  return {
325
306
  success: false,
326
- error: 'Blank page load in new tab timed out'
307
+ error: navResult.error
327
308
  };
328
309
  }
329
310
  }
330
- // Otherwise, i.e. if the device is invalid:
331
- else {
311
+ // If it fails to become stable after load:
312
+ catch(error) {
332
313
  // Return this.
333
- console.log(`ERROR: Device ${deviceID} invalid`);
314
+ console.log(`ERROR: Blank page load in new tab timed out (${error.message})`);
334
315
  return {
335
316
  success: false,
336
- error: `${deviceID} device invalid`
317
+ error: 'Blank page load in new tab timed out'
337
318
  };
338
319
  }
339
320
  }
@@ -622,12 +603,10 @@ const doActs = async (report, actIndex, page) => {
622
603
  // Launch the specified browser on the specified device and navigate to the specified URL.
623
604
  const launchResult = await launch(
624
605
  report,
625
- act.url || report.target.url,
626
606
  debug,
627
607
  waits,
628
- act.deviceID || report.deviceID,
629
608
  act.browserID || report.browserID,
630
- act.lowMotion || report.lowMotion
609
+ act.target || report.target
631
610
  );
632
611
  // If the launch and navigation succeeded:
633
612
  if (launchResult && launchResult.success) {
package/testaro/focAll.js CHANGED
@@ -50,7 +50,7 @@ exports.reporter = async page => {
50
50
  */
51
51
  let tabFocused = 0;
52
52
  let refocused = 0;
53
- const keyName = page.browserTypeName === 'webkit' ? 'Alt+Tab' : 'Tab';
53
+ const keyName = page.browserID === 'webkit' ? 'Alt+Tab' : 'Tab';
54
54
  while (refocused < 100 && tabFocused < 2000) {
55
55
  await page.keyboard.press(keyName);
56
56
  const isNewFocus = await page.evaluate(() => {
package/procs/device.js DELETED
@@ -1,59 +0,0 @@
1
- /*
2
- © 2024 CVS Health and/or one of its affiliates. All rights reserved.
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- SOFTWARE.
21
- */
22
-
23
- // device
24
-
25
- // IMPORTS
26
-
27
- const {devices} = require('playwright');
28
-
29
- // FUNCTIONS
30
-
31
- // Returns whether a device ID is valid.
32
- exports.isDeviceID = deviceID => deviceID === 'default' || !! devices[deviceID];
33
-
34
- // Returns options for the browser.newContext() function.
35
- exports.getDeviceOptions = (deviceID, motion) => {
36
- const options = {
37
- reduceMotion: motion
38
- };
39
- // If a non-default device was specified:
40
- if (deviceID && deviceID !== 'default') {
41
- // Get its properties if it exists.
42
- const deviceProperties = devices[deviceID];
43
- // Return options or report the device as invalid.
44
- if (deviceProperties) {
45
- return {
46
- ... options,
47
- ... deviceProperties
48
- };
49
- }
50
- else {
51
- return {};
52
- }
53
- }
54
- // Otherwise, i.e. if no non-default device was specified:
55
- else {
56
- // Return options.
57
- return options;
58
- }
59
- };