testaro 47.2.1 → 48.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.
- package/README.md +5 -7
- package/call.js +1 -1
- package/dirWatch.js +5 -7
- package/netWatch.js +1 -1
- package/package.json +1 -1
- package/procs/doTestAct.js +121 -0
- package/run.js +712 -724
- package/tests/alfa.js +3 -6
- package/tests/ibm.js +10 -5
- package/validation/executors/run.js +2 -2
- package/validation/validateTest.js +1 -1
package/run.js
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
// IMPORTS
|
|
31
31
|
|
|
32
|
+
// Module to perform file operations.
|
|
33
|
+
const fs = require('fs/promises');
|
|
32
34
|
// Module to keep secrets.
|
|
33
35
|
require('dotenv').config();
|
|
34
36
|
// Module to validate jobs.
|
|
@@ -39,6 +41,8 @@ const {standardize} = require('./procs/standardize');
|
|
|
39
41
|
const {identify} = require('./procs/identify');
|
|
40
42
|
// Module to send a notice to an observer.
|
|
41
43
|
const {tellServer} = require('./procs/tellServer');
|
|
44
|
+
// Module to create child processes.
|
|
45
|
+
const {fork} = require('child_process');
|
|
42
46
|
|
|
43
47
|
// CONSTANTS
|
|
44
48
|
|
|
@@ -194,7 +198,7 @@ const browserClose = async () => {
|
|
|
194
198
|
}
|
|
195
199
|
};
|
|
196
200
|
// Launches a browser, navigates to a URL, and returns browser data.
|
|
197
|
-
const launch = async (report, debug, waits, tempBrowserID, tempURL) => {
|
|
201
|
+
const launch = exports.launch = async (report, debug, waits, tempBrowserID, tempURL) => {
|
|
198
202
|
const act = report.acts[actIndex];
|
|
199
203
|
const {device} = report;
|
|
200
204
|
const deviceID = device && device.id;
|
|
@@ -306,25 +310,21 @@ const launch = async (report, debug, waits, tempBrowserID, tempURL) => {
|
|
|
306
310
|
// Otherwise, i.e. if the launch or navigation failed:
|
|
307
311
|
else {
|
|
308
312
|
// Report this and abort the job.
|
|
309
|
-
actIndex
|
|
310
|
-
true, true, report, actIndex, `ERROR: Launch failed (${navResult.error})`
|
|
311
|
-
);
|
|
313
|
+
addError(true, true, report, actIndex, `ERROR: Launch failed (${navResult.error})`);
|
|
312
314
|
page = null;
|
|
313
315
|
}
|
|
314
316
|
}
|
|
315
317
|
// If an error occurred:
|
|
316
318
|
catch(error) {
|
|
317
319
|
// Report this and abort the job.
|
|
318
|
-
actIndex
|
|
319
|
-
true, true, report, actIndex, `ERROR launching or navigating ${error.message}`
|
|
320
|
-
);
|
|
320
|
+
addError(true, true, report, actIndex, `ERROR launching or navigating ${error.message}`);
|
|
321
321
|
page = null;
|
|
322
322
|
};
|
|
323
323
|
}
|
|
324
324
|
// Otherwise, i.e. if the browser or device ID is invalid:
|
|
325
325
|
else {
|
|
326
326
|
// Report this and abort the job.
|
|
327
|
-
|
|
327
|
+
addError(
|
|
328
328
|
true,
|
|
329
329
|
true,
|
|
330
330
|
report,
|
|
@@ -332,6 +332,7 @@ const launch = async (report, debug, waits, tempBrowserID, tempURL) => {
|
|
|
332
332
|
`ERROR: Browser ${browserID}, device ${deviceID}, or URL ${url} invalid`
|
|
333
333
|
);
|
|
334
334
|
}
|
|
335
|
+
exports.page = page;
|
|
335
336
|
};
|
|
336
337
|
// Returns a string representing the date and time.
|
|
337
338
|
const nowString = () => (new Date()).toISOString().slice(2, 16);
|
|
@@ -501,18 +502,16 @@ const wait = ms => {
|
|
|
501
502
|
});
|
|
502
503
|
};
|
|
503
504
|
// Reports a job being aborted and returns an abortive act index.
|
|
504
|
-
const abortActs =
|
|
505
|
+
const abortActs = (report, actIndex) => {
|
|
505
506
|
// Add data on the aborted act to the report.
|
|
506
507
|
report.jobData.abortTime = nowString();
|
|
507
508
|
report.jobData.abortedAct = actIndex;
|
|
508
509
|
report.jobData.aborted = true;
|
|
509
510
|
// Report that the job is aborted.
|
|
510
511
|
console.log(`ERROR: Job aborted on act ${actIndex}`);
|
|
511
|
-
// Return an abortive act index.
|
|
512
|
-
return -2;
|
|
513
512
|
};
|
|
514
513
|
// Adds an error result to an act.
|
|
515
|
-
const addError =
|
|
514
|
+
const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
516
515
|
// If the error is to be logged:
|
|
517
516
|
if (alsoLog) {
|
|
518
517
|
// Log it.
|
|
@@ -524,6 +523,7 @@ const addError = async(alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
524
523
|
act.result.success ??= false;
|
|
525
524
|
act.result.error ??= message;
|
|
526
525
|
if (act.type === 'test') {
|
|
526
|
+
act.data ??= {};
|
|
527
527
|
act.data.success = false;
|
|
528
528
|
act.data.prevented = true;
|
|
529
529
|
act.data.error = message;
|
|
@@ -532,816 +532,803 @@ const addError = async(alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
532
532
|
}
|
|
533
533
|
// If the job is to be aborted:
|
|
534
534
|
if (alsoAbort) {
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
// Otherwise, i.e. if the job is not to be aborted:
|
|
539
|
-
else {
|
|
540
|
-
// Return the current act index.
|
|
541
|
-
return actIndex;
|
|
535
|
+
// Add this to the report.
|
|
536
|
+
abortActs(report, actIndex);
|
|
542
537
|
}
|
|
543
538
|
};
|
|
544
|
-
//
|
|
545
|
-
const doActs = async (report
|
|
539
|
+
// Performs the acts in a report.
|
|
540
|
+
const doActs = async (report) => {
|
|
546
541
|
const {acts} = report;
|
|
547
|
-
//
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
console.log(message);
|
|
565
|
-
}
|
|
566
|
-
// Increment the count of acts performed.
|
|
567
|
-
actCount++;
|
|
568
|
-
act.startTime = Date.now();
|
|
569
|
-
// If the act is an index changer:
|
|
570
|
-
if (type === 'next') {
|
|
571
|
-
const condition = act.if;
|
|
572
|
-
const logSuffix = condition.length === 3 ? ` ${condition[1]} ${condition[2]}` : '';
|
|
573
|
-
console.log(`>> ${condition[0]}${logSuffix}`);
|
|
574
|
-
// Identify the act to be checked.
|
|
575
|
-
const ifActIndex = report.acts.map(act => act.type !== 'next').lastIndexOf(true);
|
|
576
|
-
// Determine whether its jump condition is true.
|
|
577
|
-
const truth = isTrue(report.acts[ifActIndex].result, condition);
|
|
578
|
-
// Add the result to the act.
|
|
579
|
-
act.result = {
|
|
580
|
-
property: condition[0],
|
|
581
|
-
relation: condition[1],
|
|
582
|
-
criterion: condition[2],
|
|
583
|
-
value: truth[0],
|
|
584
|
-
jumpRequired: truth[1]
|
|
585
|
-
};
|
|
586
|
-
// If the condition is true:
|
|
587
|
-
if (truth[1]) {
|
|
588
|
-
// If the performance of acts is to stop:
|
|
589
|
-
if (act.jump === 0) {
|
|
590
|
-
// Quit.
|
|
591
|
-
actIndex = -2;
|
|
592
|
-
}
|
|
593
|
-
// Otherwise, if there is a numerical jump:
|
|
594
|
-
else if (act.jump) {
|
|
595
|
-
// Set the act index accordingly.
|
|
596
|
-
actIndex += act.jump - 1;
|
|
597
|
-
}
|
|
598
|
-
// Otherwise, if there is a named next act:
|
|
599
|
-
else if (act.next) {
|
|
600
|
-
// Set the new index accordingly, or stop if it does not exist.
|
|
601
|
-
actIndex = acts.map(act => act.name).indexOf(act.next) - 1;
|
|
602
|
-
}
|
|
542
|
+
// Get the standardization specification.
|
|
543
|
+
const standard = report.standard || 'only';
|
|
544
|
+
const reportPath = 'temp/report.json';
|
|
545
|
+
// For each act in the reeport.
|
|
546
|
+
for (const actIndex in acts) {
|
|
547
|
+
// If the job has not been aborted:
|
|
548
|
+
if (report.jobData && ! report.jobData.aborted) {
|
|
549
|
+
let act = acts[actIndex];
|
|
550
|
+
const {type, which} = act;
|
|
551
|
+
const actSuffix = type === 'test' ? ` ${which}` : '';
|
|
552
|
+
const message = `>>>> ${type}${actSuffix}`;
|
|
553
|
+
// If granular reporting has been specified:
|
|
554
|
+
if (report.observe) {
|
|
555
|
+
// Notify the observer of the act and log it.
|
|
556
|
+
const whichParam = which ? `&which=${which}` : '';
|
|
557
|
+
const messageParams = `act=${type}${whichParam}`;
|
|
558
|
+
tellServer(report, messageParams, message);
|
|
603
559
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
await launch(
|
|
609
|
-
report,
|
|
610
|
-
debug,
|
|
611
|
-
waits,
|
|
612
|
-
act.browserID || report.browserID || '',
|
|
613
|
-
act.target && act.target.url || report.target && report.target.url || ''
|
|
614
|
-
);
|
|
615
|
-
// If this failed:
|
|
616
|
-
if (page.prevented) {
|
|
617
|
-
// Add this to the act.
|
|
618
|
-
act.prevented = true;
|
|
619
|
-
act.error = page.error || '';
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
// Otherwise, if the act performs tests of a tool:
|
|
623
|
-
else if (act.type === 'test') {
|
|
624
|
-
// Add a description of the tool to the act.
|
|
625
|
-
act.what = tools[act.which];
|
|
626
|
-
// Get the start time of the act.
|
|
627
|
-
const startTime = Date.now();
|
|
628
|
-
try {
|
|
629
|
-
// Get the time limit in seconds for the act.
|
|
630
|
-
const timeLimit = timeLimits[act.which] || 15;
|
|
631
|
-
// If a new browser is to be launched:
|
|
632
|
-
if (act.launch) {
|
|
633
|
-
// Launch it, navigate to a URL, and replace the page.
|
|
634
|
-
await launch(
|
|
635
|
-
report,
|
|
636
|
-
debug,
|
|
637
|
-
waits,
|
|
638
|
-
act.launch.browserID || report.browserID,
|
|
639
|
-
act.launch.target && act.launch.target.url || report.target.url
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
// If the page has not prevented the tool from testing:
|
|
643
|
-
if (! page.prevented) {
|
|
644
|
-
// Perform the specified tests of the tool.
|
|
645
|
-
const actReport = await require(`./tests/${act.which}`)
|
|
646
|
-
.reporter(page, report, actIndex, timeLimit);
|
|
647
|
-
// Add the data and result to the act.
|
|
648
|
-
act.data = actReport.data;
|
|
649
|
-
act.result = actReport.result;
|
|
650
|
-
// If the tool reported that the page prevented testing:
|
|
651
|
-
if (actReport.data.prevented) {
|
|
652
|
-
// Add prevention data to the job data.
|
|
653
|
-
report.jobData.preventions[act.which] = act.data.error;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
// If the tool invocation failed:
|
|
658
|
-
catch(error) {
|
|
659
|
-
// Report the failure.
|
|
660
|
-
const message = error.message.slice(0, 400);
|
|
661
|
-
console.log(`ERROR: Test act ${act.which} failed (${message})`);
|
|
662
|
-
act.data ??= {};
|
|
663
|
-
act.data.prevented = true;
|
|
664
|
-
act.data.error = act.data.error ? `${act.data.error}; ${message}` : message;
|
|
665
|
-
};
|
|
666
|
-
// Add the elapsed time of the tool to the report.
|
|
667
|
-
const time = Math.round((Date.now() - startTime) / 1000);
|
|
668
|
-
const {toolTimes} = report.jobData;
|
|
669
|
-
if (! toolTimes[act.which]) {
|
|
670
|
-
toolTimes[act.which] = 0;
|
|
560
|
+
// Otherwise, i.e. if granular reporting has not been specified:
|
|
561
|
+
else {
|
|
562
|
+
// Log the act.
|
|
563
|
+
console.log(message);
|
|
671
564
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
act
|
|
678
|
-
|
|
679
|
-
|
|
565
|
+
// If the act is an index changer:
|
|
566
|
+
if (type === 'next') {
|
|
567
|
+
const condition = act.if;
|
|
568
|
+
const logSuffix = condition.length === 3 ? ` ${condition[1]} ${condition[2]}` : '';
|
|
569
|
+
console.log(`>> ${condition[0]}${logSuffix}`);
|
|
570
|
+
// Identify the act to be checked.
|
|
571
|
+
const ifActIndex = acts.map(act => act.type !== 'next').lastIndexOf(true);
|
|
572
|
+
// Determine whether its jump condition is true.
|
|
573
|
+
const truth = isTrue(acts[ifActIndex].result, condition);
|
|
574
|
+
// Add the result to the act.
|
|
575
|
+
act.result = {
|
|
576
|
+
property: condition[0],
|
|
577
|
+
relation: condition[1],
|
|
578
|
+
criterion: condition[2],
|
|
579
|
+
value: truth[0],
|
|
580
|
+
jumpRequired: truth[1]
|
|
680
581
|
};
|
|
681
|
-
//
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
instance.boxID = elementID ? elementID.boxID : '';
|
|
582
|
+
// If the condition is true:
|
|
583
|
+
if (truth[1]) {
|
|
584
|
+
// If the performance of acts is to stop:
|
|
585
|
+
if (act.jump === 0) {
|
|
586
|
+
// Quit.
|
|
587
|
+
actIndex = -2;
|
|
688
588
|
}
|
|
689
|
-
if
|
|
690
|
-
|
|
589
|
+
// Otherwise, if there is a numerical jump:
|
|
590
|
+
else if (act.jump) {
|
|
591
|
+
// Set the act index accordingly.
|
|
592
|
+
actIndex += act.jump - 1;
|
|
691
593
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
594
|
+
// Otherwise, if there is a named next act:
|
|
595
|
+
else if (act.next) {
|
|
596
|
+
// Set the new index accordingly, or stop if it does not exist.
|
|
597
|
+
actIndex = acts.map(act => act.name).indexOf(act.next) - 1;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Otherwise, if the act is a launch:
|
|
602
|
+
else if (type === 'launch') {
|
|
603
|
+
// Launch a browser, navigate to a page, and add the result to the act.
|
|
604
|
+
await launch(
|
|
605
|
+
report,
|
|
606
|
+
debug,
|
|
607
|
+
waits,
|
|
608
|
+
act.browserID || report.browserID || '',
|
|
609
|
+
act.target && act.target.url || report.target && report.target.url || ''
|
|
610
|
+
);
|
|
611
|
+
// If this failed:
|
|
612
|
+
if (page.prevented) {
|
|
613
|
+
// Add this to the act.
|
|
614
|
+
act.prevented = true;
|
|
615
|
+
act.error = page.error || '';
|
|
697
616
|
}
|
|
698
617
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
act.
|
|
704
|
-
|
|
705
|
-
//
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
618
|
+
// Otherwise, if the act is a test act:
|
|
619
|
+
else if (type === 'test') {
|
|
620
|
+
// Add a description of the tool to the act.
|
|
621
|
+
act.what = tools[act.which];
|
|
622
|
+
// Get the start time of the act.
|
|
623
|
+
const startTime = Date.now();
|
|
624
|
+
// Add it to the act.
|
|
625
|
+
act.startTime = startTime;
|
|
626
|
+
// Save the report.
|
|
627
|
+
let reportJSON = JSON.stringify(report);
|
|
628
|
+
await fs.writeFile(reportPath, reportJSON);
|
|
629
|
+
// Create a process and wait for it to perform the act and add the result to the saved report.
|
|
630
|
+
const actResult = await new Promise(resolve => {
|
|
631
|
+
let closed = false;
|
|
632
|
+
const child = fork(
|
|
633
|
+
'procs/doTestAct', [actIndex], {timeout: 1000 * timeLimits[act.which] || 15000}
|
|
634
|
+
);
|
|
635
|
+
child.on('message', message => {
|
|
636
|
+
if (! closed) {
|
|
637
|
+
closed = true;
|
|
638
|
+
resolve(message);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
child.on('close', code => {
|
|
642
|
+
if (! closed) {
|
|
643
|
+
closed = true;
|
|
644
|
+
resolve(code);
|
|
645
|
+
}
|
|
715
646
|
});
|
|
716
|
-
if (! truth[1]) {
|
|
717
|
-
failureCount++;
|
|
718
|
-
}
|
|
719
647
|
});
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const navResult = await goTo(report, page, requestedURL, 15000, 'domcontentloaded');
|
|
732
|
-
// If the visit succeeded:
|
|
733
|
-
if (navResult.success) {
|
|
734
|
-
// Add the script nonce, if any, to the act.
|
|
735
|
-
const {response} = navResult;
|
|
736
|
-
const scriptNonce = getNonce(response);
|
|
737
|
-
if (scriptNonce) {
|
|
738
|
-
report.jobData.lastScriptNonce = scriptNonce;
|
|
739
|
-
}
|
|
740
|
-
// Add the resulting URL to the act.
|
|
741
|
-
if (! act.result) {
|
|
742
|
-
act.result = {};
|
|
743
|
-
}
|
|
744
|
-
act.result.url = page.url();
|
|
745
|
-
// If a prohibited redirection occurred:
|
|
746
|
-
if (response.exception === 'badRedirection') {
|
|
747
|
-
// Report this and abort the job.
|
|
748
|
-
actIndex = await addError(
|
|
749
|
-
true, true, report, actIndex, 'ERROR: Navigation illicitly redirected'
|
|
750
|
-
);
|
|
751
|
-
}
|
|
648
|
+
// Get the revised report.
|
|
649
|
+
reportJSON = await fs.readFile(reportPath, 'utf8');
|
|
650
|
+
report = JSON.parse(reportJSON);
|
|
651
|
+
// Get the revised act.
|
|
652
|
+
act = report.acts[actIndex];
|
|
653
|
+
// If the result is an error code:
|
|
654
|
+
if (typeof actResult === 'number') {
|
|
655
|
+
// Add the error data to the act.
|
|
656
|
+
act.data ??= {};
|
|
657
|
+
act.data.prevented = true;
|
|
658
|
+
act.data.error = actResult;
|
|
752
659
|
}
|
|
753
|
-
// Otherwise, i.e. if
|
|
660
|
+
// Otherwise, i.e. if it is not an error code:
|
|
754
661
|
else {
|
|
755
|
-
//
|
|
756
|
-
|
|
662
|
+
// Add the elapsed time of the tool to the report.
|
|
663
|
+
const time = Math.round((Date.now() - startTime) / 1000);
|
|
664
|
+
const {toolTimes} = report.jobData;
|
|
665
|
+
toolTimes[act.which] ??= 0;
|
|
666
|
+
toolTimes[act.which] += time;
|
|
667
|
+
// If the act was not prevented and standardization is required:
|
|
668
|
+
if (['also', 'only'].includes(standard) && act.data && ! act.data.prevented) {
|
|
669
|
+
// Initialize the standard result.
|
|
670
|
+
act.standardResult = {
|
|
671
|
+
totals: [0, 0, 0, 0],
|
|
672
|
+
instances: []
|
|
673
|
+
};
|
|
674
|
+
// Populate it.
|
|
675
|
+
standardize(act);
|
|
676
|
+
// Add a box ID and a path ID to each of its standard instances if missing.
|
|
677
|
+
for (const instance of act.standardResult.instances) {
|
|
678
|
+
const elementID = await identify(instance, page);
|
|
679
|
+
if (! instance.boxID) {
|
|
680
|
+
instance.boxID = elementID ? elementID.boxID : '';
|
|
681
|
+
}
|
|
682
|
+
if (! instance.pathID) {
|
|
683
|
+
instance.pathID = elementID ? elementID.pathID : '';
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
// If the original-format result is not to be included in the report:
|
|
687
|
+
if (standard === 'only') {
|
|
688
|
+
// Remove it.
|
|
689
|
+
delete act.result;
|
|
690
|
+
}
|
|
691
|
+
const expectations = act.expect;
|
|
692
|
+
// If the act was not prevented and has expectations:
|
|
693
|
+
if (expectations && act.data && ! act.data.prevented) {
|
|
694
|
+
// Initialize whether they were fulfilled.
|
|
695
|
+
act.expectations = [];
|
|
696
|
+
let failureCount = 0;
|
|
697
|
+
// For each expectation:
|
|
698
|
+
expectations.forEach(spec => {
|
|
699
|
+
// Add its result to the act.
|
|
700
|
+
const truth = isTrue(act, spec);
|
|
701
|
+
act.expectations.push({
|
|
702
|
+
property: spec[0],
|
|
703
|
+
relation: spec[1],
|
|
704
|
+
criterion: spec[2],
|
|
705
|
+
actual: truth[0],
|
|
706
|
+
passed: truth[1]
|
|
707
|
+
});
|
|
708
|
+
if (! truth[1]) {
|
|
709
|
+
failureCount++;
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
act.expectationFailures = failureCount;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
757
715
|
}
|
|
758
716
|
}
|
|
759
|
-
// Otherwise, if
|
|
760
|
-
else if (
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
//
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
717
|
+
// Otherwise, if a current page exists:
|
|
718
|
+
else if (page) {
|
|
719
|
+
// If the act is navigation to a url:
|
|
720
|
+
if (act.type === 'url') {
|
|
721
|
+
// Identify the URL.
|
|
722
|
+
const resolved = act.which.replace('__dirname', __dirname);
|
|
723
|
+
requestedURL = resolved;
|
|
724
|
+
// Visit it and wait until the DOM is loaded.
|
|
725
|
+
const navResult = await goTo(report, page, requestedURL, 15000, 'domcontentloaded');
|
|
726
|
+
// If the visit succeeded:
|
|
727
|
+
if (navResult.success) {
|
|
728
|
+
// Add the script nonce, if any, to the act.
|
|
729
|
+
const {response} = navResult;
|
|
730
|
+
const scriptNonce = getNonce(response);
|
|
731
|
+
if (scriptNonce) {
|
|
732
|
+
report.jobData.lastScriptNonce = scriptNonce;
|
|
733
|
+
}
|
|
734
|
+
// Add the resulting URL to the act.
|
|
735
|
+
if (! act.result) {
|
|
736
|
+
act.result = {};
|
|
737
|
+
}
|
|
738
|
+
act.result.url = page.url();
|
|
739
|
+
// If a prohibited redirection occurred:
|
|
740
|
+
if (response.exception === 'badRedirection') {
|
|
741
|
+
// Report this and abort the job.
|
|
742
|
+
addError(true, true, report, actIndex, 'ERROR: Navigation illicitly redirected');
|
|
743
|
+
}
|
|
771
744
|
}
|
|
772
|
-
//
|
|
773
|
-
|
|
774
|
-
//
|
|
775
|
-
|
|
776
|
-
waitError(page, act, error, 'text in the URL');
|
|
745
|
+
// Otherwise, i.e. if the visit failed:
|
|
746
|
+
else {
|
|
747
|
+
// Report this and abort the job.
|
|
748
|
+
addError(true, true, report, actIndex, 'ERROR: Visit failed');
|
|
777
749
|
}
|
|
778
750
|
}
|
|
779
|
-
// Otherwise, if the
|
|
780
|
-
else if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
{
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
751
|
+
// Otherwise, if the act is a wait for text:
|
|
752
|
+
else if (act.type === 'wait') {
|
|
753
|
+
const {what, which} = act;
|
|
754
|
+
console.log(`>> ${what}`);
|
|
755
|
+
const result = act.result = {};
|
|
756
|
+
// If the text is to be the URL:
|
|
757
|
+
if (what === 'url') {
|
|
758
|
+
// Wait for the URL to be the exact text.
|
|
759
|
+
try {
|
|
760
|
+
await page.waitForURL(which, {timeout: 15000});
|
|
761
|
+
result.found = true;
|
|
762
|
+
result.url = page.url();
|
|
763
|
+
}
|
|
764
|
+
// If the wait times out:
|
|
765
|
+
catch(error) {
|
|
766
|
+
// Quit.
|
|
767
|
+
actIndex = await abortActs(report, actIndex);
|
|
768
|
+
waitError(page, act, error, 'text in the URL');
|
|
769
|
+
}
|
|
795
770
|
}
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
|
|
771
|
+
// Otherwise, if the text is to be a substring of the page title:
|
|
772
|
+
else if (what === 'title') {
|
|
773
|
+
// Wait for the page title to include the text, case-insensitively.
|
|
774
|
+
try {
|
|
775
|
+
await page.waitForFunction(
|
|
776
|
+
text => document
|
|
777
|
+
&& document.title
|
|
778
|
+
&& document.title.toLowerCase().includes(text.toLowerCase()),
|
|
779
|
+
which,
|
|
780
|
+
{
|
|
781
|
+
polling: 1000,
|
|
782
|
+
timeout: 5000
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
result.found = true;
|
|
786
|
+
result.title = await page.title();
|
|
787
|
+
}
|
|
788
|
+
// If the wait times out:
|
|
789
|
+
catch(error) {
|
|
790
|
+
// Quit.
|
|
791
|
+
actIndex = await abortActs(report, actIndex);
|
|
792
|
+
waitError(page, act, error, 'text in the title');
|
|
793
|
+
}
|
|
801
794
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
795
|
+
// Otherwise, if the text is to be a substring of the text of the page body:
|
|
796
|
+
else if (what === 'body') {
|
|
797
|
+
// Wait for the body to include the text, case-insensitively.
|
|
798
|
+
try {
|
|
799
|
+
await page.waitForFunction(
|
|
800
|
+
text => document
|
|
801
|
+
&& document.body
|
|
802
|
+
&& document.body.innerText.toLowerCase().includes(text.toLowerCase()),
|
|
803
|
+
which,
|
|
804
|
+
{
|
|
805
|
+
polling: 2000,
|
|
806
|
+
timeout: 15000
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
result.found = true;
|
|
810
|
+
}
|
|
811
|
+
// If the wait times out:
|
|
812
|
+
catch(error) {
|
|
813
|
+
// Quit.
|
|
814
|
+
actIndex = await abortActs(report, actIndex);
|
|
815
|
+
waitError(page, act, error, 'text in the body');
|
|
816
|
+
}
|
|
818
817
|
}
|
|
818
|
+
}
|
|
819
|
+
// Otherwise, if the act is a wait for a state:
|
|
820
|
+
else if (act.type === 'state') {
|
|
821
|
+
// Wait for it.
|
|
822
|
+
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
823
|
+
await page.waitForLoadState(
|
|
824
|
+
['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 15000][stateIndex]}
|
|
825
|
+
)
|
|
819
826
|
// If the wait times out:
|
|
820
|
-
catch(error
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
|
|
827
|
+
.catch(async error => {
|
|
828
|
+
// Report this and abort the job.
|
|
829
|
+
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
830
|
+
addError(true, true, report, actIndex, `ERROR waiting for page to be ${act.which}`);
|
|
831
|
+
});
|
|
832
|
+
// If the wait succeeded:
|
|
833
|
+
if (actIndex > -2) {
|
|
834
|
+
// Add state data to the report.
|
|
835
|
+
act.result = {
|
|
836
|
+
success: true,
|
|
837
|
+
state: act.which
|
|
838
|
+
};
|
|
824
839
|
}
|
|
825
840
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
.catch(async error => {
|
|
836
|
-
// Report this and abort the job.
|
|
837
|
-
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
838
|
-
actIndex = await addError(
|
|
839
|
-
true, true, report, actIndex, `ERROR waiting for page to be ${act.which}`
|
|
840
|
-
);
|
|
841
|
-
});
|
|
842
|
-
// If the wait succeeded:
|
|
843
|
-
if (actIndex > -2) {
|
|
844
|
-
// Add state data to the report.
|
|
845
|
-
act.result = {
|
|
846
|
-
success: true,
|
|
847
|
-
state: act.which
|
|
841
|
+
// Otherwise, if the act is a page switch:
|
|
842
|
+
else if (act.type === 'page') {
|
|
843
|
+
// Wait for a page to be created and identify it as current.
|
|
844
|
+
page = await browserContext.waitForEvent('page');
|
|
845
|
+
// Wait until it is idle.
|
|
846
|
+
await page.waitForLoadState('networkidle', {timeout: 15000});
|
|
847
|
+
// Add the resulting URL to the act.
|
|
848
|
+
const result = {
|
|
849
|
+
url: page.url()
|
|
848
850
|
};
|
|
851
|
+
act.result = result;
|
|
849
852
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
if (['hidden', 'collapse'].includes(styleDec.visibility)) {
|
|
878
|
-
element.style.visibility = 'inherit';
|
|
879
|
-
}
|
|
853
|
+
// Otherwise, if the page has a URL:
|
|
854
|
+
else if (page.url() && page.url() !== 'about:blank') {
|
|
855
|
+
const url = page.url();
|
|
856
|
+
// Add the URL to the act.
|
|
857
|
+
act.actualURL = url;
|
|
858
|
+
// If the act is a revelation:
|
|
859
|
+
if (act.type === 'reveal') {
|
|
860
|
+
// Make all elements in the page visible.
|
|
861
|
+
await page.$$eval('body *', elements => {
|
|
862
|
+
elements.forEach(element => {
|
|
863
|
+
const styleDec = window.getComputedStyle(element);
|
|
864
|
+
if (styleDec.display === 'none') {
|
|
865
|
+
element.style.display = 'initial';
|
|
866
|
+
}
|
|
867
|
+
if (['hidden', 'collapse'].includes(styleDec.visibility)) {
|
|
868
|
+
element.style.visibility = 'inherit';
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
act.result = {
|
|
872
|
+
success: true
|
|
873
|
+
};
|
|
874
|
+
})
|
|
875
|
+
.catch(error => {
|
|
876
|
+
console.log(`ERROR making all elements visible (${error.message})`);
|
|
877
|
+
act.result = {
|
|
878
|
+
success: false
|
|
879
|
+
};
|
|
880
880
|
});
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
881
|
+
}
|
|
882
|
+
// Otherwise, if the act is a move:
|
|
883
|
+
else if (moves[act.type]) {
|
|
884
|
+
const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
|
|
885
|
+
// Try up to 5 times to:
|
|
886
|
+
act.result = {found: false};
|
|
887
|
+
let selection = {};
|
|
888
|
+
let tries = 0;
|
|
889
|
+
const slimText = act.which ? debloat(act.which) : '';
|
|
890
|
+
while (tries++ < 5 && ! act.result.found) {
|
|
891
|
+
if (page) {
|
|
892
|
+
// Identify the elements of the specified type.
|
|
893
|
+
const selections = await page.$$(selector);
|
|
894
|
+
// If there are any:
|
|
895
|
+
if (selections.length) {
|
|
896
|
+
// If there are enough to make a match possible:
|
|
897
|
+
if ((act.index || 0) < selections.length) {
|
|
898
|
+
// For each element of the specified type:
|
|
899
|
+
let matchCount = 0;
|
|
900
|
+
const selectionTexts = [];
|
|
901
|
+
for (selection of selections) {
|
|
902
|
+
// Add its lower-case text or an empty string to the list of element texts.
|
|
903
|
+
const selectionText = slimText ? await textOf(page, selection) : '';
|
|
904
|
+
selectionTexts.push(selectionText);
|
|
905
|
+
// If its text includes any specified text, case-insensitively:
|
|
906
|
+
if (selectionText.includes(slimText)) {
|
|
907
|
+
// If the element has the specified index among such elements:
|
|
908
|
+
if (matchCount++ === (act.index || 0)) {
|
|
909
|
+
// Report it as the matching element and stop checking.
|
|
910
|
+
act.result.found = true;
|
|
911
|
+
act.result.textSpec = slimText;
|
|
912
|
+
act.result.textContent = selectionText;
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// If no element satisfied the specifications:
|
|
918
|
+
if (! act.result.found) {
|
|
919
|
+
// Add the failure data to the report.
|
|
920
|
+
act.result.success = false;
|
|
921
|
+
act.result.error = 'exhausted';
|
|
922
|
+
act.result.typeElementCount = selections.length;
|
|
923
|
+
if (slimText) {
|
|
924
|
+
act.result.textElementCount = --matchCount;
|
|
924
925
|
}
|
|
926
|
+
act.result.message = 'Not enough specified elements exist';
|
|
927
|
+
act.result.candidateTexts = selectionTexts;
|
|
925
928
|
}
|
|
926
929
|
}
|
|
927
|
-
//
|
|
928
|
-
|
|
930
|
+
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
931
|
+
else {
|
|
929
932
|
// Add the failure data to the report.
|
|
930
933
|
act.result.success = false;
|
|
931
|
-
act.result.error = '
|
|
934
|
+
act.result.error = 'fewer';
|
|
932
935
|
act.result.typeElementCount = selections.length;
|
|
933
|
-
|
|
934
|
-
act.result.textElementCount = --matchCount;
|
|
935
|
-
}
|
|
936
|
-
act.result.message = 'Not enough specified elements exist';
|
|
937
|
-
act.result.candidateTexts = selectionTexts;
|
|
936
|
+
act.result.message = 'Elements of specified type too few';
|
|
938
937
|
}
|
|
939
938
|
}
|
|
940
|
-
// Otherwise, i.e. if there are
|
|
939
|
+
// Otherwise, i.e. if there are no elements of the specified type:
|
|
941
940
|
else {
|
|
942
941
|
// Add the failure data to the report.
|
|
943
942
|
act.result.success = false;
|
|
944
|
-
act.result.error = '
|
|
945
|
-
act.result.typeElementCount =
|
|
946
|
-
act.result.message = '
|
|
943
|
+
act.result.error = 'none';
|
|
944
|
+
act.result.typeElementCount = 0;
|
|
945
|
+
act.result.message = 'No elements of specified type found';
|
|
947
946
|
}
|
|
948
947
|
}
|
|
949
|
-
// Otherwise, i.e. if
|
|
948
|
+
// Otherwise, i.e. if the page no longer exists:
|
|
950
949
|
else {
|
|
951
950
|
// Add the failure data to the report.
|
|
952
951
|
act.result.success = false;
|
|
953
|
-
act.result.error = '
|
|
954
|
-
act.result.
|
|
955
|
-
act.result.message = 'No elements of specified type found';
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
// Otherwise, i.e. if the page no longer exists:
|
|
959
|
-
else {
|
|
960
|
-
// Add the failure data to the report.
|
|
961
|
-
act.result.success = false;
|
|
962
|
-
act.result.error = 'gone';
|
|
963
|
-
act.result.message = 'Page gone';
|
|
964
|
-
}
|
|
965
|
-
if (! act.result.found) {
|
|
966
|
-
await wait(2000);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
// If a match was found:
|
|
970
|
-
if (act.result.found) {
|
|
971
|
-
// FUNCTION DEFINITION START
|
|
972
|
-
// Performs a click or Enter keypress and waits for the network to be idle.
|
|
973
|
-
const doAndWait = async isClick => {
|
|
974
|
-
// Perform and report the move.
|
|
975
|
-
const move = isClick ? 'click' : 'Enter keypress';
|
|
976
|
-
try {
|
|
977
|
-
await isClick
|
|
978
|
-
? selection.click({timeout: 4000})
|
|
979
|
-
: selection.press('Enter', {timeout: 4000});
|
|
980
|
-
act.result.success = true;
|
|
981
|
-
act.result.move = move;
|
|
952
|
+
act.result.error = 'gone';
|
|
953
|
+
act.result.message = 'Page gone';
|
|
982
954
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
// Add the error result to the act and abort the job.
|
|
986
|
-
actIndex = await addError(true, true, report, actIndex, `ERROR: ${move} failed`);
|
|
955
|
+
if (! act.result.found) {
|
|
956
|
+
await wait(2000);
|
|
987
957
|
}
|
|
988
|
-
|
|
958
|
+
}
|
|
959
|
+
// If a match was found:
|
|
960
|
+
if (act.result.found) {
|
|
961
|
+
// FUNCTION DEFINITION START
|
|
962
|
+
// Performs a click or Enter keypress and waits for the network to be idle.
|
|
963
|
+
const doAndWait = async isClick => {
|
|
964
|
+
// Perform and report the move.
|
|
965
|
+
const move = isClick ? 'click' : 'Enter keypress';
|
|
989
966
|
try {
|
|
990
|
-
await
|
|
991
|
-
|
|
967
|
+
await isClick
|
|
968
|
+
? selection.click({timeout: 4000})
|
|
969
|
+
: selection.press('Enter', {timeout: 4000});
|
|
970
|
+
act.result.success = true;
|
|
971
|
+
act.result.move = move;
|
|
992
972
|
}
|
|
973
|
+
// If the move fails:
|
|
993
974
|
catch(error) {
|
|
994
|
-
|
|
995
|
-
|
|
975
|
+
// Add the error result to the act and abort the job.
|
|
976
|
+
addError(true, true, report, actIndex, `ERROR: ${move} failed`);
|
|
996
977
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
act.result.success = true;
|
|
1006
|
-
act.result.move = 'clicked';
|
|
1007
|
-
}
|
|
1008
|
-
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
1009
|
-
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
1010
|
-
await selection.waitForElementState('stable', {timeout: 2000})
|
|
1011
|
-
.catch(error => {
|
|
1012
|
-
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
1013
|
-
act.result.success = false;
|
|
1014
|
-
act.result.error = `ERROR waiting for stable ${act.type}`;
|
|
1015
|
-
});
|
|
1016
|
-
if (! act.result.error) {
|
|
1017
|
-
const isEnabled = await selection.isEnabled();
|
|
1018
|
-
if (isEnabled) {
|
|
1019
|
-
await selection.check({
|
|
1020
|
-
force: true,
|
|
1021
|
-
timeout: 2000
|
|
1022
|
-
})
|
|
1023
|
-
.catch(error => {
|
|
1024
|
-
console.log(`ERROR checking ${act.type} (${error.message})`);
|
|
1025
|
-
act.result.success = false;
|
|
1026
|
-
act.result.error = `ERROR checking ${act.type}`;
|
|
1027
|
-
});
|
|
1028
|
-
if (! act.result.error) {
|
|
1029
|
-
act.result.success = true;
|
|
1030
|
-
act.result.move = 'checked';
|
|
978
|
+
if (act.result.success) {
|
|
979
|
+
try {
|
|
980
|
+
await page.context().waitForEvent('networkidle', {timeout: 10000});
|
|
981
|
+
act.result.idleTimely = true;
|
|
982
|
+
}
|
|
983
|
+
catch(error) {
|
|
984
|
+
console.log(`ERROR: Network busy after ${move} (${errorStart(error)})`);
|
|
985
|
+
act.result.idleTimely = false;
|
|
1031
986
|
}
|
|
987
|
+
// Add the page URL to the result.
|
|
988
|
+
act.result.newURL = page.url();
|
|
1032
989
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
990
|
+
};
|
|
991
|
+
// FUNCTION DEFINITION END
|
|
992
|
+
// If the move is a button click, perform it.
|
|
993
|
+
if (act.type === 'button') {
|
|
994
|
+
await selection.click({timeout: 3000});
|
|
995
|
+
act.result.success = true;
|
|
996
|
+
act.result.move = 'clicked';
|
|
997
|
+
}
|
|
998
|
+
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
999
|
+
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
1000
|
+
await selection.waitForElementState('stable', {timeout: 2000})
|
|
1001
|
+
.catch(error => {
|
|
1002
|
+
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
1036
1003
|
act.result.success = false;
|
|
1037
|
-
act.result.error =
|
|
1004
|
+
act.result.error = `ERROR waiting for stable ${act.type}`;
|
|
1005
|
+
});
|
|
1006
|
+
if (! act.result.error) {
|
|
1007
|
+
const isEnabled = await selection.isEnabled();
|
|
1008
|
+
if (isEnabled) {
|
|
1009
|
+
await selection.check({
|
|
1010
|
+
force: true,
|
|
1011
|
+
timeout: 2000
|
|
1012
|
+
})
|
|
1013
|
+
.catch(error => {
|
|
1014
|
+
console.log(`ERROR checking ${act.type} (${error.message})`);
|
|
1015
|
+
act.result.success = false;
|
|
1016
|
+
act.result.error = `ERROR checking ${act.type}`;
|
|
1017
|
+
});
|
|
1018
|
+
if (! act.result.error) {
|
|
1019
|
+
act.result.success = true;
|
|
1020
|
+
act.result.move = 'checked';
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
const report = `ERROR: could not check ${act.type} because disabled`;
|
|
1025
|
+
act.result.success = false;
|
|
1026
|
+
act.result.error = report;
|
|
1027
|
+
}
|
|
1038
1028
|
}
|
|
1039
1029
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
act.result.move = 'focused';
|
|
1046
|
-
}
|
|
1047
|
-
// Otherwise, if it is clicking a link:
|
|
1048
|
-
else if (act.type === 'link') {
|
|
1049
|
-
const href = await selection.getAttribute('href');
|
|
1050
|
-
const target = await selection.getAttribute('target');
|
|
1051
|
-
act.result.href = href || 'NONE';
|
|
1052
|
-
act.result.target = target || 'DEFAULT';
|
|
1053
|
-
// If the destination is a new page:
|
|
1054
|
-
if (target && target !== '_self') {
|
|
1055
|
-
// Click the link and wait for the network to be idle.
|
|
1056
|
-
doAndWait(true);
|
|
1030
|
+
// Otherwise, if it is focusing the element, perform it.
|
|
1031
|
+
else if (act.type === 'focus') {
|
|
1032
|
+
await selection.focus({timeout: 2000});
|
|
1033
|
+
act.result.success = true;
|
|
1034
|
+
act.result.move = 'focused';
|
|
1057
1035
|
}
|
|
1058
|
-
// Otherwise,
|
|
1059
|
-
else {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1036
|
+
// Otherwise, if it is clicking a link:
|
|
1037
|
+
else if (act.type === 'link') {
|
|
1038
|
+
const href = await selection.getAttribute('href');
|
|
1039
|
+
const target = await selection.getAttribute('target');
|
|
1040
|
+
act.result.href = href || 'NONE';
|
|
1041
|
+
act.result.target = target || 'DEFAULT';
|
|
1042
|
+
// If the destination is a new page:
|
|
1043
|
+
if (target && target !== '_self') {
|
|
1044
|
+
// Click the link and wait for the network to be idle.
|
|
1045
|
+
doAndWait(true);
|
|
1068
1046
|
}
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1047
|
+
// Otherwise, i.e. if the destination is in the current page:
|
|
1048
|
+
else {
|
|
1049
|
+
// Click the link and wait for the resulting navigation.
|
|
1050
|
+
try {
|
|
1051
|
+
await selection.click({timeout: 5000});
|
|
1052
|
+
// Wait for the new content to load.
|
|
1053
|
+
await page.waitForLoadState('domcontentloaded', {timeout: 6000});
|
|
1054
|
+
act.result.success = true;
|
|
1055
|
+
act.result.move = 'clicked';
|
|
1056
|
+
act.result.newURL = page.url();
|
|
1057
|
+
}
|
|
1058
|
+
// If the click or load failed:
|
|
1059
|
+
catch(error) {
|
|
1060
|
+
// Quit and add failure data to the report.
|
|
1061
|
+
console.log(`ERROR clicking link (${errorStart(error)})`);
|
|
1062
|
+
act.result.success = false;
|
|
1063
|
+
act.result.error = 'unclickable';
|
|
1064
|
+
act.result.message = 'ERROR: click or load timed out';
|
|
1065
|
+
actIndex = await abortActs(report, actIndex);
|
|
1066
|
+
}
|
|
1067
|
+
// If the link click succeeded:
|
|
1068
|
+
if (! act.result.error) {
|
|
1069
|
+
// Add success data to the report.
|
|
1070
|
+
act.result.success = true;
|
|
1071
|
+
act.result.move = 'clicked';
|
|
1072
|
+
}
|
|
1077
1073
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1074
|
+
}
|
|
1075
|
+
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
1076
|
+
else if (act.type === 'select') {
|
|
1077
|
+
const options = await selection.$$('option');
|
|
1078
|
+
let optionText = '';
|
|
1079
|
+
if (options && Array.isArray(options) && options.length) {
|
|
1080
|
+
const optionTexts = [];
|
|
1081
|
+
for (const option of options) {
|
|
1082
|
+
const optionText = await option.textContent();
|
|
1083
|
+
optionTexts.push(optionText);
|
|
1084
|
+
}
|
|
1085
|
+
const matchTexts = optionTexts.map(
|
|
1086
|
+
(text, index) => text.includes(act.what) ? index : -1
|
|
1087
|
+
);
|
|
1088
|
+
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
1089
|
+
if (index !== undefined) {
|
|
1090
|
+
await selection.selectOption({index});
|
|
1091
|
+
optionText = optionTexts[index];
|
|
1092
|
+
}
|
|
1083
1093
|
}
|
|
1094
|
+
act.result.success = true;
|
|
1095
|
+
act.result.move = 'selected';
|
|
1096
|
+
act.result.option = optionText;
|
|
1084
1097
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1098
|
+
// Otherwise, if it is entering text in an input element:
|
|
1099
|
+
else if (['text', 'search'].includes(act.type)) {
|
|
1100
|
+
act.result.attributes = {};
|
|
1101
|
+
const {attributes} = act.result;
|
|
1102
|
+
const type = await selection.getAttribute('type');
|
|
1103
|
+
const label = await selection.getAttribute('aria-label');
|
|
1104
|
+
const labelRefs = await selection.getAttribute('aria-labelledby');
|
|
1105
|
+
attributes.type = type || '';
|
|
1106
|
+
attributes.label = label || '';
|
|
1107
|
+
attributes.labelRefs = labelRefs || '';
|
|
1108
|
+
// If the text contains a placeholder for an environment variable:
|
|
1109
|
+
let {what} = act;
|
|
1110
|
+
if (/__[A-Z]+__/.test(what)) {
|
|
1111
|
+
// Replace it.
|
|
1112
|
+
const envKey = /__([A-Z]+)__/.exec(what)[1];
|
|
1113
|
+
const envValue = process.env[envKey];
|
|
1114
|
+
what = what.replace(/__[A-Z]+__/, envValue);
|
|
1095
1115
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1116
|
+
// Enter the text.
|
|
1117
|
+
await selection.type(what);
|
|
1118
|
+
report.jobData.presses += what.length;
|
|
1119
|
+
act.result.success = true;
|
|
1120
|
+
act.result.move = 'entered';
|
|
1121
|
+
// If the input is a search input:
|
|
1122
|
+
if (act.type === 'search') {
|
|
1123
|
+
// Press the Enter key and wait for a network to be idle.
|
|
1124
|
+
doAndWait(false);
|
|
1103
1125
|
}
|
|
1104
1126
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
act.result.attributes = {};
|
|
1112
|
-
const {attributes} = act.result;
|
|
1113
|
-
const type = await selection.getAttribute('type');
|
|
1114
|
-
const label = await selection.getAttribute('aria-label');
|
|
1115
|
-
const labelRefs = await selection.getAttribute('aria-labelledby');
|
|
1116
|
-
attributes.type = type || '';
|
|
1117
|
-
attributes.label = label || '';
|
|
1118
|
-
attributes.labelRefs = labelRefs || '';
|
|
1119
|
-
// If the text contains a placeholder for an environment variable:
|
|
1120
|
-
let {what} = act;
|
|
1121
|
-
if (/__[A-Z]+__/.test(what)) {
|
|
1122
|
-
// Replace it.
|
|
1123
|
-
const envKey = /__([A-Z]+)__/.exec(what)[1];
|
|
1124
|
-
const envValue = process.env[envKey];
|
|
1125
|
-
what = what.replace(/__[A-Z]+__/, envValue);
|
|
1126
|
-
}
|
|
1127
|
-
// Enter the text.
|
|
1128
|
-
await selection.type(what);
|
|
1129
|
-
report.jobData.presses += what.length;
|
|
1130
|
-
act.result.success = true;
|
|
1131
|
-
act.result.move = 'entered';
|
|
1132
|
-
// If the input is a search input:
|
|
1133
|
-
if (act.type === 'search') {
|
|
1134
|
-
// Press the Enter key and wait for a network to be idle.
|
|
1135
|
-
doAndWait(false);
|
|
1127
|
+
// Otherwise, i.e. if the move is unknown, add the failure to the act.
|
|
1128
|
+
else {
|
|
1129
|
+
// Report the error.
|
|
1130
|
+
const report = 'ERROR: move unknown';
|
|
1131
|
+
act.result.success = false;
|
|
1132
|
+
act.result.error = report;
|
|
1136
1133
|
}
|
|
1137
1134
|
}
|
|
1138
|
-
// Otherwise, i.e. if
|
|
1135
|
+
// Otherwise, i.e. if no match was found:
|
|
1139
1136
|
else {
|
|
1140
|
-
//
|
|
1141
|
-
const report = 'ERROR: move unknown';
|
|
1137
|
+
// Quit and add failure data to the report.
|
|
1142
1138
|
act.result.success = false;
|
|
1143
|
-
act.result.error =
|
|
1144
|
-
|
|
1139
|
+
act.result.error = 'absent';
|
|
1140
|
+
act.result.message = 'ERROR: specified element not found';
|
|
1141
|
+
console.log('ERROR: Specified element not found');
|
|
1142
|
+
actIndex = await abortActs(report, actIndex);
|
|
1145
1143
|
}
|
|
1146
1144
|
}
|
|
1147
|
-
// Otherwise,
|
|
1148
|
-
else {
|
|
1149
|
-
//
|
|
1150
|
-
act.
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const key = act.which;
|
|
1163
|
-
// Press the key.
|
|
1164
|
-
while (times--) {
|
|
1165
|
-
await page.keyboard.press(key);
|
|
1145
|
+
// Otherwise, if the act is a keypress:
|
|
1146
|
+
else if (act.type === 'press') {
|
|
1147
|
+
// Identify the number of times to press the key.
|
|
1148
|
+
let times = 1 + (act.again || 0);
|
|
1149
|
+
report.jobData.presses += times;
|
|
1150
|
+
const key = act.which;
|
|
1151
|
+
// Press the key.
|
|
1152
|
+
while (times--) {
|
|
1153
|
+
await page.keyboard.press(key);
|
|
1154
|
+
}
|
|
1155
|
+
const qualifier = act.again ? `${1 + act.again} times` : 'once';
|
|
1156
|
+
act.result = {
|
|
1157
|
+
success: true,
|
|
1158
|
+
message: `pressed ${qualifier}`
|
|
1159
|
+
};
|
|
1166
1160
|
}
|
|
1167
|
-
|
|
1168
|
-
act.
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
currentElement.
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1161
|
+
// Otherwise, if it is a repetitive keyboard navigation:
|
|
1162
|
+
else if (act.type === 'presses') {
|
|
1163
|
+
const {navKey, what, which, withItems} = act;
|
|
1164
|
+
const matchTexts = which ? which.map(text => debloat(text)) : [];
|
|
1165
|
+
// Initialize the loop variables.
|
|
1166
|
+
let status = 'more';
|
|
1167
|
+
let presses = 0;
|
|
1168
|
+
let amountRead = 0;
|
|
1169
|
+
let items = [];
|
|
1170
|
+
let matchedText;
|
|
1171
|
+
// As long as a matching element has not been reached:
|
|
1172
|
+
while (status === 'more') {
|
|
1173
|
+
// Press the Escape key to dismiss any modal dialog.
|
|
1174
|
+
await page.keyboard.press('Escape');
|
|
1175
|
+
// Press the specified navigation key.
|
|
1176
|
+
await page.keyboard.press(navKey);
|
|
1177
|
+
presses++;
|
|
1178
|
+
// Identify the newly current element or a failure.
|
|
1179
|
+
const currentJSHandle = await page.evaluateHandle(actCount => {
|
|
1180
|
+
// Initialize it as the focused element.
|
|
1181
|
+
let currentElement = document.activeElement;
|
|
1182
|
+
// If it exists in the page:
|
|
1183
|
+
if (currentElement && currentElement.tagName !== 'BODY') {
|
|
1184
|
+
// Change it, if necessary, to its active descendant.
|
|
1185
|
+
if (currentElement.hasAttribute('aria-activedescendant')) {
|
|
1186
|
+
currentElement = document.getElementById(
|
|
1187
|
+
currentElement.getAttribute('aria-activedescendant')
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
// Or change it, if necessary, to its selected option.
|
|
1191
|
+
else if (currentElement.tagName === 'SELECT') {
|
|
1192
|
+
const currentIndex = Math.max(0, currentElement.selectedIndex);
|
|
1193
|
+
const options = currentElement.querySelectorAll('option');
|
|
1194
|
+
currentElement = options[currentIndex];
|
|
1195
|
+
}
|
|
1196
|
+
// Or change it, if necessary, to its active shadow-DOM element.
|
|
1197
|
+
else if (currentElement.shadowRoot) {
|
|
1198
|
+
currentElement = currentElement.shadowRoot.activeElement;
|
|
1199
|
+
}
|
|
1200
|
+
// If there is a current element:
|
|
1201
|
+
if (currentElement) {
|
|
1202
|
+
// If it was already reached within this act:
|
|
1203
|
+
if (currentElement.dataset.pressesReached === actCount.toString(10)) {
|
|
1204
|
+
// Report the error.
|
|
1205
|
+
console.log(`ERROR: ${currentElement.tagName} element reached again`);
|
|
1206
|
+
status = 'ERROR';
|
|
1207
|
+
return 'ERROR: locallyExhausted';
|
|
1208
|
+
}
|
|
1209
|
+
// Otherwise, i.e. if it is newly reached within this act:
|
|
1210
|
+
else {
|
|
1211
|
+
// Mark and return it.
|
|
1212
|
+
currentElement.dataset.pressesReached = actCount;
|
|
1213
|
+
return currentElement;
|
|
1214
|
+
}
|
|
1220
1215
|
}
|
|
1221
|
-
// Otherwise, i.e. if
|
|
1216
|
+
// Otherwise, i.e. if there is no current element:
|
|
1222
1217
|
else {
|
|
1223
|
-
//
|
|
1224
|
-
|
|
1225
|
-
return
|
|
1218
|
+
// Report the error.
|
|
1219
|
+
status = 'ERROR';
|
|
1220
|
+
return 'noActiveElement';
|
|
1226
1221
|
}
|
|
1227
1222
|
}
|
|
1228
|
-
// Otherwise, i.e. if there is no
|
|
1223
|
+
// Otherwise, i.e. if there is no focus in the page:
|
|
1229
1224
|
else {
|
|
1230
1225
|
// Report the error.
|
|
1231
1226
|
status = 'ERROR';
|
|
1232
|
-
return '
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
// Otherwise, i.e. if there is no focus in the page:
|
|
1236
|
-
else {
|
|
1237
|
-
// Report the error.
|
|
1238
|
-
status = 'ERROR';
|
|
1239
|
-
return 'ERROR: globallyExhausted';
|
|
1240
|
-
}
|
|
1241
|
-
}, actCount);
|
|
1242
|
-
// If the current element exists:
|
|
1243
|
-
const currentElement = currentJSHandle.asElement();
|
|
1244
|
-
if (currentElement) {
|
|
1245
|
-
// Update the data.
|
|
1246
|
-
const tagNameJSHandle = await currentElement.getProperty('tagName');
|
|
1247
|
-
const tagName = await tagNameJSHandle.jsonValue();
|
|
1248
|
-
const text = await textOf(page, currentElement);
|
|
1249
|
-
// If the text of the current element was found:
|
|
1250
|
-
if (text !== null) {
|
|
1251
|
-
const textLength = text.length;
|
|
1252
|
-
// If it is non-empty and there are texts to match:
|
|
1253
|
-
if (matchTexts.length && textLength) {
|
|
1254
|
-
// Identify the matching text.
|
|
1255
|
-
matchedText = matchTexts.find(matchText => text.includes(matchText));
|
|
1227
|
+
return 'ERROR: globallyExhausted';
|
|
1256
1228
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1229
|
+
}, actCount);
|
|
1230
|
+
// If the current element exists:
|
|
1231
|
+
const currentElement = currentJSHandle.asElement();
|
|
1232
|
+
if (currentElement) {
|
|
1233
|
+
// Update the data.
|
|
1234
|
+
const tagNameJSHandle = await currentElement.getProperty('tagName');
|
|
1235
|
+
const tagName = await tagNameJSHandle.jsonValue();
|
|
1236
|
+
const text = await textOf(page, currentElement);
|
|
1237
|
+
// If the text of the current element was found:
|
|
1238
|
+
if (text !== null) {
|
|
1239
|
+
const textLength = text.length;
|
|
1240
|
+
// If it is non-empty and there are texts to match:
|
|
1241
|
+
if (matchTexts.length && textLength) {
|
|
1242
|
+
// Identify the matching text.
|
|
1243
|
+
matchedText = matchTexts.find(matchText => text.includes(matchText));
|
|
1266
1244
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
// Perform the action.
|
|
1277
|
-
const inputText = act.text;
|
|
1278
|
-
if (inputText) {
|
|
1279
|
-
await page.keyboard.type(inputText);
|
|
1280
|
-
presses += inputText.length;
|
|
1245
|
+
// Update the item data if required.
|
|
1246
|
+
if (withItems) {
|
|
1247
|
+
const itemData = {
|
|
1248
|
+
tagName,
|
|
1249
|
+
text,
|
|
1250
|
+
textLength
|
|
1251
|
+
};
|
|
1252
|
+
if (matchedText) {
|
|
1253
|
+
itemData.matchedText = matchedText;
|
|
1281
1254
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1255
|
+
items.push(itemData);
|
|
1256
|
+
}
|
|
1257
|
+
amountRead += textLength;
|
|
1258
|
+
// If there is no text-match failure:
|
|
1259
|
+
if (matchedText || ! matchTexts.length) {
|
|
1260
|
+
// If the element has any specified tag name:
|
|
1261
|
+
if (! what || tagName === what) {
|
|
1262
|
+
// Change the status.
|
|
1263
|
+
status = 'done';
|
|
1264
|
+
// Perform the action.
|
|
1265
|
+
const inputText = act.text;
|
|
1266
|
+
if (inputText) {
|
|
1267
|
+
await page.keyboard.type(inputText);
|
|
1268
|
+
presses += inputText.length;
|
|
1269
|
+
}
|
|
1270
|
+
if (act.action) {
|
|
1271
|
+
presses++;
|
|
1272
|
+
await page.keyboard.press(act.action);
|
|
1273
|
+
await page.waitForLoadState();
|
|
1274
|
+
}
|
|
1286
1275
|
}
|
|
1287
1276
|
}
|
|
1288
1277
|
}
|
|
1278
|
+
else {
|
|
1279
|
+
status = 'ERROR';
|
|
1280
|
+
}
|
|
1289
1281
|
}
|
|
1282
|
+
// Otherwise, i.e. if there was a failure:
|
|
1290
1283
|
else {
|
|
1291
|
-
|
|
1284
|
+
// Update the status.
|
|
1285
|
+
status = await currentJSHandle.jsonValue();
|
|
1292
1286
|
}
|
|
1293
1287
|
}
|
|
1294
|
-
//
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
status
|
|
1288
|
+
// Add the result to the act.
|
|
1289
|
+
act.result = {
|
|
1290
|
+
success: true,
|
|
1291
|
+
status,
|
|
1292
|
+
totals: {
|
|
1293
|
+
presses,
|
|
1294
|
+
amountRead
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
if (status === 'done' && matchedText) {
|
|
1298
|
+
act.result.matchedText = matchedText;
|
|
1298
1299
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
act.result = {
|
|
1302
|
-
success: true,
|
|
1303
|
-
status,
|
|
1304
|
-
totals: {
|
|
1305
|
-
presses,
|
|
1306
|
-
amountRead
|
|
1300
|
+
if (withItems) {
|
|
1301
|
+
act.result.items = items;
|
|
1307
1302
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1303
|
+
// Add the totals to the report.
|
|
1304
|
+
report.jobData.presses += presses;
|
|
1305
|
+
report.jobData.amountRead += amountRead;
|
|
1311
1306
|
}
|
|
1312
|
-
if
|
|
1313
|
-
|
|
1307
|
+
// Otherwise, i.e. if the act type is unknown:
|
|
1308
|
+
else {
|
|
1309
|
+
// Add the error result to the act and abort the job.
|
|
1310
|
+
addError(true, true, report, actIndex, 'ERROR: Invalid act type');
|
|
1314
1311
|
}
|
|
1315
|
-
// Add the totals to the report.
|
|
1316
|
-
report.jobData.presses += presses;
|
|
1317
|
-
report.jobData.amountRead += amountRead;
|
|
1318
1312
|
}
|
|
1319
|
-
// Otherwise,
|
|
1313
|
+
// Otherwise, a page URL is required but does not exist, so:
|
|
1320
1314
|
else {
|
|
1321
|
-
// Add
|
|
1322
|
-
|
|
1315
|
+
// Add an error result to the act and abort the job.
|
|
1316
|
+
addError(true, true, report, actIndex, 'ERROR: Page has no URL');
|
|
1323
1317
|
}
|
|
1324
1318
|
}
|
|
1325
|
-
// Otherwise,
|
|
1319
|
+
// Otherwise, i.e. if no page exists:
|
|
1326
1320
|
else {
|
|
1327
1321
|
// Add an error result to the act and abort the job.
|
|
1328
|
-
|
|
1322
|
+
addError(true, true, report, actIndex, 'ERROR: No page identified');
|
|
1329
1323
|
}
|
|
1324
|
+
// Add the end time to the act.
|
|
1325
|
+
act.endTime = Date.now();
|
|
1330
1326
|
}
|
|
1331
|
-
// Otherwise, i.e. if no page exists:
|
|
1332
|
-
else {
|
|
1333
|
-
// Add an error result to the act and abort the job.
|
|
1334
|
-
actIndex = await addError(true, true, report, actIndex, 'ERROR: No page identified');
|
|
1335
|
-
}
|
|
1336
|
-
act.endTime = Date.now();
|
|
1337
|
-
// Perform any remaining acts if not aborted.
|
|
1338
|
-
await doActs(report, actIndex + 1);
|
|
1339
|
-
}
|
|
1340
|
-
// Otherwise, if all acts have been performed and the job succeeded:
|
|
1341
|
-
else if (! report.jobData.abortTime) {
|
|
1342
|
-
console.log('Acts completed');
|
|
1343
|
-
await browserClose();
|
|
1344
1327
|
}
|
|
1328
|
+
console.log('Acts completed');
|
|
1329
|
+
await browserClose();
|
|
1330
|
+
await fs.rm(reportPath, {force: true});
|
|
1331
|
+
return report;
|
|
1345
1332
|
};
|
|
1346
1333
|
/*
|
|
1347
1334
|
Returns whether an initialized job report is valid and, if so, runs the job and adds the results
|
|
@@ -1383,8 +1370,8 @@ exports.doJob = async report => {
|
|
|
1383
1370
|
process.exit();
|
|
1384
1371
|
}
|
|
1385
1372
|
});
|
|
1386
|
-
//
|
|
1387
|
-
await doActs(report, 0, null);
|
|
1373
|
+
// Perform the acts and get the revised report.
|
|
1374
|
+
report = await doActs(report, 0, null);
|
|
1388
1375
|
// Add the end time and duration to the report.
|
|
1389
1376
|
const endTime = new Date();
|
|
1390
1377
|
report.jobData.endTime = nowString();
|
|
@@ -1400,4 +1387,5 @@ exports.doJob = async report => {
|
|
|
1400
1387
|
report.jobData.toolTimes[item[0]] = item[1];
|
|
1401
1388
|
});
|
|
1402
1389
|
}
|
|
1390
|
+
return report;
|
|
1403
1391
|
};
|