automation_model 1.0.364-dev.0 → 1.0.364-stage
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/lib/api.d.ts +1 -1
- package/lib/api.js +2 -9
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +1 -1
- package/lib/auto_page.js.map +1 -1
- package/lib/stable_browser.d.ts +11 -0
- package/lib/stable_browser.js +327 -137
- package/lib/stable_browser.js.map +1 -1
- package/package.json +7 -3
package/lib/stable_browser.js
CHANGED
|
@@ -12,6 +12,7 @@ import drawRectangle from "./drawRect.js";
|
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
13
|
import objectPath from "object-path";
|
|
14
14
|
import { decrypt } from "./utils.js";
|
|
15
|
+
import csv from "csv-parser";
|
|
15
16
|
const Types = {
|
|
16
17
|
CLICK: "click_element",
|
|
17
18
|
NAVIGATE: "navigate",
|
|
@@ -27,6 +28,7 @@ const Types = {
|
|
|
27
28
|
SELECT: "select_combobox",
|
|
28
29
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
29
30
|
TYPE_PRESS: "type_press",
|
|
31
|
+
PRESS: "press_key",
|
|
30
32
|
HOVER: "hover_element",
|
|
31
33
|
CHECK: "check_element",
|
|
32
34
|
UNCHECK: "uncheck_element",
|
|
@@ -80,9 +82,15 @@ class StableBrowser {
|
|
|
80
82
|
this.page = page;
|
|
81
83
|
context.page = page;
|
|
82
84
|
context.pages.push(page);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
page.on("close", async () => {
|
|
86
|
+
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
87
|
+
this.context.pages.pop();
|
|
88
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
89
|
+
this.context.page = this.page;
|
|
90
|
+
let title = await this.page.title();
|
|
91
|
+
console.log("Switched to page " + title);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
86
94
|
try {
|
|
87
95
|
await this.waitForPageLoad();
|
|
88
96
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -91,7 +99,7 @@ class StableBrowser {
|
|
|
91
99
|
this.logger.error("error on page load " + e);
|
|
92
100
|
}
|
|
93
101
|
context.pageLoading.status = false;
|
|
94
|
-
});
|
|
102
|
+
}.bind(this));
|
|
95
103
|
}
|
|
96
104
|
getWebLogFile(logFolder) {
|
|
97
105
|
if (!fs.existsSync(logFolder)) {
|
|
@@ -168,23 +176,65 @@ class StableBrowser {
|
|
|
168
176
|
return text;
|
|
169
177
|
}
|
|
170
178
|
for (let key in _params) {
|
|
171
|
-
|
|
179
|
+
let regValue = key;
|
|
180
|
+
if (key.startsWith("_")) {
|
|
181
|
+
// remove the _ prefix
|
|
182
|
+
regValue = key.substring(1);
|
|
183
|
+
}
|
|
184
|
+
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
172
185
|
}
|
|
173
186
|
return text;
|
|
174
187
|
}
|
|
188
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
189
|
+
// check if not null
|
|
190
|
+
if (!locator) {
|
|
191
|
+
return locator;
|
|
192
|
+
}
|
|
193
|
+
// clone the locator
|
|
194
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
195
|
+
this.scanAndManipulate(locator, _params);
|
|
196
|
+
return locator;
|
|
197
|
+
}
|
|
198
|
+
_isObject(value) {
|
|
199
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
200
|
+
}
|
|
201
|
+
scanAndManipulate(currentObj, _params) {
|
|
202
|
+
for (const key in currentObj) {
|
|
203
|
+
if (typeof currentObj[key] === "string") {
|
|
204
|
+
// Perform string manipulation
|
|
205
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
206
|
+
}
|
|
207
|
+
else if (this._isObject(currentObj[key])) {
|
|
208
|
+
// Recursively scan nested objects
|
|
209
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
175
213
|
_getLocator(locator, scope, _params) {
|
|
214
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
215
|
+
if (locator.type === "pw_selector") {
|
|
216
|
+
return scope.locator(locator.selector);
|
|
217
|
+
}
|
|
176
218
|
if (locator.role) {
|
|
177
219
|
if (locator.role[1].nameReg) {
|
|
178
220
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
179
221
|
delete locator.role[1].nameReg;
|
|
180
222
|
}
|
|
181
|
-
if (locator.role[1].name) {
|
|
182
|
-
|
|
183
|
-
}
|
|
223
|
+
// if (locator.role[1].name) {
|
|
224
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
225
|
+
// }
|
|
184
226
|
return scope.getByRole(locator.role[0], locator.role[1]);
|
|
185
227
|
}
|
|
186
228
|
if (locator.css) {
|
|
187
|
-
return scope.locator(
|
|
229
|
+
return scope.locator(locator.css);
|
|
230
|
+
}
|
|
231
|
+
if ((locator === null || locator === void 0 ? void 0 : locator.engine) && (locator === null || locator === void 0 ? void 0 : locator.score) <= 520) {
|
|
232
|
+
let selector = locator.selector.replace(/"/g, '\\"');
|
|
233
|
+
if (locator.engine === "internal:att") {
|
|
234
|
+
selector = `[${selector}]`;
|
|
235
|
+
}
|
|
236
|
+
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
237
|
+
return locator;
|
|
188
238
|
}
|
|
189
239
|
throw new Error("unknown locator type");
|
|
190
240
|
}
|
|
@@ -309,10 +359,13 @@ class StableBrowser {
|
|
|
309
359
|
}
|
|
310
360
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
311
361
|
let locatorSearch = selectorHierarchy[index];
|
|
312
|
-
info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
362
|
+
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
313
363
|
let locator = null;
|
|
314
364
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
315
365
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
366
|
+
if (!locatorString) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
316
369
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
317
370
|
}
|
|
318
371
|
else if (locatorSearch.text) {
|
|
@@ -334,7 +387,7 @@ class StableBrowser {
|
|
|
334
387
|
// cssHref = true;
|
|
335
388
|
// }
|
|
336
389
|
let count = await locator.count();
|
|
337
|
-
info.log += "total elements found " + count + "\n";
|
|
390
|
+
//info.log += "total elements found " + count + "\n";
|
|
338
391
|
//let visibleCount = 0;
|
|
339
392
|
let visibleLocator = null;
|
|
340
393
|
if (locatorSearch.index && locatorSearch.index < count) {
|
|
@@ -344,16 +397,20 @@ class StableBrowser {
|
|
|
344
397
|
for (let j = 0; j < count; j++) {
|
|
345
398
|
let visible = await locator.nth(j).isVisible();
|
|
346
399
|
const enabled = await locator.nth(j).isEnabled();
|
|
347
|
-
info.log += "element " + j + " visible " + visible + " enabled " + enabled + "\n";
|
|
348
400
|
if (!visibleOnly) {
|
|
349
401
|
visible = true;
|
|
350
402
|
}
|
|
351
403
|
if (visible && enabled) {
|
|
352
404
|
foundLocators.push(locator.nth(j));
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
if (!info.printMessages) {
|
|
408
|
+
info.printMessages = {};
|
|
409
|
+
}
|
|
410
|
+
if (!info.printMessages[j.toString()]) {
|
|
411
|
+
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
412
|
+
info.printMessages[j.toString()] = true;
|
|
413
|
+
}
|
|
357
414
|
}
|
|
358
415
|
}
|
|
359
416
|
}
|
|
@@ -379,15 +436,30 @@ class StableBrowser {
|
|
|
379
436
|
}
|
|
380
437
|
if (result.foundElements.length > 0) {
|
|
381
438
|
// need to handle popup
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
439
|
+
const closeHandlerGroup = [];
|
|
440
|
+
closeHandlerGroup.push(this.configuration.popupHandlers[result.locatorIndex].close_dialog_locator);
|
|
441
|
+
for (let i = 0; i < scopes.length; i++) {
|
|
442
|
+
result = await this._scanLocatorsGroup(closeHandlerGroup, scopes[i], _params, info, true);
|
|
443
|
+
if (result.foundElements.length > 0) {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (result.foundElements.length > 0) {
|
|
448
|
+
let dialogCloseLocator = result.foundElements[0].locator;
|
|
449
|
+
await dialogCloseLocator.click();
|
|
450
|
+
return { rerun: true };
|
|
451
|
+
}
|
|
385
452
|
}
|
|
386
453
|
}
|
|
387
454
|
return { rerun: false };
|
|
388
455
|
}
|
|
389
456
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
390
457
|
for (let i = 0; i < 3; i++) {
|
|
458
|
+
info.log += "attempt " + i + ": totoal locators " + selectors.locators.length + "\n";
|
|
459
|
+
for (let j = 0; j < selectors.locators.length; j++) {
|
|
460
|
+
let selector = selectors.locators[j];
|
|
461
|
+
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
462
|
+
}
|
|
391
463
|
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
392
464
|
if (!element.rerun) {
|
|
393
465
|
return element;
|
|
@@ -403,6 +475,7 @@ class StableBrowser {
|
|
|
403
475
|
//let arrayMode = Array.isArray(selectors);
|
|
404
476
|
let scope = this.page;
|
|
405
477
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
478
|
+
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
406
479
|
while (true) {
|
|
407
480
|
let frameFound = false;
|
|
408
481
|
if (selectors.frameLocators) {
|
|
@@ -463,19 +536,25 @@ class StableBrowser {
|
|
|
463
536
|
if (popupResult.rerun) {
|
|
464
537
|
return popupResult;
|
|
465
538
|
}
|
|
466
|
-
info.log += "scanning locators in priority 1" + "\n";
|
|
539
|
+
// info.log += "scanning locators in priority 1" + "\n";
|
|
540
|
+
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
467
541
|
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
468
542
|
if (result.foundElements.length === 0) {
|
|
469
|
-
info.log += "scanning locators in priority 2" + "\n";
|
|
543
|
+
// info.log += "scanning locators in priority 2" + "\n";
|
|
470
544
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
471
545
|
}
|
|
472
|
-
if (result.foundElements.length === 0 &&
|
|
473
|
-
info.log += "scanning locators in priority 3" + "\n";
|
|
546
|
+
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
474
547
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
475
548
|
}
|
|
549
|
+
else {
|
|
550
|
+
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
551
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
476
554
|
let foundElements = result.foundElements;
|
|
477
555
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
478
556
|
info.box = foundElements[0].box;
|
|
557
|
+
info.log += "unique element was found, locator: " + foundElements[0].locator + "\n";
|
|
479
558
|
return foundElements[0].locator;
|
|
480
559
|
}
|
|
481
560
|
//info.log += "total elements found " + foundElements.length);
|
|
@@ -505,6 +584,7 @@ class StableBrowser {
|
|
|
505
584
|
}
|
|
506
585
|
}
|
|
507
586
|
if (maxCountElement) {
|
|
587
|
+
info.log += "unique element was found, locator: " + maxCountElement.locator + "\n";
|
|
508
588
|
info.box = await maxCountElement.locator.boundingBox();
|
|
509
589
|
return maxCountElement.locator;
|
|
510
590
|
}
|
|
@@ -513,9 +593,11 @@ class StableBrowser {
|
|
|
513
593
|
break;
|
|
514
594
|
}
|
|
515
595
|
if (performance.now() - startTime > highPriorityTimeout) {
|
|
596
|
+
info.log += "high priority timeout, will try all elements" + "\n";
|
|
516
597
|
highPriorityOnly = false;
|
|
517
598
|
}
|
|
518
599
|
if (performance.now() - startTime > visibleOnlyTimeout) {
|
|
600
|
+
info.log += "visible only timeout, will try all elements" + "\n";
|
|
519
601
|
visibleOnly = false;
|
|
520
602
|
}
|
|
521
603
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -560,7 +642,7 @@ class StableBrowser {
|
|
|
560
642
|
this._validateSelectors(selectors);
|
|
561
643
|
const startTime = Date.now();
|
|
562
644
|
const info = {};
|
|
563
|
-
info.log = "";
|
|
645
|
+
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
564
646
|
info.operation = "click";
|
|
565
647
|
info.selectors = selectors;
|
|
566
648
|
let error = null;
|
|
@@ -905,12 +987,12 @@ class StableBrowser {
|
|
|
905
987
|
await this.waitForPageLoad();
|
|
906
988
|
}
|
|
907
989
|
}
|
|
908
|
-
catch (
|
|
990
|
+
catch (err) {
|
|
909
991
|
//await this.closeUnexpectedPopups();
|
|
910
992
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
911
993
|
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
912
994
|
info.screenshotPath = screenshotPath;
|
|
913
|
-
Object.assign(
|
|
995
|
+
Object.assign(err, { info: info });
|
|
914
996
|
await element.click();
|
|
915
997
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
916
998
|
if (format) {
|
|
@@ -931,76 +1013,7 @@ class StableBrowser {
|
|
|
931
1013
|
}
|
|
932
1014
|
}
|
|
933
1015
|
}
|
|
934
|
-
catch (
|
|
935
|
-
error = e;
|
|
936
|
-
throw e;
|
|
937
|
-
}
|
|
938
|
-
finally {
|
|
939
|
-
const endTime = Date.now();
|
|
940
|
-
this._reportToWorld(world, {
|
|
941
|
-
element_name: selectors.element_name,
|
|
942
|
-
type: Types.SET_DATE_TIME,
|
|
943
|
-
screenshotId,
|
|
944
|
-
value: value,
|
|
945
|
-
text: `setDateTime input with value: ${value}`,
|
|
946
|
-
result: error
|
|
947
|
-
? {
|
|
948
|
-
status: "FAILED",
|
|
949
|
-
startTime,
|
|
950
|
-
endTime,
|
|
951
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
952
|
-
}
|
|
953
|
-
: {
|
|
954
|
-
status: "PASSED",
|
|
955
|
-
startTime,
|
|
956
|
-
endTime,
|
|
957
|
-
},
|
|
958
|
-
info: info,
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
963
|
-
this._validateSelectors(selectors);
|
|
964
|
-
const startTime = Date.now();
|
|
965
|
-
let error = null;
|
|
966
|
-
let screenshotId = null;
|
|
967
|
-
let screenshotPath = null;
|
|
968
|
-
const info = {};
|
|
969
|
-
info.log = "";
|
|
970
|
-
info.operation = Types.SET_DATE_TIME;
|
|
971
|
-
info.selectors = selectors;
|
|
972
|
-
info.value = value;
|
|
973
|
-
try {
|
|
974
|
-
let element = await this._locate(selectors, info, _params);
|
|
975
|
-
//insert red border around the element
|
|
976
|
-
await this.scrollIfNeeded(element, info);
|
|
977
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
978
|
-
await this._highlightElements(element);
|
|
979
|
-
try {
|
|
980
|
-
await element.click();
|
|
981
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
982
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
983
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
984
|
-
el.value = ""; // clear input
|
|
985
|
-
el.value = dateTimeValue;
|
|
986
|
-
}, dateTimeValue);
|
|
987
|
-
}
|
|
988
|
-
catch (error) {
|
|
989
|
-
//await this.closeUnexpectedPopups();
|
|
990
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
991
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
992
|
-
info.screenshotPath = screenshotPath;
|
|
993
|
-
Object.assign(error, { info: info });
|
|
994
|
-
await element.click();
|
|
995
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
996
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
997
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
998
|
-
el.value = ""; // clear input
|
|
999
|
-
el.value = dateTimeValue;
|
|
1000
|
-
}, dateTimeValue);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
catch (error) {
|
|
1016
|
+
catch (e) {
|
|
1004
1017
|
error = e;
|
|
1005
1018
|
throw e;
|
|
1006
1019
|
}
|
|
@@ -1035,7 +1048,7 @@ class StableBrowser {
|
|
|
1035
1048
|
let screenshotId = null;
|
|
1036
1049
|
let screenshotPath = null;
|
|
1037
1050
|
const info = {};
|
|
1038
|
-
info.log = "";
|
|
1051
|
+
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1039
1052
|
info.operation = "clickType";
|
|
1040
1053
|
info.selectors = selectors;
|
|
1041
1054
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
@@ -1050,20 +1063,32 @@ class StableBrowser {
|
|
|
1050
1063
|
await this.scrollIfNeeded(element, info);
|
|
1051
1064
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1052
1065
|
await this._highlightElements(element);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1066
|
+
if (options === null || options === undefined || !options.press) {
|
|
1067
|
+
try {
|
|
1068
|
+
let currentValue = await element.inputValue();
|
|
1069
|
+
if (currentValue) {
|
|
1070
|
+
await element.fill("");
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
catch (e) {
|
|
1074
|
+
this.logger.info("unable to clear input value");
|
|
1057
1075
|
}
|
|
1058
1076
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1077
|
+
if (options === null || options === undefined || options.press) {
|
|
1078
|
+
try {
|
|
1079
|
+
await element.click({ timeout: 5000 });
|
|
1080
|
+
}
|
|
1081
|
+
catch (e) {
|
|
1082
|
+
await element.dispatchEvent("click");
|
|
1083
|
+
}
|
|
1064
1084
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1085
|
+
else {
|
|
1086
|
+
try {
|
|
1087
|
+
await element.focus();
|
|
1088
|
+
}
|
|
1089
|
+
catch (e) {
|
|
1090
|
+
await element.dispatchEvent("focus");
|
|
1091
|
+
}
|
|
1067
1092
|
}
|
|
1068
1093
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1069
1094
|
const valueSegment = _value.split("&&");
|
|
@@ -1143,7 +1168,7 @@ class StableBrowser {
|
|
|
1143
1168
|
let screenshotId = null;
|
|
1144
1169
|
let screenshotPath = null;
|
|
1145
1170
|
const info = {};
|
|
1146
|
-
info.log = "";
|
|
1171
|
+
info.log = "***** fill on " + selectors.element_name + " with value " + value + "*****\n";
|
|
1147
1172
|
info.operation = "fill";
|
|
1148
1173
|
info.selectors = selectors;
|
|
1149
1174
|
info.value = value;
|
|
@@ -1259,7 +1284,8 @@ class StableBrowser {
|
|
|
1259
1284
|
let screenshotId = null;
|
|
1260
1285
|
let screenshotPath = null;
|
|
1261
1286
|
const info = {};
|
|
1262
|
-
info.log =
|
|
1287
|
+
info.log =
|
|
1288
|
+
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1263
1289
|
info.operation = "containsPattern";
|
|
1264
1290
|
info.selectors = selectors;
|
|
1265
1291
|
info.value = text;
|
|
@@ -1325,7 +1351,7 @@ class StableBrowser {
|
|
|
1325
1351
|
let screenshotId = null;
|
|
1326
1352
|
let screenshotPath = null;
|
|
1327
1353
|
const info = {};
|
|
1328
|
-
info.log = "";
|
|
1354
|
+
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1329
1355
|
info.operation = "containsText";
|
|
1330
1356
|
info.selectors = selectors;
|
|
1331
1357
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
@@ -1429,15 +1455,62 @@ class StableBrowser {
|
|
|
1429
1455
|
// save the data to the file
|
|
1430
1456
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1431
1457
|
}
|
|
1458
|
+
_getDataFilePath(fileName) {
|
|
1459
|
+
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1460
|
+
if (fs.existsSync(dataFile)) {
|
|
1461
|
+
return dataFile;
|
|
1462
|
+
}
|
|
1463
|
+
dataFile = path.join(this.project_path, fileName);
|
|
1464
|
+
if (fs.existsSync(dataFile)) {
|
|
1465
|
+
return dataFile;
|
|
1466
|
+
}
|
|
1467
|
+
throw new Error("data file not found " + fileName);
|
|
1468
|
+
}
|
|
1469
|
+
_parseCSVSync(filePath) {
|
|
1470
|
+
const data = fs.readFileSync(filePath, "utf8");
|
|
1471
|
+
const results = [];
|
|
1472
|
+
return new Promise((resolve, reject) => {
|
|
1473
|
+
const readableStream = new stream.Readable();
|
|
1474
|
+
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1475
|
+
readableStream.push(data);
|
|
1476
|
+
readableStream.push(null);
|
|
1477
|
+
readableStream
|
|
1478
|
+
.pipe(csv())
|
|
1479
|
+
.on("data", (data) => results.push(data))
|
|
1480
|
+
.on("end", () => resolve(results))
|
|
1481
|
+
.on("error", (error) => reject(error));
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1432
1484
|
loadTestData(type, dataSelector, world = null) {
|
|
1433
1485
|
switch (type) {
|
|
1434
1486
|
case "users":
|
|
1435
|
-
//
|
|
1436
|
-
|
|
1437
|
-
|
|
1487
|
+
// get the users.json file path
|
|
1488
|
+
let dataFile = this._getDataFilePath("users.json");
|
|
1489
|
+
// read the file and return the data
|
|
1490
|
+
const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
1491
|
+
for (let i = 0; i < users.length; i++) {
|
|
1492
|
+
if (users[i].username === dataSelector) {
|
|
1493
|
+
const userObj = {
|
|
1494
|
+
username: users[i].username,
|
|
1495
|
+
password: "secret:" + users[i].password,
|
|
1496
|
+
totp: users[i].secretKey ? "totp:" + users[i].secretKey : null,
|
|
1497
|
+
};
|
|
1498
|
+
this.setTestData(userObj, world);
|
|
1499
|
+
return userObj;
|
|
1500
|
+
}
|
|
1438
1501
|
}
|
|
1502
|
+
throw new Error("user not found " + dataSelector);
|
|
1503
|
+
default:
|
|
1504
|
+
throw new Error("unknown type " + type);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
async loadTestDataAsync(type, dataSelector, world = null) {
|
|
1508
|
+
switch (type) {
|
|
1509
|
+
case "users": {
|
|
1510
|
+
// get the users.json file path
|
|
1511
|
+
let dataFile = this._getDataFilePath("users.json");
|
|
1439
1512
|
// read the file and return the data
|
|
1440
|
-
const users = JSON.parse(fs.readFileSync(
|
|
1513
|
+
const users = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
1441
1514
|
for (let i = 0; i < users.length; i++) {
|
|
1442
1515
|
if (users[i].username === dataSelector) {
|
|
1443
1516
|
const userObj = {
|
|
@@ -1450,6 +1523,29 @@ class StableBrowser {
|
|
|
1450
1523
|
}
|
|
1451
1524
|
}
|
|
1452
1525
|
throw new Error("user not found " + dataSelector);
|
|
1526
|
+
}
|
|
1527
|
+
case "csv": {
|
|
1528
|
+
// the dataSelector should start with the file name followed by the row number: data.csv:1, if no row number is provided, it will default to 1
|
|
1529
|
+
const parts = dataSelector.split(":");
|
|
1530
|
+
let rowNumber = 0;
|
|
1531
|
+
if (parts.length > 1) {
|
|
1532
|
+
rowNumber = parseInt(parts[1]);
|
|
1533
|
+
}
|
|
1534
|
+
let dataFile = this._getDataFilePath(parts[0]);
|
|
1535
|
+
const results = await this._parseCSVSync(dataFile);
|
|
1536
|
+
// result stracture:
|
|
1537
|
+
// [
|
|
1538
|
+
// { NAME: 'Daffy Duck', AGE: '24' },
|
|
1539
|
+
// { NAME: 'Bugs Bunny', AGE: '22' }
|
|
1540
|
+
// ]
|
|
1541
|
+
// verify the row number is within the range
|
|
1542
|
+
if (rowNumber >= results.length) {
|
|
1543
|
+
throw new Error("row number is out of range " + rowNumber);
|
|
1544
|
+
}
|
|
1545
|
+
const data = results[rowNumber];
|
|
1546
|
+
this.setTestData(data, world);
|
|
1547
|
+
return data;
|
|
1548
|
+
}
|
|
1453
1549
|
default:
|
|
1454
1550
|
throw new Error("unknown type " + type);
|
|
1455
1551
|
}
|
|
@@ -1588,7 +1684,7 @@ class StableBrowser {
|
|
|
1588
1684
|
let screenshotPath = null;
|
|
1589
1685
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1590
1686
|
const info = {};
|
|
1591
|
-
info.log = "";
|
|
1687
|
+
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1592
1688
|
info.operation = "verify";
|
|
1593
1689
|
info.selectors = selectors;
|
|
1594
1690
|
try {
|
|
@@ -1641,7 +1737,7 @@ class StableBrowser {
|
|
|
1641
1737
|
let screenshotPath = null;
|
|
1642
1738
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1643
1739
|
const info = {};
|
|
1644
|
-
info.log = "";
|
|
1740
|
+
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1645
1741
|
info.operation = "extract";
|
|
1646
1742
|
info.selectors = selectors;
|
|
1647
1743
|
try {
|
|
@@ -1666,7 +1762,8 @@ class StableBrowser {
|
|
|
1666
1762
|
if (world) {
|
|
1667
1763
|
world[variable] = info.value;
|
|
1668
1764
|
}
|
|
1669
|
-
this.
|
|
1765
|
+
this.setTestData({ [variable]: info.value }, world);
|
|
1766
|
+
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1670
1767
|
return info;
|
|
1671
1768
|
}
|
|
1672
1769
|
catch (e) {
|
|
@@ -1703,6 +1800,91 @@ class StableBrowser {
|
|
|
1703
1800
|
});
|
|
1704
1801
|
}
|
|
1705
1802
|
}
|
|
1803
|
+
async extractEmailData(emailAddress, options, world) {
|
|
1804
|
+
if (!emailAddress) {
|
|
1805
|
+
throw new Error("email address is null");
|
|
1806
|
+
}
|
|
1807
|
+
// check if address contain @
|
|
1808
|
+
if (emailAddress.indexOf("@") === -1) {
|
|
1809
|
+
emailAddress = emailAddress + "@blinq-mail.io";
|
|
1810
|
+
}
|
|
1811
|
+
else {
|
|
1812
|
+
if (!emailAddress.toLowerCase().endsWith("@blinq-mail.io")) {
|
|
1813
|
+
throw new Error("email address should end with @blinq-mail.io");
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const startTime = Date.now();
|
|
1817
|
+
let timeout = 60000;
|
|
1818
|
+
if (options && options.timeout) {
|
|
1819
|
+
timeout = options.timeout;
|
|
1820
|
+
}
|
|
1821
|
+
const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1822
|
+
const request = {
|
|
1823
|
+
method: "POST",
|
|
1824
|
+
url: serviceUrl,
|
|
1825
|
+
headers: {
|
|
1826
|
+
"Content-Type": "application/json",
|
|
1827
|
+
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
1828
|
+
},
|
|
1829
|
+
data: JSON.stringify({
|
|
1830
|
+
email: emailAddress,
|
|
1831
|
+
}),
|
|
1832
|
+
};
|
|
1833
|
+
let errorCount = 0;
|
|
1834
|
+
while (true) {
|
|
1835
|
+
try {
|
|
1836
|
+
let result = await this.context.api.request(request);
|
|
1837
|
+
// the response body expected to be the following:
|
|
1838
|
+
// {
|
|
1839
|
+
// "status": true,
|
|
1840
|
+
// "content": {
|
|
1841
|
+
// "url": "",
|
|
1842
|
+
// "code": "112112",
|
|
1843
|
+
// "name": "generate_link_or_code"
|
|
1844
|
+
// }
|
|
1845
|
+
//}
|
|
1846
|
+
if ((result && result.data, result.data.status === true)) {
|
|
1847
|
+
let codeOrUrlFound = false;
|
|
1848
|
+
let emailCode = null;
|
|
1849
|
+
let emailUrl = null;
|
|
1850
|
+
// check if a code is returned
|
|
1851
|
+
if (result.data.content && result.data.content.code) {
|
|
1852
|
+
let code = result.data.content.code;
|
|
1853
|
+
this.setTestData({ emailCode: code }, world);
|
|
1854
|
+
this.logger.info("set test data: emailCode = " + code);
|
|
1855
|
+
emailCode = code;
|
|
1856
|
+
codeOrUrlFound = true;
|
|
1857
|
+
}
|
|
1858
|
+
// check if a url is returned
|
|
1859
|
+
if (result.data.content && result.data.content.url) {
|
|
1860
|
+
let url = result.data.content.url;
|
|
1861
|
+
this.setTestData({ emailUrl: url }, world);
|
|
1862
|
+
this.logger.info("set test data: emailUrl = " + url);
|
|
1863
|
+
emailUrl = url;
|
|
1864
|
+
codeOrUrlFound = true;
|
|
1865
|
+
}
|
|
1866
|
+
if (codeOrUrlFound) {
|
|
1867
|
+
return { emailUrl, emailCode };
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
this.logger.info("an email received but no code or url found");
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
catch (e) {
|
|
1875
|
+
errorCount++;
|
|
1876
|
+
if (errorCount > 3) {
|
|
1877
|
+
throw e;
|
|
1878
|
+
}
|
|
1879
|
+
// ignore
|
|
1880
|
+
}
|
|
1881
|
+
// check if the timeout is reached
|
|
1882
|
+
if (Date.now() - startTime > timeout) {
|
|
1883
|
+
throw new Error("timeout reached");
|
|
1884
|
+
}
|
|
1885
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1706
1888
|
async _highlightElements(scope, css) {
|
|
1707
1889
|
try {
|
|
1708
1890
|
if (!scope) {
|
|
@@ -1774,7 +1956,7 @@ class StableBrowser {
|
|
|
1774
1956
|
let screenshotPath = null;
|
|
1775
1957
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1776
1958
|
const info = {};
|
|
1777
|
-
info.log = "";
|
|
1959
|
+
info.log = "***** verify page path " + pathPart + " *****\n";
|
|
1778
1960
|
info.operation = "verifyPagePath";
|
|
1779
1961
|
const newValue = await this._replaceWithLocalData(pathPart, world);
|
|
1780
1962
|
if (newValue !== pathPart) {
|
|
@@ -1835,7 +2017,7 @@ class StableBrowser {
|
|
|
1835
2017
|
let screenshotPath = null;
|
|
1836
2018
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1837
2019
|
const info = {};
|
|
1838
|
-
info.log = "";
|
|
2020
|
+
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
1839
2021
|
info.operation = "verifyTextExistInPage";
|
|
1840
2022
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1841
2023
|
if (newValue !== text) {
|
|
@@ -1884,8 +2066,10 @@ class StableBrowser {
|
|
|
1884
2066
|
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
1885
2067
|
await this._highlightElements(frame, dataAttribute);
|
|
1886
2068
|
const element = await frame.$(dataAttribute);
|
|
1887
|
-
|
|
1888
|
-
|
|
2069
|
+
if (element) {
|
|
2070
|
+
await this.scrollIfNeeded(element, info);
|
|
2071
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2072
|
+
}
|
|
1889
2073
|
}
|
|
1890
2074
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1891
2075
|
return info;
|
|
@@ -1923,6 +2107,16 @@ class StableBrowser {
|
|
|
1923
2107
|
});
|
|
1924
2108
|
}
|
|
1925
2109
|
}
|
|
2110
|
+
_getServerUrl() {
|
|
2111
|
+
let serviceUrl = "https://api.blinq.io";
|
|
2112
|
+
if (process.env.NODE_ENV_BLINQ === "dev") {
|
|
2113
|
+
serviceUrl = "https://dev.api.blinq.io";
|
|
2114
|
+
}
|
|
2115
|
+
else if (process.env.NODE_ENV_BLINQ === "stage") {
|
|
2116
|
+
serviceUrl = "https://stage.api.blinq.io";
|
|
2117
|
+
}
|
|
2118
|
+
return serviceUrl;
|
|
2119
|
+
}
|
|
1926
2120
|
async visualVerification(text, options = {}, world = null) {
|
|
1927
2121
|
const startTime = Date.now();
|
|
1928
2122
|
let error = null;
|
|
@@ -1937,13 +2131,7 @@ class StableBrowser {
|
|
|
1937
2131
|
throw new Error("TOKEN is not set");
|
|
1938
2132
|
}
|
|
1939
2133
|
try {
|
|
1940
|
-
let serviceUrl =
|
|
1941
|
-
if (process.env.NODE_ENV_BLINQ === "dev") {
|
|
1942
|
-
serviceUrl = "https://dev.api.blinq.io";
|
|
1943
|
-
}
|
|
1944
|
-
else if (process.env.NODE_ENV_BLINQ === "stage") {
|
|
1945
|
-
serviceUrl = "https://stage.api.blinq.io";
|
|
1946
|
-
}
|
|
2134
|
+
let serviceUrl = this._getServerUrl();
|
|
1947
2135
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1948
2136
|
info.screenshotPath = screenshotPath;
|
|
1949
2137
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2092,7 +2280,16 @@ class StableBrowser {
|
|
|
2092
2280
|
let screenshotId = null;
|
|
2093
2281
|
let screenshotPath = null;
|
|
2094
2282
|
const info = {};
|
|
2095
|
-
info.log =
|
|
2283
|
+
info.log =
|
|
2284
|
+
"***** analyze table " +
|
|
2285
|
+
selectors.element_name +
|
|
2286
|
+
" query " +
|
|
2287
|
+
query +
|
|
2288
|
+
" operator " +
|
|
2289
|
+
operator +
|
|
2290
|
+
" value " +
|
|
2291
|
+
value +
|
|
2292
|
+
" *****\n";
|
|
2096
2293
|
info.operation = "analyzeTable";
|
|
2097
2294
|
info.selectors = selectors;
|
|
2098
2295
|
info.query = query;
|
|
@@ -2326,13 +2523,6 @@ class StableBrowser {
|
|
|
2326
2523
|
const info = {};
|
|
2327
2524
|
try {
|
|
2328
2525
|
await this.page.close();
|
|
2329
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2330
|
-
this.context.pages.pop();
|
|
2331
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2332
|
-
this.context.page = this.page;
|
|
2333
|
-
let title = await this.page.title();
|
|
2334
|
-
console.log("Switched to page " + title);
|
|
2335
|
-
}
|
|
2336
2526
|
}
|
|
2337
2527
|
catch (e) {
|
|
2338
2528
|
console.log(".");
|