testaro 6.0.4 → 8.0.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.
Files changed (6) hide show
  1. package/do.js +62 -0
  2. package/high.js +3 -8
  3. package/package.json +1 -1
  4. package/run.js +193 -232
  5. package/watch.js +30 -45
  6. package/runScript.js +0 -48
package/do.js ADDED
@@ -0,0 +1,62 @@
1
+ /*
2
+ do.js
3
+ Invokes Testaro modules with arguments.
4
+ This is the universal module for use of Testaro from a command line.
5
+ Arguments:
6
+ 0. function to execute.
7
+ 1+. arguments to pass to the function.
8
+ Usage examples:
9
+ node do high script454
10
+ node do watch dir once 30
11
+ node do watch net forever 60
12
+ */
13
+
14
+ // ########## IMPORTS
15
+
16
+ // Module to keep secrets.
17
+ require('dotenv').config();
18
+ // Function to process a high-level testing request.
19
+ const {runJob} = require('./high');
20
+ // Function to watch for jobs.
21
+ const {cycle} = require('./watch');
22
+
23
+ // ########## CONSTANTS
24
+
25
+ const fn = process.argv[2];
26
+ const fnArgs = process.argv.slice(2);
27
+ const reportDir = process.env.REPORTDIR;
28
+
29
+ // ########## FUNCTIONS
30
+
31
+ // Fulfills a high-level testing request.
32
+ const doHigh = async scriptID => {
33
+ await runJob(scriptID);
34
+ console.log(`Job completed and report ${scriptID}.json saved in ${reportDir}`);
35
+ };
36
+ // Starts a watch.
37
+ const doWatch = async (isDirWatch, isForever, interval) => {
38
+ console.log(
39
+ `Starting a ${isForever ? 'repeating' : 'one-time'} ${isDirWatch ? 'directory' : 'network'} watch`
40
+ );
41
+ await cycle(isDirWatch, isForever, interval);
42
+ console.log('Watching ended');
43
+ };
44
+
45
+ // ########## OPERATION
46
+
47
+ // Execute the requested function.
48
+ if (fn === 'high' && fnArgs.length === 1) {
49
+ doHigh(fnArgs)
50
+ .then(() => {
51
+ console.log('Execution completed');
52
+ });
53
+ }
54
+ else if (fn === 'watch' && fnArgs.length === 3) {
55
+ doWatch(... fnArgs)
56
+ .then(() => {
57
+ console.log('Execution completed');
58
+ });
59
+ }
60
+ else {
61
+ console.log('ERROR: Invalid statement');
62
+ }
package/high.js CHANGED
@@ -35,20 +35,15 @@ const runJob = async scriptID => {
35
35
  if (! script.timeLimit) {
36
36
  script.timeLimit = timeLimit;
37
37
  }
38
- // Identify the start time and a timestamp.
39
- const timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
40
- // Run the script and record the report with the timestamp as name base.
41
- const id = `${timeStamp}-${scriptID}`;
38
+ // Run the script and record the report.
42
39
  const report = {
43
- id,
44
- log: [],
45
40
  script,
46
41
  acts: []
47
42
  };
48
43
  await doJob(report);
49
44
  const reportJSON = JSON.stringify(report, null, 2);
50
- await fs.writeFile(`${reportDir}/${id}.json`, reportJSON);
51
- console.log(`Report ${timeStamp}-${scriptID}.json recorded in ${process.env.REPORTDIR}`);
45
+ await fs.writeFile(`${reportDir}/${scriptID}.json`, reportJSON);
46
+ console.log(`Report ${scriptID}.json recorded in ${process.env.REPORTDIR}`);
52
47
  }
53
48
  catch(error) {
54
49
  console.log(`ERROR running job (${error.message})\n${error.stack}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "6.0.4",
3
+ "version": "8.0.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  run.js
3
- testaro main script.
3
+ Testaro main utility module.
4
4
  */
5
5
 
6
6
  // ########## IMPORTS
@@ -113,14 +113,6 @@ const errorWords = [
113
113
  // ########## VARIABLES
114
114
 
115
115
  // Facts about the current session.
116
- let logCount = 0;
117
- let logSize = 0;
118
- let errorLogCount = 0;
119
- let errorLogSize = 0;
120
- let prohibitedCount = 0;
121
- let visitTimeoutCount = 0;
122
- let visitRejectionCount = 0;
123
- let visitLatency = 0;
124
116
  let actCount = 0;
125
117
  // Facts about the current browser.
126
118
  let browser;
@@ -241,33 +233,24 @@ const isValidCommand = command => {
241
233
  return false;
242
234
  }
243
235
  };
244
- // Validates a script.
245
- const isValidScript = script => {
246
- // Get the script data.
247
- const {what, strict, commands} = script;
248
- // Return whether the script is valid:
249
- return what
250
- && typeof strict === 'boolean'
251
- && commands
252
- && typeof what === 'string'
253
- && Array.isArray(commands)
254
- && commands[0].type === 'launch'
255
- && commands.length > 1
256
- && commands[1].type === 'url'
257
- && isURL(commands[1].which)
258
- && commands.every(command => isValidCommand(command));
259
- };
260
- // Validates an initialized reports array.
261
- const isValidActs = acts => Array.isArray(acts) && ! acts.length;
262
- // Validates an initialized log array.
263
- const isValidLog = log => Array.isArray(log) && ! log.length;
264
236
  // Validates a report object.
265
- const isValidReport = async report => {
237
+ const isValidReport = report => {
266
238
  if (report) {
267
- const {script, log, acts} = report;
268
- return isValidScript(script)
269
- && isValidLog(log)
270
- && isValidActs(acts);
239
+ // Return whether the report is valid.
240
+ const {script, acts} = report;
241
+ const {what, strict, commands} = script;
242
+ return what
243
+ && typeof strict === 'boolean'
244
+ && commands
245
+ && typeof what === 'string'
246
+ && Array.isArray(commands)
247
+ && commands[0].type === 'launch'
248
+ && commands.length > 1
249
+ && commands[1].type === 'url'
250
+ && isURL(commands[1].which)
251
+ && commands.every(command => isValidCommand(command))
252
+ && Array.isArray(acts)
253
+ && ! acts.length;
271
254
  }
272
255
  else {
273
256
  return false;
@@ -276,6 +259,8 @@ const isValidReport = async report => {
276
259
 
277
260
  // ########## OTHER FUNCTIONS
278
261
 
262
+ // Returns a string representing the date and time.
263
+ const nowString = () => (new Date()).toISOString().slice(0, 19);
279
264
  // Closes the current browser.
280
265
  const browserClose = async () => {
281
266
  if (browser) {
@@ -289,7 +274,7 @@ const browserClose = async () => {
289
274
  // Returns the first line of an error message.
290
275
  const errorStart = error => error.message.replace(/\n.+/s, '');
291
276
  // Launches a browser.
292
- const launch = async typeName => {
277
+ const launch = async (report, typeName) => {
293
278
  const browserType = require('playwright')[typeName];
294
279
  // If the specified browser type exists:
295
280
  if (browserType) {
@@ -343,15 +328,15 @@ const launch = async typeName => {
343
328
  console.log(`\n${indentedMsg}`);
344
329
  const msgTextLC = msgText.toLowerCase();
345
330
  const msgLength = msgText.length;
346
- logCount++;
347
- logSize += msgLength;
331
+ report.jobData.logCount++;
332
+ report.jobData.logSize += msgLength;
348
333
  if (errorWords.some(word => msgTextLC.includes(word))) {
349
- errorLogCount++;
350
- errorLogSize += msgLength;
334
+ report.jobData.errorLogCount++;
335
+ report.jobData.errorLogSize += msgLength;
351
336
  }
352
337
  const msgLC = msgText.toLowerCase();
353
338
  if (msgText.includes('403') && (msgLC.includes('status') || msgLC.includes('prohibited'))) {
354
- prohibitedCount++;
339
+ report.jobData.prohibitedCount++;
355
340
  }
356
341
  });
357
342
  });
@@ -459,114 +444,6 @@ const textOf = async (page, element) => {
459
444
  };
460
445
  // Returns a string with any final slash removed.
461
446
  const deSlash = string => string.endsWith('/') ? string.slice(0, -1) : string;
462
- // Tries to visit a URL.
463
- const goto = async (page, url, timeout, waitUntil, isStrict) => {
464
- if (url.startsWith('file://.')) {
465
- url = url.replace('file://', `file://${__dirname}/`);
466
- }
467
- // Visit the URL.
468
- const startTime = Date.now();
469
- const response = await page.goto(url, {
470
- timeout,
471
- waitUntil
472
- })
473
- .catch(error => {
474
- console.log(`ERROR: Visit to ${url} timed out before ${waitUntil} (${errorStart(error)})`);
475
- visitTimeoutCount++;
476
- return 'error';
477
- });
478
- visitLatency += Math.round((Date.now() - startTime) / 1000);
479
- // If the visit succeeded:
480
- if (typeof response !== 'string') {
481
- const httpStatus = response.status();
482
- // If the response status was normal:
483
- if ([200, 304].includes(httpStatus) || url.startsWith('file:')) {
484
- // If the browser was redirected in violation of a strictness requirement.
485
- const actualURL = page.url();
486
- if (isStrict && deSlash(actualURL) !== deSlash(url)) {
487
- // Return an error.
488
- console.log(`ERROR: Visit to ${url} redirected to ${actualURL}`);
489
- return 'redirection';
490
- }
491
- // Otherwise, i.e. if no prohibited redirection occurred:
492
- else {
493
- // Press the Escape key to dismiss any modal dialog.
494
- await page.keyboard.press('Escape');
495
- // Return the response.
496
- return response;
497
- }
498
- }
499
- // Otherwise, i.e. if the response status was abnormal:
500
- else {
501
- // Return an error.
502
- console.log(`ERROR: Visit to ${url} got status ${httpStatus}`);
503
- visitRejectionCount++;
504
- return 'error';
505
- }
506
- }
507
- // Otherwise, i.e. if the visit failed:
508
- else {
509
- // Return an error.
510
- return 'error';
511
- }
512
- };
513
- // Visits the URL that is the value of the “which” property of an act.
514
- const visit = async (act, page, isStrict) => {
515
- // Identify the URL.
516
- const resolved = act.which.replace('__dirname', __dirname);
517
- requestedURL = resolved;
518
- // Visit it and wait until the network is idle.
519
- let response = await goto(page, requestedURL, 15000, 'networkidle', isStrict);
520
- // If the visit fails:
521
- if (response === 'error') {
522
- // Try again until the DOM is loaded.
523
- response = await goto(page, requestedURL, 10000, 'domcontentloaded', isStrict);
524
- // If the visit fails:
525
- if (response === 'error') {
526
- // Launch another browser type.
527
- const newBrowserName = Object.keys(browserTypeNames)
528
- .find(name => name !== browserTypeName);
529
- console.log(`>> Launching ${newBrowserName} instead`);
530
- await launch(newBrowserName);
531
- // Identify its only page as current.
532
- page = browserContext.pages()[0];
533
- // Try again until the network is idle.
534
- response = await goto(page, requestedURL, 10000, 'networkidle', isStrict);
535
- // If the visit fails:
536
- if (response === 'error') {
537
- // Try again until the DOM is loaded.
538
- response = await goto(page, requestedURL, 5000, 'domcontentloaded', isStrict);
539
- // If the visit fails:
540
- if (response === 'error') {
541
- // Try again or until a load.
542
- response = await goto(page, requestedURL, 5000, 'load', isStrict);
543
- // If the visit fails:
544
- if (response === 'error') {
545
- // Give up.
546
- const errorMsg = `ERROR: Attempts to visit ${requestedURL} failed`;
547
- console.log(errorMsg);
548
- act.result = errorMsg;
549
- await page.goto('about:blank')
550
- .catch(error => {
551
- console.log(`ERROR: Navigation to blank page failed (${error.message})`);
552
- });
553
- return null;
554
- }
555
- }
556
- }
557
- }
558
- }
559
- // If one of the visits succeeded:
560
- if (response) {
561
- // Add the resulting URL to the act.
562
- if (isStrict && response === 'redirection') {
563
- act.error = 'ERROR: Navigation redirected';
564
- }
565
- act.result = page.url();
566
- // Return the page.
567
- return page;
568
- }
569
- };
570
447
  // Returns a property value and whether it satisfies an expectation.
571
448
  const isTrue = (object, specs) => {
572
449
  const property = specs[0];
@@ -634,6 +511,9 @@ const wait = ms => {
634
511
  };
635
512
  // Adds an error result to an act.
636
513
  const addError = (act, error, message) => {
514
+ if (! act.result) {
515
+ act.result = {};
516
+ }
637
517
  act.result.success = false;
638
518
  act.result.error = error;
639
519
  act.result.message = message;
@@ -641,6 +521,65 @@ const addError = (act, error, message) => {
641
521
  act.result.prevented = true;
642
522
  }
643
523
  };
524
+ // Visits a URL and returns the response of the server.
525
+ const goTo = async (report, page, url, timeout, waitUntil, isStrict) => {
526
+ if (url.startsWith('file://')) {
527
+ url = url.replace('file://', `file://${__dirname}/`);
528
+ }
529
+ // Visit the URL.
530
+ const startTime = Date.now();
531
+ const response = await page.goto(url, {
532
+ timeout,
533
+ waitUntil
534
+ })
535
+ .catch(error => {
536
+ console.log(`ERROR: Visit to ${url} timed out before ${waitUntil} (${errorStart(error)})`);
537
+ report.jobData.visitTimeoutCount++;
538
+ return {
539
+ error: 'timeout'
540
+ };
541
+ });
542
+ report.jobData.visitLatency += Math.round((Date.now() - startTime) / 1000);
543
+ // If the visit succeeded:
544
+ if (! response.error) {
545
+ const httpStatus = response.status();
546
+ // If the response status was normal:
547
+ if ([200, 304].includes(httpStatus) || url.startsWith('file:')) {
548
+ // If the browser was redirected in violation of a strictness requirement:
549
+ const actualURL = page.url();
550
+ if (isStrict && deSlash(actualURL) !== deSlash(url)) {
551
+ // Return an error.
552
+ console.log(`ERROR: Visit to ${url} redirected to ${actualURL}`);
553
+ return {
554
+ exception: 'badRedirection'
555
+ };
556
+ }
557
+ // Otherwise, i.e. if no prohibited redirection occurred:
558
+ else {
559
+ // Press the Escape key to dismiss any modal dialog.
560
+ await page.keyboard.press('Escape');
561
+ // Return the response.
562
+ return response;
563
+ }
564
+ }
565
+ // Otherwise, i.e. if the response status was abnormal:
566
+ else {
567
+ // Return an error.
568
+ console.log(`ERROR: Visit to ${url} got status ${httpStatus}`);
569
+ report.jobData.visitRejectionCount++;
570
+ return {
571
+ error: 'badStatus'
572
+ };
573
+ }
574
+ }
575
+ // Otherwise, i.e. if the visit failed:
576
+ else {
577
+ // Return an error.
578
+ return {
579
+ error: 'noStatus'
580
+ };
581
+ }
582
+ };
644
583
  // Recursively performs the acts in a report.
645
584
  const doActs = async (report, actIndex, page) => {
646
585
  process.on('message', message => {
@@ -700,7 +639,7 @@ const doActs = async (report, actIndex, page) => {
700
639
  // Otherwise, if the command is a launch:
701
640
  else if (act.type === 'launch') {
702
641
  // Launch the specified browser, creating a browser context and a page in it.
703
- await launch(act.which);
642
+ await launch(report, act.which);
704
643
  // Identify its only page as current.
705
644
  page = browserContext.pages()[0];
706
645
  }
@@ -708,8 +647,73 @@ const doActs = async (report, actIndex, page) => {
708
647
  else if (page) {
709
648
  // If the command is a url:
710
649
  if (act.type === 'url') {
711
- // Visit it and wait until it is stable.
712
- page = await visit(act, page, report.isStrict);
650
+ // Identify the URL.
651
+ const resolved = act.which.replace('__dirname', __dirname);
652
+ requestedURL = resolved;
653
+ // Visit it and wait until the network is idle.
654
+ const {strict} = report.script;
655
+ let response = await goTo(report, page, requestedURL, 15000, 'networkidle', strict);
656
+ // If the visit fails:
657
+ if (response.error) {
658
+ // Try again until the DOM is loaded.
659
+ response = await goTo(report, page, requestedURL, 10000, 'domcontentloaded', strict);
660
+ // If the visit fails:
661
+ if (response.error) {
662
+ // Launch another browser type.
663
+ const newBrowserName = Object.keys(browserTypeNames)
664
+ .find(name => name !== browserTypeName);
665
+ console.log(`>> Launching ${newBrowserName} instead`);
666
+ await launch(newBrowserName);
667
+ // Identify its only page as current.
668
+ page = browserContext.pages()[0];
669
+ // Try again until the network is idle.
670
+ response = await goTo(report, page, requestedURL, 10000, 'networkidle', strict);
671
+ // If the visit fails:
672
+ if (response.error) {
673
+ // Try again until the DOM is loaded.
674
+ response = await goTo(report, page, requestedURL, 5000, 'domcontentloaded', strict);
675
+ // If the visit fails:
676
+ if (response.error) {
677
+ // Try again or until a load.
678
+ response = await goTo(report, page, requestedURL, 5000, 'load', strict);
679
+ // If the visit fails:
680
+ if (response.error) {
681
+ // Navigate to a blank page instead.
682
+ await page.goto('about:blank')
683
+ .catch(error => {
684
+ console.log(`ERROR: Navigation to blank page failed (${error.message})`);
685
+ });
686
+ }
687
+ }
688
+ }
689
+ }
690
+ }
691
+ // If none of the visits succeeded:
692
+ if (response.error) {
693
+ // Report this.
694
+ report.jobData.aborted = true;
695
+ report.jobData.abortedAct = actIndex;
696
+ addError(act, 'failure', 'ERROR: Visits failed');
697
+ // Quit.
698
+ actIndex = -2;
699
+ }
700
+ // Otherwise, i.e. if the last visit attempt succeeded:
701
+ else {
702
+ // If a prohibited redirection occurred:
703
+ if (response.exception === 'badRedirection') {
704
+ // Report this.
705
+ report.jobData.aborted = true;
706
+ report.jobData.abortedAct = actIndex;
707
+ addError(act, 'badRedirection', 'ERROR: Navigation illicitly redirected');
708
+ // Quit.
709
+ actIndex = -2;
710
+ }
711
+ // Add the resulting URL to the act.
712
+ if (! act.result) {
713
+ act.result = {};
714
+ }
715
+ act.result.url = page.url();
716
+ }
713
717
  }
714
718
  // Otherwise, if the act is a wait for text:
715
719
  else if (act.type === 'wait') {
@@ -782,10 +786,7 @@ const doActs = async (report, actIndex, page) => {
782
786
  )
783
787
  .catch(error => {
784
788
  console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
785
- act.result = {
786
- success: false,
787
- error: `ERROR waiting for page to be ${act.which}`
788
- };
789
+ addError(act, 'timeout', `ERROR waiting for page to be ${act.which}`);
789
790
  actIndex = -2;
790
791
  });
791
792
  if (actIndex > -2) {
@@ -976,8 +977,8 @@ const doActs = async (report, actIndex, page) => {
976
977
  testReport.result.failureCount = failureCount;
977
978
  }
978
979
  testReport.result.success = true;
979
- report.testTimes.push([act.which, Math.round((Date.now() - startTime) / 1000)]);
980
- report.testTimes.sort((a, b) => b[1] - a[1]);
980
+ report.jobData.testTimes.push([act.which, Math.round((Date.now() - startTime) / 1000)]);
981
+ report.jobData.testTimes.sort((a, b) => b[1] - a[1]);
981
982
  // Add the result object (possibly an array) to the act.
982
983
  const resultCount = Object.keys(testReport.result).length;
983
984
  act.result = resultCount ? testReport.result : {success: false};
@@ -1220,7 +1221,7 @@ const doActs = async (report, actIndex, page) => {
1220
1221
  }
1221
1222
  // Enter the text.
1222
1223
  await selection.type(act.what);
1223
- report.presses += act.what.length;
1224
+ report.jobData.presses += act.what.length;
1224
1225
  act.result.success = true;
1225
1226
  act.result.move = 'entered';
1226
1227
  // If the input is a search input:
@@ -1252,7 +1253,7 @@ const doActs = async (report, actIndex, page) => {
1252
1253
  else if (act.type === 'press') {
1253
1254
  // Identify the number of times to press the key.
1254
1255
  let times = 1 + (act.again || 0);
1255
- report.presses += times;
1256
+ report.jobData.presses += times;
1256
1257
  const key = act.which;
1257
1258
  // Press the key.
1258
1259
  while (times--) {
@@ -1407,30 +1408,22 @@ const doActs = async (report, actIndex, page) => {
1407
1408
  act.result.items = items;
1408
1409
  }
1409
1410
  // Add the totals to the report.
1410
- report.presses += presses;
1411
- report.amountRead += amountRead;
1411
+ report.jobData.presses += presses;
1412
+ report.jobData.amountRead += amountRead;
1412
1413
  }
1413
1414
  // Otherwise, i.e. if the act type is unknown:
1414
1415
  else {
1415
1416
  // Add the error result to the act.
1416
- act.result = {
1417
- success: false,
1418
- error: 'badType',
1419
- message: 'ERROR: invalid command type'
1420
- };
1417
+ addError(act, 'badType', 'ERROR: Invalid command type');
1421
1418
  }
1422
1419
  }
1423
1420
  // Otherwise, i.e. if redirection is prohibited but occurred:
1424
1421
  else {
1425
1422
  // Add an error result to the act.
1426
- act.result = {
1427
- success: false,
1428
- error: 'redirection',
1429
- message: `ERROR: Page redirected to (${url})`
1430
- };
1423
+ addError(act, 'redirection', `ERROR: Page redirected to (${url})`);
1431
1424
  }
1432
1425
  }
1433
- // Otherwise, i.e. if the required page URL does not exist:
1426
+ // Otherwise, a page URL is required but does not exist, so:
1434
1427
  else {
1435
1428
  // Add an error result to the act.
1436
1429
  addError(act, 'noURL', 'ERROR: Page has no URL');
@@ -1447,60 +1440,18 @@ const doActs = async (report, actIndex, page) => {
1447
1440
  else {
1448
1441
  // Add an error result to the act.
1449
1442
  const errorMsg = `ERROR: Invalid command of type ${act.type}`;
1450
- act.result = {
1451
- success: false,
1452
- error: 'badCommand',
1453
- message: errorMsg
1454
- };
1455
1443
  console.log(errorMsg);
1444
+ addError(act, 'badCommand', errorMsg);
1456
1445
  // Quit.
1457
1446
  actIndex = -2;
1458
1447
  }
1459
- // Perform the remaining acts.
1448
+ // Perform the remaining acts unless the performance of acts has been aborted.
1460
1449
  await doActs(report, actIndex + 1, page);
1461
1450
  }
1462
1451
  else {
1463
1452
  await browserClose();
1464
1453
  }
1465
1454
  };
1466
- // Performs the commands in a script.
1467
- const doScript = async (report) => {
1468
- // Reinitialize the log statistics.
1469
- logCount = 0;
1470
- logSize = 0;
1471
- errorLogCount = 0;
1472
- errorLogSize = 0;
1473
- prohibitedCount = 0;
1474
- visitTimeoutCount = 0;
1475
- visitRejectionCount = 0;
1476
- // Add the start time to the report.
1477
- const startTime = new Date();
1478
- report.startTime = startTime.toISOString().slice(0, 19);
1479
- // Add initialized properties to the report.
1480
- report.presses = 0;
1481
- report.amountRead = 0;
1482
- report.testTimes = [];
1483
- // Perform the specified acts.
1484
- await doActs(report, 0, null);
1485
- // Add the log statistics to the report.
1486
- report.logCount = logCount;
1487
- report.logSize = logSize;
1488
- report.errorLogCount = errorLogCount;
1489
- report.errorLogSize = errorLogSize;
1490
- report.prohibitedCount = prohibitedCount;
1491
- report.visitTimeoutCount = visitTimeoutCount;
1492
- report.visitRejectionCount = visitRejectionCount;
1493
- report.visitLatency = visitLatency;
1494
- // Add the end time and duration to the report.
1495
- const endTime = new Date();
1496
- report.endTime = endTime.toISOString().slice(0, 19);
1497
- report.elapsedSeconds = Math.floor((endTime - startTime) / 1000);
1498
- // Add an end time to the log.
1499
- report.log.push({
1500
- event: 'endTime',
1501
- value: ((new Date()).toISOString().slice(0, 19))
1502
- });
1503
- };
1504
1455
  // Injects launch and url acts into a report where necessary to undo DOM changes.
1505
1456
  const injectLaunches = acts => {
1506
1457
  let injectMore = true;
@@ -1552,17 +1503,6 @@ const injectLaunches = acts => {
1552
1503
  exports.doJob = async report => {
1553
1504
  // If the report object is valid:
1554
1505
  if(isValidReport(report)) {
1555
- // Add a start time to the log.
1556
- report.log.push(
1557
- {
1558
- event: 'startTime',
1559
- value: ((new Date()).toISOString().slice(0, 19))
1560
- }
1561
- );
1562
- // Add a time stamp to the report.
1563
- report.timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
1564
- // Add an ID to the report.
1565
- report.id = `${report.timeStamp}-${report.script.id}`;
1566
1506
  // Add the script commands to the report as its initial acts.
1567
1507
  report.acts = JSON.parse(JSON.stringify(report.script.commands));
1568
1508
  /*
@@ -1572,10 +1512,31 @@ exports.doJob = async report => {
1572
1512
  if (urlInject === 'yes') {
1573
1513
  injectLaunches(report.acts);
1574
1514
  }
1575
- // Perform the acts, asynchronously adding to the log and report.
1576
- await doScript(report);
1577
- }
1578
- else {
1579
- console.log('ERROR: options missing or invalid');
1515
+ // Add initialized job data to the report.
1516
+ const startTime = new Date();
1517
+ report.jobData = {
1518
+ startTime: nowString(),
1519
+ endTime: '',
1520
+ elapsedSeconds: 0,
1521
+ visitLatency: 0,
1522
+ logCount: 0,
1523
+ logSize: 0,
1524
+ errorLogCount: 0,
1525
+ errorLogSize: 0,
1526
+ prohibitedCount: 0,
1527
+ visitTimeoutCount: 0,
1528
+ visitRejectionCount: 0,
1529
+ aborted: false,
1530
+ abortedAct: null,
1531
+ presses: 0,
1532
+ amountRead: 0,
1533
+ testTimes: []
1534
+ };
1535
+ // Recursively perform the specified acts.
1536
+ await doActs(report, 0, null);
1537
+ // Add the end time and duration to the report.
1538
+ const endTime = new Date();
1539
+ report.jobData.endTime = nowString();
1540
+ report.jobData.elapsedSeconds = Math.floor((endTime - startTime) / 1000);
1580
1541
  }
1581
1542
  };
package/watch.js CHANGED
@@ -1,11 +1,6 @@
1
1
  /*
2
2
  watch.js
3
- Watches for a script and runs it.
4
- Arguments:
5
- 0. Watch type: 'dir' or 'net'.
6
- 1. How long to watch: 'once' or 'forever'.
7
- 2. How often to check in seconds.
8
- Usage example: node watch dir once 15
3
+ Module for watching for a script and running it when found.
9
4
  */
10
5
 
11
6
  // ########## IMPORTS
@@ -19,15 +14,10 @@ const {doJob} = require('./run');
19
14
 
20
15
  // ########## CONSTANTS
21
16
 
22
- const watchType = process.argv[2];
23
- const watchForever = process.argv[3] === 'forever';
24
- const interval = Number.parseInt(process.argv[4]);
25
- let client;
26
- if (watchType === 'net') {
27
- client = require(process.env.PROTOCOL || 'https');
28
- }
17
+ const protocol = process.env.PROTOCOL || 'https';
18
+ const client = require(protocol);
29
19
  const jobURL = process.env.JOB_URL;
30
- const worker = process.env.WORKER;
20
+ const agent = process.env.AGENT;
31
21
  const watchDir = process.env.WATCHDIR;
32
22
  const doneDir = process.env.DONEDIR;
33
23
  const reportURL = process.env.REPORT_URL;
@@ -64,7 +54,7 @@ const checkDirJob = async () => {
64
54
  // Checks for a network job.
65
55
  const checkNetJob = async () => {
66
56
  const script = await new Promise(resolve => {
67
- const wholeURL = `${process.env.PROTOCOL}://${jobURL}?worker=${worker}`;
57
+ const wholeURL = `${protocol}://${jobURL}?agent=${agent}`;
68
58
  const request = client.request(wholeURL, response => {
69
59
  const chunks = [];
70
60
  response.on('data', chunk => {
@@ -96,7 +86,7 @@ const writeDirReport = async report => {
96
86
  if (scriptID) {
97
87
  try {
98
88
  const reportJSON = JSON.stringify(report, null, 2);
99
- const reportName = `${report.timeStamp}-${scriptID}.json`;
89
+ const reportName = `${report.script.timeStamp}-${scriptID}.json`;
100
90
  await fs.writeFile(`${reportDir}/${reportName}`, reportJSON);
101
91
  console.log(`Report ${reportName} saved`);
102
92
  return true;
@@ -110,7 +100,7 @@ const writeDirReport = async report => {
110
100
  // Submits a network report.
111
101
  const writeNetReport = async report => {
112
102
  const ack = await new Promise(resolve => {
113
- const wholeURL = `${process.env.PROTOCOL}://${reportURL}?worker=${worker}`;
103
+ const wholeURL = `${process.env.PROTOCOL}://${reportURL}?agent=${agent}`;
114
104
  const request = client.request(wholeURL, {method: 'POST'}, response => {
115
105
  const chunks = [];
116
106
  response.on('data', chunk => {
@@ -131,14 +121,14 @@ const writeNetReport = async report => {
131
121
  });
132
122
  request.write(JSON.stringify(report, null, 2));
133
123
  request.end();
134
- console.log(`Report ${report.timeStamp}-${report.script.id} submitted`);
124
+ console.log(`Report ${report.script.id} submitted`);
135
125
  });
136
126
  return ack;
137
127
  };
138
128
  // Archives a job.
139
129
  const archiveJob = async script => {
140
130
  const scriptJSON = JSON.stringify(script, null, 2);
141
- await fs.writeFile(`${doneDir}/${script.timeStamp}-${script.id}.json`, scriptJSON);
131
+ await fs.writeFile(`${doneDir}/${script.id}.json`, scriptJSON);
142
132
  await fs.rm(`${watchDir}/${script.id}.json`);
143
133
  };
144
134
  // Waits.
@@ -149,25 +139,27 @@ const wait = ms => {
149
139
  }, ms);
150
140
  });
151
141
  };
152
- // Runs a script, time-stamps it, and returns a report.
153
- const runJob = async script => {
142
+ // Runs a job and returns a report.
143
+ exports.runJob = async (script, isDirWatch) => {
154
144
  const {id} = script;
155
145
  if (id) {
156
146
  try {
157
- // Identify the start time and a time stamp.
158
- const timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
159
- script.timeStamp = timeStamp;
160
147
  // Initialize a report.
161
148
  const report = {
162
149
  log: [],
163
150
  script,
164
151
  acts: []
165
152
  };
153
+ // Run the job, adding to the report.
166
154
  await doJob(report);
167
- if (watchType === 'dir') {
155
+ // If a directory was watched:
156
+ if (isDirWatch) {
157
+ // Save the report.
168
158
  return await writeDirReport(report);
169
159
  }
160
+ // Otherwise, i.e. if the network was watched:
170
161
  else {
162
+ // Send the report to the server.
171
163
  const ack = await writeNetReport(report);
172
164
  if (ack.error) {
173
165
  console.log(JSON.stringify(ack, null, 2));
@@ -193,9 +185,10 @@ const runJob = async script => {
193
185
  }
194
186
  };
195
187
  // Checks for a job, performs it, and submits a report, once or repeatedly.
196
- const cycle = async forever => {
188
+ exports.cycle = async (isDirWatch, isForever, interval) => {
197
189
  const intervalMS = 1000 * Number.parseInt(interval);
198
190
  let statusOK = true;
191
+ // Prevent a wait before the first iteration.
199
192
  let empty = false;
200
193
  console.log(`Watching started with intervals of ${interval} seconds when idle`);
201
194
  while (statusOK) {
@@ -204,32 +197,29 @@ const cycle = async forever => {
204
197
  }
205
198
  // Check for a job.
206
199
  let script;
207
- if (watchType === 'dir') {
200
+ if (isDirWatch) {
208
201
  script = await checkDirJob();
209
202
  }
210
- else if (watchType === 'net') {
211
- script = await checkNetJob();
212
- }
213
203
  else {
214
- script = {};
215
- console.log('ERROR: invalid WATCH_TYPE environment variable');
216
- statusOK = false;
204
+ script = await checkNetJob();
217
205
  }
218
206
  // If there was one:
219
207
  if (script.id) {
220
- // Run it, add a timestamp to it, and save a report.
208
+ // Run it and save a report.
221
209
  console.log(`Running script ${script.id}`);
222
- statusOK = await runJob(script);
223
- console.log(`Job ${script.id} finished with time stamp ${script.timeStamp}`);
210
+ statusOK = await exports.runJob(script, isDirWatch);
211
+ console.log(`Job ${script.id} finished`);
224
212
  if (statusOK) {
225
- // If the script was in a directory:
226
- if (watchType === 'dir') {
213
+ // If a directory was watched:
214
+ if (isDirWatch) {
227
215
  // Archive the script.
228
216
  await archiveJob(script);
229
- console.log(`Script ${script.id}.json archived as ${script.timeStamp}-${script.id}.json`);
217
+ console.log(`Script ${script.id} archived`);
230
218
  }
231
219
  // If watching was specified for only 1 job, stop.
232
- statusOK = forever;
220
+ statusOK = isForever;
221
+ // Prevent a wait before the next iteration.
222
+ empty = false;
233
223
  }
234
224
  }
235
225
  else {
@@ -238,8 +228,3 @@ const cycle = async forever => {
238
228
  }
239
229
  console.log('Watching ended');
240
230
  };
241
-
242
- // ########## OPERATION
243
-
244
- // Start watching, as specified, either forever or until 1 job is run.
245
- cycle(watchForever);
package/runScript.js DELETED
@@ -1,48 +0,0 @@
1
- /*
2
- runScript.js
3
- Runs a script and writes a report file.
4
- */
5
-
6
- // ########## IMPORTS
7
-
8
- const {doJob} = require('./run');
9
-
10
- // ########## FUNCTIONS
11
-
12
- // Runs a script and returns the report.
13
- const runScript = async (id, scriptJSON) => {
14
- const report = {
15
- id,
16
- log: [],
17
- script: JSON.parse(scriptJSON),
18
- acts: []
19
- };
20
- let reportJSON = JSON.stringify(report, null, 2);
21
- await doJob(report);
22
- report.acts.forEach(act => {
23
- try {
24
- JSON.stringify(act);
25
- }
26
- catch (error) {
27
- console.log(`ERROR: act of type ${act.type} malformatted`);
28
- act = {
29
- type: act.type || 'ERROR',
30
- which: act.which || 'N/A',
31
- prevented: true,
32
- error: error.message
33
- };
34
- console.log(`act changed to:\n${JSON.stringify(act, null, 2)}`);
35
- }
36
- });
37
- try {
38
- reportJSON = JSON.stringify(report, null, 2);
39
- return reportJSON;
40
- }
41
- catch(error) {
42
- console.log(`ERROR: report for host ${id} not JSON (${error.message})`);
43
- return '';
44
- }
45
- };
46
-
47
- // ########## OPERATION
48
- runScript(... process.argv.slice(2));