automation_model 1.0.579-dev → 1.0.579-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/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/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 +749 -237
- 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 +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,11 +338,11 @@ 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++) {
|
|
@@ -344,53 +355,43 @@ class StableBrowser {
|
|
|
344
355
|
}
|
|
345
356
|
return resultCss;
|
|
346
357
|
}
|
|
347
|
-
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (text.length < 3) {
|
|
364
|
-
throw new Error("Invalid regex pattern: empty pattern");
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
const pattern = text.slice(1, -1);
|
|
368
|
-
new RegExp(pattern); // Validate pattern
|
|
369
|
-
text = pattern;
|
|
370
|
-
options.singleRegex = true;
|
|
371
|
-
}
|
|
372
|
-
catch (e) {
|
|
373
|
-
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
const elements = window.findMatchingElements(text, options);
|
|
377
|
-
let randomToken = null;
|
|
378
|
-
const foundElements = [];
|
|
379
|
-
let elementCount = 0;
|
|
380
|
-
if (elements.length > 0) {
|
|
381
|
-
for (let i = 0; i < elements.length; i++) {
|
|
382
|
-
if (randomToken === null) {
|
|
383
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
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;
|
|
384
374
|
}
|
|
385
|
-
let element = elements[i];
|
|
386
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
387
|
-
elementCount++;
|
|
388
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;
|
|
389
389
|
}
|
|
390
|
-
|
|
391
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
392
393
|
}
|
|
393
|
-
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) {
|
|
394
395
|
if (!info) {
|
|
395
396
|
info = {};
|
|
396
397
|
}
|
|
@@ -413,23 +414,24 @@ class StableBrowser {
|
|
|
413
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
414
415
|
let locator = null;
|
|
415
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
416
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
417
419
|
if (!locatorString) {
|
|
418
420
|
info.failCause.textNotFound = true;
|
|
419
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
420
422
|
return;
|
|
421
423
|
}
|
|
422
424
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
423
425
|
}
|
|
424
426
|
else if (locatorSearch.text) {
|
|
425
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
426
|
-
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);
|
|
427
429
|
if (result.elementCount === 0) {
|
|
428
430
|
info.failCause.textNotFound = true;
|
|
429
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
430
432
|
return;
|
|
431
433
|
}
|
|
432
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
433
435
|
if (locatorSearch.childCss) {
|
|
434
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
435
437
|
}
|
|
@@ -465,7 +467,7 @@ class StableBrowser {
|
|
|
465
467
|
if (!visibleOnly) {
|
|
466
468
|
visible = true;
|
|
467
469
|
}
|
|
468
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
469
471
|
foundLocators.push(locator.nth(j));
|
|
470
472
|
if (info.locatorLog) {
|
|
471
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -478,9 +480,11 @@ class StableBrowser {
|
|
|
478
480
|
info.printMessages = {};
|
|
479
481
|
}
|
|
480
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
481
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
482
485
|
}
|
|
483
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
484
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
485
489
|
}
|
|
486
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -548,7 +552,7 @@ class StableBrowser {
|
|
|
548
552
|
}
|
|
549
553
|
return { rerun: false };
|
|
550
554
|
}
|
|
551
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
552
556
|
if (!timeout) {
|
|
553
557
|
timeout = 30000;
|
|
554
558
|
}
|
|
@@ -558,9 +562,30 @@ class StableBrowser {
|
|
|
558
562
|
let selector = selectors.locators[j];
|
|
559
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
560
564
|
}
|
|
561
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
562
566
|
if (!element.rerun) {
|
|
563
|
-
|
|
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);
|
|
564
589
|
}
|
|
565
590
|
}
|
|
566
591
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -633,7 +658,7 @@ class StableBrowser {
|
|
|
633
658
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
634
659
|
if (Date.now() - startTime > timeout) {
|
|
635
660
|
info.failCause.iframeNotFound = true;
|
|
636
|
-
info.failCause.lastError =
|
|
661
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
637
662
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
638
663
|
}
|
|
639
664
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -658,7 +683,7 @@ class StableBrowser {
|
|
|
658
683
|
return bodyContent;
|
|
659
684
|
});
|
|
660
685
|
}
|
|
661
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
686
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
662
687
|
if (!info) {
|
|
663
688
|
info = {};
|
|
664
689
|
info.failCause = {};
|
|
@@ -707,18 +732,13 @@ class StableBrowser {
|
|
|
707
732
|
}
|
|
708
733
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
709
734
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
710
|
-
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);
|
|
711
736
|
if (result.foundElements.length === 0) {
|
|
712
737
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
713
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
714
|
-
}
|
|
715
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
716
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
738
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
717
739
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
721
|
-
}
|
|
740
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
741
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
722
742
|
}
|
|
723
743
|
let foundElements = result.foundElements;
|
|
724
744
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -762,7 +782,7 @@ class StableBrowser {
|
|
|
762
782
|
break;
|
|
763
783
|
}
|
|
764
784
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
765
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
785
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
766
786
|
highPriorityOnly = false;
|
|
767
787
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
768
788
|
lazy_scroll = true;
|
|
@@ -770,10 +790,15 @@ class StableBrowser {
|
|
|
770
790
|
}
|
|
771
791
|
}
|
|
772
792
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
773
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
793
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
774
794
|
visibleOnly = false;
|
|
775
795
|
}
|
|
776
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
|
+
}
|
|
777
802
|
}
|
|
778
803
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
779
804
|
// if (info.locatorLog) {
|
|
@@ -784,10 +809,12 @@ class StableBrowser {
|
|
|
784
809
|
// }
|
|
785
810
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
786
811
|
info.failCause.locatorNotFound = true;
|
|
787
|
-
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
|
+
}
|
|
788
815
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
789
816
|
}
|
|
790
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
817
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
791
818
|
let foundElements = [];
|
|
792
819
|
const result = {
|
|
793
820
|
foundElements: foundElements,
|
|
@@ -795,7 +822,7 @@ class StableBrowser {
|
|
|
795
822
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
796
823
|
let foundLocators = [];
|
|
797
824
|
try {
|
|
798
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
825
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
799
826
|
}
|
|
800
827
|
catch (e) {
|
|
801
828
|
// this call can fail it the browser is navigating
|
|
@@ -803,7 +830,7 @@ class StableBrowser {
|
|
|
803
830
|
// this.logger.debug(e);
|
|
804
831
|
foundLocators = [];
|
|
805
832
|
try {
|
|
806
|
-
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);
|
|
807
834
|
}
|
|
808
835
|
catch (e) {
|
|
809
836
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -818,9 +845,40 @@ class StableBrowser {
|
|
|
818
845
|
result.locatorIndex = i;
|
|
819
846
|
}
|
|
820
847
|
if (foundLocators.length > 1) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
}
|
|
824
882
|
}
|
|
825
883
|
}
|
|
826
884
|
}
|
|
@@ -931,25 +989,14 @@ class StableBrowser {
|
|
|
931
989
|
options,
|
|
932
990
|
world,
|
|
933
991
|
text: "Click element",
|
|
992
|
+
_text: "Click on " + selectors.element_name,
|
|
934
993
|
type: Types.CLICK,
|
|
935
994
|
operation: "click",
|
|
936
995
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
937
996
|
};
|
|
938
997
|
try {
|
|
939
998
|
await _preCommand(state, this);
|
|
940
|
-
|
|
941
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
942
|
-
// }
|
|
943
|
-
try {
|
|
944
|
-
await state.element.click();
|
|
945
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
946
|
-
}
|
|
947
|
-
catch (e) {
|
|
948
|
-
// await this.closeUnexpectedPopups();
|
|
949
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
950
|
-
await state.element.dispatchEvent("click");
|
|
951
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
952
|
-
}
|
|
999
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
953
1000
|
await this.waitForPageLoad();
|
|
954
1001
|
return state.info;
|
|
955
1002
|
}
|
|
@@ -960,6 +1007,38 @@ class StableBrowser {
|
|
|
960
1007
|
_commandFinally(state, this);
|
|
961
1008
|
}
|
|
962
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
|
+
}
|
|
963
1042
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
964
1043
|
const state = {
|
|
965
1044
|
selectors,
|
|
@@ -968,6 +1047,7 @@ class StableBrowser {
|
|
|
968
1047
|
world,
|
|
969
1048
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
970
1049
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1050
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
971
1051
|
operation: "setCheck",
|
|
972
1052
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
973
1053
|
};
|
|
@@ -977,9 +1057,15 @@ class StableBrowser {
|
|
|
977
1057
|
// let element = await this._locate(selectors, info, _params);
|
|
978
1058
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
979
1059
|
try {
|
|
980
|
-
//
|
|
1060
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1061
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1062
|
+
await this._highlightElements(state.element);
|
|
981
1063
|
await state.element.setChecked(checked);
|
|
982
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);
|
|
983
1069
|
}
|
|
984
1070
|
catch (e) {
|
|
985
1071
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1011,22 +1097,13 @@ class StableBrowser {
|
|
|
1011
1097
|
world,
|
|
1012
1098
|
type: Types.HOVER,
|
|
1013
1099
|
text: `Hover element`,
|
|
1100
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1014
1101
|
operation: "hover",
|
|
1015
1102
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1016
1103
|
};
|
|
1017
1104
|
try {
|
|
1018
1105
|
await _preCommand(state, this);
|
|
1019
|
-
|
|
1020
|
-
await state.element.hover();
|
|
1021
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1022
|
-
}
|
|
1023
|
-
catch (e) {
|
|
1024
|
-
//await this.closeUnexpectedPopups();
|
|
1025
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1026
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1027
|
-
await state.element.hover({ timeout: 10000 });
|
|
1028
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1029
|
-
}
|
|
1106
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1030
1107
|
await _screenshot(state, this);
|
|
1031
1108
|
await this.waitForPageLoad();
|
|
1032
1109
|
return state.info;
|
|
@@ -1050,6 +1127,7 @@ class StableBrowser {
|
|
|
1050
1127
|
value: values.toString(),
|
|
1051
1128
|
type: Types.SELECT,
|
|
1052
1129
|
text: `Select option: ${values}`,
|
|
1130
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1053
1131
|
operation: "selectOption",
|
|
1054
1132
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1055
1133
|
};
|
|
@@ -1084,6 +1162,7 @@ class StableBrowser {
|
|
|
1084
1162
|
highlight: false,
|
|
1085
1163
|
type: Types.TYPE_PRESS,
|
|
1086
1164
|
text: `Type value: ${_value}`,
|
|
1165
|
+
_text: `Type value: ${_value}`,
|
|
1087
1166
|
operation: "type",
|
|
1088
1167
|
log: "",
|
|
1089
1168
|
};
|
|
@@ -1163,6 +1242,7 @@ class StableBrowser {
|
|
|
1163
1242
|
world,
|
|
1164
1243
|
type: Types.SET_DATE_TIME,
|
|
1165
1244
|
text: `Set date time value: ${value}`,
|
|
1245
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1166
1246
|
operation: "setDateTime",
|
|
1167
1247
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1168
1248
|
throwError: false,
|
|
@@ -1170,7 +1250,7 @@ class StableBrowser {
|
|
|
1170
1250
|
try {
|
|
1171
1251
|
await _preCommand(state, this);
|
|
1172
1252
|
try {
|
|
1173
|
-
await state.element
|
|
1253
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1174
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1175
1255
|
if (format) {
|
|
1176
1256
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1234,9 +1314,13 @@ class StableBrowser {
|
|
|
1234
1314
|
world,
|
|
1235
1315
|
type: Types.FILL,
|
|
1236
1316
|
text: `Click type input with value: ${_value}`,
|
|
1317
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1237
1318
|
operation: "clickType",
|
|
1238
1319
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1239
1320
|
};
|
|
1321
|
+
if (!options) {
|
|
1322
|
+
options = {};
|
|
1323
|
+
}
|
|
1240
1324
|
if (newValue !== _value) {
|
|
1241
1325
|
//this.logger.info(_value + "=" + newValue);
|
|
1242
1326
|
_value = newValue;
|
|
@@ -1244,7 +1328,7 @@ class StableBrowser {
|
|
|
1244
1328
|
try {
|
|
1245
1329
|
await _preCommand(state, this);
|
|
1246
1330
|
state.info.value = _value;
|
|
1247
|
-
if (
|
|
1331
|
+
if (!options.press) {
|
|
1248
1332
|
try {
|
|
1249
1333
|
let currentValue = await state.element.inputValue();
|
|
1250
1334
|
if (currentValue) {
|
|
@@ -1255,13 +1339,9 @@ class StableBrowser {
|
|
|
1255
1339
|
this.logger.info("unable to clear input value");
|
|
1256
1340
|
}
|
|
1257
1341
|
}
|
|
1258
|
-
if (options
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
catch (e) {
|
|
1263
|
-
await state.element.dispatchEvent("click");
|
|
1264
|
-
}
|
|
1342
|
+
if (options.press) {
|
|
1343
|
+
options.timeout = 5000;
|
|
1344
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1265
1345
|
}
|
|
1266
1346
|
else {
|
|
1267
1347
|
try {
|
|
@@ -1299,7 +1379,12 @@ class StableBrowser {
|
|
|
1299
1379
|
await this.waitForPageLoad();
|
|
1300
1380
|
}
|
|
1301
1381
|
else if (enter === false) {
|
|
1302
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1384
|
+
}
|
|
1385
|
+
catch (e) {
|
|
1386
|
+
// ignore
|
|
1387
|
+
}
|
|
1303
1388
|
//await this.page.keyboard.press("Tab");
|
|
1304
1389
|
}
|
|
1305
1390
|
else {
|
|
@@ -1351,6 +1436,7 @@ class StableBrowser {
|
|
|
1351
1436
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1352
1437
|
}
|
|
1353
1438
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1439
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1354
1440
|
_validateSelectors(selectors);
|
|
1355
1441
|
let screenshotId = null;
|
|
1356
1442
|
let screenshotPath = null;
|
|
@@ -1360,7 +1446,7 @@ class StableBrowser {
|
|
|
1360
1446
|
}
|
|
1361
1447
|
info.operation = "getText";
|
|
1362
1448
|
info.selectors = selectors;
|
|
1363
|
-
let element = await this._locate(selectors, info, _params);
|
|
1449
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1364
1450
|
if (climb > 0) {
|
|
1365
1451
|
const climbArray = [];
|
|
1366
1452
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1379,6 +1465,18 @@ class StableBrowser {
|
|
|
1379
1465
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1380
1466
|
try {
|
|
1381
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
|
+
// }
|
|
1382
1480
|
const elementText = await element.innerText();
|
|
1383
1481
|
return {
|
|
1384
1482
|
text: elementText,
|
|
@@ -1390,7 +1488,7 @@ class StableBrowser {
|
|
|
1390
1488
|
}
|
|
1391
1489
|
catch (e) {
|
|
1392
1490
|
//await this.closeUnexpectedPopups();
|
|
1393
|
-
this.logger.info("no innerText will use textContent");
|
|
1491
|
+
this.logger.info("no innerText, will use textContent");
|
|
1394
1492
|
const elementText = await element.textContent();
|
|
1395
1493
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1396
1494
|
}
|
|
@@ -1415,6 +1513,7 @@ class StableBrowser {
|
|
|
1415
1513
|
highlight: false,
|
|
1416
1514
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1417
1515
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1516
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1418
1517
|
operation: "containsPattern",
|
|
1419
1518
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1420
1519
|
};
|
|
@@ -1450,6 +1549,8 @@ class StableBrowser {
|
|
|
1450
1549
|
}
|
|
1451
1550
|
}
|
|
1452
1551
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1552
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1553
|
+
const startTime = Date.now();
|
|
1453
1554
|
const state = {
|
|
1454
1555
|
selectors,
|
|
1455
1556
|
_params,
|
|
@@ -1476,62 +1577,54 @@ class StableBrowser {
|
|
|
1476
1577
|
}
|
|
1477
1578
|
let foundObj = null;
|
|
1478
1579
|
try {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1486
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1487
|
-
if (dateAlternatives.date) {
|
|
1488
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1489
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1490
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1491
|
-
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);
|
|
1492
1586
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
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)) {
|
|
1500
1607
|
return state.info;
|
|
1501
1608
|
}
|
|
1502
1609
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
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
|
|
1509
1615
|
}
|
|
1510
|
-
|
|
1616
|
+
state.info.foundText = foundObj?.text;
|
|
1617
|
+
state.info.value = foundObj?.value;
|
|
1618
|
+
throw new Error("element doesn't contain text " + text);
|
|
1511
1619
|
}
|
|
1512
1620
|
catch (e) {
|
|
1513
1621
|
await _commandError(state, e, this);
|
|
1622
|
+
throw e;
|
|
1514
1623
|
}
|
|
1515
1624
|
finally {
|
|
1516
1625
|
_commandFinally(state, this);
|
|
1517
1626
|
}
|
|
1518
1627
|
}
|
|
1519
|
-
_getDataFile(world = null) {
|
|
1520
|
-
let dataFile = null;
|
|
1521
|
-
if (world && world.reportFolder) {
|
|
1522
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1523
|
-
}
|
|
1524
|
-
else if (this.reportFolder) {
|
|
1525
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1526
|
-
}
|
|
1527
|
-
else if (this.context && this.context.reportFolder) {
|
|
1528
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1529
|
-
}
|
|
1530
|
-
else {
|
|
1531
|
-
dataFile = "data.json";
|
|
1532
|
-
}
|
|
1533
|
-
return dataFile;
|
|
1534
|
-
}
|
|
1535
1628
|
async waitForUserInput(message, world = null) {
|
|
1536
1629
|
if (!message) {
|
|
1537
1630
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1560,13 +1653,22 @@ class StableBrowser {
|
|
|
1560
1653
|
return;
|
|
1561
1654
|
}
|
|
1562
1655
|
// if data file exists, load it
|
|
1563
|
-
const dataFile =
|
|
1656
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1564
1657
|
let data = this.getTestData(world);
|
|
1565
1658
|
// merge the testData with the existing data
|
|
1566
1659
|
Object.assign(data, testData);
|
|
1567
1660
|
// save the data to the file
|
|
1568
1661
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1569
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
|
+
}
|
|
1570
1672
|
_getDataFilePath(fileName) {
|
|
1571
1673
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1572
1674
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1663,7 +1765,7 @@ class StableBrowser {
|
|
|
1663
1765
|
}
|
|
1664
1766
|
}
|
|
1665
1767
|
getTestData(world = null) {
|
|
1666
|
-
const dataFile =
|
|
1768
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1667
1769
|
let data = {};
|
|
1668
1770
|
if (fs.existsSync(dataFile)) {
|
|
1669
1771
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1750,6 +1852,15 @@ class StableBrowser {
|
|
|
1750
1852
|
document.documentElement.clientWidth,
|
|
1751
1853
|
])));
|
|
1752
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
|
+
// }
|
|
1753
1864
|
if (this.context.browserName === "chromium") {
|
|
1754
1865
|
const client = await playContext.newCDPSession(this.page);
|
|
1755
1866
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1771,6 +1882,10 @@ class StableBrowser {
|
|
|
1771
1882
|
else {
|
|
1772
1883
|
screenshotBuffer = await this.page.screenshot();
|
|
1773
1884
|
}
|
|
1885
|
+
// if (focusedElement) {
|
|
1886
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1887
|
+
// await this._unhighlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1774
1889
|
let image = await Jimp.read(screenshotBuffer);
|
|
1775
1890
|
// Get the image dimensions
|
|
1776
1891
|
const { width, height } = image.bitmap;
|
|
@@ -1783,6 +1898,7 @@ class StableBrowser {
|
|
|
1783
1898
|
else {
|
|
1784
1899
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1785
1900
|
}
|
|
1901
|
+
return screenshotBuffer;
|
|
1786
1902
|
}
|
|
1787
1903
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1788
1904
|
const state = {
|
|
@@ -1818,8 +1934,10 @@ class StableBrowser {
|
|
|
1818
1934
|
world,
|
|
1819
1935
|
type: Types.EXTRACT,
|
|
1820
1936
|
text: `Extract attribute from element`,
|
|
1937
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1821
1938
|
operation: "extractAttribute",
|
|
1822
1939
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1940
|
+
allowDisabled: true,
|
|
1823
1941
|
};
|
|
1824
1942
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1825
1943
|
try {
|
|
@@ -1834,6 +1952,9 @@ class StableBrowser {
|
|
|
1834
1952
|
case "value":
|
|
1835
1953
|
state.value = await state.element.inputValue();
|
|
1836
1954
|
break;
|
|
1955
|
+
case "text":
|
|
1956
|
+
state.value = await state.element.textContent();
|
|
1957
|
+
break;
|
|
1837
1958
|
default:
|
|
1838
1959
|
state.value = await state.element.getAttribute(attribute);
|
|
1839
1960
|
break;
|
|
@@ -1841,6 +1962,7 @@ class StableBrowser {
|
|
|
1841
1962
|
state.info.value = state.value;
|
|
1842
1963
|
this.setTestData({ [variable]: state.value }, world);
|
|
1843
1964
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1965
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1844
1966
|
return state.info;
|
|
1845
1967
|
}
|
|
1846
1968
|
catch (e) {
|
|
@@ -1859,18 +1981,28 @@ class StableBrowser {
|
|
|
1859
1981
|
options,
|
|
1860
1982
|
world,
|
|
1861
1983
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1984
|
+
highlight: true,
|
|
1985
|
+
screenshot: true,
|
|
1862
1986
|
text: `Verify element attribute`,
|
|
1987
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1863
1988
|
operation: "verifyAttribute",
|
|
1864
1989
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1990
|
+
allowDisabled: true,
|
|
1865
1991
|
};
|
|
1866
1992
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1867
1993
|
let val;
|
|
1994
|
+
let expectedValue;
|
|
1868
1995
|
try {
|
|
1869
1996
|
await _preCommand(state, this);
|
|
1997
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1998
|
+
state.info.expectedValue = expectedValue;
|
|
1870
1999
|
switch (attribute) {
|
|
1871
2000
|
case "innerText":
|
|
1872
2001
|
val = String(await state.element.innerText());
|
|
1873
2002
|
break;
|
|
2003
|
+
case "text":
|
|
2004
|
+
val = String(await state.element.textContent());
|
|
2005
|
+
break;
|
|
1874
2006
|
case "value":
|
|
1875
2007
|
val = String(await state.element.inputValue());
|
|
1876
2008
|
break;
|
|
@@ -1888,19 +2020,22 @@ class StableBrowser {
|
|
|
1888
2020
|
val = String(await state.element.getAttribute(attribute));
|
|
1889
2021
|
break;
|
|
1890
2022
|
}
|
|
2023
|
+
state.info.value = val;
|
|
1891
2024
|
let regex;
|
|
1892
|
-
if (
|
|
1893
|
-
const patternBody =
|
|
2025
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2026
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1894
2027
|
regex = new RegExp(patternBody, "g");
|
|
1895
2028
|
}
|
|
1896
2029
|
else {
|
|
1897
|
-
const escapedPattern =
|
|
2030
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1898
2031
|
regex = new RegExp(escapedPattern, "g");
|
|
1899
2032
|
}
|
|
1900
2033
|
if (!val.match(regex)) {
|
|
1901
|
-
|
|
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);
|
|
1902
2038
|
}
|
|
1903
|
-
state.info.expectedValue = val;
|
|
1904
2039
|
return state.info;
|
|
1905
2040
|
}
|
|
1906
2041
|
catch (e) {
|
|
@@ -1999,27 +2134,32 @@ class StableBrowser {
|
|
|
1999
2134
|
async _highlightElements(scope, css) {
|
|
2000
2135
|
try {
|
|
2001
2136
|
if (!scope) {
|
|
2137
|
+
// console.log(`Scope is not defined`);
|
|
2002
2138
|
return;
|
|
2003
2139
|
}
|
|
2004
2140
|
if (!css) {
|
|
2005
2141
|
scope
|
|
2006
2142
|
.evaluate((node) => {
|
|
2007
2143
|
if (node && node.style) {
|
|
2008
|
-
let
|
|
2009
|
-
|
|
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}`);
|
|
2010
2149
|
if (window) {
|
|
2011
2150
|
window.addEventListener("beforeunload", function (e) {
|
|
2012
|
-
node.style.
|
|
2151
|
+
node.style.outline = originalOutline;
|
|
2013
2152
|
});
|
|
2014
2153
|
}
|
|
2015
2154
|
setTimeout(function () {
|
|
2016
|
-
node.style.
|
|
2155
|
+
node.style.outline = originalOutline;
|
|
2017
2156
|
}, 2000);
|
|
2018
2157
|
}
|
|
2019
2158
|
})
|
|
2020
2159
|
.then(() => { })
|
|
2021
2160
|
.catch((e) => {
|
|
2022
2161
|
// ignore
|
|
2162
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2023
2163
|
});
|
|
2024
2164
|
}
|
|
2025
2165
|
else {
|
|
@@ -2035,17 +2175,18 @@ class StableBrowser {
|
|
|
2035
2175
|
if (!element.style) {
|
|
2036
2176
|
return;
|
|
2037
2177
|
}
|
|
2038
|
-
|
|
2178
|
+
let originalOutline = element.style.outline;
|
|
2179
|
+
element.__previousOutline = originalOutline;
|
|
2039
2180
|
// Set the new border to be red and 2px solid
|
|
2040
|
-
element.style.
|
|
2181
|
+
element.style.outline = "2px solid red";
|
|
2041
2182
|
if (window) {
|
|
2042
2183
|
window.addEventListener("beforeunload", function (e) {
|
|
2043
|
-
element.style.
|
|
2184
|
+
element.style.outline = originalOutline;
|
|
2044
2185
|
});
|
|
2045
2186
|
}
|
|
2046
2187
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2047
2188
|
setTimeout(function () {
|
|
2048
|
-
element.style.
|
|
2189
|
+
element.style.outline = originalOutline;
|
|
2049
2190
|
}, 2000);
|
|
2050
2191
|
}
|
|
2051
2192
|
return;
|
|
@@ -2053,6 +2194,7 @@ class StableBrowser {
|
|
|
2053
2194
|
.then(() => { })
|
|
2054
2195
|
.catch((e) => {
|
|
2055
2196
|
// ignore
|
|
2197
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2056
2198
|
});
|
|
2057
2199
|
}
|
|
2058
2200
|
}
|
|
@@ -2060,6 +2202,54 @@ class StableBrowser {
|
|
|
2060
2202
|
console.debug(error);
|
|
2061
2203
|
}
|
|
2062
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
|
+
// }
|
|
2063
2253
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2064
2254
|
const startTime = Date.now();
|
|
2065
2255
|
let error = null;
|
|
@@ -2104,6 +2294,69 @@ class StableBrowser {
|
|
|
2104
2294
|
_reportToWorld(world, {
|
|
2105
2295
|
type: Types.VERIFY_PAGE_PATH,
|
|
2106
2296
|
text: "Verify page path",
|
|
2297
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2298
|
+
screenshotId,
|
|
2299
|
+
result: error
|
|
2300
|
+
? {
|
|
2301
|
+
status: "FAILED",
|
|
2302
|
+
startTime,
|
|
2303
|
+
endTime,
|
|
2304
|
+
message: error?.message,
|
|
2305
|
+
}
|
|
2306
|
+
: {
|
|
2307
|
+
status: "PASSED",
|
|
2308
|
+
startTime,
|
|
2309
|
+
endTime,
|
|
2310
|
+
},
|
|
2311
|
+
info: info,
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
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,
|
|
2107
2360
|
screenshotId,
|
|
2108
2361
|
result: error
|
|
2109
2362
|
? {
|
|
@@ -2121,26 +2374,27 @@ class StableBrowser {
|
|
|
2121
2374
|
});
|
|
2122
2375
|
}
|
|
2123
2376
|
}
|
|
2124
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2377
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2125
2378
|
const frames = this.page.frames();
|
|
2126
2379
|
let results = [];
|
|
2380
|
+
// let ignoreCase = false;
|
|
2127
2381
|
for (let i = 0; i < frames.length; i++) {
|
|
2128
2382
|
if (dateAlternatives.date) {
|
|
2129
2383
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2130
|
-
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, {});
|
|
2131
2385
|
result.frame = frames[i];
|
|
2132
2386
|
results.push(result);
|
|
2133
2387
|
}
|
|
2134
2388
|
}
|
|
2135
2389
|
else if (numberAlternatives.number) {
|
|
2136
2390
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2137
|
-
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, {});
|
|
2138
2392
|
result.frame = frames[i];
|
|
2139
2393
|
results.push(result);
|
|
2140
2394
|
}
|
|
2141
2395
|
}
|
|
2142
2396
|
else {
|
|
2143
|
-
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, {});
|
|
2144
2398
|
result.frame = frames[i];
|
|
2145
2399
|
results.push(result);
|
|
2146
2400
|
}
|
|
@@ -2159,11 +2413,15 @@ class StableBrowser {
|
|
|
2159
2413
|
scroll: false,
|
|
2160
2414
|
highlight: false,
|
|
2161
2415
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2162
|
-
text: `Verify text exists in page`,
|
|
2416
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2417
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2163
2418
|
operation: "verifyTextExistInPage",
|
|
2164
2419
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2165
2420
|
};
|
|
2166
|
-
|
|
2421
|
+
if (testForRegex(text)) {
|
|
2422
|
+
text = text.replace(/\\"/g, '"');
|
|
2423
|
+
}
|
|
2424
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2167
2425
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2168
2426
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2169
2427
|
if (newValue !== text) {
|
|
@@ -2176,7 +2434,15 @@ class StableBrowser {
|
|
|
2176
2434
|
await _preCommand(state, this);
|
|
2177
2435
|
state.info.text = text;
|
|
2178
2436
|
while (true) {
|
|
2179
|
-
|
|
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
|
+
}
|
|
2180
2446
|
if (resultWithElementsFound.length === 0) {
|
|
2181
2447
|
if (Date.now() - state.startTime > timeout) {
|
|
2182
2448
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2184,18 +2450,40 @@ class StableBrowser {
|
|
|
2184
2450
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2185
2451
|
continue;
|
|
2186
2452
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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
|
+
}
|
|
2195
2480
|
}
|
|
2481
|
+
await _screenshot(state, this);
|
|
2482
|
+
return state.info;
|
|
2483
|
+
}
|
|
2484
|
+
catch (error) {
|
|
2485
|
+
console.error(error);
|
|
2196
2486
|
}
|
|
2197
|
-
await _screenshot(state, this);
|
|
2198
|
-
return state.info;
|
|
2199
2487
|
}
|
|
2200
2488
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2201
2489
|
}
|
|
@@ -2216,11 +2504,15 @@ class StableBrowser {
|
|
|
2216
2504
|
scroll: false,
|
|
2217
2505
|
highlight: false,
|
|
2218
2506
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2219
|
-
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`,
|
|
2220
2509
|
operation: "verifyTextNotExistInPage",
|
|
2221
2510
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2222
2511
|
};
|
|
2223
|
-
|
|
2512
|
+
if (testForRegex(text)) {
|
|
2513
|
+
text = text.replace(/\\"/g, '"');
|
|
2514
|
+
}
|
|
2515
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2224
2516
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2225
2517
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2226
2518
|
if (newValue !== text) {
|
|
@@ -2232,8 +2524,16 @@ class StableBrowser {
|
|
|
2232
2524
|
try {
|
|
2233
2525
|
await _preCommand(state, this);
|
|
2234
2526
|
state.info.text = text;
|
|
2527
|
+
let resultWithElementsFound = {
|
|
2528
|
+
length: null, // initial cannot be 0
|
|
2529
|
+
};
|
|
2235
2530
|
while (true) {
|
|
2236
|
-
|
|
2531
|
+
try {
|
|
2532
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2533
|
+
}
|
|
2534
|
+
catch (error) {
|
|
2535
|
+
// ignore
|
|
2536
|
+
}
|
|
2237
2537
|
if (resultWithElementsFound.length === 0) {
|
|
2238
2538
|
await _screenshot(state, this);
|
|
2239
2539
|
return state.info;
|
|
@@ -2263,10 +2563,11 @@ class StableBrowser {
|
|
|
2263
2563
|
highlight: false,
|
|
2264
2564
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2265
2565
|
text: `Verify text with relation to another text`,
|
|
2566
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2266
2567
|
operation: "verify_text_with_relation",
|
|
2267
2568
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2268
2569
|
};
|
|
2269
|
-
const timeout = this.
|
|
2570
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2270
2571
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2271
2572
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2272
2573
|
if (newValue !== textAnchor) {
|
|
@@ -2284,8 +2585,16 @@ class StableBrowser {
|
|
|
2284
2585
|
try {
|
|
2285
2586
|
await _preCommand(state, this);
|
|
2286
2587
|
state.info.text = textToVerify;
|
|
2588
|
+
let resultWithElementsFound = {
|
|
2589
|
+
length: 0,
|
|
2590
|
+
};
|
|
2287
2591
|
while (true) {
|
|
2288
|
-
|
|
2592
|
+
try {
|
|
2593
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2594
|
+
}
|
|
2595
|
+
catch (error) {
|
|
2596
|
+
// ignore
|
|
2597
|
+
}
|
|
2289
2598
|
if (resultWithElementsFound.length === 0) {
|
|
2290
2599
|
if (Date.now() - state.startTime > timeout) {
|
|
2291
2600
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2293,51 +2602,56 @@ class StableBrowser {
|
|
|
2293
2602
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2294
2603
|
continue;
|
|
2295
2604
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
const
|
|
2304
|
-
for (let i = 0; i <
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
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");
|
|
2311
2645
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
continue;
|
|
2315
|
-
}
|
|
2316
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2317
|
-
if (foundElements.length > 0) {
|
|
2318
|
-
// set the container element attribute
|
|
2319
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2320
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2321
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2322
|
-
return { found: true };
|
|
2646
|
+
await _screenshot(state, this);
|
|
2647
|
+
return state.info;
|
|
2323
2648
|
}
|
|
2324
2649
|
}
|
|
2325
|
-
return { found: false };
|
|
2326
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2327
|
-
if (findResult.found === true) {
|
|
2328
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2329
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2330
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2331
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2332
|
-
const element = await frame.locator(dataAttribute).first();
|
|
2333
|
-
if (element) {
|
|
2334
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2335
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2336
|
-
}
|
|
2337
|
-
await _screenshot(state, this);
|
|
2338
|
-
return state.info;
|
|
2339
2650
|
}
|
|
2340
2651
|
}
|
|
2652
|
+
catch (error) {
|
|
2653
|
+
console.error(error);
|
|
2654
|
+
}
|
|
2341
2655
|
}
|
|
2342
2656
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2343
2657
|
}
|
|
@@ -2348,6 +2662,30 @@ class StableBrowser {
|
|
|
2348
2662
|
_commandFinally(state, this);
|
|
2349
2663
|
}
|
|
2350
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
|
+
}
|
|
2351
2689
|
async visualVerification(text, options = {}, world = null) {
|
|
2352
2690
|
const startTime = Date.now();
|
|
2353
2691
|
let error = null;
|
|
@@ -2366,10 +2704,13 @@ class StableBrowser {
|
|
|
2366
2704
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2367
2705
|
info.screenshotPath = screenshotPath;
|
|
2368
2706
|
const screenshot = await this.takeScreenshot();
|
|
2369
|
-
|
|
2370
|
-
method: "
|
|
2707
|
+
let request = {
|
|
2708
|
+
method: "post",
|
|
2709
|
+
maxBodyLength: Infinity,
|
|
2371
2710
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2372
2711
|
headers: {
|
|
2712
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2713
|
+
"x-source": "aaa",
|
|
2373
2714
|
"Content-Type": "application/json",
|
|
2374
2715
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2375
2716
|
},
|
|
@@ -2378,7 +2719,7 @@ class StableBrowser {
|
|
|
2378
2719
|
screenshot: screenshot,
|
|
2379
2720
|
}),
|
|
2380
2721
|
};
|
|
2381
|
-
|
|
2722
|
+
const result = await axios.request(request);
|
|
2382
2723
|
if (result.data.status !== true) {
|
|
2383
2724
|
throw new Error("Visual validation failed");
|
|
2384
2725
|
}
|
|
@@ -2406,6 +2747,7 @@ class StableBrowser {
|
|
|
2406
2747
|
_reportToWorld(world, {
|
|
2407
2748
|
type: Types.VERIFY_VISUAL,
|
|
2408
2749
|
text: "Visual verification",
|
|
2750
|
+
_text: "Visual verification of " + text,
|
|
2409
2751
|
screenshotId,
|
|
2410
2752
|
result: error
|
|
2411
2753
|
? {
|
|
@@ -2672,6 +3014,32 @@ class StableBrowser {
|
|
|
2672
3014
|
}
|
|
2673
3015
|
return timeout;
|
|
2674
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
|
+
}
|
|
2675
3043
|
async waitForPageLoad(options = {}, world = null) {
|
|
2676
3044
|
let timeout = this._getLoadTimeout(options);
|
|
2677
3045
|
const promiseArray = [];
|
|
@@ -2739,6 +3107,7 @@ class StableBrowser {
|
|
|
2739
3107
|
highlight: false,
|
|
2740
3108
|
type: Types.CLOSE_PAGE,
|
|
2741
3109
|
text: `Close page`,
|
|
3110
|
+
_text: `Close the page`,
|
|
2742
3111
|
operation: "closePage",
|
|
2743
3112
|
log: "***** close page *****\n",
|
|
2744
3113
|
throwError: false,
|
|
@@ -2755,8 +3124,95 @@ class StableBrowser {
|
|
|
2755
3124
|
_commandFinally(state, this);
|
|
2756
3125
|
}
|
|
2757
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
|
+
}
|
|
2758
3214
|
saveTestDataAsGlobal(options, world) {
|
|
2759
|
-
const dataFile =
|
|
3215
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2760
3216
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2761
3217
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2762
3218
|
}
|
|
@@ -2786,6 +3242,7 @@ class StableBrowser {
|
|
|
2786
3242
|
_reportToWorld(world, {
|
|
2787
3243
|
type: Types.SET_VIEWPORT,
|
|
2788
3244
|
text: "set viewport size to " + width + "x" + hight,
|
|
3245
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2789
3246
|
screenshotId,
|
|
2790
3247
|
result: error
|
|
2791
3248
|
? {
|
|
@@ -2873,6 +3330,9 @@ class StableBrowser {
|
|
|
2873
3330
|
else {
|
|
2874
3331
|
this.stepName = "step " + this.stepIndex;
|
|
2875
3332
|
}
|
|
3333
|
+
if (this.context) {
|
|
3334
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3335
|
+
}
|
|
2876
3336
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2877
3337
|
if (this.context.browserObject.context) {
|
|
2878
3338
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2885,6 +3345,41 @@ class StableBrowser {
|
|
|
2885
3345
|
this.saveTestDataAsGlobal({}, world);
|
|
2886
3346
|
}
|
|
2887
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;
|
|
2888
3383
|
}
|
|
2889
3384
|
async afterStep(world, step) {
|
|
2890
3385
|
this.stepName = null;
|
|
@@ -2893,6 +3388,23 @@ class StableBrowser {
|
|
|
2893
3388
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2894
3389
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2895
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");
|
|
2896
3408
|
}
|
|
2897
3409
|
}
|
|
2898
3410
|
}
|