testaro 72.4.4 → 72.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/README.md +1 -33
- package/call.js +0 -2
- package/dirWatch.js +0 -3
- package/env.example +32 -0
- package/package.json +1 -1
- package/procs/doActs.js +117 -165
- package/procs/doTestAct.js +2 -2
- package/procs/error.js +13 -5
- package/procs/job.js +0 -7
- package/procs/launch.js +56 -35
- package/run.js +3 -1
- package/tests/testaro.js +1 -5
- package/validation/jobs/todo/240101T1200-simple-example.json +0 -1
- package/validation/validateTest.js +0 -1
- package/validation/watch/done/240101T1200-simple-example.json +0 -1
package/CLAUDE.md
CHANGED
|
@@ -82,7 +82,7 @@ Key variables:
|
|
|
82
82
|
- `WAVE_KEY` — API key for the WAVE subscription API
|
|
83
83
|
- `JOBDIR` / `REPORTDIR` — root directories for job files and report output
|
|
84
84
|
- `AGENT` — instance name used in network-watch mode
|
|
85
|
-
- `NETWATCH_URL_<N>_JOB/
|
|
85
|
+
- `NETWATCH_URL_<N>_JOB/REPORT/AUTH`, `NETWATCH_URLS` — server polling configuration
|
|
86
86
|
- `TIMEOUT_MULTIPLIER` — scales all per-tool time limits (default 1)
|
|
87
87
|
|
|
88
88
|
## Code style
|
package/README.md
CHANGED
|
@@ -137,38 +137,7 @@ You can make `testaro` a dependency in another application. As noted at the begi
|
|
|
137
137
|
|
|
138
138
|
## Environment configuration
|
|
139
139
|
|
|
140
|
-
The `.env` file stores your decisions about the environment in which Testaro runs. The variables that can be defined there are
|
|
141
|
-
|
|
142
|
-
```conf
|
|
143
|
-
# Whether the browsers launched by Testaro should have visible windows.
|
|
144
|
-
HEADED_BROWSER=false
|
|
145
|
-
# Whether console logging in launched browsers should be mirrored to the Testaro console.
|
|
146
|
-
DEBUG=false
|
|
147
|
-
# Whether to disable Puppeteer log warnings of a future headless-mode deprecation.
|
|
148
|
-
PUPPETEER_DISABLE_HEADLESS_WARNING=true
|
|
149
|
-
# How much time, in milliseconds, to insert between Playwright operations for debugging.
|
|
150
|
-
WAITS=0
|
|
151
|
-
# API key to enable the WAVE tool.
|
|
152
|
-
WAVE_KEY=yourwavekey (get it from [WebAim](https://wave.webaim.org/api/)).
|
|
153
|
-
#----------------------------
|
|
154
|
-
# When Testaro listens for new jobs in a directory:
|
|
155
|
-
# Directory where it listens for them.
|
|
156
|
-
JOBDIR=../testing/jobs
|
|
157
|
-
# Directory into which Testaro saves the reports of those jobs.
|
|
158
|
-
REPORTDIR=../testing/reports
|
|
159
|
-
# Name of this Testaro instance when it listens for jobs and sends reports to requesting hosts.
|
|
160
|
-
AGENT=agentabc
|
|
161
|
-
#----------------------------
|
|
162
|
-
# When Testaro polls a network host to ask for new jobs, data on the host.
|
|
163
|
-
# URL to poll.
|
|
164
|
-
NETWATCH_JOB=http://localhost:3000/api/assignJob/agentabc
|
|
165
|
-
# URL to which to send progress reports during jobs.
|
|
166
|
-
NETWATCH_OBSERVE=http://localhost:3000/api/granular/agentabc
|
|
167
|
-
# URL to which to send completed job reports.
|
|
168
|
-
NETWATCH_REPORT=http://localhost:3000/api/takeReport/agentabc
|
|
169
|
-
# Password to give to the host to authenticate this instance.
|
|
170
|
-
NETWATCH_AUTH=abcxyz
|
|
171
|
-
```
|
|
140
|
+
The `.env` file stores your decisions about the environment in which Testaro runs. The variables that can be defined there are documented in the `env.example` file.
|
|
172
141
|
|
|
173
142
|
## Jobs
|
|
174
143
|
|
|
@@ -184,7 +153,6 @@ Here is a sample job, showing properties that you can set:
|
|
|
184
153
|
what: 'monthly health check', // Job description
|
|
185
154
|
strict: true, // Whether to reject redirections from the target URL
|
|
186
155
|
standard: 'also', // or 'only' or 'no' (whether to report a standard result)
|
|
187
|
-
observe: false, // Whether to send progress notices to the requesting host
|
|
188
156
|
device: { // Device to emulate
|
|
189
157
|
id: 'iPhone 8',
|
|
190
158
|
windowOptions: {
|
package/call.js
CHANGED
|
@@ -56,8 +56,6 @@ const callRun = async jobIDStart => {
|
|
|
56
56
|
// Get it.
|
|
57
57
|
const jobJSON = await fs.readFile(`${todoDir}/${jobFileName}`, 'utf8');
|
|
58
58
|
let report = JSON.parse(jobJSON);
|
|
59
|
-
// Ensure it does not specify server properties.
|
|
60
|
-
report.observe = false;
|
|
61
59
|
// Run it.
|
|
62
60
|
report = await doJob(report);
|
|
63
61
|
// Archive it.
|
package/dirWatch.js
CHANGED
|
@@ -100,9 +100,6 @@ exports.dirWatch = async (isForever, intervalInSeconds) => {
|
|
|
100
100
|
try {
|
|
101
101
|
const job = JSON.parse(jobJSON);
|
|
102
102
|
let report = JSON.parse(jobJSON);
|
|
103
|
-
// Ensure it has no server properties.
|
|
104
|
-
job.observe = false;
|
|
105
|
-
report.observe = false;
|
|
106
103
|
const {id} = job;
|
|
107
104
|
console.log(`\n\nDirectory job ${id} ready to do (${nowString()})`);
|
|
108
105
|
// Perform it and get a report.
|
package/env.example
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# You can get a WAVE API key at https://wave.webaim.org/api.
|
|
2
|
+
WAVE_KEY=__placeholder__
|
|
3
|
+
# You can get an Anthropic API key at https://console.anthropic.com/.
|
|
4
|
+
ANTHROPIC_API_KEY=__placeholder__
|
|
5
|
+
# Name to identify this Testaro instance to a server.
|
|
6
|
+
AGENT=__placeholder__
|
|
7
|
+
# Whether to make the browser visible (normally false).
|
|
8
|
+
HEADED_BROWSER=false
|
|
9
|
+
# Whether to output even forked-job logging to the primary log (normally false).
|
|
10
|
+
DEBUG=false
|
|
11
|
+
# Number of seconds to wait between actions (normally 0).
|
|
12
|
+
WAITS=0
|
|
13
|
+
# See https://nodejs.org/api/cli.html#options for all permitted options.
|
|
14
|
+
NODE_OPTIONS='--trace-uncaught --trace-warnings'
|
|
15
|
+
# Suppresses a routine warning from QualWeb.
|
|
16
|
+
PUPPETEER_DISABLE_HEADLESS_WARNING=true
|
|
17
|
+
# URL to poll for available jobs when watching the network.
|
|
18
|
+
NETWATCH_JOB=__placeholder__/api/testaro-agent/job
|
|
19
|
+
# URL to report results to when watching the network.
|
|
20
|
+
NETWATCH_REPORT=__placeholder__/api/testaro-agent/report
|
|
21
|
+
# Password of this Testaro agent
|
|
22
|
+
NETWATCH_AUTH=__placeholder__
|
|
23
|
+
# Directory (relative to project root) to watch for jobs.
|
|
24
|
+
JOBDIR=__placeholder__
|
|
25
|
+
# Directory (relative to project root) to write reports to when watching a directory for jobs.
|
|
26
|
+
REPORTDIR=__placeholder__
|
|
27
|
+
# Multiplier for time limits (normally 1).
|
|
28
|
+
TIMEOUT_MULTIPLIER=1
|
|
29
|
+
# Name of the directory at the project root used for temporary files.
|
|
30
|
+
TMPDIRNAME=scratch
|
|
31
|
+
# Whether to abort the job when any test act fork crashes (default false).
|
|
32
|
+
ABORT_ASSERTIVELY=false
|
package/package.json
CHANGED
package/procs/doActs.js
CHANGED
|
@@ -14,22 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
// IMPORTS
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const {abortActs, addError} = require('./error');
|
|
19
|
-
// Function to close a browser and/or its context.
|
|
17
|
+
const {addError} = require('./error');
|
|
20
18
|
const {getNonce, goTo, launch, wait} = require('./launch');
|
|
21
|
-
// Constant describing the tools.
|
|
22
19
|
const {tools} = require('./job');
|
|
23
|
-
// Module to create child processes.
|
|
24
20
|
const {fork} = require('child_process');
|
|
25
21
|
const os = require('os');
|
|
26
|
-
// Function to prune a catalog.
|
|
27
22
|
const {pruneCatalog} = require('./catalog');
|
|
28
|
-
// Module to handle file system operations.
|
|
29
23
|
const fs = require('fs/promises');
|
|
30
|
-
const httpClient = require('http');
|
|
31
|
-
const httpsClient = require('https');
|
|
32
|
-
const agent = process.env.AGENT;
|
|
33
24
|
|
|
34
25
|
// CONSTANTS
|
|
35
26
|
|
|
@@ -62,27 +53,11 @@ const timeLimits = {
|
|
|
62
53
|
};
|
|
63
54
|
// Timeout multiplier.
|
|
64
55
|
const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1;
|
|
56
|
+
// Abort aggressiveness.
|
|
57
|
+
const abortAssertively = process.env.ABORT_ASSERTIVELY === 'true';
|
|
65
58
|
|
|
66
59
|
// FUNCTIONS
|
|
67
60
|
|
|
68
|
-
// Sends a notice to a network observer.
|
|
69
|
-
const tellServer = (report, messageParams, logMessage) => {
|
|
70
|
-
const observerURL = process.env.NETWATCH_URL_OBSERVE;
|
|
71
|
-
if (observerURL) {
|
|
72
|
-
const whoParams = `agent=${agent}&jobID=${report.id || ''}`;
|
|
73
|
-
const wholeURL = `${observerURL}?${whoParams}&${messageParams}`;
|
|
74
|
-
const client = wholeURL.startsWith('https://') ? httpsClient : httpClient;
|
|
75
|
-
client.request(wholeURL)
|
|
76
|
-
// If the notification threw an error:
|
|
77
|
-
.on('error', error => {
|
|
78
|
-
// Report the error.
|
|
79
|
-
const errorMessage = 'ERROR notifying the server';
|
|
80
|
-
console.log(`${errorMessage} (${error.message})`);
|
|
81
|
-
})
|
|
82
|
-
.end();
|
|
83
|
-
console.log(`${logMessage} (server notified)`);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
61
|
// Normalizes spacing characters and cases in a string.
|
|
87
62
|
const debloat = string => string.replace(/\s/g, ' ').trim().replace(/ {2,}/g, ' ').toLowerCase();
|
|
88
63
|
// Returns the first line of an error message.
|
|
@@ -249,83 +224,50 @@ const waitError = (page, act, error, what) => {
|
|
|
249
224
|
return false;
|
|
250
225
|
};
|
|
251
226
|
// Performs the acts in a report and adds the results to the report.
|
|
252
|
-
exports.doActs = async
|
|
253
|
-
// Make a
|
|
254
|
-
let
|
|
227
|
+
exports.doActs = async report => {
|
|
228
|
+
// Make a temporary copy of the report. Precondition: report is valid.
|
|
229
|
+
let tempReport = JSON.parse(JSON.stringify(report));
|
|
255
230
|
let page = null;
|
|
256
|
-
let {acts} =
|
|
257
|
-
// Get the granular observation options, if any.
|
|
258
|
-
const {onProgress = null, signal = null} = opts;
|
|
231
|
+
let {acts} = tempReport;
|
|
259
232
|
// Get the standardization specification.
|
|
260
|
-
const standard =
|
|
261
|
-
|
|
262
|
-
let tmpDir =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
catch(error) {
|
|
267
|
-
console.log(`ERROR: ${tmpDir} is not writable`);
|
|
268
|
-
tmpDir = os.tmpdir();
|
|
233
|
+
const standard = tempReport.standard || 'only';
|
|
234
|
+
const tmpDirs = [`${__dirname}/../${process.env.TMPDIRNAME || 'scratch'}`, os.tmpdir(), '/tmp'];
|
|
235
|
+
let tmpDir = null;
|
|
236
|
+
// For each potential temporary directory:
|
|
237
|
+
for (const tmpDirAlternative of tmpDirs) {
|
|
269
238
|
try {
|
|
270
|
-
|
|
239
|
+
// Verify that it is writable.
|
|
240
|
+
await fs.access(tmpDirAlternative, fs.constants.W_OK);
|
|
241
|
+
tmpDir = tmpDirAlternative;
|
|
242
|
+
break;
|
|
271
243
|
}
|
|
244
|
+
// If it is not:
|
|
272
245
|
catch(error) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
await fs.access(tmpDir, fs.constants.W_OK);
|
|
277
|
-
}
|
|
278
|
-
catch(error) {
|
|
279
|
-
console.log(`ERROR: ${tmpDir} is not writable; quitting`);
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
246
|
+
// Report this.
|
|
247
|
+
console.log(`ERROR: ${tmpDirAlternative} is not a writable directory for temporary reports`);
|
|
282
248
|
}
|
|
283
249
|
}
|
|
250
|
+
// If no writable temporary directory was found:
|
|
251
|
+
if (! tmpDir) {
|
|
252
|
+
// Report this.
|
|
253
|
+
console.log('ERROR: No writable temporary directory was found; quitting');
|
|
254
|
+
// Quit.
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
284
257
|
// Get a path for temporary reports.
|
|
285
|
-
const reportPath = `${tmpDir}/${
|
|
258
|
+
const reportPath = `${tmpDir}/${tempReport.id}.json`;
|
|
286
259
|
// Initialize the count of completed acts.
|
|
287
260
|
let actCount = 0;
|
|
288
|
-
// For each act in the
|
|
261
|
+
// For each act in the temporary report:
|
|
289
262
|
for (const actIndex in acts) {
|
|
290
|
-
// If the job has been aborted
|
|
291
|
-
if (
|
|
292
|
-
// Report this.
|
|
293
|
-
throw new Error('doActs aborted');
|
|
294
|
-
}
|
|
295
|
-
// Otherwise, and if the job has not been aborted internally:
|
|
296
|
-
if (localReport.jobData && ! localReport.jobData.aborted) {
|
|
263
|
+
// If the job has not been aborted:
|
|
264
|
+
if (tempReport?.jobData && ! tempReport.jobData.aborted) {
|
|
297
265
|
let act = acts[actIndex];
|
|
298
266
|
const {type, which} = act;
|
|
299
267
|
const actSuffix = type === 'test' ? ` ${which}` : '';
|
|
300
268
|
const message = `>>>> ${type}${actSuffix}`;
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
const whichParam = which ? `&which=${which}` : '';
|
|
304
|
-
const messageParams = `act=${type}${whichParam}`;
|
|
305
|
-
// If a progress callback has been provided by a caller on this host:
|
|
306
|
-
if (onProgress) {
|
|
307
|
-
// Notify the observer of the act.
|
|
308
|
-
try {
|
|
309
|
-
onProgress({
|
|
310
|
-
type,
|
|
311
|
-
which
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
console.log(`${message} (observer notification failed: ${errorStart(error)})`);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
// Otherwise, i.e. if no progress callback has been provided:
|
|
319
|
-
else {
|
|
320
|
-
// Notify the remote observer of the act and log it.
|
|
321
|
-
tellServer(localReport, messageParams, message);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// Otherwise, i.e. if granular reporting has not been specified:
|
|
325
|
-
else {
|
|
326
|
-
// Log the act.
|
|
327
|
-
console.log(message);
|
|
328
|
-
}
|
|
269
|
+
// Log the act.
|
|
270
|
+
console.log(message);
|
|
329
271
|
// If the act is an index changer:
|
|
330
272
|
if (type === 'next') {
|
|
331
273
|
const condition = act.if;
|
|
@@ -347,7 +289,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
347
289
|
if (truth[1]) {
|
|
348
290
|
// If the performance of acts is to stop:
|
|
349
291
|
if (act.jump === 0) {
|
|
350
|
-
//
|
|
292
|
+
// Stop processing acts.
|
|
351
293
|
break;
|
|
352
294
|
}
|
|
353
295
|
// Otherwise, if there is a numerical jump:
|
|
@@ -366,16 +308,16 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
366
308
|
else if (type === 'launch') {
|
|
367
309
|
// Launch a browser, navigate to a page, and add the result to the act.
|
|
368
310
|
page = await launch({
|
|
369
|
-
|
|
311
|
+
tempReport,
|
|
370
312
|
actIndex,
|
|
371
|
-
tempBrowserID: getActBrowserID(
|
|
372
|
-
tempURL: getActTargetURL(
|
|
313
|
+
tempBrowserID: getActBrowserID(tempReport, actIndex),
|
|
314
|
+
tempURL: getActTargetURL(tempReport, actIndex),
|
|
373
315
|
xPathNeed: 'none'
|
|
374
316
|
});
|
|
375
317
|
// If this failed:
|
|
376
318
|
if (! page) {
|
|
377
|
-
//
|
|
378
|
-
addError(false, false,
|
|
319
|
+
// Report this.
|
|
320
|
+
addError(false, false, tempReport, actIndex, page.error ?? '');
|
|
379
321
|
}
|
|
380
322
|
}
|
|
381
323
|
// Otherwise, if the act is a test act:
|
|
@@ -386,9 +328,9 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
386
328
|
const startTime = Date.now();
|
|
387
329
|
// Add it to the act.
|
|
388
330
|
act.startTime = startTime;
|
|
389
|
-
let
|
|
390
|
-
// Save a copy of the
|
|
391
|
-
await fs.writeFile(reportPath,
|
|
331
|
+
let tempReportJSON = JSON.stringify(tempReport);
|
|
332
|
+
// Save a copy of the temporary report, which the child process will read.
|
|
333
|
+
await fs.writeFile(reportPath, tempReportJSON);
|
|
392
334
|
let timedOut = false;
|
|
393
335
|
const limitMs = timeoutMultiplier * 1000 * (timeLimits[act.which] || 15);
|
|
394
336
|
const actResult = await new Promise(resolve => {
|
|
@@ -459,55 +401,70 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
459
401
|
});
|
|
460
402
|
// If the child process sent a message:
|
|
461
403
|
if (actResult.kind === 'message') {
|
|
462
|
-
// Get the revised
|
|
463
|
-
|
|
404
|
+
// Get the revised tempReport file.
|
|
405
|
+
tempReportJSON = await fs.readFile(reportPath, 'utf8');
|
|
464
406
|
try {
|
|
465
|
-
// Reassign it to the
|
|
466
|
-
|
|
467
|
-
// Redefine the acts as those in the revised
|
|
468
|
-
({acts} =
|
|
407
|
+
// Reassign it to the temporary report.
|
|
408
|
+
tempReport = JSON.parse(tempReportJSON);
|
|
409
|
+
// Redefine the acts as those in the revised temporary report.
|
|
410
|
+
({acts} = tempReport);
|
|
469
411
|
}
|
|
470
|
-
// If the
|
|
412
|
+
// If the reassignment fails, leaving the temporary report and its acts unchanged:
|
|
471
413
|
catch (error) {
|
|
472
414
|
// Report this.
|
|
473
415
|
console.log(
|
|
474
|
-
`ERROR: Tool sent message ${actResult.message}. Report is no longer JSON (${error.message}) but is instead a(n) ${typeof
|
|
416
|
+
`ERROR: Tool sent message ${actResult.message}. Report is no longer JSON (${error.message}) but is instead a(n) ${typeof tempReportJSON} of length ${tempReportJSON.length}:\n${tempReportJSON}`
|
|
475
417
|
);
|
|
476
|
-
//
|
|
418
|
+
// Report this and that the job was aborted.
|
|
477
419
|
addError(
|
|
478
420
|
false,
|
|
479
|
-
|
|
480
|
-
|
|
421
|
+
true,
|
|
422
|
+
tempReport,
|
|
481
423
|
actIndex,
|
|
482
|
-
`Non-JSON
|
|
424
|
+
`Non-JSON temporary report file after message ${actResult.message}`
|
|
483
425
|
);
|
|
426
|
+
// Stop processing acts.
|
|
427
|
+
break;
|
|
484
428
|
}
|
|
485
429
|
}
|
|
486
430
|
// Otherwise, i.e. if the child process closed abnormally:
|
|
487
431
|
else {
|
|
488
|
-
//
|
|
432
|
+
// Report this and, if so configured, that the job was aborted.
|
|
489
433
|
const {code, error, kind, signal} = actResult;
|
|
490
434
|
if (kind === 'close' && timedOut) {
|
|
491
435
|
addError(
|
|
492
|
-
false,
|
|
436
|
+
false,
|
|
437
|
+
abortAssertively,
|
|
438
|
+
tempReport,
|
|
439
|
+
actIndex,
|
|
440
|
+
`Timed out at ${Math.round(limitMs / 1000)} seconds`
|
|
493
441
|
);
|
|
494
442
|
}
|
|
495
443
|
else if (kind === 'close') {
|
|
496
444
|
addError(
|
|
497
|
-
true,
|
|
445
|
+
true,
|
|
446
|
+
abortAssertively,
|
|
447
|
+
tempReport,
|
|
448
|
+
actIndex,
|
|
449
|
+
`Closed with code ${code} and signal ${signal})`
|
|
498
450
|
);
|
|
499
451
|
}
|
|
500
452
|
else {
|
|
501
453
|
addError(
|
|
502
|
-
true,
|
|
454
|
+
true, abortAssertively, tempReport, actIndex, `Terminated with error ${error}`
|
|
503
455
|
);
|
|
504
456
|
}
|
|
457
|
+
// If the job was aborted:
|
|
458
|
+
if (abortAssertively) {
|
|
459
|
+
// Stop processing acts.
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
505
462
|
}
|
|
506
463
|
// Get the (usually revised) act.
|
|
507
464
|
act = acts[actIndex];
|
|
508
|
-
// Add the elapsed time of the tool to the
|
|
465
|
+
// Add the elapsed time of the tool to the temporary report.
|
|
509
466
|
const time = Math.round((Date.now() - startTime) / 1000);
|
|
510
|
-
const {toolTimes} =
|
|
467
|
+
const {toolTimes} = tempReport.jobData;
|
|
511
468
|
toolTimes[act.which] ??= 0;
|
|
512
469
|
toolTimes[act.which] += time;
|
|
513
470
|
// If the act was not prevented:
|
|
@@ -545,16 +502,16 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
545
502
|
const resolved = act.which.replace('__dirname', __dirname);
|
|
546
503
|
requestedURL = resolved;
|
|
547
504
|
// Visit it and wait until the DOM is loaded.
|
|
548
|
-
const navResult = await goTo(
|
|
505
|
+
const navResult = await goTo(tempReport, page, requestedURL, 15000, 'domcontentloaded');
|
|
549
506
|
// If the visit succeeded:
|
|
550
507
|
if (navResult.success) {
|
|
551
|
-
// Revise the
|
|
552
|
-
|
|
508
|
+
// Revise the temporary report URL to this URL.
|
|
509
|
+
tempReport.target.url = requestedURL;
|
|
553
510
|
// Add the script nonce, if any, to the act.
|
|
554
511
|
const {response} = navResult;
|
|
555
512
|
const scriptNonce = getNonce(response);
|
|
556
513
|
if (scriptNonce) {
|
|
557
|
-
|
|
514
|
+
tempReport.jobData.lastScriptNonce = scriptNonce;
|
|
558
515
|
}
|
|
559
516
|
// Add the resulting URL to the act.
|
|
560
517
|
if (! act.result) {
|
|
@@ -564,13 +521,13 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
564
521
|
// If a prohibited redirection occurred:
|
|
565
522
|
if (response.exception === 'badRedirection') {
|
|
566
523
|
// Report this.
|
|
567
|
-
addError(true, false,
|
|
524
|
+
addError(true, false, tempReport, actIndex, 'ERROR: Navigation illicitly redirected');
|
|
568
525
|
}
|
|
569
526
|
}
|
|
570
527
|
// Otherwise, i.e. if the visit failed:
|
|
571
528
|
else {
|
|
572
529
|
// Report this.
|
|
573
|
-
addError(true, false,
|
|
530
|
+
addError(true, false, tempReport, actIndex, 'ERROR: Visit failed');
|
|
574
531
|
}
|
|
575
532
|
}
|
|
576
533
|
// Otherwise, if the act is a wait for text:
|
|
@@ -588,8 +545,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
588
545
|
}
|
|
589
546
|
// If the wait times out:
|
|
590
547
|
catch(error) {
|
|
591
|
-
//
|
|
592
|
-
abortActs(localReport, actIndex);
|
|
548
|
+
// Report this.
|
|
593
549
|
waitError(page, act, error, 'text in the URL');
|
|
594
550
|
}
|
|
595
551
|
}
|
|
@@ -612,8 +568,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
612
568
|
}
|
|
613
569
|
// If the wait times out:
|
|
614
570
|
catch(error) {
|
|
615
|
-
//
|
|
616
|
-
abortActs(localReport, actIndex);
|
|
571
|
+
// Report this.
|
|
617
572
|
waitError(page, act, error, 'text in the title');
|
|
618
573
|
}
|
|
619
574
|
}
|
|
@@ -635,8 +590,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
635
590
|
}
|
|
636
591
|
// If the wait times out:
|
|
637
592
|
catch(error) {
|
|
638
|
-
//
|
|
639
|
-
abortActs(localReport, actIndex);
|
|
593
|
+
// Report this.
|
|
640
594
|
waitError(page, act, error, 'text in the body');
|
|
641
595
|
}
|
|
642
596
|
}
|
|
@@ -650,13 +604,13 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
650
604
|
)
|
|
651
605
|
// If the wait times out:
|
|
652
606
|
.catch(async error => {
|
|
653
|
-
// Report this
|
|
607
|
+
// Report this.
|
|
654
608
|
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
655
|
-
addError(true, false,
|
|
609
|
+
addError(true, false, tempReport, actIndex, `ERROR waiting for page to be ${act.which}`);
|
|
656
610
|
});
|
|
657
611
|
// If the wait succeeded:
|
|
658
612
|
if (actIndex > -2) {
|
|
659
|
-
// Add state data to the
|
|
613
|
+
// Add state data to the temporary report.
|
|
660
614
|
act.result = {
|
|
661
615
|
success: true,
|
|
662
616
|
state: act.which
|
|
@@ -739,7 +693,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
739
693
|
}
|
|
740
694
|
// If no element satisfied the specifications:
|
|
741
695
|
if (! act.result.found) {
|
|
742
|
-
// Add the failure data to the
|
|
696
|
+
// Add the failure data to the temporary report.
|
|
743
697
|
act.result.success = false;
|
|
744
698
|
act.result.error = 'exhausted';
|
|
745
699
|
act.result.typeElementCount = selections.length;
|
|
@@ -752,7 +706,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
752
706
|
}
|
|
753
707
|
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
754
708
|
else {
|
|
755
|
-
// Add the failure data to the
|
|
709
|
+
// Add the failure data to the temporary report.
|
|
756
710
|
act.result.success = false;
|
|
757
711
|
act.result.error = 'fewer';
|
|
758
712
|
act.result.typeElementCount = selections.length;
|
|
@@ -761,7 +715,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
761
715
|
}
|
|
762
716
|
// Otherwise, i.e. if there are no elements of the specified type:
|
|
763
717
|
else {
|
|
764
|
-
// Add the failure data to the
|
|
718
|
+
// Add the failure data to the temporary report.
|
|
765
719
|
act.result.success = false;
|
|
766
720
|
act.result.error = 'none';
|
|
767
721
|
act.result.typeElementCount = 0;
|
|
@@ -770,7 +724,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
770
724
|
}
|
|
771
725
|
// Otherwise, i.e. if the page no longer exists:
|
|
772
726
|
else {
|
|
773
|
-
// Add the failure data to the
|
|
727
|
+
// Add the failure data to the temporary report.
|
|
774
728
|
act.result.success = false;
|
|
775
729
|
act.result.error = 'gone';
|
|
776
730
|
act.result.message = 'Page gone';
|
|
@@ -795,8 +749,8 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
795
749
|
}
|
|
796
750
|
// If the move fails:
|
|
797
751
|
catch(error) {
|
|
798
|
-
//
|
|
799
|
-
addError(true, false,
|
|
752
|
+
// Report this.
|
|
753
|
+
addError(true, false, tempReport, actIndex, `ERROR: ${move} failed`);
|
|
800
754
|
}
|
|
801
755
|
if (act.result.success) {
|
|
802
756
|
try {
|
|
@@ -880,16 +834,15 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
880
834
|
}
|
|
881
835
|
// If the click or load failed:
|
|
882
836
|
catch(error) {
|
|
883
|
-
//
|
|
837
|
+
// Report this.
|
|
884
838
|
console.log(`ERROR clicking link (${errorStart(error)})`);
|
|
885
839
|
act.result.success = false;
|
|
886
840
|
act.result.error = 'unclickable';
|
|
887
841
|
act.result.message = 'ERROR: click or load timed out';
|
|
888
|
-
abortActs(localReport, actIndex);
|
|
889
842
|
}
|
|
890
843
|
// If the link click succeeded:
|
|
891
844
|
if (! act.result.error) {
|
|
892
|
-
// Add success data to the
|
|
845
|
+
// Add success data to the temporary report.
|
|
893
846
|
act.result.success = true;
|
|
894
847
|
act.result.move = 'clicked';
|
|
895
848
|
}
|
|
@@ -938,7 +891,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
938
891
|
}
|
|
939
892
|
// Enter the text.
|
|
940
893
|
await selection.type(what);
|
|
941
|
-
|
|
894
|
+
tempReport.jobData.presses += what.length;
|
|
942
895
|
act.result.success = true;
|
|
943
896
|
act.result.move = 'entered';
|
|
944
897
|
// If the input is a search input:
|
|
@@ -957,19 +910,18 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
957
910
|
}
|
|
958
911
|
// Otherwise, i.e. if no match was found:
|
|
959
912
|
else {
|
|
960
|
-
//
|
|
913
|
+
// RLeport this.
|
|
961
914
|
act.result.success = false;
|
|
962
915
|
act.result.error = 'absent';
|
|
963
916
|
act.result.message = 'ERROR: specified element not found';
|
|
964
917
|
console.log('ERROR: Specified element not found');
|
|
965
|
-
abortActs(localReport, actIndex);
|
|
966
918
|
}
|
|
967
919
|
}
|
|
968
920
|
// Otherwise, if the act is a keypress:
|
|
969
921
|
else if (type === 'press') {
|
|
970
922
|
// Identify the number of times to press the key.
|
|
971
923
|
let times = 1 + (act.again || 0);
|
|
972
|
-
|
|
924
|
+
tempReport.jobData.presses += times;
|
|
973
925
|
const key = act.which;
|
|
974
926
|
// Press the key.
|
|
975
927
|
while (times--) {
|
|
@@ -1123,26 +1075,26 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
1123
1075
|
if (withItems) {
|
|
1124
1076
|
act.result.items = items;
|
|
1125
1077
|
}
|
|
1126
|
-
// Add the totals to the
|
|
1127
|
-
|
|
1128
|
-
|
|
1078
|
+
// Add the totals to the temporary report.
|
|
1079
|
+
tempReport.jobData.presses += presses;
|
|
1080
|
+
tempReport.jobData.amountRead += amountRead;
|
|
1129
1081
|
}
|
|
1130
1082
|
// Otherwise, i.e. if the act type is unknown:
|
|
1131
1083
|
else {
|
|
1132
|
-
//
|
|
1133
|
-
addError(true, false,
|
|
1084
|
+
// Report this.
|
|
1085
|
+
addError(true, false, tempReport, actIndex, 'ERROR: Invalid act type');
|
|
1134
1086
|
}
|
|
1135
1087
|
}
|
|
1136
1088
|
// Otherwise, a page URL is required but does not exist, so:
|
|
1137
1089
|
else {
|
|
1138
|
-
//
|
|
1139
|
-
addError(true, false,
|
|
1090
|
+
// Report this.
|
|
1091
|
+
addError(true, false, tempReport, actIndex, 'ERROR: Page has no URL');
|
|
1140
1092
|
}
|
|
1141
1093
|
}
|
|
1142
1094
|
// Otherwise, i.e. if no page exists:
|
|
1143
1095
|
else {
|
|
1144
|
-
//
|
|
1145
|
-
addError(true, false,
|
|
1096
|
+
// Report this.
|
|
1097
|
+
addError(true, false, tempReport, actIndex, 'ERROR: No page identified');
|
|
1146
1098
|
}
|
|
1147
1099
|
// Add the end time to the act.
|
|
1148
1100
|
act.endTime = Date.now();
|
|
@@ -1156,20 +1108,20 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
1156
1108
|
// If the native results are not to be included in the report:
|
|
1157
1109
|
if (standard === 'only') {
|
|
1158
1110
|
// Remove them.
|
|
1159
|
-
|
|
1111
|
+
tempReport.acts.forEach(act => {
|
|
1160
1112
|
if (act.result?.nativeResult) {
|
|
1161
1113
|
delete act.result.nativeResult;
|
|
1162
1114
|
}
|
|
1163
1115
|
});
|
|
1164
1116
|
}
|
|
1165
1117
|
// If a catalog was created:
|
|
1166
|
-
if (
|
|
1167
|
-
let {catalog} =
|
|
1118
|
+
if (tempReport.catalog) {
|
|
1119
|
+
let {catalog} = tempReport;
|
|
1168
1120
|
// Get its element count.
|
|
1169
1121
|
const elementCount = Object.keys(catalog).length;
|
|
1170
1122
|
// Prune it, removing elements with no reported violations.
|
|
1171
|
-
pruneCatalog(
|
|
1172
|
-
({catalog} =
|
|
1123
|
+
pruneCatalog(tempReport);
|
|
1124
|
+
({catalog} = tempReport);
|
|
1173
1125
|
// Get properties of the pruned catalog.
|
|
1174
1126
|
const textCount = Object.values(catalog).filter(entry => entry.text).length;
|
|
1175
1127
|
const linkableTextCount = Object.values(catalog).filter(entry => entry.textLinkable).length;
|
|
@@ -1186,7 +1138,7 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
1186
1138
|
},
|
|
1187
1139
|
tools: {}
|
|
1188
1140
|
};
|
|
1189
|
-
const {acts} =
|
|
1141
|
+
const {acts} = tempReport;
|
|
1190
1142
|
// For each act:
|
|
1191
1143
|
for (const act of acts) {
|
|
1192
1144
|
// If it is a test act:
|
|
@@ -1219,12 +1171,12 @@ exports.doActs = async (report, opts = {}) => {
|
|
|
1219
1171
|
}
|
|
1220
1172
|
}
|
|
1221
1173
|
}
|
|
1222
|
-
// Add the catalog data to the
|
|
1223
|
-
|
|
1174
|
+
// Add the catalog data to the temporary report.
|
|
1175
|
+
tempReport.jobData.catalogData = catalogData;
|
|
1224
1176
|
}
|
|
1225
1177
|
}
|
|
1226
|
-
// Delete the temporary
|
|
1178
|
+
// Delete the temporary temporary report file.
|
|
1227
1179
|
await fs.rm(reportPath, {force: true});
|
|
1228
|
-
// Return the
|
|
1229
|
-
return
|
|
1180
|
+
// Return the temporary report.
|
|
1181
|
+
return tempReport;
|
|
1230
1182
|
};
|
package/procs/doTestAct.js
CHANGED
|
@@ -73,9 +73,9 @@ const sendMessage = message => {
|
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
|
-
// Performs
|
|
76
|
+
// Performs tests of a test act.
|
|
77
77
|
const doTestAct = async (reportPath, actIndex) => {
|
|
78
|
-
// Get the
|
|
78
|
+
// Get the temporary report.
|
|
79
79
|
const reportJSON = await fs.readFile(reportPath, 'utf8');
|
|
80
80
|
const report = JSON.parse(reportJSON);
|
|
81
81
|
// Get a reference to the act in the report.
|
package/procs/error.js
CHANGED
|
@@ -12,14 +12,22 @@
|
|
|
12
12
|
Handles errors.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// IMPORTS
|
|
16
|
+
|
|
17
|
+
const {nowString} = require('./dateTime');
|
|
18
|
+
|
|
19
|
+
// FUNCTIONS
|
|
20
|
+
|
|
15
21
|
// Reports a job being aborted.
|
|
16
|
-
const abortActs =
|
|
22
|
+
const abortActs = (report, actIndex, message = '') => {
|
|
17
23
|
// Add data on the aborted act to the report.
|
|
18
24
|
report.jobData.abortTime = nowString();
|
|
19
|
-
report.jobData.abortedAct = actIndex;
|
|
25
|
+
report.jobData.abortedAct = actIndex ?? 'none';
|
|
20
26
|
report.jobData.aborted = true;
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
report.jobData.abortMessage = message;
|
|
28
|
+
const abortedActString = typeof actIndex === 'number' ? ` on act ${actIndex}` : '';
|
|
29
|
+
// Log that the job is aborted.
|
|
30
|
+
console.log(`ERROR: Job aborted${abortedActString}`);
|
|
23
31
|
};
|
|
24
32
|
// Adds an error result to an act.
|
|
25
33
|
exports.addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
@@ -48,6 +56,6 @@ exports.addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
48
56
|
// If the job is to be aborted:
|
|
49
57
|
if (alsoAbort) {
|
|
50
58
|
// Add this to the report.
|
|
51
|
-
abortActs(report, actIndex);
|
|
59
|
+
abortActs(report, actIndex, message);
|
|
52
60
|
}
|
|
53
61
|
};
|
package/procs/job.js
CHANGED
|
@@ -175,7 +175,6 @@ exports.isValidJob = job => {
|
|
|
175
175
|
id,
|
|
176
176
|
strict,
|
|
177
177
|
standard,
|
|
178
|
-
observe,
|
|
179
178
|
device,
|
|
180
179
|
browserID,
|
|
181
180
|
creationTimeStamp,
|
|
@@ -203,12 +202,6 @@ exports.isValidJob = job => {
|
|
|
203
202
|
error: 'Bad job standard'
|
|
204
203
|
};
|
|
205
204
|
}
|
|
206
|
-
if (typeof observe !== 'boolean') {
|
|
207
|
-
return {
|
|
208
|
-
isValid: false,
|
|
209
|
-
error: 'Bad job observe'
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
205
|
if (! isDeviceID(device.id)) {
|
|
213
206
|
return {
|
|
214
207
|
isValid: false,
|
package/procs/launch.js
CHANGED
|
@@ -50,6 +50,7 @@ const errorWords = [
|
|
|
50
50
|
];
|
|
51
51
|
// Seconds to wait between actions.
|
|
52
52
|
const waits = Number(process.env.WAITS) ?? 0;
|
|
53
|
+
const abortAssertively = process.env.ABORT_ASSERTIVELY === 'true';
|
|
53
54
|
|
|
54
55
|
// FUNCTIONS
|
|
55
56
|
|
|
@@ -80,6 +81,29 @@ const browserClose = exports.browserClose = async page => {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
};
|
|
84
|
+
// Normalizes a file URL in case it has the Windows path format.
|
|
85
|
+
const normalizeURL = url => {
|
|
86
|
+
// If a URL was provided:
|
|
87
|
+
if (url) {
|
|
88
|
+
// If it is that of a local file:
|
|
89
|
+
if (url.toLowerCase().startsWith('file:')) {
|
|
90
|
+
let path = url.replace(/^file:\/+/i, '');
|
|
91
|
+
path = path.replace(/\\/g, '/');
|
|
92
|
+
// Return the URL normalized.
|
|
93
|
+
return 'file:///' + path.replace(/^\//, '');
|
|
94
|
+
}
|
|
95
|
+
// Otherwise, i.e. if it is not that of a local file:
|
|
96
|
+
else {
|
|
97
|
+
// Return it.
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Otherwise, i.e. if no URL was provided:
|
|
102
|
+
else {
|
|
103
|
+
// Return this.
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
83
107
|
// Visits a URL and returns the response of the server.
|
|
84
108
|
const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
85
109
|
// If the URL is a file path relative to the project root:
|
|
@@ -96,11 +120,11 @@ const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
96
120
|
});
|
|
97
121
|
report.jobData.visitLatency += Math.round((Date.now() - startTime) / 1000);
|
|
98
122
|
const httpStatus = response.status();
|
|
99
|
-
// If the response status was normal:
|
|
123
|
+
// If the response status was normal or the URL points to a local file:
|
|
100
124
|
if ([200, 304].includes(httpStatus) || url.startsWith('file:')) {
|
|
101
125
|
const actualURL = page.url();
|
|
102
|
-
const actualNorm = actualURL.startsWith('file:') ?
|
|
103
|
-
const urlNorm = url.startsWith('file:') ?
|
|
126
|
+
const actualNorm = actualURL.startsWith('file:') ? normalizeURL(actualURL) : actualURL;
|
|
127
|
+
const urlNorm = url.startsWith('file:') ? normalizeURL(url) : url;
|
|
104
128
|
// If the browser was redirected in violation of a strictness requirement:
|
|
105
129
|
if (report.strict && deSlash(actualNorm) !== deSlash(urlNorm)) {
|
|
106
130
|
// Return an error.
|
|
@@ -151,11 +175,10 @@ const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
151
175
|
// Otherwise, i.e. if the response status was otherwise abnormal:
|
|
152
176
|
else {
|
|
153
177
|
// Return an error.
|
|
154
|
-
console.log(`ERROR: Visit to ${url} got status ${httpStatus}`);
|
|
155
178
|
report.jobData.visitRejectionCount++;
|
|
156
179
|
return {
|
|
157
180
|
success: false,
|
|
158
|
-
error:
|
|
181
|
+
error: `ERROR: Visit to ${url} got status ${httpStatus}`
|
|
159
182
|
};
|
|
160
183
|
}
|
|
161
184
|
}
|
|
@@ -165,7 +188,7 @@ const goTo = exports.goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
165
188
|
}
|
|
166
189
|
return {
|
|
167
190
|
success: false,
|
|
168
|
-
error:
|
|
191
|
+
error: `ERROR visiting ${url} (${error.message.slice(0, 200)})`
|
|
169
192
|
};
|
|
170
193
|
}
|
|
171
194
|
};
|
|
@@ -208,15 +231,13 @@ const launchOnce = async opts => {
|
|
|
208
231
|
const {device} = report;
|
|
209
232
|
const deviceID = device?.id;
|
|
210
233
|
const browserID = tempBrowserID || report.browserID || '';
|
|
211
|
-
const url = tempURL || report.target?.url || '';
|
|
234
|
+
const url = normalizeURL(tempURL || report.target?.url || '');
|
|
212
235
|
let page;
|
|
213
236
|
// If the specified browser and device types and URL are valid:
|
|
214
237
|
if (isBrowserID(browserID) && isDeviceID(deviceID) && isURL(url)) {
|
|
215
|
-
// Replace the report target URL with
|
|
238
|
+
// Replace the report target URL with the specified URL.
|
|
216
239
|
report.target.url = url;
|
|
217
|
-
// Create a browser of the specified or default type.
|
|
218
240
|
const browserType = playwrightBrowsers[browserID];
|
|
219
|
-
// Define the browser-option args, depending on the browser type and head-emulation level.
|
|
220
241
|
const browserOptionArgs = [];
|
|
221
242
|
if (browserID === 'chromium') {
|
|
222
243
|
browserOptionArgs.push(
|
|
@@ -244,7 +265,7 @@ const launchOnce = async opts => {
|
|
|
244
265
|
);
|
|
245
266
|
}
|
|
246
267
|
}
|
|
247
|
-
//
|
|
268
|
+
// Get the browser options.
|
|
248
269
|
const browserOptions = {
|
|
249
270
|
logger: {
|
|
250
271
|
isEnabled: () => false,
|
|
@@ -462,34 +483,31 @@ const launchOnce = async opts => {
|
|
|
462
483
|
throw new Error(`Navigation failed (${navResult.error})`);
|
|
463
484
|
}
|
|
464
485
|
}
|
|
465
|
-
// If an error
|
|
486
|
+
// If the browser and page creation and navigation threw an error:
|
|
466
487
|
catch(error) {
|
|
467
|
-
// Report this.
|
|
468
|
-
console.log(`ERROR launching or navigating (${error.message})`);
|
|
469
488
|
// Close the browser and its context, if they exist.
|
|
470
489
|
await browserClose(page);
|
|
471
|
-
// Return
|
|
490
|
+
// Return the error.
|
|
472
491
|
return {
|
|
473
492
|
success: false,
|
|
474
493
|
error: error.message
|
|
475
494
|
};
|
|
476
495
|
}
|
|
477
496
|
}
|
|
478
|
-
//
|
|
497
|
+
// Otherwise, i.e. if the specified browser or device type or URL is invalid:
|
|
498
|
+
else {
|
|
499
|
+
// Return this.
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
error: 'Invalid browser, device type, or URL'
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// If the browser and page creation and navigation succeeded, return the page.
|
|
479
506
|
return {
|
|
480
507
|
success: true,
|
|
481
508
|
page
|
|
482
509
|
};
|
|
483
510
|
};
|
|
484
|
-
// Normalizes a file URL in case it has the Windows path format.
|
|
485
|
-
const normalizeFile = u => {
|
|
486
|
-
if (!u) return u;
|
|
487
|
-
if (!u.toLowerCase().startsWith('file:')) return u;
|
|
488
|
-
// Ensure forward slashes and three slashes after file:
|
|
489
|
-
let path = u.replace(/^file:\/+/i, '');
|
|
490
|
-
path = path.replace(/\\/g, '/');
|
|
491
|
-
return 'file:///' + path.replace(/^\//, '');
|
|
492
|
-
};
|
|
493
511
|
// Manages browser launching and navigating and returns a page.
|
|
494
512
|
exports.launch = async (opts = {}) => {
|
|
495
513
|
const {
|
|
@@ -527,9 +545,9 @@ exports.launch = async (opts = {}) => {
|
|
|
527
545
|
// Otherwise, i.e. if the launch or navigation failed:
|
|
528
546
|
else {
|
|
529
547
|
let retriesLeft = retries;
|
|
548
|
+
let {error} = launchResult;
|
|
530
549
|
// As long as retries remain, decrement the allowed retry count and:
|
|
531
|
-
while (retriesLeft
|
|
532
|
-
const {error} = launchResult;
|
|
550
|
+
while (retriesLeft) {
|
|
533
551
|
// Prepare to wait 1 second before a retry.
|
|
534
552
|
let waitSeconds = 1;
|
|
535
553
|
// If the error was a visit failure due to rate limiting:
|
|
@@ -543,7 +561,7 @@ exports.launch = async (opts = {}) => {
|
|
|
543
561
|
}
|
|
544
562
|
// Report the wait.
|
|
545
563
|
console.log(
|
|
546
|
-
`WARNING: Waiting ${waitSeconds} sec. before retrying (retries left: ${
|
|
564
|
+
`WARNING: Waiting ${waitSeconds} sec. before retrying (retries left: ${retriesLeft--})`
|
|
547
565
|
);
|
|
548
566
|
// Wait as specified.
|
|
549
567
|
await wait(1000 * waitSeconds);
|
|
@@ -567,14 +585,17 @@ exports.launch = async (opts = {}) => {
|
|
|
567
585
|
}
|
|
568
586
|
// Otherwise, i.e. if the launch or navigation failed:
|
|
569
587
|
else {
|
|
588
|
+
error = launchResult.error;
|
|
570
589
|
// Report this.
|
|
571
|
-
console.log(`WARNING: Retry failed
|
|
590
|
+
console.log(`WARNING: Retry failed (${error})`);
|
|
572
591
|
}
|
|
573
592
|
}
|
|
574
593
|
// If the retries were exhausted:
|
|
575
|
-
if (retriesLeft
|
|
576
|
-
// Report this.
|
|
577
|
-
addError(
|
|
594
|
+
if (! retriesLeft) {
|
|
595
|
+
// Report this and, if so configured, that the job was aborted.
|
|
596
|
+
addError(
|
|
597
|
+
true, abortAssertively, report, actIndex, `Launch or navigation failed; retries exhausted`
|
|
598
|
+
);
|
|
578
599
|
}
|
|
579
600
|
// Return a failure.
|
|
580
601
|
return null;
|
|
@@ -582,13 +603,13 @@ exports.launch = async (opts = {}) => {
|
|
|
582
603
|
}
|
|
583
604
|
// Otherwise, i.e. if the report is invalid:
|
|
584
605
|
else {
|
|
585
|
-
// Report this.
|
|
606
|
+
// Report this and that the job was aborted.
|
|
586
607
|
addError(
|
|
587
608
|
true,
|
|
588
|
-
|
|
609
|
+
true,
|
|
589
610
|
report,
|
|
590
611
|
actIndex,
|
|
591
|
-
`ERROR:
|
|
612
|
+
`ERROR: Job invalid (${jobValidation.error})`
|
|
592
613
|
);
|
|
593
614
|
// Return a failure.
|
|
594
615
|
return null;
|
package/run.js
CHANGED
|
@@ -48,7 +48,7 @@ exports.doJob = async (job, opts = {}) => {
|
|
|
48
48
|
console.log(`ERROR: ${jobInvalidity.error}`);
|
|
49
49
|
jobData.aborted = true;
|
|
50
50
|
jobData.abortedAct = null;
|
|
51
|
-
jobData.
|
|
51
|
+
jobData.abortMessage = jobInvalidity.error;
|
|
52
52
|
}
|
|
53
53
|
// Otherwise, i.e. if it is valid:
|
|
54
54
|
else {
|
|
@@ -69,6 +69,8 @@ exports.doJob = async (job, opts = {}) => {
|
|
|
69
69
|
visitRejectionCount: 0,
|
|
70
70
|
aborted: false,
|
|
71
71
|
abortedAct: null,
|
|
72
|
+
abortTime: '',
|
|
73
|
+
abortMessage: '',
|
|
72
74
|
presses: 0,
|
|
73
75
|
amountRead: 0,
|
|
74
76
|
toolTimes: {},
|
package/tests/testaro.js
CHANGED
|
@@ -501,11 +501,7 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
501
501
|
&& ['y', 'n'].includes(ruleSpec[0])
|
|
502
502
|
&& ruleSpec.slice(1).every(ruleID => allRuleIDs.includes(ruleID))
|
|
503
503
|
) {
|
|
504
|
-
//
|
|
505
|
-
await wait(1000);
|
|
506
|
-
// Get the rules to be tested for and their execution order.
|
|
507
|
-
// 'y' = include-list: run exactly the rules in ruleSpec.slice(1).
|
|
508
|
-
// 'n' = exclude-list: run all defaultOn rules EXCEPT those in ruleSpec.slice(1).
|
|
504
|
+
// Get the rules to be (y) or not to be (n) tested for and their execution order.
|
|
509
505
|
const excludeIDs = ruleSpec.slice(1);
|
|
510
506
|
const jobRuleIDs = ruleSpec[0] === 'y'
|
|
511
507
|
? excludeIDs
|