automation_model 1.0.597-dev → 1.0.597-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/README.md +130 -0
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +140 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +53 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +1 -0
- package/lib/bruno.js +301 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +51 -37
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +6 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +24 -7
- package/lib/stable_browser.js +795 -348
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +8 -3
- package/lib/utils.js +209 -19
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
package/lib/stable_browser.js
CHANGED
|
@@ -7,20 +7,25 @@ import path from "path";
|
|
|
7
7
|
import reg_parser from "regex-parser";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
|
+
import drawRectangle from "./drawRect.js";
|
|
10
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
11
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
12
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
13
14
|
import csv from "csv-parser";
|
|
14
15
|
import { Readable } from "node:stream";
|
|
15
16
|
import readline from "readline";
|
|
16
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
17
19
|
import { locate_element } from "./locate_element.js";
|
|
18
20
|
import { randomUUID } from "crypto";
|
|
19
21
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
22
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
23
|
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
22
26
|
export const Types = {
|
|
23
27
|
CLICK: "click_element",
|
|
28
|
+
WAIT_ELEMENT: "wait_element",
|
|
24
29
|
NAVIGATE: "navigate",
|
|
25
30
|
FILL: "fill_element",
|
|
26
31
|
EXECUTE: "execute_page_method",
|
|
@@ -42,6 +47,7 @@ export const Types = {
|
|
|
42
47
|
UNCHECK: "uncheck_element",
|
|
43
48
|
EXTRACT: "extract_attribute",
|
|
44
49
|
CLOSE_PAGE: "close_page",
|
|
50
|
+
TABLE_OPERATION: "table_operation",
|
|
45
51
|
SET_DATE_TIME: "set_date_time",
|
|
46
52
|
SET_VIEWPORT: "set_viewport",
|
|
47
53
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -50,8 +56,12 @@ export const Types = {
|
|
|
50
56
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
51
57
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
52
58
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
59
|
+
BRUNO: "bruno",
|
|
53
60
|
};
|
|
54
61
|
export const apps = {};
|
|
62
|
+
const formatElementName = (elementName) => {
|
|
63
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
64
|
+
};
|
|
55
65
|
class StableBrowser {
|
|
56
66
|
browser;
|
|
57
67
|
page;
|
|
@@ -65,6 +75,7 @@ class StableBrowser {
|
|
|
65
75
|
appName = "main";
|
|
66
76
|
tags = null;
|
|
67
77
|
isRecording = false;
|
|
78
|
+
initSnapshotTaken = false;
|
|
68
79
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
69
80
|
this.browser = browser;
|
|
70
81
|
this.page = page;
|
|
@@ -171,6 +182,30 @@ class StableBrowser {
|
|
|
171
182
|
await this.waitForPageLoad();
|
|
172
183
|
}
|
|
173
184
|
}
|
|
185
|
+
async switchTab(tabTitleOrIndex) {
|
|
186
|
+
// first check if the tabNameOrIndex is a number
|
|
187
|
+
let index = parseInt(tabTitleOrIndex);
|
|
188
|
+
if (!isNaN(index)) {
|
|
189
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
190
|
+
this.page = this.context.pages[index];
|
|
191
|
+
this.context.page = this.page;
|
|
192
|
+
await this.page.bringToFront();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
197
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
198
|
+
let page = this.context.pages[i];
|
|
199
|
+
let title = await page.title();
|
|
200
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
201
|
+
this.page = page;
|
|
202
|
+
this.context.page = this.page;
|
|
203
|
+
await this.page.bringToFront();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
208
|
+
}
|
|
174
209
|
registerConsoleLogListener(page, context) {
|
|
175
210
|
if (!this.context.webLogger) {
|
|
176
211
|
this.context.webLogger = [];
|
|
@@ -235,6 +270,9 @@ class StableBrowser {
|
|
|
235
270
|
// await closeUnexpectedPopups(this.page);
|
|
236
271
|
// }
|
|
237
272
|
async goto(url, world = null) {
|
|
273
|
+
if (!url) {
|
|
274
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
275
|
+
}
|
|
238
276
|
if (!url.startsWith("http")) {
|
|
239
277
|
url = "https://" + url;
|
|
240
278
|
}
|
|
@@ -252,7 +290,7 @@ class StableBrowser {
|
|
|
252
290
|
highlight: false,
|
|
253
291
|
};
|
|
254
292
|
try {
|
|
255
|
-
await _preCommand(state, this
|
|
293
|
+
await _preCommand(state, this);
|
|
256
294
|
await this.page.goto(url, {
|
|
257
295
|
timeout: 60000,
|
|
258
296
|
});
|
|
@@ -263,7 +301,7 @@ class StableBrowser {
|
|
|
263
301
|
_commandError(state, error, this);
|
|
264
302
|
}
|
|
265
303
|
finally {
|
|
266
|
-
_commandFinally(state, this);
|
|
304
|
+
await _commandFinally(state, this);
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
async _getLocator(locator, scope, _params) {
|
|
@@ -344,7 +382,7 @@ class StableBrowser {
|
|
|
344
382
|
return resultCss;
|
|
345
383
|
}
|
|
346
384
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
347
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
385
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
348
386
|
const locator = scope.locator(query);
|
|
349
387
|
const count = await locator.count();
|
|
350
388
|
if (!tag1) {
|
|
@@ -364,6 +402,12 @@ class StableBrowser {
|
|
|
364
402
|
if (!el.setAttribute) {
|
|
365
403
|
el = el.parentElement;
|
|
366
404
|
}
|
|
405
|
+
// remove any attributes start with data-blinq-id
|
|
406
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
407
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
408
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
409
|
+
// }
|
|
410
|
+
// }
|
|
367
411
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
368
412
|
return true;
|
|
369
413
|
}, [tag1, randomToken]))) {
|
|
@@ -373,7 +417,7 @@ class StableBrowser {
|
|
|
373
417
|
}
|
|
374
418
|
return { elementCount: tagCount, randomToken };
|
|
375
419
|
}
|
|
376
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
|
|
420
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
377
421
|
if (!info) {
|
|
378
422
|
info = {};
|
|
379
423
|
}
|
|
@@ -400,7 +444,7 @@ class StableBrowser {
|
|
|
400
444
|
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
401
445
|
if (!locatorString) {
|
|
402
446
|
info.failCause.textNotFound = true;
|
|
403
|
-
info.failCause.lastError =
|
|
447
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
404
448
|
return;
|
|
405
449
|
}
|
|
406
450
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -410,7 +454,7 @@ class StableBrowser {
|
|
|
410
454
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
411
455
|
if (result.elementCount === 0) {
|
|
412
456
|
info.failCause.textNotFound = true;
|
|
413
|
-
info.failCause.lastError =
|
|
457
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
414
458
|
return;
|
|
415
459
|
}
|
|
416
460
|
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
@@ -462,11 +506,11 @@ class StableBrowser {
|
|
|
462
506
|
info.printMessages = {};
|
|
463
507
|
}
|
|
464
508
|
if (info.locatorLog && !visible) {
|
|
465
|
-
info.failCause.lastError =
|
|
509
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
466
510
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
467
511
|
}
|
|
468
512
|
if (info.locatorLog && !enabled) {
|
|
469
|
-
info.failCause.lastError =
|
|
513
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
470
514
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
471
515
|
}
|
|
472
516
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -546,7 +590,28 @@ class StableBrowser {
|
|
|
546
590
|
}
|
|
547
591
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
548
592
|
if (!element.rerun) {
|
|
549
|
-
|
|
593
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
594
|
+
element.evaluate((el, randomToken) => {
|
|
595
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
596
|
+
}, randomToken);
|
|
597
|
+
// if (element._frame) {
|
|
598
|
+
// return element;
|
|
599
|
+
// }
|
|
600
|
+
const scope = element._frame ?? element.page();
|
|
601
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
602
|
+
let prefixSelector = "";
|
|
603
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
604
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
605
|
+
if (frameSelectorIndex !== -1) {
|
|
606
|
+
// remove everything after the >> internal:control=enter-frame
|
|
607
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
608
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
609
|
+
}
|
|
610
|
+
// if (element?._frame?._selector) {
|
|
611
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
612
|
+
// }
|
|
613
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
614
|
+
return scope.locator(newSelector);
|
|
550
615
|
}
|
|
551
616
|
}
|
|
552
617
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -619,7 +684,7 @@ class StableBrowser {
|
|
|
619
684
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
620
685
|
if (Date.now() - startTime > timeout) {
|
|
621
686
|
info.failCause.iframeNotFound = true;
|
|
622
|
-
info.failCause.lastError =
|
|
687
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
623
688
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
624
689
|
}
|
|
625
690
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -693,18 +758,13 @@ class StableBrowser {
|
|
|
693
758
|
}
|
|
694
759
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
695
760
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
696
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
|
|
761
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
697
762
|
if (result.foundElements.length === 0) {
|
|
698
763
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
699
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
|
|
700
|
-
}
|
|
701
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
702
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
764
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
703
765
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
707
|
-
}
|
|
766
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
767
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
708
768
|
}
|
|
709
769
|
let foundElements = result.foundElements;
|
|
710
770
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -760,6 +820,11 @@ class StableBrowser {
|
|
|
760
820
|
visibleOnly = false;
|
|
761
821
|
}
|
|
762
822
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
823
|
+
// sheck of more of half of the timeout has passed
|
|
824
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
825
|
+
highPriorityOnly = false;
|
|
826
|
+
visibleOnly = false;
|
|
827
|
+
}
|
|
763
828
|
}
|
|
764
829
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
765
830
|
// if (info.locatorLog) {
|
|
@@ -771,11 +836,11 @@ class StableBrowser {
|
|
|
771
836
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
772
837
|
info.failCause.locatorNotFound = true;
|
|
773
838
|
if (!info?.failCause?.lastError) {
|
|
774
|
-
info.failCause.lastError =
|
|
839
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
775
840
|
}
|
|
776
841
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
777
842
|
}
|
|
778
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
843
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
779
844
|
let foundElements = [];
|
|
780
845
|
const result = {
|
|
781
846
|
foundElements: foundElements,
|
|
@@ -783,7 +848,7 @@ class StableBrowser {
|
|
|
783
848
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
784
849
|
let foundLocators = [];
|
|
785
850
|
try {
|
|
786
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
851
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
787
852
|
}
|
|
788
853
|
catch (e) {
|
|
789
854
|
// this call can fail it the browser is navigating
|
|
@@ -791,7 +856,7 @@ class StableBrowser {
|
|
|
791
856
|
// this.logger.debug(e);
|
|
792
857
|
foundLocators = [];
|
|
793
858
|
try {
|
|
794
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
859
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
795
860
|
}
|
|
796
861
|
catch (e) {
|
|
797
862
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -806,9 +871,40 @@ class StableBrowser {
|
|
|
806
871
|
result.locatorIndex = i;
|
|
807
872
|
}
|
|
808
873
|
if (foundLocators.length > 1) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
874
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
875
|
+
const boxes = [];
|
|
876
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
877
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
878
|
+
}
|
|
879
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
880
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
881
|
+
if (j === k) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
885
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
886
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
888
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
889
|
+
// as the element is not unique, will remove it
|
|
890
|
+
boxes.splice(k, 1);
|
|
891
|
+
k--;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (boxes.length === 1) {
|
|
896
|
+
result.foundElements.push({
|
|
897
|
+
locator: boxes[0].locator.first(),
|
|
898
|
+
box: boxes[0].box,
|
|
899
|
+
unique: true,
|
|
900
|
+
});
|
|
901
|
+
result.locatorIndex = i;
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
info.failCause.foundMultiple = true;
|
|
905
|
+
if (info.locatorLog) {
|
|
906
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
907
|
+
}
|
|
812
908
|
}
|
|
813
909
|
}
|
|
814
910
|
}
|
|
@@ -827,7 +923,7 @@ class StableBrowser {
|
|
|
827
923
|
operation: "simpleClick",
|
|
828
924
|
log: "***** click on " + elementDescription + " *****\n",
|
|
829
925
|
};
|
|
830
|
-
_preCommand(state, this
|
|
926
|
+
_preCommand(state, this);
|
|
831
927
|
const startTime = Date.now();
|
|
832
928
|
let timeout = 30000;
|
|
833
929
|
if (options && options.timeout) {
|
|
@@ -856,7 +952,7 @@ class StableBrowser {
|
|
|
856
952
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
857
953
|
}
|
|
858
954
|
finally {
|
|
859
|
-
_commandFinally(state, this);
|
|
955
|
+
await _commandFinally(state, this);
|
|
860
956
|
}
|
|
861
957
|
}
|
|
862
958
|
}
|
|
@@ -876,7 +972,7 @@ class StableBrowser {
|
|
|
876
972
|
operation: "simpleClickType",
|
|
877
973
|
log: "***** click type on " + elementDescription + " *****\n",
|
|
878
974
|
};
|
|
879
|
-
_preCommand(state, this
|
|
975
|
+
_preCommand(state, this);
|
|
880
976
|
const startTime = Date.now();
|
|
881
977
|
let timeout = 30000;
|
|
882
978
|
if (options && options.timeout) {
|
|
@@ -905,7 +1001,7 @@ class StableBrowser {
|
|
|
905
1001
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
906
1002
|
}
|
|
907
1003
|
finally {
|
|
908
|
-
_commandFinally(state, this);
|
|
1004
|
+
await _commandFinally(state, this);
|
|
909
1005
|
}
|
|
910
1006
|
}
|
|
911
1007
|
}
|
|
@@ -919,25 +1015,14 @@ class StableBrowser {
|
|
|
919
1015
|
options,
|
|
920
1016
|
world,
|
|
921
1017
|
text: "Click element",
|
|
1018
|
+
_text: "Click on " + selectors.element_name,
|
|
922
1019
|
type: Types.CLICK,
|
|
923
1020
|
operation: "click",
|
|
924
1021
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
925
1022
|
};
|
|
926
1023
|
try {
|
|
927
|
-
await _preCommand(state, this
|
|
928
|
-
|
|
929
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
930
|
-
// }
|
|
931
|
-
try {
|
|
932
|
-
await state.element.click();
|
|
933
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
934
|
-
}
|
|
935
|
-
catch (e) {
|
|
936
|
-
// await this.closeUnexpectedPopups();
|
|
937
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
938
|
-
await state.element.dispatchEvent("click");
|
|
939
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
940
|
-
}
|
|
1024
|
+
await _preCommand(state, this);
|
|
1025
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
941
1026
|
await this.waitForPageLoad();
|
|
942
1027
|
return state.info;
|
|
943
1028
|
}
|
|
@@ -945,8 +1030,40 @@ class StableBrowser {
|
|
|
945
1030
|
await _commandError(state, e, this);
|
|
946
1031
|
}
|
|
947
1032
|
finally {
|
|
948
|
-
_commandFinally(state, this);
|
|
1033
|
+
await _commandFinally(state, this);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1037
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1038
|
+
const state = {
|
|
1039
|
+
selectors,
|
|
1040
|
+
_params,
|
|
1041
|
+
options,
|
|
1042
|
+
world,
|
|
1043
|
+
text: "Wait for element",
|
|
1044
|
+
_text: "Wait for " + selectors.element_name,
|
|
1045
|
+
type: Types.WAIT_ELEMENT,
|
|
1046
|
+
operation: "waitForElement",
|
|
1047
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1048
|
+
};
|
|
1049
|
+
let found = false;
|
|
1050
|
+
try {
|
|
1051
|
+
await _preCommand(state, this);
|
|
1052
|
+
// if (state.options && state.options.context) {
|
|
1053
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1054
|
+
// }
|
|
1055
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1056
|
+
found = true;
|
|
1057
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1058
|
+
}
|
|
1059
|
+
catch (e) {
|
|
1060
|
+
console.error("Error on waitForElement", e);
|
|
1061
|
+
// await _commandError(state, e, this);
|
|
1062
|
+
}
|
|
1063
|
+
finally {
|
|
1064
|
+
await _commandFinally(state, this);
|
|
949
1065
|
}
|
|
1066
|
+
return found;
|
|
950
1067
|
}
|
|
951
1068
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
952
1069
|
const state = {
|
|
@@ -956,22 +1073,23 @@ class StableBrowser {
|
|
|
956
1073
|
world,
|
|
957
1074
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
958
1075
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1076
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
959
1077
|
operation: "setCheck",
|
|
960
1078
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
961
1079
|
};
|
|
962
1080
|
try {
|
|
963
|
-
await _preCommand(state, this
|
|
1081
|
+
await _preCommand(state, this);
|
|
964
1082
|
state.info.checked = checked;
|
|
965
1083
|
// let element = await this._locate(selectors, info, _params);
|
|
966
1084
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
967
1085
|
try {
|
|
968
|
-
if (world && world.screenshot && !world.screenshotPath) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
972
|
-
await this._unHighlightElements(element);
|
|
973
|
-
}
|
|
1086
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1087
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1088
|
+
await this._highlightElements(state.element);
|
|
974
1089
|
await state.element.setChecked(checked);
|
|
1090
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1091
|
+
// await this._unHighlightElements(element);
|
|
1092
|
+
// }
|
|
975
1093
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
976
1094
|
// await this._unHighlightElements(element);
|
|
977
1095
|
}
|
|
@@ -994,7 +1112,7 @@ class StableBrowser {
|
|
|
994
1112
|
await _commandError(state, e, this);
|
|
995
1113
|
}
|
|
996
1114
|
finally {
|
|
997
|
-
_commandFinally(state, this);
|
|
1115
|
+
await _commandFinally(state, this);
|
|
998
1116
|
}
|
|
999
1117
|
}
|
|
1000
1118
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1005,25 +1123,14 @@ class StableBrowser {
|
|
|
1005
1123
|
world,
|
|
1006
1124
|
type: Types.HOVER,
|
|
1007
1125
|
text: `Hover element`,
|
|
1126
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1008
1127
|
operation: "hover",
|
|
1009
1128
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1010
1129
|
};
|
|
1011
1130
|
try {
|
|
1012
|
-
await _preCommand(state, this
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
await _screenshot(state, this);
|
|
1016
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1017
|
-
}
|
|
1018
|
-
catch (e) {
|
|
1019
|
-
//await this.closeUnexpectedPopups();
|
|
1020
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1021
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1022
|
-
await state.element.hover({ timeout: 10000 });
|
|
1023
|
-
await _screenshot(state, this);
|
|
1024
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1025
|
-
}
|
|
1026
|
-
// await _screenshot(state, this);
|
|
1131
|
+
await _preCommand(state, this);
|
|
1132
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1133
|
+
await _screenshot(state, this);
|
|
1027
1134
|
await this.waitForPageLoad();
|
|
1028
1135
|
return state.info;
|
|
1029
1136
|
}
|
|
@@ -1031,7 +1138,7 @@ class StableBrowser {
|
|
|
1031
1138
|
await _commandError(state, e, this);
|
|
1032
1139
|
}
|
|
1033
1140
|
finally {
|
|
1034
|
-
_commandFinally(state, this);
|
|
1141
|
+
await _commandFinally(state, this);
|
|
1035
1142
|
}
|
|
1036
1143
|
}
|
|
1037
1144
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1046,11 +1153,12 @@ class StableBrowser {
|
|
|
1046
1153
|
value: values.toString(),
|
|
1047
1154
|
type: Types.SELECT,
|
|
1048
1155
|
text: `Select option: ${values}`,
|
|
1156
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1049
1157
|
operation: "selectOption",
|
|
1050
1158
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1051
1159
|
};
|
|
1052
1160
|
try {
|
|
1053
|
-
await _preCommand(state, this
|
|
1161
|
+
await _preCommand(state, this);
|
|
1054
1162
|
try {
|
|
1055
1163
|
await state.element.selectOption(values);
|
|
1056
1164
|
}
|
|
@@ -1066,7 +1174,7 @@ class StableBrowser {
|
|
|
1066
1174
|
await _commandError(state, e, this);
|
|
1067
1175
|
}
|
|
1068
1176
|
finally {
|
|
1069
|
-
_commandFinally(state, this);
|
|
1177
|
+
await _commandFinally(state, this);
|
|
1070
1178
|
}
|
|
1071
1179
|
}
|
|
1072
1180
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1080,11 +1188,12 @@ class StableBrowser {
|
|
|
1080
1188
|
highlight: false,
|
|
1081
1189
|
type: Types.TYPE_PRESS,
|
|
1082
1190
|
text: `Type value: ${_value}`,
|
|
1191
|
+
_text: `Type value: ${_value}`,
|
|
1083
1192
|
operation: "type",
|
|
1084
1193
|
log: "",
|
|
1085
1194
|
};
|
|
1086
1195
|
try {
|
|
1087
|
-
await _preCommand(state, this
|
|
1196
|
+
await _preCommand(state, this);
|
|
1088
1197
|
const valueSegment = state.value.split("&&");
|
|
1089
1198
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1090
1199
|
if (i > 0) {
|
|
@@ -1111,7 +1220,7 @@ class StableBrowser {
|
|
|
1111
1220
|
await _commandError(state, e, this);
|
|
1112
1221
|
}
|
|
1113
1222
|
finally {
|
|
1114
|
-
_commandFinally(state, this);
|
|
1223
|
+
await _commandFinally(state, this);
|
|
1115
1224
|
}
|
|
1116
1225
|
}
|
|
1117
1226
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1127,7 +1236,7 @@ class StableBrowser {
|
|
|
1127
1236
|
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1128
1237
|
};
|
|
1129
1238
|
try {
|
|
1130
|
-
await _preCommand(state, this
|
|
1239
|
+
await _preCommand(state, this);
|
|
1131
1240
|
let value = await this._replaceWithLocalData(state.value, this);
|
|
1132
1241
|
try {
|
|
1133
1242
|
await state.element.evaluateHandle((el, value) => {
|
|
@@ -1147,7 +1256,7 @@ class StableBrowser {
|
|
|
1147
1256
|
await _commandError(state, e, this);
|
|
1148
1257
|
}
|
|
1149
1258
|
finally {
|
|
1150
|
-
_commandFinally(state, this);
|
|
1259
|
+
await _commandFinally(state, this);
|
|
1151
1260
|
}
|
|
1152
1261
|
}
|
|
1153
1262
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1159,14 +1268,15 @@ class StableBrowser {
|
|
|
1159
1268
|
world,
|
|
1160
1269
|
type: Types.SET_DATE_TIME,
|
|
1161
1270
|
text: `Set date time value: ${value}`,
|
|
1271
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1162
1272
|
operation: "setDateTime",
|
|
1163
1273
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1164
1274
|
throwError: false,
|
|
1165
1275
|
};
|
|
1166
1276
|
try {
|
|
1167
|
-
await _preCommand(state, this
|
|
1277
|
+
await _preCommand(state, this);
|
|
1168
1278
|
try {
|
|
1169
|
-
await state.element
|
|
1279
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1170
1280
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1171
1281
|
if (format) {
|
|
1172
1282
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1215,7 +1325,7 @@ class StableBrowser {
|
|
|
1215
1325
|
await _commandError(state, e, this);
|
|
1216
1326
|
}
|
|
1217
1327
|
finally {
|
|
1218
|
-
_commandFinally(state, this);
|
|
1328
|
+
await _commandFinally(state, this);
|
|
1219
1329
|
}
|
|
1220
1330
|
}
|
|
1221
1331
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1230,17 +1340,21 @@ class StableBrowser {
|
|
|
1230
1340
|
world,
|
|
1231
1341
|
type: Types.FILL,
|
|
1232
1342
|
text: `Click type input with value: ${_value}`,
|
|
1343
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1233
1344
|
operation: "clickType",
|
|
1234
1345
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1235
1346
|
};
|
|
1347
|
+
if (!options) {
|
|
1348
|
+
options = {};
|
|
1349
|
+
}
|
|
1236
1350
|
if (newValue !== _value) {
|
|
1237
1351
|
//this.logger.info(_value + "=" + newValue);
|
|
1238
1352
|
_value = newValue;
|
|
1239
1353
|
}
|
|
1240
1354
|
try {
|
|
1241
|
-
await _preCommand(state, this
|
|
1355
|
+
await _preCommand(state, this);
|
|
1242
1356
|
state.info.value = _value;
|
|
1243
|
-
if (
|
|
1357
|
+
if (!options.press) {
|
|
1244
1358
|
try {
|
|
1245
1359
|
let currentValue = await state.element.inputValue();
|
|
1246
1360
|
if (currentValue) {
|
|
@@ -1251,13 +1365,9 @@ class StableBrowser {
|
|
|
1251
1365
|
this.logger.info("unable to clear input value");
|
|
1252
1366
|
}
|
|
1253
1367
|
}
|
|
1254
|
-
if (options
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
catch (e) {
|
|
1259
|
-
await state.element.dispatchEvent("click");
|
|
1260
|
-
}
|
|
1368
|
+
if (options.press) {
|
|
1369
|
+
options.timeout = 5000;
|
|
1370
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1261
1371
|
}
|
|
1262
1372
|
else {
|
|
1263
1373
|
try {
|
|
@@ -1315,7 +1425,7 @@ class StableBrowser {
|
|
|
1315
1425
|
await _commandError(state, e, this);
|
|
1316
1426
|
}
|
|
1317
1427
|
finally {
|
|
1318
|
-
_commandFinally(state, this);
|
|
1428
|
+
await _commandFinally(state, this);
|
|
1319
1429
|
}
|
|
1320
1430
|
}
|
|
1321
1431
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1331,7 +1441,7 @@ class StableBrowser {
|
|
|
1331
1441
|
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1332
1442
|
};
|
|
1333
1443
|
try {
|
|
1334
|
-
await _preCommand(state, this
|
|
1444
|
+
await _preCommand(state, this);
|
|
1335
1445
|
await state.element.fill(value);
|
|
1336
1446
|
await state.element.dispatchEvent("change");
|
|
1337
1447
|
if (enter) {
|
|
@@ -1345,13 +1455,14 @@ class StableBrowser {
|
|
|
1345
1455
|
await _commandError(state, e, this);
|
|
1346
1456
|
}
|
|
1347
1457
|
finally {
|
|
1348
|
-
_commandFinally(state, this);
|
|
1458
|
+
await _commandFinally(state, this);
|
|
1349
1459
|
}
|
|
1350
1460
|
}
|
|
1351
1461
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1352
1462
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1353
1463
|
}
|
|
1354
1464
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1465
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1355
1466
|
_validateSelectors(selectors);
|
|
1356
1467
|
let screenshotId = null;
|
|
1357
1468
|
let screenshotPath = null;
|
|
@@ -1361,7 +1472,7 @@ class StableBrowser {
|
|
|
1361
1472
|
}
|
|
1362
1473
|
info.operation = "getText";
|
|
1363
1474
|
info.selectors = selectors;
|
|
1364
|
-
let element = await this._locate(selectors, info, _params);
|
|
1475
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1365
1476
|
if (climb > 0) {
|
|
1366
1477
|
const climbArray = [];
|
|
1367
1478
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1377,19 +1488,21 @@ class StableBrowser {
|
|
|
1377
1488
|
catch (e) {
|
|
1378
1489
|
//ignore
|
|
1379
1490
|
}
|
|
1380
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
1491
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1381
1492
|
try {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1493
|
+
await this._highlightElements(element);
|
|
1494
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1495
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1496
|
+
// this._highlightElements(element)
|
|
1497
|
+
// .then(async () => {
|
|
1498
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1499
|
+
// this._unhighlightElements(element).then(
|
|
1500
|
+
// () => {}
|
|
1501
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1502
|
+
// );
|
|
1503
|
+
// })
|
|
1504
|
+
// .catch(e);
|
|
1505
|
+
// }
|
|
1393
1506
|
const elementText = await element.innerText();
|
|
1394
1507
|
return {
|
|
1395
1508
|
text: elementText,
|
|
@@ -1426,6 +1539,7 @@ class StableBrowser {
|
|
|
1426
1539
|
highlight: false,
|
|
1427
1540
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1428
1541
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1542
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1429
1543
|
operation: "containsPattern",
|
|
1430
1544
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1431
1545
|
};
|
|
@@ -1436,13 +1550,13 @@ class StableBrowser {
|
|
|
1436
1550
|
}
|
|
1437
1551
|
let foundObj = null;
|
|
1438
1552
|
try {
|
|
1439
|
-
await _preCommand(state, this
|
|
1553
|
+
await _preCommand(state, this);
|
|
1440
1554
|
state.info.pattern = pattern;
|
|
1441
1555
|
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1442
1556
|
if (foundObj && foundObj.element) {
|
|
1443
1557
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1444
1558
|
}
|
|
1445
|
-
await _screenshot(state, this
|
|
1559
|
+
await _screenshot(state, this);
|
|
1446
1560
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1447
1561
|
pattern = pattern.replace("{text}", escapedText);
|
|
1448
1562
|
let regex = new RegExp(pattern, "im");
|
|
@@ -1457,10 +1571,12 @@ class StableBrowser {
|
|
|
1457
1571
|
await _commandError(state, e, this);
|
|
1458
1572
|
}
|
|
1459
1573
|
finally {
|
|
1460
|
-
_commandFinally(state, this);
|
|
1574
|
+
await _commandFinally(state, this);
|
|
1461
1575
|
}
|
|
1462
1576
|
}
|
|
1463
1577
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1579
|
+
const startTime = Date.now();
|
|
1464
1580
|
const state = {
|
|
1465
1581
|
selectors,
|
|
1466
1582
|
_params,
|
|
@@ -1487,61 +1603,53 @@ class StableBrowser {
|
|
|
1487
1603
|
}
|
|
1488
1604
|
let foundObj = null;
|
|
1489
1605
|
try {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1497
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1498
|
-
if (dateAlternatives.date) {
|
|
1499
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1500
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1501
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1502
|
-
return state.info;
|
|
1606
|
+
while (Date.now() - startTime < timeout) {
|
|
1607
|
+
try {
|
|
1608
|
+
await _preCommand(state, this);
|
|
1609
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1610
|
+
if (foundObj && foundObj.element) {
|
|
1611
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1503
1612
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1613
|
+
await _screenshot(state, this);
|
|
1614
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1615
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1616
|
+
if (dateAlternatives.date) {
|
|
1617
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1618
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1619
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1620
|
+
return state.info;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
else if (numberAlternatives.number) {
|
|
1625
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1626
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1627
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1628
|
+
return state.info;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1511
1633
|
return state.info;
|
|
1512
1634
|
}
|
|
1513
1635
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
throw new Error("element doesn't contain text " + text);
|
|
1636
|
+
catch (e) {
|
|
1637
|
+
// Log error but continue retrying until timeout is reached
|
|
1638
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1639
|
+
}
|
|
1640
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1520
1641
|
}
|
|
1521
|
-
|
|
1642
|
+
state.info.foundText = foundObj?.text;
|
|
1643
|
+
state.info.value = foundObj?.value;
|
|
1644
|
+
throw new Error("element doesn't contain text " + text);
|
|
1522
1645
|
}
|
|
1523
1646
|
catch (e) {
|
|
1524
1647
|
await _commandError(state, e, this);
|
|
1648
|
+
throw e;
|
|
1525
1649
|
}
|
|
1526
1650
|
finally {
|
|
1527
|
-
_commandFinally(state, this);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
_getDataFile(world = null) {
|
|
1531
|
-
let dataFile = null;
|
|
1532
|
-
if (world && world.reportFolder) {
|
|
1533
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1534
|
-
}
|
|
1535
|
-
else if (this.reportFolder) {
|
|
1536
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1537
|
-
}
|
|
1538
|
-
else if (this.context && this.context.reportFolder) {
|
|
1539
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1651
|
+
await _commandFinally(state, this);
|
|
1540
1652
|
}
|
|
1541
|
-
else {
|
|
1542
|
-
dataFile = "data.json";
|
|
1543
|
-
}
|
|
1544
|
-
return dataFile;
|
|
1545
1653
|
}
|
|
1546
1654
|
async waitForUserInput(message, world = null) {
|
|
1547
1655
|
if (!message) {
|
|
@@ -1571,13 +1679,22 @@ class StableBrowser {
|
|
|
1571
1679
|
return;
|
|
1572
1680
|
}
|
|
1573
1681
|
// if data file exists, load it
|
|
1574
|
-
const dataFile =
|
|
1682
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1575
1683
|
let data = this.getTestData(world);
|
|
1576
1684
|
// merge the testData with the existing data
|
|
1577
1685
|
Object.assign(data, testData);
|
|
1578
1686
|
// save the data to the file
|
|
1579
1687
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1580
1688
|
}
|
|
1689
|
+
overwriteTestData(testData, world = null) {
|
|
1690
|
+
if (!testData) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
// if data file exists, load it
|
|
1694
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1695
|
+
// save the data to the file
|
|
1696
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1697
|
+
}
|
|
1581
1698
|
_getDataFilePath(fileName) {
|
|
1582
1699
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1583
1700
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1674,14 +1791,14 @@ class StableBrowser {
|
|
|
1674
1791
|
}
|
|
1675
1792
|
}
|
|
1676
1793
|
getTestData(world = null) {
|
|
1677
|
-
const dataFile =
|
|
1794
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1678
1795
|
let data = {};
|
|
1679
1796
|
if (fs.existsSync(dataFile)) {
|
|
1680
1797
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
1681
1798
|
}
|
|
1682
1799
|
return data;
|
|
1683
1800
|
}
|
|
1684
|
-
async _screenShot(options = {}, world = null, info = null
|
|
1801
|
+
async _screenShot(options = {}, world = null, info = null) {
|
|
1685
1802
|
// collect url/path/title
|
|
1686
1803
|
if (info) {
|
|
1687
1804
|
if (!info.title) {
|
|
@@ -1710,7 +1827,7 @@ class StableBrowser {
|
|
|
1710
1827
|
const uuidStr = "id_" + randomUUID();
|
|
1711
1828
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1712
1829
|
try {
|
|
1713
|
-
await this.takeScreenshot(screenshotPath
|
|
1830
|
+
await this.takeScreenshot(screenshotPath);
|
|
1714
1831
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1715
1832
|
// // save the buffer to the screenshot path asynchrously
|
|
1716
1833
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -1721,7 +1838,7 @@ class StableBrowser {
|
|
|
1721
1838
|
result.screenshotId = uuidStr;
|
|
1722
1839
|
result.screenshotPath = screenshotPath;
|
|
1723
1840
|
if (info && info.box) {
|
|
1724
|
-
|
|
1841
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1725
1842
|
}
|
|
1726
1843
|
}
|
|
1727
1844
|
catch (e) {
|
|
@@ -1731,7 +1848,7 @@ class StableBrowser {
|
|
|
1731
1848
|
else if (options && options.screenshot) {
|
|
1732
1849
|
result.screenshotPath = options.screenshotPath;
|
|
1733
1850
|
try {
|
|
1734
|
-
await this.takeScreenshot(options.screenshotPath
|
|
1851
|
+
await this.takeScreenshot(options.screenshotPath);
|
|
1735
1852
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1736
1853
|
// // save the buffer to the screenshot path asynchrously
|
|
1737
1854
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -1744,12 +1861,12 @@ class StableBrowser {
|
|
|
1744
1861
|
this.logger.info("unable to take screenshot, ignored");
|
|
1745
1862
|
}
|
|
1746
1863
|
if (info && info.box) {
|
|
1747
|
-
|
|
1864
|
+
await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1748
1865
|
}
|
|
1749
1866
|
}
|
|
1750
1867
|
return result;
|
|
1751
1868
|
}
|
|
1752
|
-
async takeScreenshot(screenshotPath
|
|
1869
|
+
async takeScreenshot(screenshotPath) {
|
|
1753
1870
|
const playContext = this.context.playContext;
|
|
1754
1871
|
// Using CDP to capture the screenshot
|
|
1755
1872
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -1767,9 +1884,9 @@ class StableBrowser {
|
|
|
1767
1884
|
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1768
1885
|
// console.log(`Unhighlighted previous element`);
|
|
1769
1886
|
// }
|
|
1770
|
-
if (focusedElement) {
|
|
1771
|
-
|
|
1772
|
-
}
|
|
1887
|
+
// if (focusedElement) {
|
|
1888
|
+
// await this._highlightElements(focusedElement);
|
|
1889
|
+
// }
|
|
1773
1890
|
if (this.context.browserName === "chromium") {
|
|
1774
1891
|
const client = await playContext.newCDPSession(this.page);
|
|
1775
1892
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1791,10 +1908,10 @@ class StableBrowser {
|
|
|
1791
1908
|
else {
|
|
1792
1909
|
screenshotBuffer = await this.page.screenshot();
|
|
1793
1910
|
}
|
|
1794
|
-
if (focusedElement) {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1911
|
+
// if (focusedElement) {
|
|
1912
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1913
|
+
// await this._unhighlightElements(focusedElement);
|
|
1914
|
+
// }
|
|
1798
1915
|
let image = await Jimp.read(screenshotBuffer);
|
|
1799
1916
|
// Get the image dimensions
|
|
1800
1917
|
const { width, height } = image.bitmap;
|
|
@@ -1807,6 +1924,7 @@ class StableBrowser {
|
|
|
1807
1924
|
else {
|
|
1808
1925
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1809
1926
|
}
|
|
1927
|
+
return screenshotBuffer;
|
|
1810
1928
|
}
|
|
1811
1929
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1812
1930
|
const state = {
|
|
@@ -1821,7 +1939,7 @@ class StableBrowser {
|
|
|
1821
1939
|
};
|
|
1822
1940
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1823
1941
|
try {
|
|
1824
|
-
await _preCommand(state, this
|
|
1942
|
+
await _preCommand(state, this);
|
|
1825
1943
|
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1826
1944
|
return state.info;
|
|
1827
1945
|
}
|
|
@@ -1829,7 +1947,7 @@ class StableBrowser {
|
|
|
1829
1947
|
await _commandError(state, e, this);
|
|
1830
1948
|
}
|
|
1831
1949
|
finally {
|
|
1832
|
-
_commandFinally(state, this);
|
|
1950
|
+
await _commandFinally(state, this);
|
|
1833
1951
|
}
|
|
1834
1952
|
}
|
|
1835
1953
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1842,12 +1960,14 @@ class StableBrowser {
|
|
|
1842
1960
|
world,
|
|
1843
1961
|
type: Types.EXTRACT,
|
|
1844
1962
|
text: `Extract attribute from element`,
|
|
1963
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1845
1964
|
operation: "extractAttribute",
|
|
1846
1965
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1847
1967
|
};
|
|
1848
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1849
1969
|
try {
|
|
1850
|
-
await _preCommand(state, this
|
|
1970
|
+
await _preCommand(state, this);
|
|
1851
1971
|
switch (attribute) {
|
|
1852
1972
|
case "inner_text":
|
|
1853
1973
|
state.value = await state.element.innerText();
|
|
@@ -1858,6 +1978,9 @@ class StableBrowser {
|
|
|
1858
1978
|
case "value":
|
|
1859
1979
|
state.value = await state.element.inputValue();
|
|
1860
1980
|
break;
|
|
1981
|
+
case "text":
|
|
1982
|
+
state.value = await state.element.textContent();
|
|
1983
|
+
break;
|
|
1861
1984
|
default:
|
|
1862
1985
|
state.value = await state.element.getAttribute(attribute);
|
|
1863
1986
|
break;
|
|
@@ -1865,14 +1988,14 @@ class StableBrowser {
|
|
|
1865
1988
|
state.info.value = state.value;
|
|
1866
1989
|
this.setTestData({ [variable]: state.value }, world);
|
|
1867
1990
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1868
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1991
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1869
1992
|
return state.info;
|
|
1870
1993
|
}
|
|
1871
1994
|
catch (e) {
|
|
1872
1995
|
await _commandError(state, e, this);
|
|
1873
1996
|
}
|
|
1874
1997
|
finally {
|
|
1875
|
-
_commandFinally(state, this);
|
|
1998
|
+
await _commandFinally(state, this);
|
|
1876
1999
|
}
|
|
1877
2000
|
}
|
|
1878
2001
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1887,18 +2010,25 @@ class StableBrowser {
|
|
|
1887
2010
|
highlight: true,
|
|
1888
2011
|
screenshot: true,
|
|
1889
2012
|
text: `Verify element attribute`,
|
|
2013
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1890
2014
|
operation: "verifyAttribute",
|
|
1891
2015
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1892
2016
|
allowDisabled: true,
|
|
1893
2017
|
};
|
|
1894
2018
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1895
2019
|
let val;
|
|
2020
|
+
let expectedValue;
|
|
1896
2021
|
try {
|
|
1897
|
-
await _preCommand(state, this
|
|
2022
|
+
await _preCommand(state, this);
|
|
2023
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2024
|
+
state.info.expectedValue = expectedValue;
|
|
1898
2025
|
switch (attribute) {
|
|
1899
2026
|
case "innerText":
|
|
1900
2027
|
val = String(await state.element.innerText());
|
|
1901
2028
|
break;
|
|
2029
|
+
case "text":
|
|
2030
|
+
val = String(await state.element.textContent());
|
|
2031
|
+
break;
|
|
1902
2032
|
case "value":
|
|
1903
2033
|
val = String(await state.element.inputValue());
|
|
1904
2034
|
break;
|
|
@@ -1916,26 +2046,29 @@ class StableBrowser {
|
|
|
1916
2046
|
val = String(await state.element.getAttribute(attribute));
|
|
1917
2047
|
break;
|
|
1918
2048
|
}
|
|
2049
|
+
state.info.value = val;
|
|
1919
2050
|
let regex;
|
|
1920
|
-
if (
|
|
1921
|
-
const patternBody =
|
|
2051
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2052
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1922
2053
|
regex = new RegExp(patternBody, "g");
|
|
1923
2054
|
}
|
|
1924
2055
|
else {
|
|
1925
|
-
const escapedPattern =
|
|
2056
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1926
2057
|
regex = new RegExp(escapedPattern, "g");
|
|
1927
2058
|
}
|
|
1928
2059
|
if (!val.match(regex)) {
|
|
1929
|
-
|
|
2060
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2061
|
+
state.info.failCause.assertionFailed = true;
|
|
2062
|
+
state.info.failCause.lastError = errorMessage;
|
|
2063
|
+
throw new Error(errorMessage);
|
|
1930
2064
|
}
|
|
1931
|
-
state.info.expectedValue = val;
|
|
1932
2065
|
return state.info;
|
|
1933
2066
|
}
|
|
1934
2067
|
catch (e) {
|
|
1935
2068
|
await _commandError(state, e, this);
|
|
1936
2069
|
}
|
|
1937
2070
|
finally {
|
|
1938
|
-
_commandFinally(state, this);
|
|
2071
|
+
await _commandFinally(state, this);
|
|
1939
2072
|
}
|
|
1940
2073
|
}
|
|
1941
2074
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2036,17 +2169,17 @@ class StableBrowser {
|
|
|
2036
2169
|
if (node && node.style) {
|
|
2037
2170
|
let originalOutline = node.style.outline;
|
|
2038
2171
|
// console.log(`Original outline was: ${originalOutline}`);
|
|
2039
|
-
node.__previousOutline = originalOutline;
|
|
2172
|
+
// node.__previousOutline = originalOutline;
|
|
2040
2173
|
node.style.outline = "2px solid red";
|
|
2041
2174
|
// console.log(`New outline is: ${node.style.outline}`);
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2175
|
+
if (window) {
|
|
2176
|
+
window.addEventListener("beforeunload", function (e) {
|
|
2177
|
+
node.style.outline = originalOutline;
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
setTimeout(function () {
|
|
2181
|
+
node.style.outline = originalOutline;
|
|
2182
|
+
}, 2000);
|
|
2050
2183
|
}
|
|
2051
2184
|
})
|
|
2052
2185
|
.then(() => { })
|
|
@@ -2072,15 +2205,15 @@ class StableBrowser {
|
|
|
2072
2205
|
element.__previousOutline = originalOutline;
|
|
2073
2206
|
// Set the new border to be red and 2px solid
|
|
2074
2207
|
element.style.outline = "2px solid red";
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2208
|
+
if (window) {
|
|
2209
|
+
window.addEventListener("beforeunload", function (e) {
|
|
2210
|
+
element.style.outline = originalOutline;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2080
2213
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2214
|
+
setTimeout(function () {
|
|
2215
|
+
element.style.outline = originalOutline;
|
|
2216
|
+
}, 2000);
|
|
2084
2217
|
}
|
|
2085
2218
|
return;
|
|
2086
2219
|
}, [css])
|
|
@@ -2095,58 +2228,54 @@ class StableBrowser {
|
|
|
2095
2228
|
console.debug(error);
|
|
2096
2229
|
}
|
|
2097
2230
|
}
|
|
2098
|
-
async _unhighlightElements(scope, css) {
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
catch (error) {
|
|
2147
|
-
// console.debug(error);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2231
|
+
// async _unhighlightElements(scope, css) {
|
|
2232
|
+
// try {
|
|
2233
|
+
// if (!scope) {
|
|
2234
|
+
// return;
|
|
2235
|
+
// }
|
|
2236
|
+
// if (!css) {
|
|
2237
|
+
// scope
|
|
2238
|
+
// .evaluate((node) => {
|
|
2239
|
+
// if (node && node.style) {
|
|
2240
|
+
// if (!node.__previousOutline) {
|
|
2241
|
+
// node.style.outline = "";
|
|
2242
|
+
// } else {
|
|
2243
|
+
// node.style.outline = node.__previousOutline;
|
|
2244
|
+
// }
|
|
2245
|
+
// }
|
|
2246
|
+
// })
|
|
2247
|
+
// .then(() => {})
|
|
2248
|
+
// .catch((e) => {
|
|
2249
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2250
|
+
// });
|
|
2251
|
+
// } else {
|
|
2252
|
+
// scope
|
|
2253
|
+
// .evaluate(([css]) => {
|
|
2254
|
+
// if (!css) {
|
|
2255
|
+
// return;
|
|
2256
|
+
// }
|
|
2257
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2258
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2259
|
+
// let element = elements[i];
|
|
2260
|
+
// if (!element.style) {
|
|
2261
|
+
// return;
|
|
2262
|
+
// }
|
|
2263
|
+
// if (!element.__previousOutline) {
|
|
2264
|
+
// element.style.outline = "";
|
|
2265
|
+
// } else {
|
|
2266
|
+
// element.style.outline = element.__previousOutline;
|
|
2267
|
+
// }
|
|
2268
|
+
// }
|
|
2269
|
+
// })
|
|
2270
|
+
// .then(() => {})
|
|
2271
|
+
// .catch((e) => {
|
|
2272
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2273
|
+
// });
|
|
2274
|
+
// }
|
|
2275
|
+
// } catch (error) {
|
|
2276
|
+
// // console.debug(error);
|
|
2277
|
+
// }
|
|
2278
|
+
// }
|
|
2150
2279
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2151
2280
|
const startTime = Date.now();
|
|
2152
2281
|
let error = null;
|
|
@@ -2191,6 +2320,7 @@ class StableBrowser {
|
|
|
2191
2320
|
_reportToWorld(world, {
|
|
2192
2321
|
type: Types.VERIFY_PAGE_PATH,
|
|
2193
2322
|
text: "Verify page path",
|
|
2323
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2194
2324
|
screenshotId,
|
|
2195
2325
|
result: error
|
|
2196
2326
|
? {
|
|
@@ -2208,27 +2338,89 @@ class StableBrowser {
|
|
|
2208
2338
|
});
|
|
2209
2339
|
}
|
|
2210
2340
|
}
|
|
2211
|
-
async
|
|
2341
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2342
|
+
const startTime = Date.now();
|
|
2343
|
+
let error = null;
|
|
2344
|
+
let screenshotId = null;
|
|
2345
|
+
let screenshotPath = null;
|
|
2346
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2347
|
+
const info = {};
|
|
2348
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2349
|
+
info.operation = "verifyPageTitle";
|
|
2350
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2351
|
+
if (newValue !== title) {
|
|
2352
|
+
this.logger.info(title + "=" + newValue);
|
|
2353
|
+
title = newValue;
|
|
2354
|
+
}
|
|
2355
|
+
info.title = title;
|
|
2356
|
+
try {
|
|
2357
|
+
for (let i = 0; i < 30; i++) {
|
|
2358
|
+
const foundTitle = await this.page.title();
|
|
2359
|
+
if (!foundTitle.includes(title)) {
|
|
2360
|
+
if (i === 29) {
|
|
2361
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2362
|
+
}
|
|
2363
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2367
|
+
return info;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
catch (e) {
|
|
2371
|
+
//await this.closeUnexpectedPopups();
|
|
2372
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2373
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2374
|
+
info.screenshotPath = screenshotPath;
|
|
2375
|
+
Object.assign(e, { info: info });
|
|
2376
|
+
error = e;
|
|
2377
|
+
// throw e;
|
|
2378
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2379
|
+
}
|
|
2380
|
+
finally {
|
|
2381
|
+
const endTime = Date.now();
|
|
2382
|
+
_reportToWorld(world, {
|
|
2383
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2384
|
+
text: "Verify page title",
|
|
2385
|
+
_text: "Verify the page title contains " + title,
|
|
2386
|
+
screenshotId,
|
|
2387
|
+
result: error
|
|
2388
|
+
? {
|
|
2389
|
+
status: "FAILED",
|
|
2390
|
+
startTime,
|
|
2391
|
+
endTime,
|
|
2392
|
+
message: error?.message,
|
|
2393
|
+
}
|
|
2394
|
+
: {
|
|
2395
|
+
status: "PASSED",
|
|
2396
|
+
startTime,
|
|
2397
|
+
endTime,
|
|
2398
|
+
},
|
|
2399
|
+
info: info,
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2212
2404
|
const frames = this.page.frames();
|
|
2213
2405
|
let results = [];
|
|
2214
|
-
let ignoreCase = false;
|
|
2406
|
+
// let ignoreCase = false;
|
|
2215
2407
|
for (let i = 0; i < frames.length; i++) {
|
|
2216
2408
|
if (dateAlternatives.date) {
|
|
2217
2409
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2218
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2410
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2219
2411
|
result.frame = frames[i];
|
|
2220
2412
|
results.push(result);
|
|
2221
2413
|
}
|
|
2222
2414
|
}
|
|
2223
2415
|
else if (numberAlternatives.number) {
|
|
2224
2416
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2225
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2417
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2226
2418
|
result.frame = frames[i];
|
|
2227
2419
|
results.push(result);
|
|
2228
2420
|
}
|
|
2229
2421
|
}
|
|
2230
2422
|
else {
|
|
2231
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2423
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2232
2424
|
result.frame = frames[i];
|
|
2233
2425
|
results.push(result);
|
|
2234
2426
|
}
|
|
@@ -2247,11 +2439,15 @@ class StableBrowser {
|
|
|
2247
2439
|
scroll: false,
|
|
2248
2440
|
highlight: false,
|
|
2249
2441
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2250
|
-
text: `Verify text exists in page`,
|
|
2442
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2443
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2251
2444
|
operation: "verifyTextExistInPage",
|
|
2252
2445
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2253
2446
|
};
|
|
2254
|
-
|
|
2447
|
+
if (testForRegex(text)) {
|
|
2448
|
+
text = text.replace(/\\"/g, '"');
|
|
2449
|
+
}
|
|
2450
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2255
2451
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2256
2452
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2257
2453
|
if (newValue !== text) {
|
|
@@ -2261,10 +2457,18 @@ class StableBrowser {
|
|
|
2261
2457
|
let dateAlternatives = findDateAlternatives(text);
|
|
2262
2458
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2263
2459
|
try {
|
|
2264
|
-
await _preCommand(state, this
|
|
2460
|
+
await _preCommand(state, this);
|
|
2265
2461
|
state.info.text = text;
|
|
2266
2462
|
while (true) {
|
|
2267
|
-
|
|
2463
|
+
let resultWithElementsFound = {
|
|
2464
|
+
length: 0,
|
|
2465
|
+
};
|
|
2466
|
+
try {
|
|
2467
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2468
|
+
}
|
|
2469
|
+
catch (error) {
|
|
2470
|
+
// ignore
|
|
2471
|
+
}
|
|
2268
2472
|
if (resultWithElementsFound.length === 0) {
|
|
2269
2473
|
if (Date.now() - state.startTime > timeout) {
|
|
2270
2474
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2272,35 +2476,40 @@ class StableBrowser {
|
|
|
2272
2476
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2273
2477
|
continue;
|
|
2274
2478
|
}
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2479
|
+
try {
|
|
2480
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2481
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2482
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2483
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2484
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2279
2485
|
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2280
|
-
this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2486
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2487
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2488
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2489
|
+
// .then(async () => {
|
|
2490
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2491
|
+
// })
|
|
2492
|
+
// .catch(
|
|
2493
|
+
// (e) => {}
|
|
2494
|
+
// console.error(e)
|
|
2495
|
+
// );
|
|
2496
|
+
// });
|
|
2497
|
+
// }
|
|
2498
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2499
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2500
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2501
|
+
if (element) {
|
|
2502
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2503
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2504
|
+
// await _screenshot(state, this, element);
|
|
2505
|
+
}
|
|
2298
2506
|
}
|
|
2299
|
-
}
|
|
2300
|
-
else {
|
|
2301
2507
|
await _screenshot(state, this);
|
|
2508
|
+
return state.info;
|
|
2509
|
+
}
|
|
2510
|
+
catch (error) {
|
|
2511
|
+
console.error(error);
|
|
2302
2512
|
}
|
|
2303
|
-
return state.info;
|
|
2304
2513
|
}
|
|
2305
2514
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2306
2515
|
}
|
|
@@ -2308,7 +2517,7 @@ class StableBrowser {
|
|
|
2308
2517
|
await _commandError(state, e, this);
|
|
2309
2518
|
}
|
|
2310
2519
|
finally {
|
|
2311
|
-
_commandFinally(state, this);
|
|
2520
|
+
await _commandFinally(state, this);
|
|
2312
2521
|
}
|
|
2313
2522
|
}
|
|
2314
2523
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2321,11 +2530,15 @@ class StableBrowser {
|
|
|
2321
2530
|
scroll: false,
|
|
2322
2531
|
highlight: false,
|
|
2323
2532
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2324
|
-
text: `Verify text does not exist in page`,
|
|
2533
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2534
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2325
2535
|
operation: "verifyTextNotExistInPage",
|
|
2326
2536
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2327
2537
|
};
|
|
2328
|
-
|
|
2538
|
+
if (testForRegex(text)) {
|
|
2539
|
+
text = text.replace(/\\"/g, '"');
|
|
2540
|
+
}
|
|
2541
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2329
2542
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2330
2543
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2331
2544
|
if (newValue !== text) {
|
|
@@ -2335,10 +2548,18 @@ class StableBrowser {
|
|
|
2335
2548
|
let dateAlternatives = findDateAlternatives(text);
|
|
2336
2549
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2337
2550
|
try {
|
|
2338
|
-
await _preCommand(state, this
|
|
2551
|
+
await _preCommand(state, this);
|
|
2339
2552
|
state.info.text = text;
|
|
2553
|
+
let resultWithElementsFound = {
|
|
2554
|
+
length: null, // initial cannot be 0
|
|
2555
|
+
};
|
|
2340
2556
|
while (true) {
|
|
2341
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2559
|
+
}
|
|
2560
|
+
catch (error) {
|
|
2561
|
+
// ignore
|
|
2562
|
+
}
|
|
2342
2563
|
if (resultWithElementsFound.length === 0) {
|
|
2343
2564
|
await _screenshot(state, this);
|
|
2344
2565
|
return state.info;
|
|
@@ -2353,7 +2574,7 @@ class StableBrowser {
|
|
|
2353
2574
|
await _commandError(state, e, this);
|
|
2354
2575
|
}
|
|
2355
2576
|
finally {
|
|
2356
|
-
_commandFinally(state, this);
|
|
2577
|
+
await _commandFinally(state, this);
|
|
2357
2578
|
}
|
|
2358
2579
|
}
|
|
2359
2580
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2368,10 +2589,11 @@ class StableBrowser {
|
|
|
2368
2589
|
highlight: false,
|
|
2369
2590
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2370
2591
|
text: `Verify text with relation to another text`,
|
|
2592
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2371
2593
|
operation: "verify_text_with_relation",
|
|
2372
2594
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2373
2595
|
};
|
|
2374
|
-
const timeout = this.
|
|
2596
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2375
2597
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2376
2598
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2377
2599
|
if (newValue !== textAnchor) {
|
|
@@ -2387,10 +2609,18 @@ class StableBrowser {
|
|
|
2387
2609
|
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2388
2610
|
let foundAncore = false;
|
|
2389
2611
|
try {
|
|
2390
|
-
await _preCommand(state, this
|
|
2612
|
+
await _preCommand(state, this);
|
|
2391
2613
|
state.info.text = textToVerify;
|
|
2614
|
+
let resultWithElementsFound = {
|
|
2615
|
+
length: 0,
|
|
2616
|
+
};
|
|
2392
2617
|
while (true) {
|
|
2393
|
-
|
|
2618
|
+
try {
|
|
2619
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2620
|
+
}
|
|
2621
|
+
catch (error) {
|
|
2622
|
+
// ignore
|
|
2623
|
+
}
|
|
2394
2624
|
if (resultWithElementsFound.length === 0) {
|
|
2395
2625
|
if (Date.now() - state.startTime > timeout) {
|
|
2396
2626
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2398,52 +2628,56 @@ class StableBrowser {
|
|
|
2398
2628
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2399
2629
|
continue;
|
|
2400
2630
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2631
|
+
try {
|
|
2632
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2633
|
+
foundAncore = true;
|
|
2634
|
+
const result = resultWithElementsFound[i];
|
|
2635
|
+
const token = result.randomToken;
|
|
2636
|
+
const frame = result.frame;
|
|
2637
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2638
|
+
const climbArray1 = [];
|
|
2639
|
+
for (let i = 0; i < climb; i++) {
|
|
2640
|
+
climbArray1.push("..");
|
|
2641
|
+
}
|
|
2642
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2643
|
+
css = css + " >> " + climbXpath;
|
|
2644
|
+
const count = await frame.locator(css).count();
|
|
2645
|
+
for (let j = 0; j < count; j++) {
|
|
2646
|
+
const continer = await frame.locator(css).nth(j);
|
|
2647
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2648
|
+
if (result.elementCount > 0) {
|
|
2649
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2650
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2651
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2652
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2421
2653
|
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2422
|
-
this._highlightElements(frame, dataAttribute)
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
else {
|
|
2654
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2655
|
+
// .then(async () => {
|
|
2656
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2657
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2658
|
+
// () => {}
|
|
2659
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2660
|
+
// );
|
|
2661
|
+
// })
|
|
2662
|
+
// .catch(e);
|
|
2663
|
+
// }
|
|
2664
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2665
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2666
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2667
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2668
|
+
if (element) {
|
|
2669
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2670
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2671
|
+
}
|
|
2441
2672
|
await _screenshot(state, this);
|
|
2673
|
+
return state.info;
|
|
2442
2674
|
}
|
|
2443
|
-
return state.info;
|
|
2444
2675
|
}
|
|
2445
2676
|
}
|
|
2446
2677
|
}
|
|
2678
|
+
catch (error) {
|
|
2679
|
+
console.error(error);
|
|
2680
|
+
}
|
|
2447
2681
|
}
|
|
2448
2682
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2449
2683
|
}
|
|
@@ -2451,8 +2685,32 @@ class StableBrowser {
|
|
|
2451
2685
|
await _commandError(state, e, this);
|
|
2452
2686
|
}
|
|
2453
2687
|
finally {
|
|
2454
|
-
_commandFinally(state, this);
|
|
2688
|
+
await _commandFinally(state, this);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2692
|
+
const frames = this.page.frames();
|
|
2693
|
+
let results = [];
|
|
2694
|
+
let ignoreCase = false;
|
|
2695
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2696
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2697
|
+
result.frame = frames[i];
|
|
2698
|
+
const climbArray = [];
|
|
2699
|
+
for (let i = 0; i < climb; i++) {
|
|
2700
|
+
climbArray.push("..");
|
|
2701
|
+
}
|
|
2702
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2703
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2704
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2705
|
+
if (count > 0) {
|
|
2706
|
+
result.elementCount = count;
|
|
2707
|
+
result.locator = newLocator;
|
|
2708
|
+
results.push(result);
|
|
2709
|
+
}
|
|
2455
2710
|
}
|
|
2711
|
+
// state.info.results = results;
|
|
2712
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2713
|
+
return resultWithElementsFound;
|
|
2456
2714
|
}
|
|
2457
2715
|
async visualVerification(text, options = {}, world = null) {
|
|
2458
2716
|
const startTime = Date.now();
|
|
@@ -2472,10 +2730,13 @@ class StableBrowser {
|
|
|
2472
2730
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2473
2731
|
info.screenshotPath = screenshotPath;
|
|
2474
2732
|
const screenshot = await this.takeScreenshot();
|
|
2475
|
-
|
|
2476
|
-
method: "
|
|
2733
|
+
let request = {
|
|
2734
|
+
method: "post",
|
|
2735
|
+
maxBodyLength: Infinity,
|
|
2477
2736
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2478
2737
|
headers: {
|
|
2738
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2739
|
+
"x-source": "aaa",
|
|
2479
2740
|
"Content-Type": "application/json",
|
|
2480
2741
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2481
2742
|
},
|
|
@@ -2484,7 +2745,7 @@ class StableBrowser {
|
|
|
2484
2745
|
screenshot: screenshot,
|
|
2485
2746
|
}),
|
|
2486
2747
|
};
|
|
2487
|
-
|
|
2748
|
+
const result = await axios.request(request);
|
|
2488
2749
|
if (result.data.status !== true) {
|
|
2489
2750
|
throw new Error("Visual validation failed");
|
|
2490
2751
|
}
|
|
@@ -2512,6 +2773,7 @@ class StableBrowser {
|
|
|
2512
2773
|
_reportToWorld(world, {
|
|
2513
2774
|
type: Types.VERIFY_VISUAL,
|
|
2514
2775
|
text: "Visual verification",
|
|
2776
|
+
_text: "Visual verification of " + text,
|
|
2515
2777
|
screenshotId,
|
|
2516
2778
|
result: error
|
|
2517
2779
|
? {
|
|
@@ -2562,14 +2824,14 @@ class StableBrowser {
|
|
|
2562
2824
|
info.selectors = selectors;
|
|
2563
2825
|
try {
|
|
2564
2826
|
let table = await this._locate(selectors, info, _params);
|
|
2565
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
2827
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2566
2828
|
const tableData = await getTableData(this.page, table);
|
|
2567
2829
|
return tableData;
|
|
2568
2830
|
}
|
|
2569
2831
|
catch (e) {
|
|
2570
2832
|
this.logger.error("getTableData failed " + info.log);
|
|
2571
2833
|
this.logger.error(e);
|
|
2572
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
2834
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2573
2835
|
info.screenshotPath = screenshotPath;
|
|
2574
2836
|
Object.assign(e, { info: info });
|
|
2575
2837
|
error = e;
|
|
@@ -2778,6 +3040,32 @@ class StableBrowser {
|
|
|
2778
3040
|
}
|
|
2779
3041
|
return timeout;
|
|
2780
3042
|
}
|
|
3043
|
+
_getFindElementTimeout(options) {
|
|
3044
|
+
if (options && options.timeout) {
|
|
3045
|
+
return options.timeout;
|
|
3046
|
+
}
|
|
3047
|
+
if (this.configuration.find_element_timeout) {
|
|
3048
|
+
return this.configuration.find_element_timeout;
|
|
3049
|
+
}
|
|
3050
|
+
return 30000;
|
|
3051
|
+
}
|
|
3052
|
+
async saveStoreState(path = null, world = null) {
|
|
3053
|
+
const storageState = await this.page.context().storageState();
|
|
3054
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3055
|
+
if (path) {
|
|
3056
|
+
// save { storageState: storageState } into the path
|
|
3057
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3058
|
+
}
|
|
3059
|
+
else {
|
|
3060
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
async restoreSaveState(path = null, world = null) {
|
|
3064
|
+
await refreshBrowser(this, path, world);
|
|
3065
|
+
this.registerEventListeners(this.context);
|
|
3066
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3067
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3068
|
+
}
|
|
2781
3069
|
async waitForPageLoad(options = {}, world = null) {
|
|
2782
3070
|
let timeout = this._getLoadTimeout(options);
|
|
2783
3071
|
const promiseArray = [];
|
|
@@ -2845,12 +3133,13 @@ class StableBrowser {
|
|
|
2845
3133
|
highlight: false,
|
|
2846
3134
|
type: Types.CLOSE_PAGE,
|
|
2847
3135
|
text: `Close page`,
|
|
3136
|
+
_text: `Close the page`,
|
|
2848
3137
|
operation: "closePage",
|
|
2849
3138
|
log: "***** close page *****\n",
|
|
2850
3139
|
throwError: false,
|
|
2851
3140
|
};
|
|
2852
3141
|
try {
|
|
2853
|
-
await _preCommand(state, this
|
|
3142
|
+
await _preCommand(state, this);
|
|
2854
3143
|
await this.page.close();
|
|
2855
3144
|
}
|
|
2856
3145
|
catch (e) {
|
|
@@ -2858,11 +3147,98 @@ class StableBrowser {
|
|
|
2858
3147
|
await _commandError(state, e, this);
|
|
2859
3148
|
}
|
|
2860
3149
|
finally {
|
|
2861
|
-
_commandFinally(state, this);
|
|
3150
|
+
await _commandFinally(state, this);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3154
|
+
let operation = null;
|
|
3155
|
+
if (!options || !options.operation) {
|
|
3156
|
+
throw new Error("operation is not defined");
|
|
3157
|
+
}
|
|
3158
|
+
operation = options.operation;
|
|
3159
|
+
// validate operation is one of the supported operations
|
|
3160
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3161
|
+
throw new Error("operation is not supported");
|
|
3162
|
+
}
|
|
3163
|
+
const state = {
|
|
3164
|
+
options,
|
|
3165
|
+
world,
|
|
3166
|
+
locate: false,
|
|
3167
|
+
scroll: false,
|
|
3168
|
+
highlight: false,
|
|
3169
|
+
type: Types.TABLE_OPERATION,
|
|
3170
|
+
text: `Table operation`,
|
|
3171
|
+
_text: `Table ${operation} operation`,
|
|
3172
|
+
operation: operation,
|
|
3173
|
+
log: "***** Table operation *****\n",
|
|
3174
|
+
};
|
|
3175
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3176
|
+
try {
|
|
3177
|
+
await _preCommand(state, this);
|
|
3178
|
+
const start = Date.now();
|
|
3179
|
+
let cellArea = null;
|
|
3180
|
+
while (true) {
|
|
3181
|
+
try {
|
|
3182
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3183
|
+
if (cellArea) {
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
catch (e) {
|
|
3188
|
+
// ignore
|
|
3189
|
+
}
|
|
3190
|
+
if (Date.now() - start > timeout) {
|
|
3191
|
+
throw new Error(`Cell not found in table`);
|
|
3192
|
+
}
|
|
3193
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3194
|
+
}
|
|
3195
|
+
switch (operation) {
|
|
3196
|
+
case "click":
|
|
3197
|
+
if (!options.css) {
|
|
3198
|
+
// will click in the center of the cell
|
|
3199
|
+
let xOffset = 0;
|
|
3200
|
+
let yOffset = 0;
|
|
3201
|
+
if (options.xOffset) {
|
|
3202
|
+
xOffset = options.xOffset;
|
|
3203
|
+
}
|
|
3204
|
+
if (options.yOffset) {
|
|
3205
|
+
yOffset = options.yOffset;
|
|
3206
|
+
}
|
|
3207
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3208
|
+
}
|
|
3209
|
+
else {
|
|
3210
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3211
|
+
if (results.length === 0) {
|
|
3212
|
+
throw new Error(`Element not found in cell area`);
|
|
3213
|
+
}
|
|
3214
|
+
state.element = results[0];
|
|
3215
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3216
|
+
}
|
|
3217
|
+
break;
|
|
3218
|
+
case "hover+click":
|
|
3219
|
+
if (!options.css) {
|
|
3220
|
+
throw new Error("css is not defined");
|
|
3221
|
+
}
|
|
3222
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3223
|
+
if (results.length === 0) {
|
|
3224
|
+
throw new Error(`Element not found in cell area`);
|
|
3225
|
+
}
|
|
3226
|
+
state.element = results[0];
|
|
3227
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3228
|
+
break;
|
|
3229
|
+
default:
|
|
3230
|
+
throw new Error("operation is not supported");
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
catch (e) {
|
|
3234
|
+
await _commandError(state, e, this);
|
|
3235
|
+
}
|
|
3236
|
+
finally {
|
|
3237
|
+
await _commandFinally(state, this);
|
|
2862
3238
|
}
|
|
2863
3239
|
}
|
|
2864
3240
|
saveTestDataAsGlobal(options, world) {
|
|
2865
|
-
const dataFile =
|
|
3241
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2866
3242
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2867
3243
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2868
3244
|
}
|
|
@@ -2892,6 +3268,7 @@ class StableBrowser {
|
|
|
2892
3268
|
_reportToWorld(world, {
|
|
2893
3269
|
type: Types.SET_VIEWPORT,
|
|
2894
3270
|
text: "set viewport size to " + width + "x" + hight,
|
|
3271
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2895
3272
|
screenshotId,
|
|
2896
3273
|
result: error
|
|
2897
3274
|
? {
|
|
@@ -2962,7 +3339,38 @@ class StableBrowser {
|
|
|
2962
3339
|
console.log("#-#");
|
|
2963
3340
|
}
|
|
2964
3341
|
}
|
|
3342
|
+
async beforeScenario(world, scenario) {
|
|
3343
|
+
this.beforeScenarioCalled = true;
|
|
3344
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3345
|
+
this.scenarioName = scenario.pickle.name;
|
|
3346
|
+
}
|
|
3347
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3348
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3349
|
+
}
|
|
3350
|
+
if (this.context) {
|
|
3351
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3352
|
+
}
|
|
3353
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3354
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3355
|
+
// check if @global_test_data tag is present
|
|
3356
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3357
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
// update test data based on feature/scenario
|
|
3361
|
+
let envName = null;
|
|
3362
|
+
if (this.context && this.context.environment) {
|
|
3363
|
+
envName = this.context.environment.name;
|
|
3364
|
+
}
|
|
3365
|
+
if (!process.env.TEMP_RUN) {
|
|
3366
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
async afterScenario(world, scenario) { }
|
|
2965
3370
|
async beforeStep(world, step) {
|
|
3371
|
+
if (!this.beforeScenarioCalled) {
|
|
3372
|
+
this.beforeScenario(world, step);
|
|
3373
|
+
}
|
|
2966
3374
|
if (this.stepIndex === undefined) {
|
|
2967
3375
|
this.stepIndex = 0;
|
|
2968
3376
|
}
|
|
@@ -2979,22 +3387,47 @@ class StableBrowser {
|
|
|
2979
3387
|
else {
|
|
2980
3388
|
this.stepName = "step " + this.stepIndex;
|
|
2981
3389
|
}
|
|
2982
|
-
if (this.context) {
|
|
2983
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2984
|
-
}
|
|
2985
3390
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2986
3391
|
if (this.context.browserObject.context) {
|
|
2987
3392
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2988
3393
|
}
|
|
2989
3394
|
}
|
|
2990
|
-
if (this.
|
|
2991
|
-
this.
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3395
|
+
if (this.initSnapshotTaken === false) {
|
|
3396
|
+
this.initSnapshotTaken = true;
|
|
3397
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3398
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3399
|
+
if (snapshot) {
|
|
3400
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3401
|
+
}
|
|
2995
3402
|
}
|
|
2996
3403
|
}
|
|
2997
3404
|
}
|
|
3405
|
+
async getAriaSnapshot() {
|
|
3406
|
+
try {
|
|
3407
|
+
// find the page url
|
|
3408
|
+
const url = await this.page.url();
|
|
3409
|
+
// extract the path from the url
|
|
3410
|
+
const path = new URL(url).pathname;
|
|
3411
|
+
// get the page title
|
|
3412
|
+
const title = await this.page.title();
|
|
3413
|
+
// go over other frams
|
|
3414
|
+
const frames = this.page.frames();
|
|
3415
|
+
const snapshots = [];
|
|
3416
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3417
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3418
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3419
|
+
content.push(`- frame: ${i}`);
|
|
3420
|
+
const frame = frames[i];
|
|
3421
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3422
|
+
content.push(snapshot);
|
|
3423
|
+
}
|
|
3424
|
+
return content.join("\n");
|
|
3425
|
+
}
|
|
3426
|
+
catch (e) {
|
|
3427
|
+
console.error(e);
|
|
3428
|
+
}
|
|
3429
|
+
return null;
|
|
3430
|
+
}
|
|
2998
3431
|
async afterStep(world, step) {
|
|
2999
3432
|
this.stepName = null;
|
|
3000
3433
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3002,11 +3435,25 @@ class StableBrowser {
|
|
|
3002
3435
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3003
3436
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3004
3437
|
});
|
|
3438
|
+
if (world && world.attach) {
|
|
3439
|
+
await world.attach(JSON.stringify({
|
|
3440
|
+
type: "trace",
|
|
3441
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3442
|
+
}), "application/json+trace");
|
|
3443
|
+
}
|
|
3444
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3005
3445
|
}
|
|
3006
3446
|
}
|
|
3007
3447
|
if (this.context) {
|
|
3008
3448
|
this.context.examplesRow = null;
|
|
3009
3449
|
}
|
|
3450
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3451
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3452
|
+
if (snapshot) {
|
|
3453
|
+
const obj = {};
|
|
3454
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3010
3457
|
}
|
|
3011
3458
|
}
|
|
3012
3459
|
function createTimedPromise(promise, label) {
|