automation_model 1.0.583-dev → 1.0.583-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 +108 -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 +241 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +60 -22
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -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 +45 -19
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +22 -6
- package/lib/stable_browser.js +768 -202
- 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 +9 -3
- package/lib/utils.js +330 -24
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
package/lib/stable_browser.js
CHANGED
|
@@ -10,18 +10,21 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, 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";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
22
|
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
25
|
export const Types = {
|
|
24
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
28
|
NAVIGATE: "navigate",
|
|
26
29
|
FILL: "fill_element",
|
|
27
30
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +46,7 @@ export const Types = {
|
|
|
43
46
|
UNCHECK: "uncheck_element",
|
|
44
47
|
EXTRACT: "extract_attribute",
|
|
45
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
46
50
|
SET_DATE_TIME: "set_date_time",
|
|
47
51
|
SET_VIEWPORT: "set_viewport",
|
|
48
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -51,8 +55,12 @@ export const Types = {
|
|
|
51
55
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
56
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
58
|
+
BRUNO: "bruno",
|
|
54
59
|
};
|
|
55
60
|
export const apps = {};
|
|
61
|
+
const formatElementName = (elementName) => {
|
|
62
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
63
|
+
};
|
|
56
64
|
class StableBrowser {
|
|
57
65
|
browser;
|
|
58
66
|
page;
|
|
@@ -66,6 +74,7 @@ class StableBrowser {
|
|
|
66
74
|
appName = "main";
|
|
67
75
|
tags = null;
|
|
68
76
|
isRecording = false;
|
|
77
|
+
initSnapshotTaken = false;
|
|
69
78
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
79
|
this.browser = browser;
|
|
71
80
|
this.page = page;
|
|
@@ -172,6 +181,30 @@ class StableBrowser {
|
|
|
172
181
|
await this.waitForPageLoad();
|
|
173
182
|
}
|
|
174
183
|
}
|
|
184
|
+
async switchTab(tabTitleOrIndex) {
|
|
185
|
+
// first check if the tabNameOrIndex is a number
|
|
186
|
+
let index = parseInt(tabTitleOrIndex);
|
|
187
|
+
if (!isNaN(index)) {
|
|
188
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
189
|
+
this.page = this.context.pages[index];
|
|
190
|
+
this.context.page = this.page;
|
|
191
|
+
await this.page.bringToFront();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
196
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
197
|
+
let page = this.context.pages[i];
|
|
198
|
+
let title = await page.title();
|
|
199
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
200
|
+
this.page = page;
|
|
201
|
+
this.context.page = this.page;
|
|
202
|
+
await this.page.bringToFront();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
207
|
+
}
|
|
175
208
|
registerConsoleLogListener(page, context) {
|
|
176
209
|
if (!this.context.webLogger) {
|
|
177
210
|
this.context.webLogger = [];
|
|
@@ -236,6 +269,9 @@ class StableBrowser {
|
|
|
236
269
|
// await closeUnexpectedPopups(this.page);
|
|
237
270
|
// }
|
|
238
271
|
async goto(url, world = null) {
|
|
272
|
+
if (!url) {
|
|
273
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
274
|
+
}
|
|
239
275
|
if (!url.startsWith("http")) {
|
|
240
276
|
url = "https://" + url;
|
|
241
277
|
}
|
|
@@ -264,7 +300,7 @@ class StableBrowser {
|
|
|
264
300
|
_commandError(state, error, this);
|
|
265
301
|
}
|
|
266
302
|
finally {
|
|
267
|
-
_commandFinally(state, this);
|
|
303
|
+
await _commandFinally(state, this);
|
|
268
304
|
}
|
|
269
305
|
}
|
|
270
306
|
async _getLocator(locator, scope, _params) {
|
|
@@ -331,7 +367,7 @@ class StableBrowser {
|
|
|
331
367
|
if (result.elementCount === 0) {
|
|
332
368
|
return;
|
|
333
369
|
}
|
|
334
|
-
let textElementCss = "[data-blinq-id
|
|
370
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
335
371
|
// css climb to parent element
|
|
336
372
|
const climbArray = [];
|
|
337
373
|
for (let i = 0; i < climb; i++) {
|
|
@@ -345,7 +381,7 @@ class StableBrowser {
|
|
|
345
381
|
return resultCss;
|
|
346
382
|
}
|
|
347
383
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
348
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
384
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
349
385
|
const locator = scope.locator(query);
|
|
350
386
|
const count = await locator.count();
|
|
351
387
|
if (!tag1) {
|
|
@@ -365,7 +401,13 @@ class StableBrowser {
|
|
|
365
401
|
if (!el.setAttribute) {
|
|
366
402
|
el = el.parentElement;
|
|
367
403
|
}
|
|
368
|
-
|
|
404
|
+
// remove any attributes start with data-blinq-id
|
|
405
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
406
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
407
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
408
|
+
// }
|
|
409
|
+
// }
|
|
410
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
369
411
|
return true;
|
|
370
412
|
}, [tag1, randomToken]))) {
|
|
371
413
|
continue;
|
|
@@ -374,7 +416,7 @@ class StableBrowser {
|
|
|
374
416
|
}
|
|
375
417
|
return { elementCount: tagCount, randomToken };
|
|
376
418
|
}
|
|
377
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
419
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
378
420
|
if (!info) {
|
|
379
421
|
info = {};
|
|
380
422
|
}
|
|
@@ -397,10 +439,11 @@ class StableBrowser {
|
|
|
397
439
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
398
440
|
let locator = null;
|
|
399
441
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
400
|
-
|
|
442
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
443
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
401
444
|
if (!locatorString) {
|
|
402
445
|
info.failCause.textNotFound = true;
|
|
403
|
-
info.failCause.lastError =
|
|
446
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
404
447
|
return;
|
|
405
448
|
}
|
|
406
449
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -410,10 +453,10 @@ class StableBrowser {
|
|
|
410
453
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
411
454
|
if (result.elementCount === 0) {
|
|
412
455
|
info.failCause.textNotFound = true;
|
|
413
|
-
info.failCause.lastError =
|
|
456
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
414
457
|
return;
|
|
415
458
|
}
|
|
416
|
-
locatorSearch.css = "[data-blinq-id
|
|
459
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
417
460
|
if (locatorSearch.childCss) {
|
|
418
461
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
419
462
|
}
|
|
@@ -449,7 +492,7 @@ class StableBrowser {
|
|
|
449
492
|
if (!visibleOnly) {
|
|
450
493
|
visible = true;
|
|
451
494
|
}
|
|
452
|
-
if (visible && enabled) {
|
|
495
|
+
if (visible && (allowDisabled || enabled)) {
|
|
453
496
|
foundLocators.push(locator.nth(j));
|
|
454
497
|
if (info.locatorLog) {
|
|
455
498
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -462,9 +505,11 @@ class StableBrowser {
|
|
|
462
505
|
info.printMessages = {};
|
|
463
506
|
}
|
|
464
507
|
if (info.locatorLog && !visible) {
|
|
508
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
465
509
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
466
510
|
}
|
|
467
511
|
if (info.locatorLog && !enabled) {
|
|
512
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
468
513
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
469
514
|
}
|
|
470
515
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -532,7 +577,7 @@ class StableBrowser {
|
|
|
532
577
|
}
|
|
533
578
|
return { rerun: false };
|
|
534
579
|
}
|
|
535
|
-
async _locate(selectors, info, _params, timeout) {
|
|
580
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
536
581
|
if (!timeout) {
|
|
537
582
|
timeout = 30000;
|
|
538
583
|
}
|
|
@@ -542,9 +587,30 @@ class StableBrowser {
|
|
|
542
587
|
let selector = selectors.locators[j];
|
|
543
588
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
544
589
|
}
|
|
545
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
590
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
546
591
|
if (!element.rerun) {
|
|
547
|
-
|
|
592
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
593
|
+
element.evaluate((el, randomToken) => {
|
|
594
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
595
|
+
}, randomToken);
|
|
596
|
+
// if (element._frame) {
|
|
597
|
+
// return element;
|
|
598
|
+
// }
|
|
599
|
+
const scope = element._frame ?? element.page();
|
|
600
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
601
|
+
let prefixSelector = "";
|
|
602
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
603
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
604
|
+
if (frameSelectorIndex !== -1) {
|
|
605
|
+
// remove everything after the >> internal:control=enter-frame
|
|
606
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
607
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
608
|
+
}
|
|
609
|
+
// if (element?._frame?._selector) {
|
|
610
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
611
|
+
// }
|
|
612
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
613
|
+
return scope.locator(newSelector);
|
|
548
614
|
}
|
|
549
615
|
}
|
|
550
616
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -617,7 +683,7 @@ class StableBrowser {
|
|
|
617
683
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
618
684
|
if (Date.now() - startTime > timeout) {
|
|
619
685
|
info.failCause.iframeNotFound = true;
|
|
620
|
-
info.failCause.lastError =
|
|
686
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
621
687
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
622
688
|
}
|
|
623
689
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -642,7 +708,7 @@ class StableBrowser {
|
|
|
642
708
|
return bodyContent;
|
|
643
709
|
});
|
|
644
710
|
}
|
|
645
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
711
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
646
712
|
if (!info) {
|
|
647
713
|
info = {};
|
|
648
714
|
info.failCause = {};
|
|
@@ -691,18 +757,13 @@ class StableBrowser {
|
|
|
691
757
|
}
|
|
692
758
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
693
759
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
694
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
760
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
695
761
|
if (result.foundElements.length === 0) {
|
|
696
762
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
697
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
698
|
-
}
|
|
699
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
700
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
763
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
701
764
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
705
|
-
}
|
|
765
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
766
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
706
767
|
}
|
|
707
768
|
let foundElements = result.foundElements;
|
|
708
769
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -758,6 +819,11 @@ class StableBrowser {
|
|
|
758
819
|
visibleOnly = false;
|
|
759
820
|
}
|
|
760
821
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
822
|
+
// sheck of more of half of the timeout has passed
|
|
823
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
824
|
+
highPriorityOnly = false;
|
|
825
|
+
visibleOnly = false;
|
|
826
|
+
}
|
|
761
827
|
}
|
|
762
828
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
763
829
|
// if (info.locatorLog) {
|
|
@@ -768,10 +834,12 @@ class StableBrowser {
|
|
|
768
834
|
// }
|
|
769
835
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
770
836
|
info.failCause.locatorNotFound = true;
|
|
771
|
-
info
|
|
837
|
+
if (!info?.failCause?.lastError) {
|
|
838
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
839
|
+
}
|
|
772
840
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
773
841
|
}
|
|
774
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
842
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
775
843
|
let foundElements = [];
|
|
776
844
|
const result = {
|
|
777
845
|
foundElements: foundElements,
|
|
@@ -779,7 +847,7 @@ class StableBrowser {
|
|
|
779
847
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
780
848
|
let foundLocators = [];
|
|
781
849
|
try {
|
|
782
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
850
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
783
851
|
}
|
|
784
852
|
catch (e) {
|
|
785
853
|
// this call can fail it the browser is navigating
|
|
@@ -787,7 +855,7 @@ class StableBrowser {
|
|
|
787
855
|
// this.logger.debug(e);
|
|
788
856
|
foundLocators = [];
|
|
789
857
|
try {
|
|
790
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
858
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
791
859
|
}
|
|
792
860
|
catch (e) {
|
|
793
861
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -802,9 +870,40 @@ class StableBrowser {
|
|
|
802
870
|
result.locatorIndex = i;
|
|
803
871
|
}
|
|
804
872
|
if (foundLocators.length > 1) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
873
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
874
|
+
const boxes = [];
|
|
875
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
876
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
877
|
+
}
|
|
878
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
879
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
880
|
+
if (j === k) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
884
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
885
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
886
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
888
|
+
// as the element is not unique, will remove it
|
|
889
|
+
boxes.splice(k, 1);
|
|
890
|
+
k--;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (boxes.length === 1) {
|
|
895
|
+
result.foundElements.push({
|
|
896
|
+
locator: boxes[0].locator.first(),
|
|
897
|
+
box: boxes[0].box,
|
|
898
|
+
unique: true,
|
|
899
|
+
});
|
|
900
|
+
result.locatorIndex = i;
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
info.failCause.foundMultiple = true;
|
|
904
|
+
if (info.locatorLog) {
|
|
905
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
906
|
+
}
|
|
808
907
|
}
|
|
809
908
|
}
|
|
810
909
|
}
|
|
@@ -852,7 +951,7 @@ class StableBrowser {
|
|
|
852
951
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
853
952
|
}
|
|
854
953
|
finally {
|
|
855
|
-
_commandFinally(state, this);
|
|
954
|
+
await _commandFinally(state, this);
|
|
856
955
|
}
|
|
857
956
|
}
|
|
858
957
|
}
|
|
@@ -901,7 +1000,7 @@ class StableBrowser {
|
|
|
901
1000
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
902
1001
|
}
|
|
903
1002
|
finally {
|
|
904
|
-
_commandFinally(state, this);
|
|
1003
|
+
await _commandFinally(state, this);
|
|
905
1004
|
}
|
|
906
1005
|
}
|
|
907
1006
|
}
|
|
@@ -915,25 +1014,14 @@ class StableBrowser {
|
|
|
915
1014
|
options,
|
|
916
1015
|
world,
|
|
917
1016
|
text: "Click element",
|
|
1017
|
+
_text: "Click on " + selectors.element_name,
|
|
918
1018
|
type: Types.CLICK,
|
|
919
1019
|
operation: "click",
|
|
920
1020
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
921
1021
|
};
|
|
922
1022
|
try {
|
|
923
1023
|
await _preCommand(state, this);
|
|
924
|
-
|
|
925
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
926
|
-
// }
|
|
927
|
-
try {
|
|
928
|
-
await state.element.click();
|
|
929
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
930
|
-
}
|
|
931
|
-
catch (e) {
|
|
932
|
-
// await this.closeUnexpectedPopups();
|
|
933
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
934
|
-
await state.element.dispatchEvent("click");
|
|
935
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
936
|
-
}
|
|
1024
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
937
1025
|
await this.waitForPageLoad();
|
|
938
1026
|
return state.info;
|
|
939
1027
|
}
|
|
@@ -941,9 +1029,41 @@ class StableBrowser {
|
|
|
941
1029
|
await _commandError(state, e, this);
|
|
942
1030
|
}
|
|
943
1031
|
finally {
|
|
944
|
-
_commandFinally(state, this);
|
|
1032
|
+
await _commandFinally(state, this);
|
|
945
1033
|
}
|
|
946
1034
|
}
|
|
1035
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1036
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1037
|
+
const state = {
|
|
1038
|
+
selectors,
|
|
1039
|
+
_params,
|
|
1040
|
+
options,
|
|
1041
|
+
world,
|
|
1042
|
+
text: "Wait for element",
|
|
1043
|
+
_text: "Wait for " + selectors.element_name,
|
|
1044
|
+
type: Types.WAIT_ELEMENT,
|
|
1045
|
+
operation: "waitForElement",
|
|
1046
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1047
|
+
};
|
|
1048
|
+
let found = false;
|
|
1049
|
+
try {
|
|
1050
|
+
await _preCommand(state, this);
|
|
1051
|
+
// if (state.options && state.options.context) {
|
|
1052
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1053
|
+
// }
|
|
1054
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1055
|
+
found = true;
|
|
1056
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1057
|
+
}
|
|
1058
|
+
catch (e) {
|
|
1059
|
+
console.error("Error on waitForElement", e);
|
|
1060
|
+
// await _commandError(state, e, this);
|
|
1061
|
+
}
|
|
1062
|
+
finally {
|
|
1063
|
+
await _commandFinally(state, this);
|
|
1064
|
+
}
|
|
1065
|
+
return found;
|
|
1066
|
+
}
|
|
947
1067
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
948
1068
|
const state = {
|
|
949
1069
|
selectors,
|
|
@@ -952,6 +1072,7 @@ class StableBrowser {
|
|
|
952
1072
|
world,
|
|
953
1073
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
954
1074
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1075
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
955
1076
|
operation: "setCheck",
|
|
956
1077
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
957
1078
|
};
|
|
@@ -961,9 +1082,15 @@ class StableBrowser {
|
|
|
961
1082
|
// let element = await this._locate(selectors, info, _params);
|
|
962
1083
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
963
1084
|
try {
|
|
964
|
-
//
|
|
1085
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1086
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1087
|
+
await this._highlightElements(state.element);
|
|
965
1088
|
await state.element.setChecked(checked);
|
|
966
1089
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1090
|
+
// await this._unHighlightElements(element);
|
|
1091
|
+
// }
|
|
1092
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1093
|
+
// await this._unHighlightElements(element);
|
|
967
1094
|
}
|
|
968
1095
|
catch (e) {
|
|
969
1096
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -984,7 +1111,7 @@ class StableBrowser {
|
|
|
984
1111
|
await _commandError(state, e, this);
|
|
985
1112
|
}
|
|
986
1113
|
finally {
|
|
987
|
-
_commandFinally(state, this);
|
|
1114
|
+
await _commandFinally(state, this);
|
|
988
1115
|
}
|
|
989
1116
|
}
|
|
990
1117
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -995,22 +1122,13 @@ class StableBrowser {
|
|
|
995
1122
|
world,
|
|
996
1123
|
type: Types.HOVER,
|
|
997
1124
|
text: `Hover element`,
|
|
1125
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
998
1126
|
operation: "hover",
|
|
999
1127
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1000
1128
|
};
|
|
1001
1129
|
try {
|
|
1002
1130
|
await _preCommand(state, this);
|
|
1003
|
-
|
|
1004
|
-
await state.element.hover();
|
|
1005
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1006
|
-
}
|
|
1007
|
-
catch (e) {
|
|
1008
|
-
//await this.closeUnexpectedPopups();
|
|
1009
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1010
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1011
|
-
await state.element.hover({ timeout: 10000 });
|
|
1012
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1013
|
-
}
|
|
1131
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1014
1132
|
await _screenshot(state, this);
|
|
1015
1133
|
await this.waitForPageLoad();
|
|
1016
1134
|
return state.info;
|
|
@@ -1019,7 +1137,7 @@ class StableBrowser {
|
|
|
1019
1137
|
await _commandError(state, e, this);
|
|
1020
1138
|
}
|
|
1021
1139
|
finally {
|
|
1022
|
-
_commandFinally(state, this);
|
|
1140
|
+
await _commandFinally(state, this);
|
|
1023
1141
|
}
|
|
1024
1142
|
}
|
|
1025
1143
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1034,6 +1152,7 @@ class StableBrowser {
|
|
|
1034
1152
|
value: values.toString(),
|
|
1035
1153
|
type: Types.SELECT,
|
|
1036
1154
|
text: `Select option: ${values}`,
|
|
1155
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1037
1156
|
operation: "selectOption",
|
|
1038
1157
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1039
1158
|
};
|
|
@@ -1054,7 +1173,7 @@ class StableBrowser {
|
|
|
1054
1173
|
await _commandError(state, e, this);
|
|
1055
1174
|
}
|
|
1056
1175
|
finally {
|
|
1057
|
-
_commandFinally(state, this);
|
|
1176
|
+
await _commandFinally(state, this);
|
|
1058
1177
|
}
|
|
1059
1178
|
}
|
|
1060
1179
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1068,6 +1187,7 @@ class StableBrowser {
|
|
|
1068
1187
|
highlight: false,
|
|
1069
1188
|
type: Types.TYPE_PRESS,
|
|
1070
1189
|
text: `Type value: ${_value}`,
|
|
1190
|
+
_text: `Type value: ${_value}`,
|
|
1071
1191
|
operation: "type",
|
|
1072
1192
|
log: "",
|
|
1073
1193
|
};
|
|
@@ -1099,7 +1219,7 @@ class StableBrowser {
|
|
|
1099
1219
|
await _commandError(state, e, this);
|
|
1100
1220
|
}
|
|
1101
1221
|
finally {
|
|
1102
|
-
_commandFinally(state, this);
|
|
1222
|
+
await _commandFinally(state, this);
|
|
1103
1223
|
}
|
|
1104
1224
|
}
|
|
1105
1225
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1135,7 +1255,7 @@ class StableBrowser {
|
|
|
1135
1255
|
await _commandError(state, e, this);
|
|
1136
1256
|
}
|
|
1137
1257
|
finally {
|
|
1138
|
-
_commandFinally(state, this);
|
|
1258
|
+
await _commandFinally(state, this);
|
|
1139
1259
|
}
|
|
1140
1260
|
}
|
|
1141
1261
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1147,6 +1267,7 @@ class StableBrowser {
|
|
|
1147
1267
|
world,
|
|
1148
1268
|
type: Types.SET_DATE_TIME,
|
|
1149
1269
|
text: `Set date time value: ${value}`,
|
|
1270
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1150
1271
|
operation: "setDateTime",
|
|
1151
1272
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1152
1273
|
throwError: false,
|
|
@@ -1154,7 +1275,7 @@ class StableBrowser {
|
|
|
1154
1275
|
try {
|
|
1155
1276
|
await _preCommand(state, this);
|
|
1156
1277
|
try {
|
|
1157
|
-
await state.element
|
|
1278
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1158
1279
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1159
1280
|
if (format) {
|
|
1160
1281
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1203,7 +1324,7 @@ class StableBrowser {
|
|
|
1203
1324
|
await _commandError(state, e, this);
|
|
1204
1325
|
}
|
|
1205
1326
|
finally {
|
|
1206
|
-
_commandFinally(state, this);
|
|
1327
|
+
await _commandFinally(state, this);
|
|
1207
1328
|
}
|
|
1208
1329
|
}
|
|
1209
1330
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1218,9 +1339,13 @@ class StableBrowser {
|
|
|
1218
1339
|
world,
|
|
1219
1340
|
type: Types.FILL,
|
|
1220
1341
|
text: `Click type input with value: ${_value}`,
|
|
1342
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1221
1343
|
operation: "clickType",
|
|
1222
1344
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1223
1345
|
};
|
|
1346
|
+
if (!options) {
|
|
1347
|
+
options = {};
|
|
1348
|
+
}
|
|
1224
1349
|
if (newValue !== _value) {
|
|
1225
1350
|
//this.logger.info(_value + "=" + newValue);
|
|
1226
1351
|
_value = newValue;
|
|
@@ -1228,7 +1353,7 @@ class StableBrowser {
|
|
|
1228
1353
|
try {
|
|
1229
1354
|
await _preCommand(state, this);
|
|
1230
1355
|
state.info.value = _value;
|
|
1231
|
-
if (
|
|
1356
|
+
if (!options.press) {
|
|
1232
1357
|
try {
|
|
1233
1358
|
let currentValue = await state.element.inputValue();
|
|
1234
1359
|
if (currentValue) {
|
|
@@ -1239,13 +1364,9 @@ class StableBrowser {
|
|
|
1239
1364
|
this.logger.info("unable to clear input value");
|
|
1240
1365
|
}
|
|
1241
1366
|
}
|
|
1242
|
-
if (options
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
catch (e) {
|
|
1247
|
-
await state.element.dispatchEvent("click");
|
|
1248
|
-
}
|
|
1367
|
+
if (options.press) {
|
|
1368
|
+
options.timeout = 5000;
|
|
1369
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1249
1370
|
}
|
|
1250
1371
|
else {
|
|
1251
1372
|
try {
|
|
@@ -1283,7 +1404,12 @@ class StableBrowser {
|
|
|
1283
1404
|
await this.waitForPageLoad();
|
|
1284
1405
|
}
|
|
1285
1406
|
else if (enter === false) {
|
|
1286
|
-
|
|
1407
|
+
try {
|
|
1408
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1409
|
+
}
|
|
1410
|
+
catch (e) {
|
|
1411
|
+
// ignore
|
|
1412
|
+
}
|
|
1287
1413
|
//await this.page.keyboard.press("Tab");
|
|
1288
1414
|
}
|
|
1289
1415
|
else {
|
|
@@ -1298,7 +1424,7 @@ class StableBrowser {
|
|
|
1298
1424
|
await _commandError(state, e, this);
|
|
1299
1425
|
}
|
|
1300
1426
|
finally {
|
|
1301
|
-
_commandFinally(state, this);
|
|
1427
|
+
await _commandFinally(state, this);
|
|
1302
1428
|
}
|
|
1303
1429
|
}
|
|
1304
1430
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1328,13 +1454,14 @@ class StableBrowser {
|
|
|
1328
1454
|
await _commandError(state, e, this);
|
|
1329
1455
|
}
|
|
1330
1456
|
finally {
|
|
1331
|
-
_commandFinally(state, this);
|
|
1457
|
+
await _commandFinally(state, this);
|
|
1332
1458
|
}
|
|
1333
1459
|
}
|
|
1334
1460
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1335
1461
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1336
1462
|
}
|
|
1337
1463
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1464
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1338
1465
|
_validateSelectors(selectors);
|
|
1339
1466
|
let screenshotId = null;
|
|
1340
1467
|
let screenshotPath = null;
|
|
@@ -1344,7 +1471,7 @@ class StableBrowser {
|
|
|
1344
1471
|
}
|
|
1345
1472
|
info.operation = "getText";
|
|
1346
1473
|
info.selectors = selectors;
|
|
1347
|
-
let element = await this._locate(selectors, info, _params);
|
|
1474
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1348
1475
|
if (climb > 0) {
|
|
1349
1476
|
const climbArray = [];
|
|
1350
1477
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1363,6 +1490,18 @@ class StableBrowser {
|
|
|
1363
1490
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1364
1491
|
try {
|
|
1365
1492
|
await this._highlightElements(element);
|
|
1493
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1494
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1495
|
+
// this._highlightElements(element)
|
|
1496
|
+
// .then(async () => {
|
|
1497
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1498
|
+
// this._unhighlightElements(element).then(
|
|
1499
|
+
// () => {}
|
|
1500
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1501
|
+
// );
|
|
1502
|
+
// })
|
|
1503
|
+
// .catch(e);
|
|
1504
|
+
// }
|
|
1366
1505
|
const elementText = await element.innerText();
|
|
1367
1506
|
return {
|
|
1368
1507
|
text: elementText,
|
|
@@ -1374,7 +1513,7 @@ class StableBrowser {
|
|
|
1374
1513
|
}
|
|
1375
1514
|
catch (e) {
|
|
1376
1515
|
//await this.closeUnexpectedPopups();
|
|
1377
|
-
this.logger.info("no innerText will use textContent");
|
|
1516
|
+
this.logger.info("no innerText, will use textContent");
|
|
1378
1517
|
const elementText = await element.textContent();
|
|
1379
1518
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1380
1519
|
}
|
|
@@ -1399,6 +1538,7 @@ class StableBrowser {
|
|
|
1399
1538
|
highlight: false,
|
|
1400
1539
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1401
1540
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1541
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1402
1542
|
operation: "containsPattern",
|
|
1403
1543
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1404
1544
|
};
|
|
@@ -1430,10 +1570,12 @@ class StableBrowser {
|
|
|
1430
1570
|
await _commandError(state, e, this);
|
|
1431
1571
|
}
|
|
1432
1572
|
finally {
|
|
1433
|
-
_commandFinally(state, this);
|
|
1573
|
+
await _commandFinally(state, this);
|
|
1434
1574
|
}
|
|
1435
1575
|
}
|
|
1436
1576
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1577
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1578
|
+
const startTime = Date.now();
|
|
1437
1579
|
const state = {
|
|
1438
1580
|
selectors,
|
|
1439
1581
|
_params,
|
|
@@ -1460,62 +1602,54 @@ class StableBrowser {
|
|
|
1460
1602
|
}
|
|
1461
1603
|
let foundObj = null;
|
|
1462
1604
|
try {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1470
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1471
|
-
if (dateAlternatives.date) {
|
|
1472
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1473
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1474
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1475
|
-
return state.info;
|
|
1605
|
+
while (Date.now() - startTime < timeout) {
|
|
1606
|
+
try {
|
|
1607
|
+
await _preCommand(state, this);
|
|
1608
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1609
|
+
if (foundObj && foundObj.element) {
|
|
1610
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1476
1611
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1612
|
+
await _screenshot(state, this);
|
|
1613
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1614
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1615
|
+
if (dateAlternatives.date) {
|
|
1616
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1617
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1618
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1619
|
+
return state.info;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
else if (numberAlternatives.number) {
|
|
1624
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1625
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1626
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1627
|
+
return state.info;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1484
1632
|
return state.info;
|
|
1485
1633
|
}
|
|
1486
1634
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
throw new Error("element doesn't contain text " + text);
|
|
1635
|
+
catch (e) {
|
|
1636
|
+
// Log error but continue retrying until timeout is reached
|
|
1637
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1638
|
+
}
|
|
1639
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1493
1640
|
}
|
|
1494
|
-
|
|
1641
|
+
state.info.foundText = foundObj?.text;
|
|
1642
|
+
state.info.value = foundObj?.value;
|
|
1643
|
+
throw new Error("element doesn't contain text " + text);
|
|
1495
1644
|
}
|
|
1496
1645
|
catch (e) {
|
|
1497
1646
|
await _commandError(state, e, this);
|
|
1647
|
+
throw e;
|
|
1498
1648
|
}
|
|
1499
1649
|
finally {
|
|
1500
|
-
_commandFinally(state, this);
|
|
1650
|
+
await _commandFinally(state, this);
|
|
1501
1651
|
}
|
|
1502
1652
|
}
|
|
1503
|
-
_getDataFile(world = null) {
|
|
1504
|
-
let dataFile = null;
|
|
1505
|
-
if (world && world.reportFolder) {
|
|
1506
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1507
|
-
}
|
|
1508
|
-
else if (this.reportFolder) {
|
|
1509
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1510
|
-
}
|
|
1511
|
-
else if (this.context && this.context.reportFolder) {
|
|
1512
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1513
|
-
}
|
|
1514
|
-
else {
|
|
1515
|
-
dataFile = "data.json";
|
|
1516
|
-
}
|
|
1517
|
-
return dataFile;
|
|
1518
|
-
}
|
|
1519
1653
|
async waitForUserInput(message, world = null) {
|
|
1520
1654
|
if (!message) {
|
|
1521
1655
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1544,13 +1678,22 @@ class StableBrowser {
|
|
|
1544
1678
|
return;
|
|
1545
1679
|
}
|
|
1546
1680
|
// if data file exists, load it
|
|
1547
|
-
const dataFile =
|
|
1681
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1548
1682
|
let data = this.getTestData(world);
|
|
1549
1683
|
// merge the testData with the existing data
|
|
1550
1684
|
Object.assign(data, testData);
|
|
1551
1685
|
// save the data to the file
|
|
1552
1686
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1553
1687
|
}
|
|
1688
|
+
overwriteTestData(testData, world = null) {
|
|
1689
|
+
if (!testData) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
// if data file exists, load it
|
|
1693
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1694
|
+
// save the data to the file
|
|
1695
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1696
|
+
}
|
|
1554
1697
|
_getDataFilePath(fileName) {
|
|
1555
1698
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1556
1699
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1647,7 +1790,7 @@ class StableBrowser {
|
|
|
1647
1790
|
}
|
|
1648
1791
|
}
|
|
1649
1792
|
getTestData(world = null) {
|
|
1650
|
-
const dataFile =
|
|
1793
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1651
1794
|
let data = {};
|
|
1652
1795
|
if (fs.existsSync(dataFile)) {
|
|
1653
1796
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1734,6 +1877,15 @@ class StableBrowser {
|
|
|
1734
1877
|
document.documentElement.clientWidth,
|
|
1735
1878
|
])));
|
|
1736
1879
|
let screenshotBuffer = null;
|
|
1880
|
+
// if (focusedElement) {
|
|
1881
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1882
|
+
// await this._unhighlightElements(focusedElement);
|
|
1883
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1884
|
+
// console.log(`Unhighlighted previous element`);
|
|
1885
|
+
// }
|
|
1886
|
+
// if (focusedElement) {
|
|
1887
|
+
// await this._highlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1737
1889
|
if (this.context.browserName === "chromium") {
|
|
1738
1890
|
const client = await playContext.newCDPSession(this.page);
|
|
1739
1891
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1755,6 +1907,10 @@ class StableBrowser {
|
|
|
1755
1907
|
else {
|
|
1756
1908
|
screenshotBuffer = await this.page.screenshot();
|
|
1757
1909
|
}
|
|
1910
|
+
// if (focusedElement) {
|
|
1911
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1912
|
+
// await this._unhighlightElements(focusedElement);
|
|
1913
|
+
// }
|
|
1758
1914
|
let image = await Jimp.read(screenshotBuffer);
|
|
1759
1915
|
// Get the image dimensions
|
|
1760
1916
|
const { width, height } = image.bitmap;
|
|
@@ -1767,6 +1923,7 @@ class StableBrowser {
|
|
|
1767
1923
|
else {
|
|
1768
1924
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1769
1925
|
}
|
|
1926
|
+
return screenshotBuffer;
|
|
1770
1927
|
}
|
|
1771
1928
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1772
1929
|
const state = {
|
|
@@ -1789,7 +1946,7 @@ class StableBrowser {
|
|
|
1789
1946
|
await _commandError(state, e, this);
|
|
1790
1947
|
}
|
|
1791
1948
|
finally {
|
|
1792
|
-
_commandFinally(state, this);
|
|
1949
|
+
await _commandFinally(state, this);
|
|
1793
1950
|
}
|
|
1794
1951
|
}
|
|
1795
1952
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1802,8 +1959,10 @@ class StableBrowser {
|
|
|
1802
1959
|
world,
|
|
1803
1960
|
type: Types.EXTRACT,
|
|
1804
1961
|
text: `Extract attribute from element`,
|
|
1962
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1805
1963
|
operation: "extractAttribute",
|
|
1806
1964
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1965
|
+
allowDisabled: true,
|
|
1807
1966
|
};
|
|
1808
1967
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1809
1968
|
try {
|
|
@@ -1818,6 +1977,9 @@ class StableBrowser {
|
|
|
1818
1977
|
case "value":
|
|
1819
1978
|
state.value = await state.element.inputValue();
|
|
1820
1979
|
break;
|
|
1980
|
+
case "text":
|
|
1981
|
+
state.value = await state.element.textContent();
|
|
1982
|
+
break;
|
|
1821
1983
|
default:
|
|
1822
1984
|
state.value = await state.element.getAttribute(attribute);
|
|
1823
1985
|
break;
|
|
@@ -1825,13 +1987,14 @@ class StableBrowser {
|
|
|
1825
1987
|
state.info.value = state.value;
|
|
1826
1988
|
this.setTestData({ [variable]: state.value }, world);
|
|
1827
1989
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1990
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1828
1991
|
return state.info;
|
|
1829
1992
|
}
|
|
1830
1993
|
catch (e) {
|
|
1831
1994
|
await _commandError(state, e, this);
|
|
1832
1995
|
}
|
|
1833
1996
|
finally {
|
|
1834
|
-
_commandFinally(state, this);
|
|
1997
|
+
await _commandFinally(state, this);
|
|
1835
1998
|
}
|
|
1836
1999
|
}
|
|
1837
2000
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1843,18 +2006,28 @@ class StableBrowser {
|
|
|
1843
2006
|
options,
|
|
1844
2007
|
world,
|
|
1845
2008
|
type: Types.VERIFY_ATTRIBUTE,
|
|
2009
|
+
highlight: true,
|
|
2010
|
+
screenshot: true,
|
|
1846
2011
|
text: `Verify element attribute`,
|
|
2012
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1847
2013
|
operation: "verifyAttribute",
|
|
1848
2014
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
2015
|
+
allowDisabled: true,
|
|
1849
2016
|
};
|
|
1850
2017
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1851
2018
|
let val;
|
|
2019
|
+
let expectedValue;
|
|
1852
2020
|
try {
|
|
1853
2021
|
await _preCommand(state, this);
|
|
2022
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2023
|
+
state.info.expectedValue = expectedValue;
|
|
1854
2024
|
switch (attribute) {
|
|
1855
2025
|
case "innerText":
|
|
1856
2026
|
val = String(await state.element.innerText());
|
|
1857
2027
|
break;
|
|
2028
|
+
case "text":
|
|
2029
|
+
val = String(await state.element.textContent());
|
|
2030
|
+
break;
|
|
1858
2031
|
case "value":
|
|
1859
2032
|
val = String(await state.element.inputValue());
|
|
1860
2033
|
break;
|
|
@@ -1872,26 +2045,29 @@ class StableBrowser {
|
|
|
1872
2045
|
val = String(await state.element.getAttribute(attribute));
|
|
1873
2046
|
break;
|
|
1874
2047
|
}
|
|
2048
|
+
state.info.value = val;
|
|
1875
2049
|
let regex;
|
|
1876
|
-
if (
|
|
1877
|
-
const patternBody =
|
|
2050
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2051
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1878
2052
|
regex = new RegExp(patternBody, "g");
|
|
1879
2053
|
}
|
|
1880
2054
|
else {
|
|
1881
|
-
const escapedPattern =
|
|
2055
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1882
2056
|
regex = new RegExp(escapedPattern, "g");
|
|
1883
2057
|
}
|
|
1884
2058
|
if (!val.match(regex)) {
|
|
1885
|
-
|
|
2059
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2060
|
+
state.info.failCause.assertionFailed = true;
|
|
2061
|
+
state.info.failCause.lastError = errorMessage;
|
|
2062
|
+
throw new Error(errorMessage);
|
|
1886
2063
|
}
|
|
1887
|
-
state.info.expectedValue = val;
|
|
1888
2064
|
return state.info;
|
|
1889
2065
|
}
|
|
1890
2066
|
catch (e) {
|
|
1891
2067
|
await _commandError(state, e, this);
|
|
1892
2068
|
}
|
|
1893
2069
|
finally {
|
|
1894
|
-
_commandFinally(state, this);
|
|
2070
|
+
await _commandFinally(state, this);
|
|
1895
2071
|
}
|
|
1896
2072
|
}
|
|
1897
2073
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1983,27 +2159,32 @@ class StableBrowser {
|
|
|
1983
2159
|
async _highlightElements(scope, css) {
|
|
1984
2160
|
try {
|
|
1985
2161
|
if (!scope) {
|
|
2162
|
+
// console.log(`Scope is not defined`);
|
|
1986
2163
|
return;
|
|
1987
2164
|
}
|
|
1988
2165
|
if (!css) {
|
|
1989
2166
|
scope
|
|
1990
2167
|
.evaluate((node) => {
|
|
1991
2168
|
if (node && node.style) {
|
|
1992
|
-
let
|
|
1993
|
-
|
|
2169
|
+
let originalOutline = node.style.outline;
|
|
2170
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2171
|
+
// node.__previousOutline = originalOutline;
|
|
2172
|
+
node.style.outline = "2px solid red";
|
|
2173
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1994
2174
|
if (window) {
|
|
1995
2175
|
window.addEventListener("beforeunload", function (e) {
|
|
1996
|
-
node.style.
|
|
2176
|
+
node.style.outline = originalOutline;
|
|
1997
2177
|
});
|
|
1998
2178
|
}
|
|
1999
2179
|
setTimeout(function () {
|
|
2000
|
-
node.style.
|
|
2180
|
+
node.style.outline = originalOutline;
|
|
2001
2181
|
}, 2000);
|
|
2002
2182
|
}
|
|
2003
2183
|
})
|
|
2004
2184
|
.then(() => { })
|
|
2005
2185
|
.catch((e) => {
|
|
2006
2186
|
// ignore
|
|
2187
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2007
2188
|
});
|
|
2008
2189
|
}
|
|
2009
2190
|
else {
|
|
@@ -2019,17 +2200,18 @@ class StableBrowser {
|
|
|
2019
2200
|
if (!element.style) {
|
|
2020
2201
|
return;
|
|
2021
2202
|
}
|
|
2022
|
-
|
|
2203
|
+
let originalOutline = element.style.outline;
|
|
2204
|
+
element.__previousOutline = originalOutline;
|
|
2023
2205
|
// Set the new border to be red and 2px solid
|
|
2024
|
-
element.style.
|
|
2206
|
+
element.style.outline = "2px solid red";
|
|
2025
2207
|
if (window) {
|
|
2026
2208
|
window.addEventListener("beforeunload", function (e) {
|
|
2027
|
-
element.style.
|
|
2209
|
+
element.style.outline = originalOutline;
|
|
2028
2210
|
});
|
|
2029
2211
|
}
|
|
2030
2212
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2031
2213
|
setTimeout(function () {
|
|
2032
|
-
element.style.
|
|
2214
|
+
element.style.outline = originalOutline;
|
|
2033
2215
|
}, 2000);
|
|
2034
2216
|
}
|
|
2035
2217
|
return;
|
|
@@ -2037,6 +2219,7 @@ class StableBrowser {
|
|
|
2037
2219
|
.then(() => { })
|
|
2038
2220
|
.catch((e) => {
|
|
2039
2221
|
// ignore
|
|
2222
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2040
2223
|
});
|
|
2041
2224
|
}
|
|
2042
2225
|
}
|
|
@@ -2044,6 +2227,54 @@ class StableBrowser {
|
|
|
2044
2227
|
console.debug(error);
|
|
2045
2228
|
}
|
|
2046
2229
|
}
|
|
2230
|
+
// async _unhighlightElements(scope, css) {
|
|
2231
|
+
// try {
|
|
2232
|
+
// if (!scope) {
|
|
2233
|
+
// return;
|
|
2234
|
+
// }
|
|
2235
|
+
// if (!css) {
|
|
2236
|
+
// scope
|
|
2237
|
+
// .evaluate((node) => {
|
|
2238
|
+
// if (node && node.style) {
|
|
2239
|
+
// if (!node.__previousOutline) {
|
|
2240
|
+
// node.style.outline = "";
|
|
2241
|
+
// } else {
|
|
2242
|
+
// node.style.outline = node.__previousOutline;
|
|
2243
|
+
// }
|
|
2244
|
+
// }
|
|
2245
|
+
// })
|
|
2246
|
+
// .then(() => {})
|
|
2247
|
+
// .catch((e) => {
|
|
2248
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2249
|
+
// });
|
|
2250
|
+
// } else {
|
|
2251
|
+
// scope
|
|
2252
|
+
// .evaluate(([css]) => {
|
|
2253
|
+
// if (!css) {
|
|
2254
|
+
// return;
|
|
2255
|
+
// }
|
|
2256
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2257
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2258
|
+
// let element = elements[i];
|
|
2259
|
+
// if (!element.style) {
|
|
2260
|
+
// return;
|
|
2261
|
+
// }
|
|
2262
|
+
// if (!element.__previousOutline) {
|
|
2263
|
+
// element.style.outline = "";
|
|
2264
|
+
// } else {
|
|
2265
|
+
// element.style.outline = element.__previousOutline;
|
|
2266
|
+
// }
|
|
2267
|
+
// }
|
|
2268
|
+
// })
|
|
2269
|
+
// .then(() => {})
|
|
2270
|
+
// .catch((e) => {
|
|
2271
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2272
|
+
// });
|
|
2273
|
+
// }
|
|
2274
|
+
// } catch (error) {
|
|
2275
|
+
// // console.debug(error);
|
|
2276
|
+
// }
|
|
2277
|
+
// }
|
|
2047
2278
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2048
2279
|
const startTime = Date.now();
|
|
2049
2280
|
let error = null;
|
|
@@ -2088,6 +2319,69 @@ class StableBrowser {
|
|
|
2088
2319
|
_reportToWorld(world, {
|
|
2089
2320
|
type: Types.VERIFY_PAGE_PATH,
|
|
2090
2321
|
text: "Verify page path",
|
|
2322
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2323
|
+
screenshotId,
|
|
2324
|
+
result: error
|
|
2325
|
+
? {
|
|
2326
|
+
status: "FAILED",
|
|
2327
|
+
startTime,
|
|
2328
|
+
endTime,
|
|
2329
|
+
message: error?.message,
|
|
2330
|
+
}
|
|
2331
|
+
: {
|
|
2332
|
+
status: "PASSED",
|
|
2333
|
+
startTime,
|
|
2334
|
+
endTime,
|
|
2335
|
+
},
|
|
2336
|
+
info: info,
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2341
|
+
const startTime = Date.now();
|
|
2342
|
+
let error = null;
|
|
2343
|
+
let screenshotId = null;
|
|
2344
|
+
let screenshotPath = null;
|
|
2345
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2346
|
+
const info = {};
|
|
2347
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2348
|
+
info.operation = "verifyPageTitle";
|
|
2349
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2350
|
+
if (newValue !== title) {
|
|
2351
|
+
this.logger.info(title + "=" + newValue);
|
|
2352
|
+
title = newValue;
|
|
2353
|
+
}
|
|
2354
|
+
info.title = title;
|
|
2355
|
+
try {
|
|
2356
|
+
for (let i = 0; i < 30; i++) {
|
|
2357
|
+
const foundTitle = await this.page.title();
|
|
2358
|
+
if (!foundTitle.includes(title)) {
|
|
2359
|
+
if (i === 29) {
|
|
2360
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2361
|
+
}
|
|
2362
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2366
|
+
return info;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
catch (e) {
|
|
2370
|
+
//await this.closeUnexpectedPopups();
|
|
2371
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2372
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2373
|
+
info.screenshotPath = screenshotPath;
|
|
2374
|
+
Object.assign(e, { info: info });
|
|
2375
|
+
error = e;
|
|
2376
|
+
// throw e;
|
|
2377
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2378
|
+
}
|
|
2379
|
+
finally {
|
|
2380
|
+
const endTime = Date.now();
|
|
2381
|
+
_reportToWorld(world, {
|
|
2382
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2383
|
+
text: "Verify page title",
|
|
2384
|
+
_text: "Verify the page title contains " + title,
|
|
2091
2385
|
screenshotId,
|
|
2092
2386
|
result: error
|
|
2093
2387
|
? {
|
|
@@ -2105,27 +2399,27 @@ class StableBrowser {
|
|
|
2105
2399
|
});
|
|
2106
2400
|
}
|
|
2107
2401
|
}
|
|
2108
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2402
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2109
2403
|
const frames = this.page.frames();
|
|
2110
2404
|
let results = [];
|
|
2111
|
-
let ignoreCase = false;
|
|
2405
|
+
// let ignoreCase = false;
|
|
2112
2406
|
for (let i = 0; i < frames.length; i++) {
|
|
2113
2407
|
if (dateAlternatives.date) {
|
|
2114
2408
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2115
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2409
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2116
2410
|
result.frame = frames[i];
|
|
2117
2411
|
results.push(result);
|
|
2118
2412
|
}
|
|
2119
2413
|
}
|
|
2120
2414
|
else if (numberAlternatives.number) {
|
|
2121
2415
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2122
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2416
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2123
2417
|
result.frame = frames[i];
|
|
2124
2418
|
results.push(result);
|
|
2125
2419
|
}
|
|
2126
2420
|
}
|
|
2127
2421
|
else {
|
|
2128
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2422
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2129
2423
|
result.frame = frames[i];
|
|
2130
2424
|
results.push(result);
|
|
2131
2425
|
}
|
|
@@ -2144,11 +2438,15 @@ class StableBrowser {
|
|
|
2144
2438
|
scroll: false,
|
|
2145
2439
|
highlight: false,
|
|
2146
2440
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2147
|
-
text: `Verify text exists in page`,
|
|
2441
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2442
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2148
2443
|
operation: "verifyTextExistInPage",
|
|
2149
2444
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2150
2445
|
};
|
|
2151
|
-
|
|
2446
|
+
if (testForRegex(text)) {
|
|
2447
|
+
text = text.replace(/\\"/g, '"');
|
|
2448
|
+
}
|
|
2449
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2152
2450
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2153
2451
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2154
2452
|
if (newValue !== text) {
|
|
@@ -2161,7 +2459,15 @@ class StableBrowser {
|
|
|
2161
2459
|
await _preCommand(state, this);
|
|
2162
2460
|
state.info.text = text;
|
|
2163
2461
|
while (true) {
|
|
2164
|
-
|
|
2462
|
+
let resultWithElementsFound = {
|
|
2463
|
+
length: 0,
|
|
2464
|
+
};
|
|
2465
|
+
try {
|
|
2466
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2467
|
+
}
|
|
2468
|
+
catch (error) {
|
|
2469
|
+
// ignore
|
|
2470
|
+
}
|
|
2165
2471
|
if (resultWithElementsFound.length === 0) {
|
|
2166
2472
|
if (Date.now() - state.startTime > timeout) {
|
|
2167
2473
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2169,18 +2475,40 @@ class StableBrowser {
|
|
|
2169
2475
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2170
2476
|
continue;
|
|
2171
2477
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2478
|
+
try {
|
|
2479
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2480
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2481
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2482
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2483
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2484
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2485
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2486
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2487
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2488
|
+
// .then(async () => {
|
|
2489
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2490
|
+
// })
|
|
2491
|
+
// .catch(
|
|
2492
|
+
// (e) => {}
|
|
2493
|
+
// console.error(e)
|
|
2494
|
+
// );
|
|
2495
|
+
// });
|
|
2496
|
+
// }
|
|
2497
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2498
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2499
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2500
|
+
if (element) {
|
|
2501
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2502
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2503
|
+
// await _screenshot(state, this, element);
|
|
2504
|
+
}
|
|
2180
2505
|
}
|
|
2506
|
+
await _screenshot(state, this);
|
|
2507
|
+
return state.info;
|
|
2508
|
+
}
|
|
2509
|
+
catch (error) {
|
|
2510
|
+
console.error(error);
|
|
2181
2511
|
}
|
|
2182
|
-
await _screenshot(state, this);
|
|
2183
|
-
return state.info;
|
|
2184
2512
|
}
|
|
2185
2513
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2186
2514
|
}
|
|
@@ -2188,7 +2516,7 @@ class StableBrowser {
|
|
|
2188
2516
|
await _commandError(state, e, this);
|
|
2189
2517
|
}
|
|
2190
2518
|
finally {
|
|
2191
|
-
_commandFinally(state, this);
|
|
2519
|
+
await _commandFinally(state, this);
|
|
2192
2520
|
}
|
|
2193
2521
|
}
|
|
2194
2522
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2201,11 +2529,15 @@ class StableBrowser {
|
|
|
2201
2529
|
scroll: false,
|
|
2202
2530
|
highlight: false,
|
|
2203
2531
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2204
|
-
text: `Verify text does not exist in page`,
|
|
2532
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2533
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2205
2534
|
operation: "verifyTextNotExistInPage",
|
|
2206
2535
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2207
2536
|
};
|
|
2208
|
-
|
|
2537
|
+
if (testForRegex(text)) {
|
|
2538
|
+
text = text.replace(/\\"/g, '"');
|
|
2539
|
+
}
|
|
2540
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2209
2541
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2210
2542
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2211
2543
|
if (newValue !== text) {
|
|
@@ -2217,8 +2549,16 @@ class StableBrowser {
|
|
|
2217
2549
|
try {
|
|
2218
2550
|
await _preCommand(state, this);
|
|
2219
2551
|
state.info.text = text;
|
|
2552
|
+
let resultWithElementsFound = {
|
|
2553
|
+
length: null, // initial cannot be 0
|
|
2554
|
+
};
|
|
2220
2555
|
while (true) {
|
|
2221
|
-
|
|
2556
|
+
try {
|
|
2557
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2558
|
+
}
|
|
2559
|
+
catch (error) {
|
|
2560
|
+
// ignore
|
|
2561
|
+
}
|
|
2222
2562
|
if (resultWithElementsFound.length === 0) {
|
|
2223
2563
|
await _screenshot(state, this);
|
|
2224
2564
|
return state.info;
|
|
@@ -2233,7 +2573,7 @@ class StableBrowser {
|
|
|
2233
2573
|
await _commandError(state, e, this);
|
|
2234
2574
|
}
|
|
2235
2575
|
finally {
|
|
2236
|
-
_commandFinally(state, this);
|
|
2576
|
+
await _commandFinally(state, this);
|
|
2237
2577
|
}
|
|
2238
2578
|
}
|
|
2239
2579
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2248,10 +2588,11 @@ class StableBrowser {
|
|
|
2248
2588
|
highlight: false,
|
|
2249
2589
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2250
2590
|
text: `Verify text with relation to another text`,
|
|
2591
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2251
2592
|
operation: "verify_text_with_relation",
|
|
2252
2593
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2253
2594
|
};
|
|
2254
|
-
const timeout = this.
|
|
2595
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2255
2596
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2256
2597
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2257
2598
|
if (newValue !== textAnchor) {
|
|
@@ -2269,8 +2610,16 @@ class StableBrowser {
|
|
|
2269
2610
|
try {
|
|
2270
2611
|
await _preCommand(state, this);
|
|
2271
2612
|
state.info.text = textToVerify;
|
|
2613
|
+
let resultWithElementsFound = {
|
|
2614
|
+
length: 0,
|
|
2615
|
+
};
|
|
2272
2616
|
while (true) {
|
|
2273
|
-
|
|
2617
|
+
try {
|
|
2618
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2619
|
+
}
|
|
2620
|
+
catch (error) {
|
|
2621
|
+
// ignore
|
|
2622
|
+
}
|
|
2274
2623
|
if (resultWithElementsFound.length === 0) {
|
|
2275
2624
|
if (Date.now() - state.startTime > timeout) {
|
|
2276
2625
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2278,37 +2627,56 @@ class StableBrowser {
|
|
|
2278
2627
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2279
2628
|
continue;
|
|
2280
2629
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2630
|
+
try {
|
|
2631
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2632
|
+
foundAncore = true;
|
|
2633
|
+
const result = resultWithElementsFound[i];
|
|
2634
|
+
const token = result.randomToken;
|
|
2635
|
+
const frame = result.frame;
|
|
2636
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2637
|
+
const climbArray1 = [];
|
|
2638
|
+
for (let i = 0; i < climb; i++) {
|
|
2639
|
+
climbArray1.push("..");
|
|
2640
|
+
}
|
|
2641
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2642
|
+
css = css + " >> " + climbXpath;
|
|
2643
|
+
const count = await frame.locator(css).count();
|
|
2644
|
+
for (let j = 0; j < count; j++) {
|
|
2645
|
+
const continer = await frame.locator(css).nth(j);
|
|
2646
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2647
|
+
if (result.elementCount > 0) {
|
|
2648
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2649
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2650
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2651
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2652
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2653
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2654
|
+
// .then(async () => {
|
|
2655
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2656
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2657
|
+
// () => {}
|
|
2658
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2659
|
+
// );
|
|
2660
|
+
// })
|
|
2661
|
+
// .catch(e);
|
|
2662
|
+
// }
|
|
2663
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2664
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2665
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2666
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2667
|
+
if (element) {
|
|
2668
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2669
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2670
|
+
}
|
|
2671
|
+
await _screenshot(state, this);
|
|
2672
|
+
return state.info;
|
|
2306
2673
|
}
|
|
2307
|
-
await _screenshot(state, this);
|
|
2308
|
-
return state.info;
|
|
2309
2674
|
}
|
|
2310
2675
|
}
|
|
2311
2676
|
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
console.error(error);
|
|
2679
|
+
}
|
|
2312
2680
|
}
|
|
2313
2681
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2314
2682
|
}
|
|
@@ -2316,9 +2684,33 @@ class StableBrowser {
|
|
|
2316
2684
|
await _commandError(state, e, this);
|
|
2317
2685
|
}
|
|
2318
2686
|
finally {
|
|
2319
|
-
_commandFinally(state, this);
|
|
2687
|
+
await _commandFinally(state, this);
|
|
2320
2688
|
}
|
|
2321
2689
|
}
|
|
2690
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2691
|
+
const frames = this.page.frames();
|
|
2692
|
+
let results = [];
|
|
2693
|
+
let ignoreCase = false;
|
|
2694
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2695
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2696
|
+
result.frame = frames[i];
|
|
2697
|
+
const climbArray = [];
|
|
2698
|
+
for (let i = 0; i < climb; i++) {
|
|
2699
|
+
climbArray.push("..");
|
|
2700
|
+
}
|
|
2701
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2702
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2703
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2704
|
+
if (count > 0) {
|
|
2705
|
+
result.elementCount = count;
|
|
2706
|
+
result.locator = newLocator;
|
|
2707
|
+
results.push(result);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
// state.info.results = results;
|
|
2711
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2712
|
+
return resultWithElementsFound;
|
|
2713
|
+
}
|
|
2322
2714
|
async visualVerification(text, options = {}, world = null) {
|
|
2323
2715
|
const startTime = Date.now();
|
|
2324
2716
|
let error = null;
|
|
@@ -2337,10 +2729,13 @@ class StableBrowser {
|
|
|
2337
2729
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2338
2730
|
info.screenshotPath = screenshotPath;
|
|
2339
2731
|
const screenshot = await this.takeScreenshot();
|
|
2340
|
-
|
|
2341
|
-
method: "
|
|
2732
|
+
let request = {
|
|
2733
|
+
method: "post",
|
|
2734
|
+
maxBodyLength: Infinity,
|
|
2342
2735
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2343
2736
|
headers: {
|
|
2737
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2738
|
+
"x-source": "aaa",
|
|
2344
2739
|
"Content-Type": "application/json",
|
|
2345
2740
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2346
2741
|
},
|
|
@@ -2349,7 +2744,7 @@ class StableBrowser {
|
|
|
2349
2744
|
screenshot: screenshot,
|
|
2350
2745
|
}),
|
|
2351
2746
|
};
|
|
2352
|
-
|
|
2747
|
+
const result = await axios.request(request);
|
|
2353
2748
|
if (result.data.status !== true) {
|
|
2354
2749
|
throw new Error("Visual validation failed");
|
|
2355
2750
|
}
|
|
@@ -2377,6 +2772,7 @@ class StableBrowser {
|
|
|
2377
2772
|
_reportToWorld(world, {
|
|
2378
2773
|
type: Types.VERIFY_VISUAL,
|
|
2379
2774
|
text: "Visual verification",
|
|
2775
|
+
_text: "Visual verification of " + text,
|
|
2380
2776
|
screenshotId,
|
|
2381
2777
|
result: error
|
|
2382
2778
|
? {
|
|
@@ -2643,6 +3039,32 @@ class StableBrowser {
|
|
|
2643
3039
|
}
|
|
2644
3040
|
return timeout;
|
|
2645
3041
|
}
|
|
3042
|
+
_getFindElementTimeout(options) {
|
|
3043
|
+
if (options && options.timeout) {
|
|
3044
|
+
return options.timeout;
|
|
3045
|
+
}
|
|
3046
|
+
if (this.configuration.find_element_timeout) {
|
|
3047
|
+
return this.configuration.find_element_timeout;
|
|
3048
|
+
}
|
|
3049
|
+
return 30000;
|
|
3050
|
+
}
|
|
3051
|
+
async saveStoreState(path = null, world = null) {
|
|
3052
|
+
const storageState = await this.page.context().storageState();
|
|
3053
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3054
|
+
if (path) {
|
|
3055
|
+
// save { storageState: storageState } into the path
|
|
3056
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3057
|
+
}
|
|
3058
|
+
else {
|
|
3059
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
async restoreSaveState(path = null, world = null) {
|
|
3063
|
+
await refreshBrowser(this, path, world);
|
|
3064
|
+
this.registerEventListeners(this.context);
|
|
3065
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3066
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3067
|
+
}
|
|
2646
3068
|
async waitForPageLoad(options = {}, world = null) {
|
|
2647
3069
|
let timeout = this._getLoadTimeout(options);
|
|
2648
3070
|
const promiseArray = [];
|
|
@@ -2710,6 +3132,7 @@ class StableBrowser {
|
|
|
2710
3132
|
highlight: false,
|
|
2711
3133
|
type: Types.CLOSE_PAGE,
|
|
2712
3134
|
text: `Close page`,
|
|
3135
|
+
_text: `Close the page`,
|
|
2713
3136
|
operation: "closePage",
|
|
2714
3137
|
log: "***** close page *****\n",
|
|
2715
3138
|
throwError: false,
|
|
@@ -2723,11 +3146,98 @@ class StableBrowser {
|
|
|
2723
3146
|
await _commandError(state, e, this);
|
|
2724
3147
|
}
|
|
2725
3148
|
finally {
|
|
2726
|
-
_commandFinally(state, this);
|
|
3149
|
+
await _commandFinally(state, this);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3153
|
+
let operation = null;
|
|
3154
|
+
if (!options || !options.operation) {
|
|
3155
|
+
throw new Error("operation is not defined");
|
|
3156
|
+
}
|
|
3157
|
+
operation = options.operation;
|
|
3158
|
+
// validate operation is one of the supported operations
|
|
3159
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3160
|
+
throw new Error("operation is not supported");
|
|
3161
|
+
}
|
|
3162
|
+
const state = {
|
|
3163
|
+
options,
|
|
3164
|
+
world,
|
|
3165
|
+
locate: false,
|
|
3166
|
+
scroll: false,
|
|
3167
|
+
highlight: false,
|
|
3168
|
+
type: Types.TABLE_OPERATION,
|
|
3169
|
+
text: `Table operation`,
|
|
3170
|
+
_text: `Table ${operation} operation`,
|
|
3171
|
+
operation: operation,
|
|
3172
|
+
log: "***** Table operation *****\n",
|
|
3173
|
+
};
|
|
3174
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3175
|
+
try {
|
|
3176
|
+
await _preCommand(state, this);
|
|
3177
|
+
const start = Date.now();
|
|
3178
|
+
let cellArea = null;
|
|
3179
|
+
while (true) {
|
|
3180
|
+
try {
|
|
3181
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3182
|
+
if (cellArea) {
|
|
3183
|
+
break;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
catch (e) {
|
|
3187
|
+
// ignore
|
|
3188
|
+
}
|
|
3189
|
+
if (Date.now() - start > timeout) {
|
|
3190
|
+
throw new Error(`Cell not found in table`);
|
|
3191
|
+
}
|
|
3192
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3193
|
+
}
|
|
3194
|
+
switch (operation) {
|
|
3195
|
+
case "click":
|
|
3196
|
+
if (!options.css) {
|
|
3197
|
+
// will click in the center of the cell
|
|
3198
|
+
let xOffset = 0;
|
|
3199
|
+
let yOffset = 0;
|
|
3200
|
+
if (options.xOffset) {
|
|
3201
|
+
xOffset = options.xOffset;
|
|
3202
|
+
}
|
|
3203
|
+
if (options.yOffset) {
|
|
3204
|
+
yOffset = options.yOffset;
|
|
3205
|
+
}
|
|
3206
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3207
|
+
}
|
|
3208
|
+
else {
|
|
3209
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3210
|
+
if (results.length === 0) {
|
|
3211
|
+
throw new Error(`Element not found in cell area`);
|
|
3212
|
+
}
|
|
3213
|
+
state.element = results[0];
|
|
3214
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3215
|
+
}
|
|
3216
|
+
break;
|
|
3217
|
+
case "hover+click":
|
|
3218
|
+
if (!options.css) {
|
|
3219
|
+
throw new Error("css is not defined");
|
|
3220
|
+
}
|
|
3221
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3222
|
+
if (results.length === 0) {
|
|
3223
|
+
throw new Error(`Element not found in cell area`);
|
|
3224
|
+
}
|
|
3225
|
+
state.element = results[0];
|
|
3226
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3227
|
+
break;
|
|
3228
|
+
default:
|
|
3229
|
+
throw new Error("operation is not supported");
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
catch (e) {
|
|
3233
|
+
await _commandError(state, e, this);
|
|
3234
|
+
}
|
|
3235
|
+
finally {
|
|
3236
|
+
await _commandFinally(state, this);
|
|
2727
3237
|
}
|
|
2728
3238
|
}
|
|
2729
3239
|
saveTestDataAsGlobal(options, world) {
|
|
2730
|
-
const dataFile =
|
|
3240
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2731
3241
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2732
3242
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2733
3243
|
}
|
|
@@ -2757,6 +3267,7 @@ class StableBrowser {
|
|
|
2757
3267
|
_reportToWorld(world, {
|
|
2758
3268
|
type: Types.SET_VIEWPORT,
|
|
2759
3269
|
text: "set viewport size to " + width + "x" + hight,
|
|
3270
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2760
3271
|
screenshotId,
|
|
2761
3272
|
result: error
|
|
2762
3273
|
? {
|
|
@@ -2844,6 +3355,9 @@ class StableBrowser {
|
|
|
2844
3355
|
else {
|
|
2845
3356
|
this.stepName = "step " + this.stepIndex;
|
|
2846
3357
|
}
|
|
3358
|
+
if (this.context) {
|
|
3359
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3360
|
+
}
|
|
2847
3361
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2848
3362
|
if (this.context.browserObject.context) {
|
|
2849
3363
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2856,6 +3370,41 @@ class StableBrowser {
|
|
|
2856
3370
|
this.saveTestDataAsGlobal({}, world);
|
|
2857
3371
|
}
|
|
2858
3372
|
}
|
|
3373
|
+
if (this.initSnapshotTaken === false) {
|
|
3374
|
+
this.initSnapshotTaken = true;
|
|
3375
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3376
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3377
|
+
if (snapshot) {
|
|
3378
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
async getAriaSnapshot() {
|
|
3384
|
+
try {
|
|
3385
|
+
// find the page url
|
|
3386
|
+
const url = await this.page.url();
|
|
3387
|
+
// extract the path from the url
|
|
3388
|
+
const path = new URL(url).pathname;
|
|
3389
|
+
// get the page title
|
|
3390
|
+
const title = await this.page.title();
|
|
3391
|
+
// go over other frams
|
|
3392
|
+
const frames = this.page.frames();
|
|
3393
|
+
const snapshots = [];
|
|
3394
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3395
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3396
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3397
|
+
content.push(`- frame: ${i}`);
|
|
3398
|
+
const frame = frames[i];
|
|
3399
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3400
|
+
content.push(snapshot);
|
|
3401
|
+
}
|
|
3402
|
+
return content.join("\n");
|
|
3403
|
+
}
|
|
3404
|
+
catch (e) {
|
|
3405
|
+
console.error(e);
|
|
3406
|
+
}
|
|
3407
|
+
return null;
|
|
2859
3408
|
}
|
|
2860
3409
|
async afterStep(world, step) {
|
|
2861
3410
|
this.stepName = null;
|
|
@@ -2864,6 +3413,23 @@ class StableBrowser {
|
|
|
2864
3413
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2865
3414
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2866
3415
|
});
|
|
3416
|
+
if (world && world.attach) {
|
|
3417
|
+
await world.attach(JSON.stringify({
|
|
3418
|
+
type: "trace",
|
|
3419
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3420
|
+
}), "application/json+trace");
|
|
3421
|
+
}
|
|
3422
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (this.context) {
|
|
3426
|
+
this.context.examplesRow = null;
|
|
3427
|
+
}
|
|
3428
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3429
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3430
|
+
if (snapshot) {
|
|
3431
|
+
const obj = {};
|
|
3432
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
2867
3433
|
}
|
|
2868
3434
|
}
|
|
2869
3435
|
}
|