automation_model 1.0.577-dev → 1.0.577-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 +36 -19
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +105 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +48 -24
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +42 -20
- 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/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 +27 -7
- package/lib/stable_browser.js +755 -239
- 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 +10 -3
- package/lib/utils.js +360 -21
- package/lib/utils.js.map +1 -1
- package/package.json +7 -8
- package/lib/scripts/find_text.js +0 -126
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 { _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",
|
|
@@ -53,6 +57,9 @@ export const Types = {
|
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
54
58
|
};
|
|
55
59
|
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
62
|
+
};
|
|
56
63
|
class StableBrowser {
|
|
57
64
|
browser;
|
|
58
65
|
page;
|
|
@@ -66,6 +73,7 @@ class StableBrowser {
|
|
|
66
73
|
appName = "main";
|
|
67
74
|
tags = null;
|
|
68
75
|
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
69
77
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
78
|
this.browser = browser;
|
|
71
79
|
this.page = page;
|
|
@@ -236,6 +244,9 @@ class StableBrowser {
|
|
|
236
244
|
// await closeUnexpectedPopups(this.page);
|
|
237
245
|
// }
|
|
238
246
|
async goto(url, world = null) {
|
|
247
|
+
if (!url) {
|
|
248
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
249
|
+
}
|
|
239
250
|
if (!url.startsWith("http")) {
|
|
240
251
|
url = "https://" + url;
|
|
241
252
|
}
|
|
@@ -327,66 +338,60 @@ class StableBrowser {
|
|
|
327
338
|
if (css && css.locator) {
|
|
328
339
|
css = css.locator;
|
|
329
340
|
}
|
|
330
|
-
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
341
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
331
342
|
if (result.elementCount === 0) {
|
|
332
343
|
return;
|
|
333
344
|
}
|
|
334
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
335
346
|
// css climb to parent element
|
|
336
347
|
const climbArray = [];
|
|
337
348
|
for (let i = 0; i < climb; i++) {
|
|
338
349
|
climbArray.push("..");
|
|
339
350
|
}
|
|
340
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const pattern = text.slice(1, -1);
|
|
364
|
-
new RegExp(pattern); // Validate pattern
|
|
365
|
-
text = pattern;
|
|
366
|
-
options.singleRegex = true;
|
|
367
|
-
}
|
|
368
|
-
catch (e) {
|
|
369
|
-
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const elements = window.findMatchingElements(text, options);
|
|
373
|
-
let randomToken = null;
|
|
374
|
-
const foundElements = [];
|
|
375
|
-
let elementCount = 0;
|
|
376
|
-
if (elements.length > 0) {
|
|
377
|
-
for (let i = 0; i < elements.length; i++) {
|
|
378
|
-
if (randomToken === null) {
|
|
379
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
352
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
353
|
+
if (css) {
|
|
354
|
+
resultCss = resultCss + " >> " + css;
|
|
355
|
+
}
|
|
356
|
+
return resultCss;
|
|
357
|
+
}
|
|
358
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
359
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
360
|
+
const locator = scope.locator(query);
|
|
361
|
+
const count = await locator.count();
|
|
362
|
+
if (!tag1) {
|
|
363
|
+
tag1 = "*";
|
|
364
|
+
}
|
|
365
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
366
|
+
let tagCount = 0;
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const element = locator.nth(i);
|
|
369
|
+
// check if the tag matches
|
|
370
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
371
|
+
if (!tag.startsWith("*")) {
|
|
372
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
373
|
+
return false;
|
|
380
374
|
}
|
|
381
|
-
let element = elements[i];
|
|
382
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
383
|
-
elementCount++;
|
|
384
375
|
}
|
|
376
|
+
if (!el.setAttribute) {
|
|
377
|
+
el = el.parentElement;
|
|
378
|
+
}
|
|
379
|
+
// remove any attributes start with data-blinq-id
|
|
380
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
381
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
382
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
383
|
+
// }
|
|
384
|
+
// }
|
|
385
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
386
|
+
return true;
|
|
387
|
+
}, [tag1, randomToken]))) {
|
|
388
|
+
continue;
|
|
385
389
|
}
|
|
386
|
-
|
|
387
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
388
393
|
}
|
|
389
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
394
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
390
395
|
if (!info) {
|
|
391
396
|
info = {};
|
|
392
397
|
}
|
|
@@ -409,23 +414,24 @@ class StableBrowser {
|
|
|
409
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
410
415
|
let locator = null;
|
|
411
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
412
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
413
419
|
if (!locatorString) {
|
|
414
420
|
info.failCause.textNotFound = true;
|
|
415
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
416
422
|
return;
|
|
417
423
|
}
|
|
418
424
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
419
425
|
}
|
|
420
426
|
else if (locatorSearch.text) {
|
|
421
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
422
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
428
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
423
429
|
if (result.elementCount === 0) {
|
|
424
430
|
info.failCause.textNotFound = true;
|
|
425
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
426
432
|
return;
|
|
427
433
|
}
|
|
428
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
429
435
|
if (locatorSearch.childCss) {
|
|
430
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
431
437
|
}
|
|
@@ -461,7 +467,7 @@ class StableBrowser {
|
|
|
461
467
|
if (!visibleOnly) {
|
|
462
468
|
visible = true;
|
|
463
469
|
}
|
|
464
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
465
471
|
foundLocators.push(locator.nth(j));
|
|
466
472
|
if (info.locatorLog) {
|
|
467
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -474,9 +480,11 @@ class StableBrowser {
|
|
|
474
480
|
info.printMessages = {};
|
|
475
481
|
}
|
|
476
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
477
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
478
485
|
}
|
|
479
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
480
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
481
489
|
}
|
|
482
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -544,7 +552,7 @@ class StableBrowser {
|
|
|
544
552
|
}
|
|
545
553
|
return { rerun: false };
|
|
546
554
|
}
|
|
547
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
548
556
|
if (!timeout) {
|
|
549
557
|
timeout = 30000;
|
|
550
558
|
}
|
|
@@ -554,9 +562,30 @@ class StableBrowser {
|
|
|
554
562
|
let selector = selectors.locators[j];
|
|
555
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
556
564
|
}
|
|
557
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
558
566
|
if (!element.rerun) {
|
|
559
|
-
|
|
567
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
+
element.evaluate((el, randomToken) => {
|
|
569
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
|
+
}, randomToken);
|
|
571
|
+
// if (element._frame) {
|
|
572
|
+
// return element;
|
|
573
|
+
// }
|
|
574
|
+
const scope = element._frame ?? element.page();
|
|
575
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
576
|
+
let prefixSelector = "";
|
|
577
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
578
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
579
|
+
if (frameSelectorIndex !== -1) {
|
|
580
|
+
// remove everything after the >> internal:control=enter-frame
|
|
581
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
582
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
583
|
+
}
|
|
584
|
+
// if (element?._frame?._selector) {
|
|
585
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
586
|
+
// }
|
|
587
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
588
|
+
return scope.locator(newSelector);
|
|
560
589
|
}
|
|
561
590
|
}
|
|
562
591
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -629,7 +658,7 @@ class StableBrowser {
|
|
|
629
658
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
630
659
|
if (Date.now() - startTime > timeout) {
|
|
631
660
|
info.failCause.iframeNotFound = true;
|
|
632
|
-
info.failCause.lastError =
|
|
661
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
633
662
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
634
663
|
}
|
|
635
664
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -654,7 +683,7 @@ class StableBrowser {
|
|
|
654
683
|
return bodyContent;
|
|
655
684
|
});
|
|
656
685
|
}
|
|
657
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
686
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
658
687
|
if (!info) {
|
|
659
688
|
info = {};
|
|
660
689
|
info.failCause = {};
|
|
@@ -703,18 +732,13 @@ class StableBrowser {
|
|
|
703
732
|
}
|
|
704
733
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
705
734
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
706
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
735
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
707
736
|
if (result.foundElements.length === 0) {
|
|
708
737
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
709
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
738
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
710
739
|
}
|
|
711
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
712
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
713
|
-
}
|
|
714
|
-
else {
|
|
715
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
716
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
717
|
-
}
|
|
740
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
741
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
718
742
|
}
|
|
719
743
|
let foundElements = result.foundElements;
|
|
720
744
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -758,7 +782,7 @@ class StableBrowser {
|
|
|
758
782
|
break;
|
|
759
783
|
}
|
|
760
784
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
761
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
785
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
762
786
|
highPriorityOnly = false;
|
|
763
787
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
764
788
|
lazy_scroll = true;
|
|
@@ -766,10 +790,15 @@ class StableBrowser {
|
|
|
766
790
|
}
|
|
767
791
|
}
|
|
768
792
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
769
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
793
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
770
794
|
visibleOnly = false;
|
|
771
795
|
}
|
|
772
796
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
797
|
+
// sheck of more of half of the timeout has passed
|
|
798
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
799
|
+
highPriorityOnly = false;
|
|
800
|
+
visibleOnly = false;
|
|
801
|
+
}
|
|
773
802
|
}
|
|
774
803
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
775
804
|
// if (info.locatorLog) {
|
|
@@ -780,10 +809,12 @@ class StableBrowser {
|
|
|
780
809
|
// }
|
|
781
810
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
782
811
|
info.failCause.locatorNotFound = true;
|
|
783
|
-
info
|
|
812
|
+
if (!info?.failCause?.lastError) {
|
|
813
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
814
|
+
}
|
|
784
815
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
785
816
|
}
|
|
786
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
817
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
787
818
|
let foundElements = [];
|
|
788
819
|
const result = {
|
|
789
820
|
foundElements: foundElements,
|
|
@@ -791,7 +822,7 @@ class StableBrowser {
|
|
|
791
822
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
792
823
|
let foundLocators = [];
|
|
793
824
|
try {
|
|
794
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
825
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
795
826
|
}
|
|
796
827
|
catch (e) {
|
|
797
828
|
// this call can fail it the browser is navigating
|
|
@@ -799,7 +830,7 @@ class StableBrowser {
|
|
|
799
830
|
// this.logger.debug(e);
|
|
800
831
|
foundLocators = [];
|
|
801
832
|
try {
|
|
802
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
833
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
803
834
|
}
|
|
804
835
|
catch (e) {
|
|
805
836
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -814,9 +845,40 @@ class StableBrowser {
|
|
|
814
845
|
result.locatorIndex = i;
|
|
815
846
|
}
|
|
816
847
|
if (foundLocators.length > 1) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
848
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
849
|
+
const boxes = [];
|
|
850
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
851
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
852
|
+
}
|
|
853
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
854
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
855
|
+
if (j === k) {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
859
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
860
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
861
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
862
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
863
|
+
// as the element is not unique, will remove it
|
|
864
|
+
boxes.splice(k, 1);
|
|
865
|
+
k--;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (boxes.length === 1) {
|
|
870
|
+
result.foundElements.push({
|
|
871
|
+
locator: boxes[0].locator.first(),
|
|
872
|
+
box: boxes[0].box,
|
|
873
|
+
unique: true,
|
|
874
|
+
});
|
|
875
|
+
result.locatorIndex = i;
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
info.failCause.foundMultiple = true;
|
|
879
|
+
if (info.locatorLog) {
|
|
880
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
881
|
+
}
|
|
820
882
|
}
|
|
821
883
|
}
|
|
822
884
|
}
|
|
@@ -927,25 +989,14 @@ class StableBrowser {
|
|
|
927
989
|
options,
|
|
928
990
|
world,
|
|
929
991
|
text: "Click element",
|
|
992
|
+
_text: "Click on " + selectors.element_name,
|
|
930
993
|
type: Types.CLICK,
|
|
931
994
|
operation: "click",
|
|
932
995
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
933
996
|
};
|
|
934
997
|
try {
|
|
935
998
|
await _preCommand(state, this);
|
|
936
|
-
|
|
937
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
938
|
-
// }
|
|
939
|
-
try {
|
|
940
|
-
await state.element.click();
|
|
941
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
942
|
-
}
|
|
943
|
-
catch (e) {
|
|
944
|
-
// await this.closeUnexpectedPopups();
|
|
945
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
946
|
-
await state.element.dispatchEvent("click");
|
|
947
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
948
|
-
}
|
|
999
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
949
1000
|
await this.waitForPageLoad();
|
|
950
1001
|
return state.info;
|
|
951
1002
|
}
|
|
@@ -956,6 +1007,38 @@ class StableBrowser {
|
|
|
956
1007
|
_commandFinally(state, this);
|
|
957
1008
|
}
|
|
958
1009
|
}
|
|
1010
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1011
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1012
|
+
const state = {
|
|
1013
|
+
selectors,
|
|
1014
|
+
_params,
|
|
1015
|
+
options,
|
|
1016
|
+
world,
|
|
1017
|
+
text: "Wait for element",
|
|
1018
|
+
_text: "Wait for " + selectors.element_name,
|
|
1019
|
+
type: Types.WAIT_ELEMENT,
|
|
1020
|
+
operation: "waitForElement",
|
|
1021
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1022
|
+
};
|
|
1023
|
+
let found = false;
|
|
1024
|
+
try {
|
|
1025
|
+
await _preCommand(state, this);
|
|
1026
|
+
// if (state.options && state.options.context) {
|
|
1027
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1028
|
+
// }
|
|
1029
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1030
|
+
found = true;
|
|
1031
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1032
|
+
}
|
|
1033
|
+
catch (e) {
|
|
1034
|
+
console.error("Error on waitForElement", e);
|
|
1035
|
+
// await _commandError(state, e, this);
|
|
1036
|
+
}
|
|
1037
|
+
finally {
|
|
1038
|
+
_commandFinally(state, this);
|
|
1039
|
+
}
|
|
1040
|
+
return found;
|
|
1041
|
+
}
|
|
959
1042
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
960
1043
|
const state = {
|
|
961
1044
|
selectors,
|
|
@@ -964,6 +1047,7 @@ class StableBrowser {
|
|
|
964
1047
|
world,
|
|
965
1048
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
966
1049
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1050
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
967
1051
|
operation: "setCheck",
|
|
968
1052
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
969
1053
|
};
|
|
@@ -973,9 +1057,15 @@ class StableBrowser {
|
|
|
973
1057
|
// let element = await this._locate(selectors, info, _params);
|
|
974
1058
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
975
1059
|
try {
|
|
976
|
-
//
|
|
1060
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1061
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1062
|
+
await this._highlightElements(state.element);
|
|
977
1063
|
await state.element.setChecked(checked);
|
|
978
1064
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1065
|
+
// await this._unHighlightElements(element);
|
|
1066
|
+
// }
|
|
1067
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1068
|
+
// await this._unHighlightElements(element);
|
|
979
1069
|
}
|
|
980
1070
|
catch (e) {
|
|
981
1071
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1007,22 +1097,13 @@ class StableBrowser {
|
|
|
1007
1097
|
world,
|
|
1008
1098
|
type: Types.HOVER,
|
|
1009
1099
|
text: `Hover element`,
|
|
1100
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1010
1101
|
operation: "hover",
|
|
1011
1102
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1012
1103
|
};
|
|
1013
1104
|
try {
|
|
1014
1105
|
await _preCommand(state, this);
|
|
1015
|
-
|
|
1016
|
-
await state.element.hover();
|
|
1017
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1018
|
-
}
|
|
1019
|
-
catch (e) {
|
|
1020
|
-
//await this.closeUnexpectedPopups();
|
|
1021
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1022
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1023
|
-
await state.element.hover({ timeout: 10000 });
|
|
1024
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1025
|
-
}
|
|
1106
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1026
1107
|
await _screenshot(state, this);
|
|
1027
1108
|
await this.waitForPageLoad();
|
|
1028
1109
|
return state.info;
|
|
@@ -1046,6 +1127,7 @@ class StableBrowser {
|
|
|
1046
1127
|
value: values.toString(),
|
|
1047
1128
|
type: Types.SELECT,
|
|
1048
1129
|
text: `Select option: ${values}`,
|
|
1130
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1049
1131
|
operation: "selectOption",
|
|
1050
1132
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1051
1133
|
};
|
|
@@ -1080,6 +1162,7 @@ class StableBrowser {
|
|
|
1080
1162
|
highlight: false,
|
|
1081
1163
|
type: Types.TYPE_PRESS,
|
|
1082
1164
|
text: `Type value: ${_value}`,
|
|
1165
|
+
_text: `Type value: ${_value}`,
|
|
1083
1166
|
operation: "type",
|
|
1084
1167
|
log: "",
|
|
1085
1168
|
};
|
|
@@ -1159,6 +1242,7 @@ class StableBrowser {
|
|
|
1159
1242
|
world,
|
|
1160
1243
|
type: Types.SET_DATE_TIME,
|
|
1161
1244
|
text: `Set date time value: ${value}`,
|
|
1245
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1162
1246
|
operation: "setDateTime",
|
|
1163
1247
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1164
1248
|
throwError: false,
|
|
@@ -1166,7 +1250,7 @@ class StableBrowser {
|
|
|
1166
1250
|
try {
|
|
1167
1251
|
await _preCommand(state, this);
|
|
1168
1252
|
try {
|
|
1169
|
-
await state.element
|
|
1253
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1170
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1171
1255
|
if (format) {
|
|
1172
1256
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1230,9 +1314,13 @@ class StableBrowser {
|
|
|
1230
1314
|
world,
|
|
1231
1315
|
type: Types.FILL,
|
|
1232
1316
|
text: `Click type input with value: ${_value}`,
|
|
1317
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1233
1318
|
operation: "clickType",
|
|
1234
1319
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1235
1320
|
};
|
|
1321
|
+
if (!options) {
|
|
1322
|
+
options = {};
|
|
1323
|
+
}
|
|
1236
1324
|
if (newValue !== _value) {
|
|
1237
1325
|
//this.logger.info(_value + "=" + newValue);
|
|
1238
1326
|
_value = newValue;
|
|
@@ -1240,7 +1328,7 @@ class StableBrowser {
|
|
|
1240
1328
|
try {
|
|
1241
1329
|
await _preCommand(state, this);
|
|
1242
1330
|
state.info.value = _value;
|
|
1243
|
-
if (
|
|
1331
|
+
if (!options.press) {
|
|
1244
1332
|
try {
|
|
1245
1333
|
let currentValue = await state.element.inputValue();
|
|
1246
1334
|
if (currentValue) {
|
|
@@ -1251,13 +1339,9 @@ class StableBrowser {
|
|
|
1251
1339
|
this.logger.info("unable to clear input value");
|
|
1252
1340
|
}
|
|
1253
1341
|
}
|
|
1254
|
-
if (options
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
catch (e) {
|
|
1259
|
-
await state.element.dispatchEvent("click");
|
|
1260
|
-
}
|
|
1342
|
+
if (options.press) {
|
|
1343
|
+
options.timeout = 5000;
|
|
1344
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1261
1345
|
}
|
|
1262
1346
|
else {
|
|
1263
1347
|
try {
|
|
@@ -1295,7 +1379,12 @@ class StableBrowser {
|
|
|
1295
1379
|
await this.waitForPageLoad();
|
|
1296
1380
|
}
|
|
1297
1381
|
else if (enter === false) {
|
|
1298
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1384
|
+
}
|
|
1385
|
+
catch (e) {
|
|
1386
|
+
// ignore
|
|
1387
|
+
}
|
|
1299
1388
|
//await this.page.keyboard.press("Tab");
|
|
1300
1389
|
}
|
|
1301
1390
|
else {
|
|
@@ -1347,6 +1436,7 @@ class StableBrowser {
|
|
|
1347
1436
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1348
1437
|
}
|
|
1349
1438
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1439
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1350
1440
|
_validateSelectors(selectors);
|
|
1351
1441
|
let screenshotId = null;
|
|
1352
1442
|
let screenshotPath = null;
|
|
@@ -1356,7 +1446,7 @@ class StableBrowser {
|
|
|
1356
1446
|
}
|
|
1357
1447
|
info.operation = "getText";
|
|
1358
1448
|
info.selectors = selectors;
|
|
1359
|
-
let element = await this._locate(selectors, info, _params);
|
|
1449
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1360
1450
|
if (climb > 0) {
|
|
1361
1451
|
const climbArray = [];
|
|
1362
1452
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1375,6 +1465,18 @@ class StableBrowser {
|
|
|
1375
1465
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1376
1466
|
try {
|
|
1377
1467
|
await this._highlightElements(element);
|
|
1468
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1469
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1470
|
+
// this._highlightElements(element)
|
|
1471
|
+
// .then(async () => {
|
|
1472
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1473
|
+
// this._unhighlightElements(element).then(
|
|
1474
|
+
// () => {}
|
|
1475
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1476
|
+
// );
|
|
1477
|
+
// })
|
|
1478
|
+
// .catch(e);
|
|
1479
|
+
// }
|
|
1378
1480
|
const elementText = await element.innerText();
|
|
1379
1481
|
return {
|
|
1380
1482
|
text: elementText,
|
|
@@ -1386,7 +1488,7 @@ class StableBrowser {
|
|
|
1386
1488
|
}
|
|
1387
1489
|
catch (e) {
|
|
1388
1490
|
//await this.closeUnexpectedPopups();
|
|
1389
|
-
this.logger.info("no innerText will use textContent");
|
|
1491
|
+
this.logger.info("no innerText, will use textContent");
|
|
1390
1492
|
const elementText = await element.textContent();
|
|
1391
1493
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1392
1494
|
}
|
|
@@ -1411,6 +1513,7 @@ class StableBrowser {
|
|
|
1411
1513
|
highlight: false,
|
|
1412
1514
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1413
1515
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1516
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1414
1517
|
operation: "containsPattern",
|
|
1415
1518
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1416
1519
|
};
|
|
@@ -1446,6 +1549,8 @@ class StableBrowser {
|
|
|
1446
1549
|
}
|
|
1447
1550
|
}
|
|
1448
1551
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1552
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1553
|
+
const startTime = Date.now();
|
|
1449
1554
|
const state = {
|
|
1450
1555
|
selectors,
|
|
1451
1556
|
_params,
|
|
@@ -1472,62 +1577,54 @@ class StableBrowser {
|
|
|
1472
1577
|
}
|
|
1473
1578
|
let foundObj = null;
|
|
1474
1579
|
try {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1482
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1483
|
-
if (dateAlternatives.date) {
|
|
1484
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1485
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1486
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1487
|
-
return state.info;
|
|
1580
|
+
while (Date.now() - startTime < timeout) {
|
|
1581
|
+
try {
|
|
1582
|
+
await _preCommand(state, this);
|
|
1583
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1584
|
+
if (foundObj && foundObj.element) {
|
|
1585
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1488
1586
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1587
|
+
await _screenshot(state, this);
|
|
1588
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1589
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1590
|
+
if (dateAlternatives.date) {
|
|
1591
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1592
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1593
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1594
|
+
return state.info;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
else if (numberAlternatives.number) {
|
|
1599
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1600
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1601
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1602
|
+
return state.info;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1496
1607
|
return state.info;
|
|
1497
1608
|
}
|
|
1498
1609
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
throw new Error("element doesn't contain text " + text);
|
|
1610
|
+
catch (e) {
|
|
1611
|
+
// Log error but continue retrying until timeout is reached
|
|
1612
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1613
|
+
}
|
|
1614
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1505
1615
|
}
|
|
1506
|
-
|
|
1616
|
+
state.info.foundText = foundObj?.text;
|
|
1617
|
+
state.info.value = foundObj?.value;
|
|
1618
|
+
throw new Error("element doesn't contain text " + text);
|
|
1507
1619
|
}
|
|
1508
1620
|
catch (e) {
|
|
1509
1621
|
await _commandError(state, e, this);
|
|
1622
|
+
throw e;
|
|
1510
1623
|
}
|
|
1511
1624
|
finally {
|
|
1512
1625
|
_commandFinally(state, this);
|
|
1513
1626
|
}
|
|
1514
1627
|
}
|
|
1515
|
-
_getDataFile(world = null) {
|
|
1516
|
-
let dataFile = null;
|
|
1517
|
-
if (world && world.reportFolder) {
|
|
1518
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1519
|
-
}
|
|
1520
|
-
else if (this.reportFolder) {
|
|
1521
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1522
|
-
}
|
|
1523
|
-
else if (this.context && this.context.reportFolder) {
|
|
1524
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1525
|
-
}
|
|
1526
|
-
else {
|
|
1527
|
-
dataFile = "data.json";
|
|
1528
|
-
}
|
|
1529
|
-
return dataFile;
|
|
1530
|
-
}
|
|
1531
1628
|
async waitForUserInput(message, world = null) {
|
|
1532
1629
|
if (!message) {
|
|
1533
1630
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1556,13 +1653,22 @@ class StableBrowser {
|
|
|
1556
1653
|
return;
|
|
1557
1654
|
}
|
|
1558
1655
|
// if data file exists, load it
|
|
1559
|
-
const dataFile =
|
|
1656
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1560
1657
|
let data = this.getTestData(world);
|
|
1561
1658
|
// merge the testData with the existing data
|
|
1562
1659
|
Object.assign(data, testData);
|
|
1563
1660
|
// save the data to the file
|
|
1564
1661
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1565
1662
|
}
|
|
1663
|
+
overwriteTestData(testData, world = null) {
|
|
1664
|
+
if (!testData) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
// if data file exists, load it
|
|
1668
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1669
|
+
// save the data to the file
|
|
1670
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1671
|
+
}
|
|
1566
1672
|
_getDataFilePath(fileName) {
|
|
1567
1673
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1568
1674
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1659,7 +1765,7 @@ class StableBrowser {
|
|
|
1659
1765
|
}
|
|
1660
1766
|
}
|
|
1661
1767
|
getTestData(world = null) {
|
|
1662
|
-
const dataFile =
|
|
1768
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1663
1769
|
let data = {};
|
|
1664
1770
|
if (fs.existsSync(dataFile)) {
|
|
1665
1771
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1746,6 +1852,15 @@ class StableBrowser {
|
|
|
1746
1852
|
document.documentElement.clientWidth,
|
|
1747
1853
|
])));
|
|
1748
1854
|
let screenshotBuffer = null;
|
|
1855
|
+
// if (focusedElement) {
|
|
1856
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1857
|
+
// await this._unhighlightElements(focusedElement);
|
|
1858
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1859
|
+
// console.log(`Unhighlighted previous element`);
|
|
1860
|
+
// }
|
|
1861
|
+
// if (focusedElement) {
|
|
1862
|
+
// await this._highlightElements(focusedElement);
|
|
1863
|
+
// }
|
|
1749
1864
|
if (this.context.browserName === "chromium") {
|
|
1750
1865
|
const client = await playContext.newCDPSession(this.page);
|
|
1751
1866
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1767,6 +1882,10 @@ class StableBrowser {
|
|
|
1767
1882
|
else {
|
|
1768
1883
|
screenshotBuffer = await this.page.screenshot();
|
|
1769
1884
|
}
|
|
1885
|
+
// if (focusedElement) {
|
|
1886
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1887
|
+
// await this._unhighlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1770
1889
|
let image = await Jimp.read(screenshotBuffer);
|
|
1771
1890
|
// Get the image dimensions
|
|
1772
1891
|
const { width, height } = image.bitmap;
|
|
@@ -1779,6 +1898,7 @@ class StableBrowser {
|
|
|
1779
1898
|
else {
|
|
1780
1899
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1781
1900
|
}
|
|
1901
|
+
return screenshotBuffer;
|
|
1782
1902
|
}
|
|
1783
1903
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1784
1904
|
const state = {
|
|
@@ -1814,8 +1934,10 @@ class StableBrowser {
|
|
|
1814
1934
|
world,
|
|
1815
1935
|
type: Types.EXTRACT,
|
|
1816
1936
|
text: `Extract attribute from element`,
|
|
1937
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1817
1938
|
operation: "extractAttribute",
|
|
1818
1939
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1940
|
+
allowDisabled: true,
|
|
1819
1941
|
};
|
|
1820
1942
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1821
1943
|
try {
|
|
@@ -1830,6 +1952,9 @@ class StableBrowser {
|
|
|
1830
1952
|
case "value":
|
|
1831
1953
|
state.value = await state.element.inputValue();
|
|
1832
1954
|
break;
|
|
1955
|
+
case "text":
|
|
1956
|
+
state.value = await state.element.textContent();
|
|
1957
|
+
break;
|
|
1833
1958
|
default:
|
|
1834
1959
|
state.value = await state.element.getAttribute(attribute);
|
|
1835
1960
|
break;
|
|
@@ -1837,6 +1962,7 @@ class StableBrowser {
|
|
|
1837
1962
|
state.info.value = state.value;
|
|
1838
1963
|
this.setTestData({ [variable]: state.value }, world);
|
|
1839
1964
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1965
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1840
1966
|
return state.info;
|
|
1841
1967
|
}
|
|
1842
1968
|
catch (e) {
|
|
@@ -1855,18 +1981,28 @@ class StableBrowser {
|
|
|
1855
1981
|
options,
|
|
1856
1982
|
world,
|
|
1857
1983
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1984
|
+
highlight: true,
|
|
1985
|
+
screenshot: true,
|
|
1858
1986
|
text: `Verify element attribute`,
|
|
1987
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1859
1988
|
operation: "verifyAttribute",
|
|
1860
1989
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1990
|
+
allowDisabled: true,
|
|
1861
1991
|
};
|
|
1862
1992
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1863
1993
|
let val;
|
|
1994
|
+
let expectedValue;
|
|
1864
1995
|
try {
|
|
1865
1996
|
await _preCommand(state, this);
|
|
1997
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1998
|
+
state.info.expectedValue = expectedValue;
|
|
1866
1999
|
switch (attribute) {
|
|
1867
2000
|
case "innerText":
|
|
1868
2001
|
val = String(await state.element.innerText());
|
|
1869
2002
|
break;
|
|
2003
|
+
case "text":
|
|
2004
|
+
val = String(await state.element.textContent());
|
|
2005
|
+
break;
|
|
1870
2006
|
case "value":
|
|
1871
2007
|
val = String(await state.element.inputValue());
|
|
1872
2008
|
break;
|
|
@@ -1884,19 +2020,22 @@ class StableBrowser {
|
|
|
1884
2020
|
val = String(await state.element.getAttribute(attribute));
|
|
1885
2021
|
break;
|
|
1886
2022
|
}
|
|
2023
|
+
state.info.value = val;
|
|
1887
2024
|
let regex;
|
|
1888
|
-
if (
|
|
1889
|
-
const patternBody =
|
|
2025
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2026
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1890
2027
|
regex = new RegExp(patternBody, "g");
|
|
1891
2028
|
}
|
|
1892
2029
|
else {
|
|
1893
|
-
const escapedPattern =
|
|
2030
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1894
2031
|
regex = new RegExp(escapedPattern, "g");
|
|
1895
2032
|
}
|
|
1896
2033
|
if (!val.match(regex)) {
|
|
1897
|
-
|
|
2034
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2035
|
+
state.info.failCause.assertionFailed = true;
|
|
2036
|
+
state.info.failCause.lastError = errorMessage;
|
|
2037
|
+
throw new Error(errorMessage);
|
|
1898
2038
|
}
|
|
1899
|
-
state.info.expectedValue = val;
|
|
1900
2039
|
return state.info;
|
|
1901
2040
|
}
|
|
1902
2041
|
catch (e) {
|
|
@@ -1995,27 +2134,32 @@ class StableBrowser {
|
|
|
1995
2134
|
async _highlightElements(scope, css) {
|
|
1996
2135
|
try {
|
|
1997
2136
|
if (!scope) {
|
|
2137
|
+
// console.log(`Scope is not defined`);
|
|
1998
2138
|
return;
|
|
1999
2139
|
}
|
|
2000
2140
|
if (!css) {
|
|
2001
2141
|
scope
|
|
2002
2142
|
.evaluate((node) => {
|
|
2003
2143
|
if (node && node.style) {
|
|
2004
|
-
let
|
|
2005
|
-
|
|
2144
|
+
let originalOutline = node.style.outline;
|
|
2145
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2146
|
+
// node.__previousOutline = originalOutline;
|
|
2147
|
+
node.style.outline = "2px solid red";
|
|
2148
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2006
2149
|
if (window) {
|
|
2007
2150
|
window.addEventListener("beforeunload", function (e) {
|
|
2008
|
-
node.style.
|
|
2151
|
+
node.style.outline = originalOutline;
|
|
2009
2152
|
});
|
|
2010
2153
|
}
|
|
2011
2154
|
setTimeout(function () {
|
|
2012
|
-
node.style.
|
|
2155
|
+
node.style.outline = originalOutline;
|
|
2013
2156
|
}, 2000);
|
|
2014
2157
|
}
|
|
2015
2158
|
})
|
|
2016
2159
|
.then(() => { })
|
|
2017
2160
|
.catch((e) => {
|
|
2018
2161
|
// ignore
|
|
2162
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2019
2163
|
});
|
|
2020
2164
|
}
|
|
2021
2165
|
else {
|
|
@@ -2031,17 +2175,18 @@ class StableBrowser {
|
|
|
2031
2175
|
if (!element.style) {
|
|
2032
2176
|
return;
|
|
2033
2177
|
}
|
|
2034
|
-
|
|
2178
|
+
let originalOutline = element.style.outline;
|
|
2179
|
+
element.__previousOutline = originalOutline;
|
|
2035
2180
|
// Set the new border to be red and 2px solid
|
|
2036
|
-
element.style.
|
|
2181
|
+
element.style.outline = "2px solid red";
|
|
2037
2182
|
if (window) {
|
|
2038
2183
|
window.addEventListener("beforeunload", function (e) {
|
|
2039
|
-
element.style.
|
|
2184
|
+
element.style.outline = originalOutline;
|
|
2040
2185
|
});
|
|
2041
2186
|
}
|
|
2042
2187
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2043
2188
|
setTimeout(function () {
|
|
2044
|
-
element.style.
|
|
2189
|
+
element.style.outline = originalOutline;
|
|
2045
2190
|
}, 2000);
|
|
2046
2191
|
}
|
|
2047
2192
|
return;
|
|
@@ -2049,6 +2194,7 @@ class StableBrowser {
|
|
|
2049
2194
|
.then(() => { })
|
|
2050
2195
|
.catch((e) => {
|
|
2051
2196
|
// ignore
|
|
2197
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2052
2198
|
});
|
|
2053
2199
|
}
|
|
2054
2200
|
}
|
|
@@ -2056,6 +2202,54 @@ class StableBrowser {
|
|
|
2056
2202
|
console.debug(error);
|
|
2057
2203
|
}
|
|
2058
2204
|
}
|
|
2205
|
+
// async _unhighlightElements(scope, css) {
|
|
2206
|
+
// try {
|
|
2207
|
+
// if (!scope) {
|
|
2208
|
+
// return;
|
|
2209
|
+
// }
|
|
2210
|
+
// if (!css) {
|
|
2211
|
+
// scope
|
|
2212
|
+
// .evaluate((node) => {
|
|
2213
|
+
// if (node && node.style) {
|
|
2214
|
+
// if (!node.__previousOutline) {
|
|
2215
|
+
// node.style.outline = "";
|
|
2216
|
+
// } else {
|
|
2217
|
+
// node.style.outline = node.__previousOutline;
|
|
2218
|
+
// }
|
|
2219
|
+
// }
|
|
2220
|
+
// })
|
|
2221
|
+
// .then(() => {})
|
|
2222
|
+
// .catch((e) => {
|
|
2223
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2224
|
+
// });
|
|
2225
|
+
// } else {
|
|
2226
|
+
// scope
|
|
2227
|
+
// .evaluate(([css]) => {
|
|
2228
|
+
// if (!css) {
|
|
2229
|
+
// return;
|
|
2230
|
+
// }
|
|
2231
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2232
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2233
|
+
// let element = elements[i];
|
|
2234
|
+
// if (!element.style) {
|
|
2235
|
+
// return;
|
|
2236
|
+
// }
|
|
2237
|
+
// if (!element.__previousOutline) {
|
|
2238
|
+
// element.style.outline = "";
|
|
2239
|
+
// } else {
|
|
2240
|
+
// element.style.outline = element.__previousOutline;
|
|
2241
|
+
// }
|
|
2242
|
+
// }
|
|
2243
|
+
// })
|
|
2244
|
+
// .then(() => {})
|
|
2245
|
+
// .catch((e) => {
|
|
2246
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2247
|
+
// });
|
|
2248
|
+
// }
|
|
2249
|
+
// } catch (error) {
|
|
2250
|
+
// // console.debug(error);
|
|
2251
|
+
// }
|
|
2252
|
+
// }
|
|
2059
2253
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2060
2254
|
const startTime = Date.now();
|
|
2061
2255
|
let error = null;
|
|
@@ -2100,6 +2294,7 @@ class StableBrowser {
|
|
|
2100
2294
|
_reportToWorld(world, {
|
|
2101
2295
|
type: Types.VERIFY_PAGE_PATH,
|
|
2102
2296
|
text: "Verify page path",
|
|
2297
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2103
2298
|
screenshotId,
|
|
2104
2299
|
result: error
|
|
2105
2300
|
? {
|
|
@@ -2117,26 +2312,89 @@ class StableBrowser {
|
|
|
2117
2312
|
});
|
|
2118
2313
|
}
|
|
2119
2314
|
}
|
|
2120
|
-
async
|
|
2315
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2316
|
+
const startTime = Date.now();
|
|
2317
|
+
let error = null;
|
|
2318
|
+
let screenshotId = null;
|
|
2319
|
+
let screenshotPath = null;
|
|
2320
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2321
|
+
const info = {};
|
|
2322
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2323
|
+
info.operation = "verifyPageTitle";
|
|
2324
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2325
|
+
if (newValue !== title) {
|
|
2326
|
+
this.logger.info(title + "=" + newValue);
|
|
2327
|
+
title = newValue;
|
|
2328
|
+
}
|
|
2329
|
+
info.title = title;
|
|
2330
|
+
try {
|
|
2331
|
+
for (let i = 0; i < 30; i++) {
|
|
2332
|
+
const foundTitle = await this.page.title();
|
|
2333
|
+
if (!foundTitle.includes(title)) {
|
|
2334
|
+
if (i === 29) {
|
|
2335
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2336
|
+
}
|
|
2337
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2341
|
+
return info;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
catch (e) {
|
|
2345
|
+
//await this.closeUnexpectedPopups();
|
|
2346
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2347
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2348
|
+
info.screenshotPath = screenshotPath;
|
|
2349
|
+
Object.assign(e, { info: info });
|
|
2350
|
+
error = e;
|
|
2351
|
+
// throw e;
|
|
2352
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2353
|
+
}
|
|
2354
|
+
finally {
|
|
2355
|
+
const endTime = Date.now();
|
|
2356
|
+
_reportToWorld(world, {
|
|
2357
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2358
|
+
text: "Verify page title",
|
|
2359
|
+
_text: "Verify the page title contains " + title,
|
|
2360
|
+
screenshotId,
|
|
2361
|
+
result: error
|
|
2362
|
+
? {
|
|
2363
|
+
status: "FAILED",
|
|
2364
|
+
startTime,
|
|
2365
|
+
endTime,
|
|
2366
|
+
message: error?.message,
|
|
2367
|
+
}
|
|
2368
|
+
: {
|
|
2369
|
+
status: "PASSED",
|
|
2370
|
+
startTime,
|
|
2371
|
+
endTime,
|
|
2372
|
+
},
|
|
2373
|
+
info: info,
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2121
2378
|
const frames = this.page.frames();
|
|
2122
2379
|
let results = [];
|
|
2380
|
+
// let ignoreCase = false;
|
|
2123
2381
|
for (let i = 0; i < frames.length; i++) {
|
|
2124
2382
|
if (dateAlternatives.date) {
|
|
2125
2383
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2126
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2384
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2127
2385
|
result.frame = frames[i];
|
|
2128
2386
|
results.push(result);
|
|
2129
2387
|
}
|
|
2130
2388
|
}
|
|
2131
2389
|
else if (numberAlternatives.number) {
|
|
2132
2390
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2133
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2391
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2134
2392
|
result.frame = frames[i];
|
|
2135
2393
|
results.push(result);
|
|
2136
2394
|
}
|
|
2137
2395
|
}
|
|
2138
2396
|
else {
|
|
2139
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2397
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2140
2398
|
result.frame = frames[i];
|
|
2141
2399
|
results.push(result);
|
|
2142
2400
|
}
|
|
@@ -2155,11 +2413,15 @@ class StableBrowser {
|
|
|
2155
2413
|
scroll: false,
|
|
2156
2414
|
highlight: false,
|
|
2157
2415
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2158
|
-
text: `Verify text exists in page`,
|
|
2416
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2417
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2159
2418
|
operation: "verifyTextExistInPage",
|
|
2160
2419
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2161
2420
|
};
|
|
2162
|
-
|
|
2421
|
+
if (testForRegex(text)) {
|
|
2422
|
+
text = text.replace(/\\"/g, '"');
|
|
2423
|
+
}
|
|
2424
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2163
2425
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2164
2426
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2165
2427
|
if (newValue !== text) {
|
|
@@ -2172,7 +2434,15 @@ class StableBrowser {
|
|
|
2172
2434
|
await _preCommand(state, this);
|
|
2173
2435
|
state.info.text = text;
|
|
2174
2436
|
while (true) {
|
|
2175
|
-
|
|
2437
|
+
let resultWithElementsFound = {
|
|
2438
|
+
length: 0,
|
|
2439
|
+
};
|
|
2440
|
+
try {
|
|
2441
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2442
|
+
}
|
|
2443
|
+
catch (error) {
|
|
2444
|
+
// ignore
|
|
2445
|
+
}
|
|
2176
2446
|
if (resultWithElementsFound.length === 0) {
|
|
2177
2447
|
if (Date.now() - state.startTime > timeout) {
|
|
2178
2448
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2180,18 +2450,40 @@ class StableBrowser {
|
|
|
2180
2450
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2181
2451
|
continue;
|
|
2182
2452
|
}
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2453
|
+
try {
|
|
2454
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2455
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2456
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2457
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2458
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2459
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2460
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2461
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2462
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2463
|
+
// .then(async () => {
|
|
2464
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2465
|
+
// })
|
|
2466
|
+
// .catch(
|
|
2467
|
+
// (e) => {}
|
|
2468
|
+
// console.error(e)
|
|
2469
|
+
// );
|
|
2470
|
+
// });
|
|
2471
|
+
// }
|
|
2472
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2473
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2474
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2475
|
+
if (element) {
|
|
2476
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2477
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2478
|
+
// await _screenshot(state, this, element);
|
|
2479
|
+
}
|
|
2191
2480
|
}
|
|
2481
|
+
await _screenshot(state, this);
|
|
2482
|
+
return state.info;
|
|
2483
|
+
}
|
|
2484
|
+
catch (error) {
|
|
2485
|
+
console.error(error);
|
|
2192
2486
|
}
|
|
2193
|
-
await _screenshot(state, this);
|
|
2194
|
-
return state.info;
|
|
2195
2487
|
}
|
|
2196
2488
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2197
2489
|
}
|
|
@@ -2212,11 +2504,15 @@ class StableBrowser {
|
|
|
2212
2504
|
scroll: false,
|
|
2213
2505
|
highlight: false,
|
|
2214
2506
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2215
|
-
text: `Verify text does not exist in page`,
|
|
2507
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2508
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2216
2509
|
operation: "verifyTextNotExistInPage",
|
|
2217
2510
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2218
2511
|
};
|
|
2219
|
-
|
|
2512
|
+
if (testForRegex(text)) {
|
|
2513
|
+
text = text.replace(/\\"/g, '"');
|
|
2514
|
+
}
|
|
2515
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2220
2516
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2221
2517
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2222
2518
|
if (newValue !== text) {
|
|
@@ -2228,8 +2524,16 @@ class StableBrowser {
|
|
|
2228
2524
|
try {
|
|
2229
2525
|
await _preCommand(state, this);
|
|
2230
2526
|
state.info.text = text;
|
|
2527
|
+
let resultWithElementsFound = {
|
|
2528
|
+
length: null, // initial cannot be 0
|
|
2529
|
+
};
|
|
2231
2530
|
while (true) {
|
|
2232
|
-
|
|
2531
|
+
try {
|
|
2532
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2533
|
+
}
|
|
2534
|
+
catch (error) {
|
|
2535
|
+
// ignore
|
|
2536
|
+
}
|
|
2233
2537
|
if (resultWithElementsFound.length === 0) {
|
|
2234
2538
|
await _screenshot(state, this);
|
|
2235
2539
|
return state.info;
|
|
@@ -2259,10 +2563,11 @@ class StableBrowser {
|
|
|
2259
2563
|
highlight: false,
|
|
2260
2564
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2261
2565
|
text: `Verify text with relation to another text`,
|
|
2566
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2262
2567
|
operation: "verify_text_with_relation",
|
|
2263
2568
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2264
2569
|
};
|
|
2265
|
-
const timeout = this.
|
|
2570
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2266
2571
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2267
2572
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2268
2573
|
if (newValue !== textAnchor) {
|
|
@@ -2280,8 +2585,16 @@ class StableBrowser {
|
|
|
2280
2585
|
try {
|
|
2281
2586
|
await _preCommand(state, this);
|
|
2282
2587
|
state.info.text = textToVerify;
|
|
2588
|
+
let resultWithElementsFound = {
|
|
2589
|
+
length: 0,
|
|
2590
|
+
};
|
|
2283
2591
|
while (true) {
|
|
2284
|
-
|
|
2592
|
+
try {
|
|
2593
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2594
|
+
}
|
|
2595
|
+
catch (error) {
|
|
2596
|
+
// ignore
|
|
2597
|
+
}
|
|
2285
2598
|
if (resultWithElementsFound.length === 0) {
|
|
2286
2599
|
if (Date.now() - state.startTime > timeout) {
|
|
2287
2600
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2289,51 +2602,56 @@ class StableBrowser {
|
|
|
2289
2602
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2290
2603
|
continue;
|
|
2291
2604
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
const
|
|
2300
|
-
for (let i = 0; i <
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2605
|
+
try {
|
|
2606
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2607
|
+
foundAncore = true;
|
|
2608
|
+
const result = resultWithElementsFound[i];
|
|
2609
|
+
const token = result.randomToken;
|
|
2610
|
+
const frame = result.frame;
|
|
2611
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2612
|
+
const climbArray1 = [];
|
|
2613
|
+
for (let i = 0; i < climb; i++) {
|
|
2614
|
+
climbArray1.push("..");
|
|
2615
|
+
}
|
|
2616
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2617
|
+
css = css + " >> " + climbXpath;
|
|
2618
|
+
const count = await frame.locator(css).count();
|
|
2619
|
+
for (let j = 0; j < count; j++) {
|
|
2620
|
+
const continer = await frame.locator(css).nth(j);
|
|
2621
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2622
|
+
if (result.elementCount > 0) {
|
|
2623
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2624
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2625
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2626
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2627
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2628
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2629
|
+
// .then(async () => {
|
|
2630
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2631
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2632
|
+
// () => {}
|
|
2633
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2634
|
+
// );
|
|
2635
|
+
// })
|
|
2636
|
+
// .catch(e);
|
|
2637
|
+
// }
|
|
2638
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2639
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2640
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2641
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2642
|
+
if (element) {
|
|
2643
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2644
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2307
2645
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
continue;
|
|
2311
|
-
}
|
|
2312
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2313
|
-
if (foundElements.length > 0) {
|
|
2314
|
-
// set the container element attribute
|
|
2315
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2316
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2317
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2318
|
-
return { found: true };
|
|
2646
|
+
await _screenshot(state, this);
|
|
2647
|
+
return state.info;
|
|
2319
2648
|
}
|
|
2320
2649
|
}
|
|
2321
|
-
return { found: false };
|
|
2322
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2323
|
-
if (findResult.found === true) {
|
|
2324
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2325
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2326
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2327
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2328
|
-
const element = await frame.locator(dataAttribute).first();
|
|
2329
|
-
if (element) {
|
|
2330
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2331
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2332
|
-
}
|
|
2333
|
-
await _screenshot(state, this);
|
|
2334
|
-
return state.info;
|
|
2335
2650
|
}
|
|
2336
2651
|
}
|
|
2652
|
+
catch (error) {
|
|
2653
|
+
console.error(error);
|
|
2654
|
+
}
|
|
2337
2655
|
}
|
|
2338
2656
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2339
2657
|
}
|
|
@@ -2344,6 +2662,30 @@ class StableBrowser {
|
|
|
2344
2662
|
_commandFinally(state, this);
|
|
2345
2663
|
}
|
|
2346
2664
|
}
|
|
2665
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2666
|
+
const frames = this.page.frames();
|
|
2667
|
+
let results = [];
|
|
2668
|
+
let ignoreCase = false;
|
|
2669
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2670
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2671
|
+
result.frame = frames[i];
|
|
2672
|
+
const climbArray = [];
|
|
2673
|
+
for (let i = 0; i < climb; i++) {
|
|
2674
|
+
climbArray.push("..");
|
|
2675
|
+
}
|
|
2676
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2677
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2678
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2679
|
+
if (count > 0) {
|
|
2680
|
+
result.elementCount = count;
|
|
2681
|
+
result.locator = newLocator;
|
|
2682
|
+
results.push(result);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
// state.info.results = results;
|
|
2686
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2687
|
+
return resultWithElementsFound;
|
|
2688
|
+
}
|
|
2347
2689
|
async visualVerification(text, options = {}, world = null) {
|
|
2348
2690
|
const startTime = Date.now();
|
|
2349
2691
|
let error = null;
|
|
@@ -2362,10 +2704,13 @@ class StableBrowser {
|
|
|
2362
2704
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2363
2705
|
info.screenshotPath = screenshotPath;
|
|
2364
2706
|
const screenshot = await this.takeScreenshot();
|
|
2365
|
-
|
|
2366
|
-
method: "
|
|
2707
|
+
let request = {
|
|
2708
|
+
method: "post",
|
|
2709
|
+
maxBodyLength: Infinity,
|
|
2367
2710
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2368
2711
|
headers: {
|
|
2712
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2713
|
+
"x-source": "aaa",
|
|
2369
2714
|
"Content-Type": "application/json",
|
|
2370
2715
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2371
2716
|
},
|
|
@@ -2374,7 +2719,7 @@ class StableBrowser {
|
|
|
2374
2719
|
screenshot: screenshot,
|
|
2375
2720
|
}),
|
|
2376
2721
|
};
|
|
2377
|
-
|
|
2722
|
+
const result = await axios.request(request);
|
|
2378
2723
|
if (result.data.status !== true) {
|
|
2379
2724
|
throw new Error("Visual validation failed");
|
|
2380
2725
|
}
|
|
@@ -2402,6 +2747,7 @@ class StableBrowser {
|
|
|
2402
2747
|
_reportToWorld(world, {
|
|
2403
2748
|
type: Types.VERIFY_VISUAL,
|
|
2404
2749
|
text: "Visual verification",
|
|
2750
|
+
_text: "Visual verification of " + text,
|
|
2405
2751
|
screenshotId,
|
|
2406
2752
|
result: error
|
|
2407
2753
|
? {
|
|
@@ -2668,6 +3014,32 @@ class StableBrowser {
|
|
|
2668
3014
|
}
|
|
2669
3015
|
return timeout;
|
|
2670
3016
|
}
|
|
3017
|
+
_getFindElementTimeout(options) {
|
|
3018
|
+
if (options && options.timeout) {
|
|
3019
|
+
return options.timeout;
|
|
3020
|
+
}
|
|
3021
|
+
if (this.configuration.find_element_timeout) {
|
|
3022
|
+
return this.configuration.find_element_timeout;
|
|
3023
|
+
}
|
|
3024
|
+
return 30000;
|
|
3025
|
+
}
|
|
3026
|
+
async saveStoreState(path = null, world = null) {
|
|
3027
|
+
const storageState = await this.page.context().storageState();
|
|
3028
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3029
|
+
if (path) {
|
|
3030
|
+
// save { storageState: storageState } into the path
|
|
3031
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3032
|
+
}
|
|
3033
|
+
else {
|
|
3034
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
async restoreSaveState(path = null, world = null) {
|
|
3038
|
+
await refreshBrowser(this, path, world);
|
|
3039
|
+
this.registerEventListeners(this.context);
|
|
3040
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3041
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3042
|
+
}
|
|
2671
3043
|
async waitForPageLoad(options = {}, world = null) {
|
|
2672
3044
|
let timeout = this._getLoadTimeout(options);
|
|
2673
3045
|
const promiseArray = [];
|
|
@@ -2735,6 +3107,7 @@ class StableBrowser {
|
|
|
2735
3107
|
highlight: false,
|
|
2736
3108
|
type: Types.CLOSE_PAGE,
|
|
2737
3109
|
text: `Close page`,
|
|
3110
|
+
_text: `Close the page`,
|
|
2738
3111
|
operation: "closePage",
|
|
2739
3112
|
log: "***** close page *****\n",
|
|
2740
3113
|
throwError: false,
|
|
@@ -2751,8 +3124,95 @@ class StableBrowser {
|
|
|
2751
3124
|
_commandFinally(state, this);
|
|
2752
3125
|
}
|
|
2753
3126
|
}
|
|
3127
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3128
|
+
let operation = null;
|
|
3129
|
+
if (!options || !options.operation) {
|
|
3130
|
+
throw new Error("operation is not defined");
|
|
3131
|
+
}
|
|
3132
|
+
operation = options.operation;
|
|
3133
|
+
// validate operation is one of the supported operations
|
|
3134
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3135
|
+
throw new Error("operation is not supported");
|
|
3136
|
+
}
|
|
3137
|
+
const state = {
|
|
3138
|
+
options,
|
|
3139
|
+
world,
|
|
3140
|
+
locate: false,
|
|
3141
|
+
scroll: false,
|
|
3142
|
+
highlight: false,
|
|
3143
|
+
type: Types.TABLE_OPERATION,
|
|
3144
|
+
text: `Table operation`,
|
|
3145
|
+
_text: `Table ${operation} operation`,
|
|
3146
|
+
operation: operation,
|
|
3147
|
+
log: "***** Table operation *****\n",
|
|
3148
|
+
};
|
|
3149
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3150
|
+
try {
|
|
3151
|
+
await _preCommand(state, this);
|
|
3152
|
+
const start = Date.now();
|
|
3153
|
+
let cellArea = null;
|
|
3154
|
+
while (true) {
|
|
3155
|
+
try {
|
|
3156
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3157
|
+
if (cellArea) {
|
|
3158
|
+
break;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
catch (e) {
|
|
3162
|
+
// ignore
|
|
3163
|
+
}
|
|
3164
|
+
if (Date.now() - start > timeout) {
|
|
3165
|
+
throw new Error(`Cell not found in table`);
|
|
3166
|
+
}
|
|
3167
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3168
|
+
}
|
|
3169
|
+
switch (operation) {
|
|
3170
|
+
case "click":
|
|
3171
|
+
if (!options.css) {
|
|
3172
|
+
// will click in the center of the cell
|
|
3173
|
+
let xOffset = 0;
|
|
3174
|
+
let yOffset = 0;
|
|
3175
|
+
if (options.xOffset) {
|
|
3176
|
+
xOffset = options.xOffset;
|
|
3177
|
+
}
|
|
3178
|
+
if (options.yOffset) {
|
|
3179
|
+
yOffset = options.yOffset;
|
|
3180
|
+
}
|
|
3181
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3182
|
+
}
|
|
3183
|
+
else {
|
|
3184
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3185
|
+
if (results.length === 0) {
|
|
3186
|
+
throw new Error(`Element not found in cell area`);
|
|
3187
|
+
}
|
|
3188
|
+
state.element = results[0];
|
|
3189
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3190
|
+
}
|
|
3191
|
+
break;
|
|
3192
|
+
case "hover+click":
|
|
3193
|
+
if (!options.css) {
|
|
3194
|
+
throw new Error("css is not defined");
|
|
3195
|
+
}
|
|
3196
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3197
|
+
if (results.length === 0) {
|
|
3198
|
+
throw new Error(`Element not found in cell area`);
|
|
3199
|
+
}
|
|
3200
|
+
state.element = results[0];
|
|
3201
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3202
|
+
break;
|
|
3203
|
+
default:
|
|
3204
|
+
throw new Error("operation is not supported");
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
catch (e) {
|
|
3208
|
+
await _commandError(state, e, this);
|
|
3209
|
+
}
|
|
3210
|
+
finally {
|
|
3211
|
+
_commandFinally(state, this);
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
2754
3214
|
saveTestDataAsGlobal(options, world) {
|
|
2755
|
-
const dataFile =
|
|
3215
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2756
3216
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2757
3217
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2758
3218
|
}
|
|
@@ -2782,6 +3242,7 @@ class StableBrowser {
|
|
|
2782
3242
|
_reportToWorld(world, {
|
|
2783
3243
|
type: Types.SET_VIEWPORT,
|
|
2784
3244
|
text: "set viewport size to " + width + "x" + hight,
|
|
3245
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2785
3246
|
screenshotId,
|
|
2786
3247
|
result: error
|
|
2787
3248
|
? {
|
|
@@ -2869,6 +3330,9 @@ class StableBrowser {
|
|
|
2869
3330
|
else {
|
|
2870
3331
|
this.stepName = "step " + this.stepIndex;
|
|
2871
3332
|
}
|
|
3333
|
+
if (this.context) {
|
|
3334
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3335
|
+
}
|
|
2872
3336
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2873
3337
|
if (this.context.browserObject.context) {
|
|
2874
3338
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2881,6 +3345,41 @@ class StableBrowser {
|
|
|
2881
3345
|
this.saveTestDataAsGlobal({}, world);
|
|
2882
3346
|
}
|
|
2883
3347
|
}
|
|
3348
|
+
if (this.initSnapshotTaken === false) {
|
|
3349
|
+
this.initSnapshotTaken = true;
|
|
3350
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3351
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3352
|
+
if (snapshot) {
|
|
3353
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
async getAriaSnapshot() {
|
|
3359
|
+
try {
|
|
3360
|
+
// find the page url
|
|
3361
|
+
const url = await this.page.url();
|
|
3362
|
+
// extract the path from the url
|
|
3363
|
+
const path = new URL(url).pathname;
|
|
3364
|
+
// get the page title
|
|
3365
|
+
const title = await this.page.title();
|
|
3366
|
+
// go over other frams
|
|
3367
|
+
const frames = this.page.frames();
|
|
3368
|
+
const snapshots = [];
|
|
3369
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3370
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3371
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3372
|
+
content.push(`- frame: ${i}`);
|
|
3373
|
+
const frame = frames[i];
|
|
3374
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3375
|
+
content.push(snapshot);
|
|
3376
|
+
}
|
|
3377
|
+
return content.join("\n");
|
|
3378
|
+
}
|
|
3379
|
+
catch (e) {
|
|
3380
|
+
console.error(e);
|
|
3381
|
+
}
|
|
3382
|
+
return null;
|
|
2884
3383
|
}
|
|
2885
3384
|
async afterStep(world, step) {
|
|
2886
3385
|
this.stepName = null;
|
|
@@ -2889,6 +3388,23 @@ class StableBrowser {
|
|
|
2889
3388
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2890
3389
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2891
3390
|
});
|
|
3391
|
+
if (world && world.attach) {
|
|
3392
|
+
await world.attach(JSON.stringify({
|
|
3393
|
+
type: "trace",
|
|
3394
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3395
|
+
}), "application/json+trace");
|
|
3396
|
+
}
|
|
3397
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
if (this.context) {
|
|
3401
|
+
this.context.examplesRow = null;
|
|
3402
|
+
}
|
|
3403
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3404
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3405
|
+
if (snapshot) {
|
|
3406
|
+
const obj = {};
|
|
3407
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
2892
3408
|
}
|
|
2893
3409
|
}
|
|
2894
3410
|
}
|