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