testaro 5.7.6 → 5.8.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/package.json +1 -1
- package/run.js +116 -49
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -70,7 +70,7 @@ const browserTypeNames = {
|
|
|
70
70
|
'firefox': 'Firefox'
|
|
71
71
|
};
|
|
72
72
|
// Items that may be waited for.
|
|
73
|
-
const waitables = ['url', 'title', 'body'];
|
|
73
|
+
const waitables = ['url', 'title', 'body', 'mailLink'];
|
|
74
74
|
// Tenon data.
|
|
75
75
|
const tenonData = {
|
|
76
76
|
accessToken: '',
|
|
@@ -442,7 +442,7 @@ const textOf = async (page, element) => {
|
|
|
442
442
|
return null;
|
|
443
443
|
}
|
|
444
444
|
};
|
|
445
|
-
// Returns an element of a type case-insensitively
|
|
445
|
+
// Returns an element of a type case-insensitively including a text.
|
|
446
446
|
const matchElement = async (page, selector, matchText, index = 0) => {
|
|
447
447
|
if (matchText) {
|
|
448
448
|
// If the page still exists:
|
|
@@ -454,14 +454,18 @@ const matchElement = async (page, selector, matchText, index = 0) => {
|
|
|
454
454
|
if (selections.length) {
|
|
455
455
|
// If there are enough to make a match possible:
|
|
456
456
|
if (index < selections.length) {
|
|
457
|
-
//
|
|
457
|
+
// For each element of the specified type:
|
|
458
458
|
let matchCount = 0;
|
|
459
459
|
const selectionTexts = [];
|
|
460
460
|
for (const selection of selections) {
|
|
461
|
+
// Add its text to the list of texts of such elements.
|
|
461
462
|
const selectionText = await textOf(page, selection);
|
|
462
463
|
selectionTexts.push(selectionText);
|
|
464
|
+
// If its text includes the specified text:
|
|
463
465
|
if (selectionText.includes(slimText)) {
|
|
466
|
+
// If the count of such elements with such texts found so far is the specified count:
|
|
464
467
|
if (matchCount++ === index) {
|
|
468
|
+
// Return it as the matching element.
|
|
465
469
|
return {
|
|
466
470
|
success: true,
|
|
467
471
|
matchingElement: selection
|
|
@@ -661,7 +665,8 @@ const isTrue = (object, specs) => {
|
|
|
661
665
|
// Adds a wait error result to an act.
|
|
662
666
|
const waitError = (page, act, error, what) => {
|
|
663
667
|
console.log(`ERROR waiting for ${what} (${error.message})`);
|
|
664
|
-
act.result =
|
|
668
|
+
act.result.found = false;
|
|
669
|
+
act.result.url = page.url();
|
|
665
670
|
act.result.error = `ERROR waiting for ${what}`;
|
|
666
671
|
return false;
|
|
667
672
|
};
|
|
@@ -754,49 +759,95 @@ const doActs = async (report, actIndex, page) => {
|
|
|
754
759
|
// Otherwise, if the act is a wait for text:
|
|
755
760
|
else if (act.type === 'wait') {
|
|
756
761
|
const {what, which} = act;
|
|
757
|
-
console.log(`>>
|
|
758
|
-
|
|
759
|
-
if
|
|
760
|
-
|
|
761
|
-
|
|
762
|
+
console.log(`>> ${what}`);
|
|
763
|
+
const result = act.result = {};
|
|
764
|
+
// Wait for the specified text, and quit if it does not appear.
|
|
765
|
+
if (what === 'url') {
|
|
766
|
+
try {
|
|
767
|
+
await page.waitForURL(which, {timeout: 15000});
|
|
768
|
+
result.found = true;
|
|
769
|
+
result.url = page.url();
|
|
770
|
+
}
|
|
771
|
+
catch(error) {
|
|
762
772
|
actIndex = -2;
|
|
763
773
|
waitError(page, act, error, 'URL');
|
|
764
|
-
}
|
|
774
|
+
}
|
|
765
775
|
}
|
|
766
|
-
else if (
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
+
else if (what === 'title') {
|
|
777
|
+
try {
|
|
778
|
+
await page.waitForFunction(
|
|
779
|
+
text => document
|
|
780
|
+
&& document.title
|
|
781
|
+
&& document.title.toLowerCase().includes(text.toLowerCase()),
|
|
782
|
+
which,
|
|
783
|
+
{
|
|
784
|
+
polling: 1000,
|
|
785
|
+
timeout: 5000
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
result.found = true;
|
|
789
|
+
result.title = await page.title();
|
|
790
|
+
}
|
|
791
|
+
catch(error) {
|
|
776
792
|
actIndex = -2;
|
|
777
793
|
waitError(page, act, error, 'title');
|
|
778
|
-
}
|
|
794
|
+
}
|
|
779
795
|
}
|
|
780
|
-
else if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
796
|
+
else if (what === 'body') {
|
|
797
|
+
try {
|
|
798
|
+
await page.waitForFunction(
|
|
799
|
+
text => document
|
|
800
|
+
&& document.body
|
|
801
|
+
&& document.body.innerText.toLowerCase().includes(text.toLowerCase()),
|
|
802
|
+
which,
|
|
803
|
+
{
|
|
804
|
+
polling: 2000,
|
|
805
|
+
timeout: 10000
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
result.found = true;
|
|
809
|
+
}
|
|
810
|
+
catch(error) {
|
|
790
811
|
actIndex = -2;
|
|
791
812
|
waitError(page, act, error, 'body');
|
|
792
|
-
}
|
|
813
|
+
}
|
|
793
814
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
815
|
+
else if (what === 'mailLink') {
|
|
816
|
+
try {
|
|
817
|
+
const addressJSHandle = await page.waitForFunction(
|
|
818
|
+
text => {
|
|
819
|
+
const mailLinks = document
|
|
820
|
+
&& document.body
|
|
821
|
+
&& document.body.querySelectorAll('a[href^="mailto:"]');
|
|
822
|
+
if (mailLinks && mailLinks.length) {
|
|
823
|
+
const textLC = text.toLowerCase();
|
|
824
|
+
const a11yLink = Array
|
|
825
|
+
.from(mailLinks)
|
|
826
|
+
.find(link => link.textContent.toLowerCase().includes(textLC));
|
|
827
|
+
if (a11yLink) {
|
|
828
|
+
return a11yLink.href.replace(/^mailto:/, '');
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
which,
|
|
839
|
+
{
|
|
840
|
+
polling: 1000,
|
|
841
|
+
timeout: 5000
|
|
842
|
+
}
|
|
843
|
+
);
|
|
844
|
+
const address = await addressJSHandle.jsonValue();
|
|
845
|
+
result.found = true;
|
|
846
|
+
result.address = address;
|
|
847
|
+
}
|
|
848
|
+
catch(error) {
|
|
849
|
+
actIndex = -2;
|
|
850
|
+
waitError(page, act, error, 'mailLink');
|
|
800
851
|
}
|
|
801
852
|
}
|
|
802
853
|
}
|
|
@@ -1054,24 +1105,36 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1054
1105
|
await matchingElement.focus({timeout: 2000});
|
|
1055
1106
|
act.result = 'focused';
|
|
1056
1107
|
}
|
|
1057
|
-
// Otherwise, if it is clicking a link
|
|
1108
|
+
// Otherwise, if it is clicking a link:
|
|
1058
1109
|
else if (act.type === 'link') {
|
|
1110
|
+
// Try to click it.
|
|
1059
1111
|
const href = await matchingElement.getAttribute('href');
|
|
1060
1112
|
const target = await matchingElement.getAttribute('target');
|
|
1061
|
-
await matchingElement.click({timeout:
|
|
1062
|
-
|
|
1063
|
-
|
|
1113
|
+
await matchingElement.click({timeout: 3000})
|
|
1114
|
+
// If it cannot be clicked within 3 seconds:
|
|
1115
|
+
.catch(async error => {
|
|
1116
|
+
// Try to force-click it without actionability checks.
|
|
1117
|
+
const errorSummary = error.message.replace(/\n.+/, '');
|
|
1118
|
+
console.log(`ERROR: Link to ${href} not clickable (${errorSummary})`);
|
|
1064
1119
|
await matchingElement.click({
|
|
1065
|
-
force: true
|
|
1066
|
-
timeout: 10000
|
|
1120
|
+
force: true
|
|
1067
1121
|
})
|
|
1068
|
-
|
|
1122
|
+
// If it cannot be force-clicked:
|
|
1123
|
+
.catch(error => {
|
|
1124
|
+
// Quit and report the failure.
|
|
1069
1125
|
actIndex = -2;
|
|
1070
|
-
|
|
1071
|
-
|
|
1126
|
+
const errorSummary = error.message.replace(/\n.+/, '');
|
|
1127
|
+
console.log(`ERROR: Link to ${href} not force-clickable (${errorSummary})`);
|
|
1128
|
+
act.result = {
|
|
1129
|
+
href: href || 'NONE',
|
|
1130
|
+
target: target || 'NONE',
|
|
1131
|
+
error: 'ERROR: Normal and forced attempts to click link timed out'
|
|
1132
|
+
};
|
|
1072
1133
|
});
|
|
1073
1134
|
});
|
|
1135
|
+
// If it was clicked:
|
|
1074
1136
|
if (actIndex > -2) {
|
|
1137
|
+
// Report the success.
|
|
1075
1138
|
act.result = {
|
|
1076
1139
|
href: href || 'NONE',
|
|
1077
1140
|
target: target || 'NONE',
|
|
@@ -1089,7 +1152,9 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1089
1152
|
const optionText = await option.textContent();
|
|
1090
1153
|
optionTexts.push(optionText);
|
|
1091
1154
|
}
|
|
1092
|
-
const matchTexts = optionTexts.map(
|
|
1155
|
+
const matchTexts = optionTexts.map(
|
|
1156
|
+
(text, index) => text.includes(act.what) ? index : -1
|
|
1157
|
+
);
|
|
1093
1158
|
const index = matchTexts.filter(text => text > -1)[act.index || 0];
|
|
1094
1159
|
if (index !== undefined) {
|
|
1095
1160
|
await matchingElement.selectOption({index});
|
|
@@ -1185,7 +1250,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1185
1250
|
}
|
|
1186
1251
|
// If there is a current element:
|
|
1187
1252
|
if (currentElement) {
|
|
1188
|
-
// If it was already reached within this
|
|
1253
|
+
// If it was already reached within this act:
|
|
1189
1254
|
if (currentElement.dataset.pressesReached === actCount.toString(10)) {
|
|
1190
1255
|
// Report the error.
|
|
1191
1256
|
console.log(`ERROR: ${currentElement.tagName} element reached again`);
|
|
@@ -1319,6 +1384,8 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1319
1384
|
const errorMsg = `ERROR: Invalid command of type ${act.type}`;
|
|
1320
1385
|
act.result = errorMsg;
|
|
1321
1386
|
console.log(errorMsg);
|
|
1387
|
+
// Quit.
|
|
1388
|
+
actIndex = -2;
|
|
1322
1389
|
}
|
|
1323
1390
|
// Perform the remaining acts.
|
|
1324
1391
|
await doActs(report, actIndex + 1, page);
|