testaro 5.9.3 → 5.11.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/README.md +2 -0
- package/commands.js +1 -1
- package/package.json +1 -1
- package/run.js +228 -178
package/README.md
CHANGED
|
@@ -316,6 +316,8 @@ Tenon recommends giving it a public URL rather than giving it the content of a p
|
|
|
316
316
|
|
|
317
317
|
The `continuum` test makes use of the files in the `continuum` directory. The test inserts the contents of all three files into the page as scripts and then uses them to perform the tests of the Continuum package.
|
|
318
318
|
|
|
319
|
+
Level Access on 22 August 2022 granted authorization for the copying of the `AccessEngine.community.js` file insofar as necessary for allowing Continuum community edition tests to be included in Testaro.
|
|
320
|
+
|
|
319
321
|
###### HTML CodeSniffer
|
|
320
322
|
|
|
321
323
|
The `htmlcs` test makes use of the`htmlcs/HTMLCS.js` file. That file was created, and can be recreated if necessary, as follows:
|
package/commands.js
CHANGED
|
@@ -32,7 +32,7 @@ exports.commands = {
|
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
link: [
|
|
35
|
-
'Click a link',
|
|
35
|
+
'Click a link and wait for the page to be idle or loaded',
|
|
36
36
|
{
|
|
37
37
|
which: [true, 'string', 'hasLength', 'substring of link text'],
|
|
38
38
|
index: [false, 'number', '', 'index among matches if not 0'],
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -12,7 +12,7 @@ const {commands} = require('./commands');
|
|
|
12
12
|
|
|
13
13
|
// ########## CONSTANTS
|
|
14
14
|
|
|
15
|
-
// Set DEBUG environment variable to 'true' to add debugging features.
|
|
15
|
+
// Set DEBUG environment variable to 'true,' to add debugging features.
|
|
16
16
|
const debug = process.env.DEBUG === 'true';
|
|
17
17
|
// Set WAITS environment variable to a positive number to insert delays (in ms).
|
|
18
18
|
const waits = Number.parseInt(process.env.WAITS) || 0;
|
|
@@ -294,16 +294,11 @@ const launch = async typeName => {
|
|
|
294
294
|
});
|
|
295
295
|
// If the launch succeeded:
|
|
296
296
|
if (healthy) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
} : {};
|
|
304
|
-
browserContext = await browser.newContext(viewport);
|
|
305
|
-
// When a page is added to the browser context:
|
|
306
|
-
browserContext.on('page', page => {
|
|
297
|
+
browserContext = await browser.newContext();
|
|
298
|
+
// When a page (i.e. browser tab) is added to the browser context (i.e. browser window):
|
|
299
|
+
browserContext.on('page', async page => {
|
|
300
|
+
// Activate the page.
|
|
301
|
+
await page.bringToFront();
|
|
307
302
|
// Make abbreviations of its console messages get reported in the Playwright console.
|
|
308
303
|
page.on('console', msg => {
|
|
309
304
|
const msgText = msg.text();
|
|
@@ -339,12 +334,6 @@ const launch = async typeName => {
|
|
|
339
334
|
});
|
|
340
335
|
// Open the first page of the context.
|
|
341
336
|
const page = await browserContext.newPage();
|
|
342
|
-
if (debug) {
|
|
343
|
-
page.setViewportSize({
|
|
344
|
-
width: 1280,
|
|
345
|
-
height: 1120
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
337
|
// Wait until it is stable.
|
|
349
338
|
await page.waitForLoadState('domcontentloaded');
|
|
350
339
|
// Update the name of the current browser type and store it in the page.
|
|
@@ -445,85 +434,6 @@ const textOf = async (page, element) => {
|
|
|
445
434
|
return null;
|
|
446
435
|
}
|
|
447
436
|
};
|
|
448
|
-
// Returns an element of a type case-insensitively including a text.
|
|
449
|
-
const matchElement = async (page, selector, matchText, index = 0) => {
|
|
450
|
-
if (matchText) {
|
|
451
|
-
// If the page still exists:
|
|
452
|
-
if (page) {
|
|
453
|
-
const slimText = debloat(matchText);
|
|
454
|
-
// Identify the elements of the specified type.
|
|
455
|
-
const selections = await page.$$(selector);
|
|
456
|
-
// If there are any:
|
|
457
|
-
if (selections.length) {
|
|
458
|
-
// If there are enough to make a match possible:
|
|
459
|
-
if (index < selections.length) {
|
|
460
|
-
// For each element of the specified type:
|
|
461
|
-
let matchCount = 0;
|
|
462
|
-
const selectionTexts = [];
|
|
463
|
-
for (const selection of selections) {
|
|
464
|
-
// Add its text to the list of texts of such elements.
|
|
465
|
-
const selectionText = await textOf(page, selection);
|
|
466
|
-
selectionTexts.push(selectionText);
|
|
467
|
-
// If its text includes the specified text:
|
|
468
|
-
if (selectionText.includes(slimText)) {
|
|
469
|
-
// If the count of such elements with such texts found so far is the specified count:
|
|
470
|
-
if (matchCount++ === index) {
|
|
471
|
-
// Return it as the matching element.
|
|
472
|
-
return {
|
|
473
|
-
success: true,
|
|
474
|
-
matchingElement: selection
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
// None satisfied the specifications, so return a failure.
|
|
480
|
-
return {
|
|
481
|
-
success: false,
|
|
482
|
-
error: 'exhausted',
|
|
483
|
-
message: `Text found in only ${matchCount} (not ${index + 1}) of ${selections.length}`,
|
|
484
|
-
candidateTexts: selectionTexts
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
488
|
-
else {
|
|
489
|
-
// Return a failure.
|
|
490
|
-
return {
|
|
491
|
-
success: false,
|
|
492
|
-
error: 'fewer',
|
|
493
|
-
message: `Count of '${selector}' elements only ${selections.length}`
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Otherwise, i.e. if there are no elements of the specified type:
|
|
498
|
-
else {
|
|
499
|
-
// Return a failure.
|
|
500
|
-
return {
|
|
501
|
-
success: false,
|
|
502
|
-
error: 'none',
|
|
503
|
-
message: `No '${selector}' elements found`
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
// Otherwise, i.e. if the page no longer exists:
|
|
508
|
-
else {
|
|
509
|
-
// Return a failure.
|
|
510
|
-
return {
|
|
511
|
-
success: false,
|
|
512
|
-
error: 'gone',
|
|
513
|
-
message: 'Page gone'
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
// Otherwise, i.e. if no text was specified:
|
|
518
|
-
else {
|
|
519
|
-
// Return a failure.
|
|
520
|
-
return {
|
|
521
|
-
success: false,
|
|
522
|
-
error: 'text',
|
|
523
|
-
message: 'No text specified'
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
437
|
// Returns a string with any final slash removed.
|
|
528
438
|
const deSlash = string => string.endsWith('/') ? string.slice(0, -1) : string;
|
|
529
439
|
// Tries to visit a URL.
|
|
@@ -682,10 +592,10 @@ const wait = ms => {
|
|
|
682
592
|
});
|
|
683
593
|
};
|
|
684
594
|
// Adds an error result to an act.
|
|
685
|
-
const addError = (act, error) => {
|
|
686
|
-
act.result =
|
|
687
|
-
|
|
688
|
-
|
|
595
|
+
const addError = (act, error, message) => {
|
|
596
|
+
act.result.success = false;
|
|
597
|
+
act.result.error = error;
|
|
598
|
+
act.result.message = message;
|
|
689
599
|
if (act.type === 'test') {
|
|
690
600
|
act.result.prevented = true;
|
|
691
601
|
}
|
|
@@ -709,14 +619,15 @@ const doActs = async (report, actIndex, page) => {
|
|
|
709
619
|
console.log(`>>>> ${act.type}${whichSuffix}`);
|
|
710
620
|
// Increment the count of commands performed.
|
|
711
621
|
actCount++;
|
|
622
|
+
act.startTime = Date.now();
|
|
712
623
|
// If the command is an index changer:
|
|
713
624
|
if (act.type === 'next') {
|
|
714
625
|
const condition = act.if;
|
|
715
626
|
const logSuffix = condition.length === 3 ? ` ${condition[1]} ${condition[2]}` : '';
|
|
716
627
|
console.log(`>> ${condition[0]}${logSuffix}`);
|
|
717
628
|
// Identify the act to be checked.
|
|
718
|
-
const ifActIndex = report.acts.map(act => act.type !== 'next').lastIndexOf(true);
|
|
719
|
-
// Determine whether its jump condition is true
|
|
629
|
+
const ifActIndex = report.acts.map(act => act.type !== 'next').lastIndexOf(true,);
|
|
630
|
+
// Determine whether its jump condition is true,.
|
|
720
631
|
const truth = isTrue(report.acts[ifActIndex].result, condition);
|
|
721
632
|
// Add the result to the act.
|
|
722
633
|
act.result = {
|
|
@@ -726,7 +637,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
726
637
|
value: truth[0],
|
|
727
638
|
jumpRequired: truth[1]
|
|
728
639
|
};
|
|
729
|
-
// If the condition is true
|
|
640
|
+
// If the condition is true,:
|
|
730
641
|
if (truth[1]) {
|
|
731
642
|
// If the performance of commands is to stop:
|
|
732
643
|
if (act.jump === 0) {
|
|
@@ -764,8 +675,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
764
675
|
const {what, which} = act;
|
|
765
676
|
console.log(`>> ${what}`);
|
|
766
677
|
const result = act.result = {};
|
|
767
|
-
//
|
|
678
|
+
// If the text is to be the URL:
|
|
768
679
|
if (what === 'url') {
|
|
680
|
+
// Wait for it up to 15 seconds and quit on failure.
|
|
769
681
|
try {
|
|
770
682
|
await page.waitForURL(which, {timeout: 15000});
|
|
771
683
|
result.found = true;
|
|
@@ -776,7 +688,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
776
688
|
waitError(page, act, error, 'URL');
|
|
777
689
|
}
|
|
778
690
|
}
|
|
691
|
+
// Otherwise, if the text is to be a substring of the page title:
|
|
779
692
|
else if (what === 'title') {
|
|
693
|
+
// Wait for it up to 5 seconds and quit on failure.
|
|
780
694
|
try {
|
|
781
695
|
await page.waitForFunction(
|
|
782
696
|
text => document
|
|
@@ -789,14 +703,16 @@ const doActs = async (report, actIndex, page) => {
|
|
|
789
703
|
}
|
|
790
704
|
);
|
|
791
705
|
result.found = true;
|
|
792
|
-
result.title = await page.title();
|
|
706
|
+
result.title = await page.title();
|
|
793
707
|
}
|
|
794
708
|
catch(error) {
|
|
795
709
|
actIndex = -2;
|
|
796
710
|
waitError(page, act, error, 'title');
|
|
797
711
|
}
|
|
798
712
|
}
|
|
713
|
+
// Otherwise, if the text is to be a substring of the text of the page body:
|
|
799
714
|
else if (what === 'body') {
|
|
715
|
+
// Wait for it up to 10 seconds and quit on failure.
|
|
800
716
|
try {
|
|
801
717
|
await page.waitForFunction(
|
|
802
718
|
text => document
|
|
@@ -818,27 +734,33 @@ const doActs = async (report, actIndex, page) => {
|
|
|
818
734
|
}
|
|
819
735
|
// Otherwise, if the act is a wait for a state:
|
|
820
736
|
else if (act.type === 'state') {
|
|
821
|
-
// Wait for it, and quit
|
|
737
|
+
// Wait for it up to 5 or 10 seconrds, and quit on failure.
|
|
822
738
|
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
823
739
|
await page.waitForLoadState(
|
|
824
740
|
['domcontentloaded', 'networkidle'][stateIndex], {timeout: [10000, 5000][stateIndex]}
|
|
825
741
|
)
|
|
826
742
|
.catch(error => {
|
|
827
743
|
console.log(`ERROR waiting for page to be ${act.which} (${error.message})`);
|
|
828
|
-
|
|
744
|
+
act.result = {
|
|
745
|
+
success: false,
|
|
746
|
+
error: `ERROR waiting for page to be ${act.which}`
|
|
747
|
+
};
|
|
829
748
|
actIndex = -2;
|
|
830
749
|
});
|
|
831
750
|
if (actIndex > -2) {
|
|
832
|
-
|
|
751
|
+
act.result = {
|
|
752
|
+
success: true,
|
|
753
|
+
state: act.which
|
|
754
|
+
};
|
|
833
755
|
}
|
|
834
756
|
}
|
|
835
757
|
// Otherwise, if the act is a page switch:
|
|
836
758
|
else if (act.type === 'page') {
|
|
837
759
|
// Wait for a page to be created and identify it as current.
|
|
838
760
|
page = await browserContext.waitForEvent('page');
|
|
839
|
-
// Wait
|
|
761
|
+
// Wait up to 20 seconds until it is idle.
|
|
840
762
|
await page.waitForLoadState('networkidle', {timeout: 20000});
|
|
841
|
-
// Add the resulting URL
|
|
763
|
+
// Add the resulting URL to the act.
|
|
842
764
|
const result = {
|
|
843
765
|
url: page.url()
|
|
844
766
|
};
|
|
@@ -855,7 +777,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
855
777
|
if (act.type === 'reveal') {
|
|
856
778
|
// Make all elements in the page visible.
|
|
857
779
|
await require('./procs/allVis').allVis(page);
|
|
858
|
-
act.result =
|
|
780
|
+
act.result = {
|
|
781
|
+
success: true,
|
|
782
|
+
};
|
|
859
783
|
}
|
|
860
784
|
// Otherwise, if the act is a tenon request:
|
|
861
785
|
else if (act.type === 'tenonRequest') {
|
|
@@ -1010,106 +934,211 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1010
934
|
});
|
|
1011
935
|
testReport.result.failureCount = failureCount;
|
|
1012
936
|
}
|
|
937
|
+
testReport.result.success = true;
|
|
1013
938
|
report.testTimes.push([act.which, Math.round((Date.now() - startTime) / 1000)]);
|
|
1014
939
|
report.testTimes.sort((a, b) => b[1] - a[1]);
|
|
1015
940
|
// Add the result object (possibly an array) to the act.
|
|
1016
941
|
const resultCount = Object.keys(testReport.result).length;
|
|
1017
|
-
act.result = resultCount ? testReport.result :
|
|
942
|
+
act.result = resultCount ? testReport.result : {success: false};
|
|
1018
943
|
}
|
|
1019
944
|
// Otherwise, if the act is a move:
|
|
1020
945
|
else if (moves[act.type]) {
|
|
1021
946
|
const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
|
|
1022
|
-
// Try up to
|
|
1023
|
-
|
|
947
|
+
// Try for up to 10 seconds to identify the element to perform the move on.
|
|
948
|
+
act.result = {found: false};
|
|
949
|
+
let selection = {};
|
|
1024
950
|
let tries = 0;
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
if (
|
|
951
|
+
const slimText = debloat(act.which);
|
|
952
|
+
while (tries++ < 5 && ! act.result.found) {
|
|
953
|
+
if (act.which) {
|
|
954
|
+
// If the page still exists:
|
|
955
|
+
if (page) {
|
|
956
|
+
// Identify the elements of the specified type.
|
|
957
|
+
const selections = await page.$$(selector);
|
|
958
|
+
// If there are any:
|
|
959
|
+
if (selections.length) {
|
|
960
|
+
// If there are enough to make a match possible:
|
|
961
|
+
if ((act.index || 0) < selections.length) {
|
|
962
|
+
// For each element of the specified type:
|
|
963
|
+
let matchCount = 0;
|
|
964
|
+
const selectionTexts = [];
|
|
965
|
+
for (selection of selections) {
|
|
966
|
+
// Add its text to the list of texts of such elements.
|
|
967
|
+
const selectionText = await textOf(page, selection);
|
|
968
|
+
selectionTexts.push(selectionText);
|
|
969
|
+
// If its text includes the specified text:
|
|
970
|
+
if (selectionText.includes(slimText)) {
|
|
971
|
+
// If the element has the specified index among such elements:
|
|
972
|
+
if (matchCount++ === (act.index || 0)) {
|
|
973
|
+
// Report it as the matching element and stop checking.
|
|
974
|
+
act.result.found = true;
|
|
975
|
+
act.result.text = slimText;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// If no element satisfied the specifications:
|
|
981
|
+
if (! act.result.found) {
|
|
982
|
+
act.result.success = false;
|
|
983
|
+
act.result.error = 'exhausted';
|
|
984
|
+
act.result.typeElementCount = selections.length;
|
|
985
|
+
act.result.textElementCount = --matchCount;
|
|
986
|
+
act.result.message = 'Not enough elements have the specified text';
|
|
987
|
+
act.result.candidateTexts = selectionTexts;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
// Otherwise, i.e. if there are too few such elements to make a match possible:
|
|
991
|
+
else {
|
|
992
|
+
// Return a failure.
|
|
993
|
+
act.result.success = false;
|
|
994
|
+
act.result.error = 'fewer';
|
|
995
|
+
act.result.typeElementCount = selections.length;
|
|
996
|
+
act.result.message = 'Elements of specified type too few';
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
// Otherwise, i.e. if there are no elements of the specified type:
|
|
1000
|
+
else {
|
|
1001
|
+
// Return a failure.
|
|
1002
|
+
act.result.success = false;
|
|
1003
|
+
act.result.error = 'none';
|
|
1004
|
+
act.result.typeElementCount = 0;
|
|
1005
|
+
act.result.message = 'No elements specified type found';
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// Otherwise, i.e. if the page no longer exists:
|
|
1009
|
+
else {
|
|
1010
|
+
// Return a failure.
|
|
1011
|
+
act.result.success = false;
|
|
1012
|
+
act.result.error = 'gone';
|
|
1013
|
+
act.result.message = 'Page gone';
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// Otherwise, i.e. if no text was specified:
|
|
1017
|
+
else {
|
|
1018
|
+
// Return a failure.
|
|
1019
|
+
act.result.success = false;
|
|
1020
|
+
act.result.error = 'text';
|
|
1021
|
+
act.result.message = 'No text specified';
|
|
1022
|
+
}
|
|
1023
|
+
if (! act.result.found) {
|
|
1028
1024
|
await wait(2000);
|
|
1029
1025
|
}
|
|
1030
1026
|
}
|
|
1031
1027
|
// If a match was found:
|
|
1032
|
-
if (
|
|
1033
|
-
const {matchingElement} = matchResult;
|
|
1028
|
+
if (act.result.found) {
|
|
1034
1029
|
// If the move is a button click, perform it.
|
|
1035
1030
|
if (act.type === 'button') {
|
|
1036
|
-
await
|
|
1037
|
-
act.result =
|
|
1031
|
+
await selection.click({timeout: 3000});
|
|
1032
|
+
act.result.success = true;
|
|
1033
|
+
act.result.move = 'clicked';
|
|
1038
1034
|
}
|
|
1039
1035
|
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
1040
1036
|
else if (['checkbox', 'radio'].includes(act.type)) {
|
|
1041
|
-
await
|
|
1037
|
+
await selection.waitForElementState('stable', {timeout: 2000})
|
|
1042
1038
|
.catch(error => {
|
|
1043
1039
|
console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
|
|
1044
|
-
|
|
1040
|
+
act.result.success = false;
|
|
1041
|
+
act.result.error = `ERROR waiting for stable ${act.type}`;
|
|
1045
1042
|
});
|
|
1046
|
-
if (! act.result) {
|
|
1047
|
-
const isEnabled = await
|
|
1043
|
+
if (! act.result.error) {
|
|
1044
|
+
const isEnabled = await selection.isEnabled();
|
|
1048
1045
|
if (isEnabled) {
|
|
1049
|
-
await
|
|
1046
|
+
await selection.check({
|
|
1050
1047
|
force: true,
|
|
1051
1048
|
timeout: 2000
|
|
1052
1049
|
})
|
|
1053
1050
|
.catch(error => {
|
|
1054
1051
|
console.log(`ERROR checking ${act.type} (${error.message})`);
|
|
1055
|
-
|
|
1052
|
+
act.result.success = false;
|
|
1053
|
+
act.result.error = `ERROR checking ${act.type}`;
|
|
1056
1054
|
});
|
|
1057
|
-
if (! act.result) {
|
|
1058
|
-
act.result =
|
|
1055
|
+
if (! act.result.error) {
|
|
1056
|
+
act.result.success = true;
|
|
1057
|
+
act.result.move = 'checked';
|
|
1059
1058
|
}
|
|
1060
1059
|
}
|
|
1061
1060
|
else {
|
|
1062
1061
|
const report = `ERROR: could not check ${act.type} because disabled`;
|
|
1063
1062
|
console.log(report);
|
|
1064
|
-
act.result =
|
|
1063
|
+
act.result.success = false;
|
|
1064
|
+
act.result.error = report;
|
|
1065
1065
|
}
|
|
1066
1066
|
}
|
|
1067
1067
|
}
|
|
1068
1068
|
// Otherwise, if it is focusing the element, perform it.
|
|
1069
1069
|
else if (act.type === 'focus') {
|
|
1070
|
-
await
|
|
1071
|
-
act.result =
|
|
1070
|
+
await selection.focus({timeout: 2000});
|
|
1071
|
+
act.result.success = true;
|
|
1072
|
+
act.result.move = 'focused';
|
|
1072
1073
|
}
|
|
1073
1074
|
// Otherwise, if it is clicking a link:
|
|
1074
1075
|
else if (act.type === 'link') {
|
|
1075
|
-
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
// If
|
|
1080
|
-
|
|
1081
|
-
//
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1076
|
+
const href = await selection.getAttribute('href');
|
|
1077
|
+
const target = await selection.getAttribute('target');
|
|
1078
|
+
act.result.href = href || 'NONE';
|
|
1079
|
+
act.result.target = target || 'DEFAULT';
|
|
1080
|
+
// If the destination is a new page:
|
|
1081
|
+
if (target && target !== '_self') {
|
|
1082
|
+
// Click the link and wait for the resulting page event.
|
|
1083
|
+
try {
|
|
1084
|
+
const [newPage] = await Promise.all([
|
|
1085
|
+
page.context().waitForEvent('page', {timeout: 6000}),
|
|
1086
|
+
selection.click({timeout: 5000})
|
|
1087
|
+
]);
|
|
1088
|
+
// Wait for the new page to load.
|
|
1089
|
+
await newPage.waitForLoadState('domcontentloaded', {timeout: 6000});
|
|
1090
|
+
// Make the new page the current page.
|
|
1091
|
+
page = newPage;
|
|
1092
|
+
act.result.success = true;
|
|
1093
|
+
act.result.move = 'clicked';
|
|
1094
|
+
act.result.newURL = page.url();
|
|
1095
|
+
}
|
|
1096
|
+
// If the click, event, or load failed:
|
|
1097
|
+
catch(error) {
|
|
1089
1098
|
// Quit and report the failure.
|
|
1099
|
+
console.log(
|
|
1100
|
+
`ERROR clicking new-page link (${error.message.replace(/\n.+/s, '')})`
|
|
1101
|
+
);
|
|
1102
|
+
act.result.success = false;
|
|
1103
|
+
act.result.error = 'unclickable';
|
|
1104
|
+
act.result.message = 'ERROR: click and new-page navigation timed out';
|
|
1090
1105
|
actIndex = -2;
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
// Otherwise, i.e. if the destination is in the current page:
|
|
1109
|
+
else {
|
|
1110
|
+
// Click the link and wait for the resulting navigation.
|
|
1111
|
+
await selection.click({timeout: 5000})
|
|
1112
|
+
// If the click and navigation time out:
|
|
1113
|
+
.catch(async error => {
|
|
1114
|
+
// Try to force-click it and wait for the navigation.
|
|
1115
|
+
const errorSummary = error.message.replace(/\n.+/s, '');
|
|
1116
|
+
console.log(`ERROR: Link to ${href} not clickable (${errorSummary})`);
|
|
1117
|
+
await selection.click({
|
|
1118
|
+
force: true,
|
|
1119
|
+
timeout: 3000
|
|
1120
|
+
})
|
|
1121
|
+
// If it cannot be force-clicked:
|
|
1122
|
+
.catch(error => {
|
|
1123
|
+
// Quit and report the failure.
|
|
1124
|
+
actIndex = -2;
|
|
1125
|
+
const errorSummary = error.message.replace(/\n.+/s, '');
|
|
1126
|
+
console.log(`ERROR: Link to ${href} not force-clickable (${errorSummary})`);
|
|
1127
|
+
act.result.success = false;
|
|
1128
|
+
act.result.error = 'unclickable';
|
|
1129
|
+
act.result.message = 'ERROR: Normal and forced click attempts timed out';
|
|
1130
|
+
});
|
|
1098
1131
|
});
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
href: href || 'NONE',
|
|
1105
|
-
target: target || 'NONE',
|
|
1106
|
-
move: 'clicked'
|
|
1107
|
-
};
|
|
1132
|
+
// If the link click succeeded:
|
|
1133
|
+
if (! act.result.error) {
|
|
1134
|
+
act.result.success = true;
|
|
1135
|
+
act.result.move = 'clicked';
|
|
1136
|
+
}
|
|
1108
1137
|
}
|
|
1109
1138
|
}
|
|
1110
1139
|
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
1111
1140
|
else if (act.type === 'select') {
|
|
1112
|
-
const options = await
|
|
1141
|
+
const options = await selection.$$('option');
|
|
1113
1142
|
let optionText = '';
|
|
1114
1143
|
if (options && Array.isArray(options) && options.length) {
|
|
1115
1144
|
const optionTexts = [];
|
|
@@ -1122,13 +1151,13 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1122
1151
|
);
|
|
1123
1152
|
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
1124
1153
|
if (index !== undefined) {
|
|
1125
|
-
await
|
|
1154
|
+
await selection.selectOption({index});
|
|
1126
1155
|
optionText = optionTexts[index];
|
|
1127
1156
|
}
|
|
1128
1157
|
}
|
|
1129
|
-
act.result =
|
|
1130
|
-
|
|
1131
|
-
|
|
1158
|
+
act.result.success = true;
|
|
1159
|
+
act.result.move = 'selected';
|
|
1160
|
+
act.result.option = optionText;
|
|
1132
1161
|
}
|
|
1133
1162
|
// Otherwise, if it is entering text on the element:
|
|
1134
1163
|
else if (act.type === 'text') {
|
|
@@ -1141,22 +1170,26 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1141
1170
|
what = what.replace(/__[A-Z]+__/, envValue);
|
|
1142
1171
|
}
|
|
1143
1172
|
// Enter the text.
|
|
1144
|
-
await
|
|
1173
|
+
await selection.type(act.what);
|
|
1145
1174
|
report.presses += act.what.length;
|
|
1146
|
-
act.result =
|
|
1175
|
+
act.result.success = true;
|
|
1176
|
+
act.result.move = 'entered';
|
|
1147
1177
|
}
|
|
1148
1178
|
// Otherwise, i.e. if the move is unknown, add the failure to the act.
|
|
1149
1179
|
else {
|
|
1150
1180
|
// Report the error.
|
|
1151
1181
|
const report = 'ERROR: move unknown';
|
|
1152
|
-
act.result =
|
|
1182
|
+
act.result.success = false;
|
|
1183
|
+
act.result.error = report;
|
|
1153
1184
|
console.log(report);
|
|
1154
1185
|
}
|
|
1155
1186
|
}
|
|
1156
1187
|
// Otherwise, i.e. if no match was found:
|
|
1157
1188
|
else {
|
|
1158
1189
|
// Stop.
|
|
1159
|
-
act.result =
|
|
1190
|
+
act.result.success = false;
|
|
1191
|
+
act.result.error = 'absent';
|
|
1192
|
+
act.result.message = 'ERROR: specified element not found';
|
|
1160
1193
|
console.log('ERROR: Specified element not found');
|
|
1161
1194
|
actIndex = -2;
|
|
1162
1195
|
}
|
|
@@ -1172,7 +1205,10 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1172
1205
|
await page.keyboard.press(key);
|
|
1173
1206
|
}
|
|
1174
1207
|
const qualifier = act.again ? `${1 + act.again} times` : 'once';
|
|
1175
|
-
act.result =
|
|
1208
|
+
act.result = {
|
|
1209
|
+
success: true,
|
|
1210
|
+
message: `pressed ${qualifier}`
|
|
1211
|
+
};
|
|
1176
1212
|
}
|
|
1177
1213
|
// Otherwise, if it is a repetitive keyboard navigation:
|
|
1178
1214
|
else if (act.type === 'presses') {
|
|
@@ -1303,6 +1339,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1303
1339
|
}
|
|
1304
1340
|
// Add the result to the act.
|
|
1305
1341
|
act.result = {
|
|
1342
|
+
success: true,
|
|
1306
1343
|
status,
|
|
1307
1344
|
totals: {
|
|
1308
1345
|
presses,
|
|
@@ -1322,32 +1359,45 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1322
1359
|
// Otherwise, i.e. if the act type is unknown:
|
|
1323
1360
|
else {
|
|
1324
1361
|
// Add the error result to the act.
|
|
1325
|
-
act.result =
|
|
1362
|
+
act.result = {
|
|
1363
|
+
success: false,
|
|
1364
|
+
error: 'badType',
|
|
1365
|
+
message: 'ERROR: invalid command type'
|
|
1366
|
+
};
|
|
1326
1367
|
}
|
|
1327
1368
|
}
|
|
1328
1369
|
// Otherwise, i.e. if redirection is prohibited but occurred:
|
|
1329
1370
|
else {
|
|
1330
|
-
// Add
|
|
1331
|
-
|
|
1371
|
+
// Add an error result to the act.
|
|
1372
|
+
act.result = {
|
|
1373
|
+
success: false,
|
|
1374
|
+
error: 'redirection',
|
|
1375
|
+
message: `ERROR: Page redirected to (${url})`
|
|
1376
|
+
};
|
|
1332
1377
|
}
|
|
1333
1378
|
}
|
|
1334
1379
|
// Otherwise, i.e. if the required page URL does not exist:
|
|
1335
1380
|
else {
|
|
1336
1381
|
// Add an error result to the act.
|
|
1337
|
-
addError(act, 'ERROR: Page has no URL');
|
|
1382
|
+
addError(act, 'noURL', 'ERROR: Page has no URL');
|
|
1338
1383
|
}
|
|
1339
1384
|
}
|
|
1340
1385
|
// Otherwise, i.e. if no page exists:
|
|
1341
1386
|
else {
|
|
1342
1387
|
// Add an error result to the act.
|
|
1343
|
-
addError(act, 'ERROR: No page identified');
|
|
1388
|
+
addError(act, 'noPage', 'ERROR: No page identified');
|
|
1344
1389
|
}
|
|
1390
|
+
act.endTime = Date.now();
|
|
1345
1391
|
}
|
|
1346
1392
|
// Otherwise, i.e. if the command is invalid:
|
|
1347
1393
|
else {
|
|
1348
1394
|
// Add an error result to the act.
|
|
1349
1395
|
const errorMsg = `ERROR: Invalid command of type ${act.type}`;
|
|
1350
|
-
act.result =
|
|
1396
|
+
act.result = {
|
|
1397
|
+
success: false,
|
|
1398
|
+
error: 'badCommand',
|
|
1399
|
+
message: errorMsg
|
|
1400
|
+
};
|
|
1351
1401
|
console.log(errorMsg);
|
|
1352
1402
|
// Quit.
|
|
1353
1403
|
actIndex = -2;
|