automation_model 1.0.573-dev → 1.0.573-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.js +36 -19
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +105 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +48 -24
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +42 -20
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +45 -19
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +28 -8
- package/lib/stable_browser.js +759 -243
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +101 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +10 -3
- package/lib/utils.js +360 -21
- package/lib/utils.js.map +1 -1
- package/package.json +7 -8
- package/lib/scripts/find_text.js +0 -126
package/lib/stable_browser.js
CHANGED
|
@@ -10,18 +10,21 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
22
|
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
25
|
export const Types = {
|
|
24
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
28
|
NAVIGATE: "navigate",
|
|
26
29
|
FILL: "fill_element",
|
|
27
30
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +46,7 @@ export const Types = {
|
|
|
43
46
|
UNCHECK: "uncheck_element",
|
|
44
47
|
EXTRACT: "extract_attribute",
|
|
45
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
46
50
|
SET_DATE_TIME: "set_date_time",
|
|
47
51
|
SET_VIEWPORT: "set_viewport",
|
|
48
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -53,6 +57,9 @@ export const Types = {
|
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
54
58
|
};
|
|
55
59
|
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
62
|
+
};
|
|
56
63
|
class StableBrowser {
|
|
57
64
|
browser;
|
|
58
65
|
page;
|
|
@@ -66,6 +73,7 @@ class StableBrowser {
|
|
|
66
73
|
appName = "main";
|
|
67
74
|
tags = null;
|
|
68
75
|
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
69
77
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
78
|
this.browser = browser;
|
|
71
79
|
this.page = page;
|
|
@@ -236,6 +244,9 @@ class StableBrowser {
|
|
|
236
244
|
// await closeUnexpectedPopups(this.page);
|
|
237
245
|
// }
|
|
238
246
|
async goto(url, world = null) {
|
|
247
|
+
if (!url) {
|
|
248
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
249
|
+
}
|
|
239
250
|
if (!url.startsWith("http")) {
|
|
240
251
|
url = "https://" + url;
|
|
241
252
|
}
|
|
@@ -267,8 +278,16 @@ class StableBrowser {
|
|
|
267
278
|
_commandFinally(state, this);
|
|
268
279
|
}
|
|
269
280
|
}
|
|
270
|
-
_getLocator(locator, scope, _params) {
|
|
281
|
+
async _getLocator(locator, scope, _params) {
|
|
271
282
|
locator = _fixLocatorUsingParams(locator, _params);
|
|
283
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
284
|
+
for (let key in locator) {
|
|
285
|
+
if (typeof locator[key] !== "string")
|
|
286
|
+
continue;
|
|
287
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
288
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
272
291
|
let locatorReturn;
|
|
273
292
|
if (locator.role) {
|
|
274
293
|
if (locator.role[1].nameReg) {
|
|
@@ -319,66 +338,60 @@ class StableBrowser {
|
|
|
319
338
|
if (css && css.locator) {
|
|
320
339
|
css = css.locator;
|
|
321
340
|
}
|
|
322
|
-
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);
|
|
323
342
|
if (result.elementCount === 0) {
|
|
324
343
|
return;
|
|
325
344
|
}
|
|
326
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
327
346
|
// css climb to parent element
|
|
328
347
|
const climbArray = [];
|
|
329
348
|
for (let i = 0; i < climb; i++) {
|
|
330
349
|
climbArray.push("..");
|
|
331
350
|
}
|
|
332
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const pattern = text.slice(1, -1);
|
|
356
|
-
new RegExp(pattern); // Validate pattern
|
|
357
|
-
text = pattern;
|
|
358
|
-
options.singleRegex = true;
|
|
359
|
-
}
|
|
360
|
-
catch (e) {
|
|
361
|
-
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const elements = window.findMatchingElements(text, options);
|
|
365
|
-
let randomToken = null;
|
|
366
|
-
const foundElements = [];
|
|
367
|
-
let elementCount = 0;
|
|
368
|
-
if (elements.length > 0) {
|
|
369
|
-
for (let i = 0; i < elements.length; i++) {
|
|
370
|
-
if (randomToken === null) {
|
|
371
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
352
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
353
|
+
if (css) {
|
|
354
|
+
resultCss = resultCss + " >> " + css;
|
|
355
|
+
}
|
|
356
|
+
return resultCss;
|
|
357
|
+
}
|
|
358
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
359
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
360
|
+
const locator = scope.locator(query);
|
|
361
|
+
const count = await locator.count();
|
|
362
|
+
if (!tag1) {
|
|
363
|
+
tag1 = "*";
|
|
364
|
+
}
|
|
365
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
366
|
+
let tagCount = 0;
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const element = locator.nth(i);
|
|
369
|
+
// check if the tag matches
|
|
370
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
371
|
+
if (!tag.startsWith("*")) {
|
|
372
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
373
|
+
return false;
|
|
372
374
|
}
|
|
373
|
-
let element = elements[i];
|
|
374
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
375
|
-
elementCount++;
|
|
376
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;
|
|
377
389
|
}
|
|
378
|
-
|
|
379
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
380
393
|
}
|
|
381
|
-
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) {
|
|
382
395
|
if (!info) {
|
|
383
396
|
info = {};
|
|
384
397
|
}
|
|
@@ -401,30 +414,31 @@ class StableBrowser {
|
|
|
401
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
402
415
|
let locator = null;
|
|
403
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
404
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
405
419
|
if (!locatorString) {
|
|
406
420
|
info.failCause.textNotFound = true;
|
|
407
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
408
422
|
return;
|
|
409
423
|
}
|
|
410
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
411
425
|
}
|
|
412
426
|
else if (locatorSearch.text) {
|
|
413
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
414
|
-
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);
|
|
415
429
|
if (result.elementCount === 0) {
|
|
416
430
|
info.failCause.textNotFound = true;
|
|
417
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
418
432
|
return;
|
|
419
433
|
}
|
|
420
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
421
435
|
if (locatorSearch.childCss) {
|
|
422
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
423
437
|
}
|
|
424
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
425
439
|
}
|
|
426
440
|
else {
|
|
427
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
428
442
|
}
|
|
429
443
|
// let cssHref = false;
|
|
430
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -453,7 +467,7 @@ class StableBrowser {
|
|
|
453
467
|
if (!visibleOnly) {
|
|
454
468
|
visible = true;
|
|
455
469
|
}
|
|
456
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
457
471
|
foundLocators.push(locator.nth(j));
|
|
458
472
|
if (info.locatorLog) {
|
|
459
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -466,9 +480,11 @@ class StableBrowser {
|
|
|
466
480
|
info.printMessages = {};
|
|
467
481
|
}
|
|
468
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
469
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
470
485
|
}
|
|
471
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
472
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
473
489
|
}
|
|
474
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -536,7 +552,7 @@ class StableBrowser {
|
|
|
536
552
|
}
|
|
537
553
|
return { rerun: false };
|
|
538
554
|
}
|
|
539
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
540
556
|
if (!timeout) {
|
|
541
557
|
timeout = 30000;
|
|
542
558
|
}
|
|
@@ -546,9 +562,30 @@ class StableBrowser {
|
|
|
546
562
|
let selector = selectors.locators[j];
|
|
547
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
548
564
|
}
|
|
549
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
550
566
|
if (!element.rerun) {
|
|
551
|
-
|
|
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);
|
|
552
589
|
}
|
|
553
590
|
}
|
|
554
591
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -621,7 +658,7 @@ class StableBrowser {
|
|
|
621
658
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
622
659
|
if (Date.now() - startTime > timeout) {
|
|
623
660
|
info.failCause.iframeNotFound = true;
|
|
624
|
-
info.failCause.lastError =
|
|
661
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
625
662
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
626
663
|
}
|
|
627
664
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -646,7 +683,7 @@ class StableBrowser {
|
|
|
646
683
|
return bodyContent;
|
|
647
684
|
});
|
|
648
685
|
}
|
|
649
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
686
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
650
687
|
if (!info) {
|
|
651
688
|
info = {};
|
|
652
689
|
info.failCause = {};
|
|
@@ -695,18 +732,13 @@ class StableBrowser {
|
|
|
695
732
|
}
|
|
696
733
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
697
734
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
698
|
-
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);
|
|
699
736
|
if (result.foundElements.length === 0) {
|
|
700
737
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
701
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
738
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
702
739
|
}
|
|
703
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
704
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
705
|
-
}
|
|
706
|
-
else {
|
|
707
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
708
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
709
|
-
}
|
|
740
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
741
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
710
742
|
}
|
|
711
743
|
let foundElements = result.foundElements;
|
|
712
744
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -750,7 +782,7 @@ class StableBrowser {
|
|
|
750
782
|
break;
|
|
751
783
|
}
|
|
752
784
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
753
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
785
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
754
786
|
highPriorityOnly = false;
|
|
755
787
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
756
788
|
lazy_scroll = true;
|
|
@@ -758,10 +790,15 @@ class StableBrowser {
|
|
|
758
790
|
}
|
|
759
791
|
}
|
|
760
792
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
761
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
793
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
762
794
|
visibleOnly = false;
|
|
763
795
|
}
|
|
764
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
|
+
}
|
|
765
802
|
}
|
|
766
803
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
767
804
|
// if (info.locatorLog) {
|
|
@@ -772,10 +809,12 @@ class StableBrowser {
|
|
|
772
809
|
// }
|
|
773
810
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
774
811
|
info.failCause.locatorNotFound = true;
|
|
775
|
-
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
|
+
}
|
|
776
815
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
777
816
|
}
|
|
778
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
817
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
779
818
|
let foundElements = [];
|
|
780
819
|
const result = {
|
|
781
820
|
foundElements: foundElements,
|
|
@@ -783,7 +822,7 @@ class StableBrowser {
|
|
|
783
822
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
784
823
|
let foundLocators = [];
|
|
785
824
|
try {
|
|
786
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
825
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
787
826
|
}
|
|
788
827
|
catch (e) {
|
|
789
828
|
// this call can fail it the browser is navigating
|
|
@@ -791,7 +830,7 @@ class StableBrowser {
|
|
|
791
830
|
// this.logger.debug(e);
|
|
792
831
|
foundLocators = [];
|
|
793
832
|
try {
|
|
794
|
-
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);
|
|
795
834
|
}
|
|
796
835
|
catch (e) {
|
|
797
836
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -806,9 +845,40 @@ class StableBrowser {
|
|
|
806
845
|
result.locatorIndex = i;
|
|
807
846
|
}
|
|
808
847
|
if (foundLocators.length > 1) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
+
}
|
|
812
882
|
}
|
|
813
883
|
}
|
|
814
884
|
}
|
|
@@ -919,25 +989,14 @@ class StableBrowser {
|
|
|
919
989
|
options,
|
|
920
990
|
world,
|
|
921
991
|
text: "Click element",
|
|
992
|
+
_text: "Click on " + selectors.element_name,
|
|
922
993
|
type: Types.CLICK,
|
|
923
994
|
operation: "click",
|
|
924
995
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
925
996
|
};
|
|
926
997
|
try {
|
|
927
998
|
await _preCommand(state, this);
|
|
928
|
-
|
|
929
|
-
state.selectors.locators[0].text = state.options.context;
|
|
930
|
-
}
|
|
931
|
-
try {
|
|
932
|
-
await state.element.click();
|
|
933
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
934
|
-
}
|
|
935
|
-
catch (e) {
|
|
936
|
-
// await this.closeUnexpectedPopups();
|
|
937
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
938
|
-
await state.element.dispatchEvent("click");
|
|
939
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
940
|
-
}
|
|
999
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
941
1000
|
await this.waitForPageLoad();
|
|
942
1001
|
return state.info;
|
|
943
1002
|
}
|
|
@@ -948,6 +1007,38 @@ class StableBrowser {
|
|
|
948
1007
|
_commandFinally(state, this);
|
|
949
1008
|
}
|
|
950
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
|
+
}
|
|
951
1042
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
952
1043
|
const state = {
|
|
953
1044
|
selectors,
|
|
@@ -956,6 +1047,7 @@ class StableBrowser {
|
|
|
956
1047
|
world,
|
|
957
1048
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
958
1049
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1050
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
959
1051
|
operation: "setCheck",
|
|
960
1052
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
961
1053
|
};
|
|
@@ -965,9 +1057,15 @@ class StableBrowser {
|
|
|
965
1057
|
// let element = await this._locate(selectors, info, _params);
|
|
966
1058
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
967
1059
|
try {
|
|
968
|
-
//
|
|
1060
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1061
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1062
|
+
await this._highlightElements(state.element);
|
|
969
1063
|
await state.element.setChecked(checked);
|
|
970
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);
|
|
971
1069
|
}
|
|
972
1070
|
catch (e) {
|
|
973
1071
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -999,22 +1097,13 @@ class StableBrowser {
|
|
|
999
1097
|
world,
|
|
1000
1098
|
type: Types.HOVER,
|
|
1001
1099
|
text: `Hover element`,
|
|
1100
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1002
1101
|
operation: "hover",
|
|
1003
1102
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1004
1103
|
};
|
|
1005
1104
|
try {
|
|
1006
1105
|
await _preCommand(state, this);
|
|
1007
|
-
|
|
1008
|
-
await state.element.hover();
|
|
1009
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1010
|
-
}
|
|
1011
|
-
catch (e) {
|
|
1012
|
-
//await this.closeUnexpectedPopups();
|
|
1013
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1014
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1015
|
-
await state.element.hover({ timeout: 10000 });
|
|
1016
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1017
|
-
}
|
|
1106
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1018
1107
|
await _screenshot(state, this);
|
|
1019
1108
|
await this.waitForPageLoad();
|
|
1020
1109
|
return state.info;
|
|
@@ -1038,6 +1127,7 @@ class StableBrowser {
|
|
|
1038
1127
|
value: values.toString(),
|
|
1039
1128
|
type: Types.SELECT,
|
|
1040
1129
|
text: `Select option: ${values}`,
|
|
1130
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1041
1131
|
operation: "selectOption",
|
|
1042
1132
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1043
1133
|
};
|
|
@@ -1072,6 +1162,7 @@ class StableBrowser {
|
|
|
1072
1162
|
highlight: false,
|
|
1073
1163
|
type: Types.TYPE_PRESS,
|
|
1074
1164
|
text: `Type value: ${_value}`,
|
|
1165
|
+
_text: `Type value: ${_value}`,
|
|
1075
1166
|
operation: "type",
|
|
1076
1167
|
log: "",
|
|
1077
1168
|
};
|
|
@@ -1151,6 +1242,7 @@ class StableBrowser {
|
|
|
1151
1242
|
world,
|
|
1152
1243
|
type: Types.SET_DATE_TIME,
|
|
1153
1244
|
text: `Set date time value: ${value}`,
|
|
1245
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1154
1246
|
operation: "setDateTime",
|
|
1155
1247
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1156
1248
|
throwError: false,
|
|
@@ -1158,7 +1250,7 @@ class StableBrowser {
|
|
|
1158
1250
|
try {
|
|
1159
1251
|
await _preCommand(state, this);
|
|
1160
1252
|
try {
|
|
1161
|
-
await state.element
|
|
1253
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1162
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1163
1255
|
if (format) {
|
|
1164
1256
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1222,9 +1314,13 @@ class StableBrowser {
|
|
|
1222
1314
|
world,
|
|
1223
1315
|
type: Types.FILL,
|
|
1224
1316
|
text: `Click type input with value: ${_value}`,
|
|
1317
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1225
1318
|
operation: "clickType",
|
|
1226
1319
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1227
1320
|
};
|
|
1321
|
+
if (!options) {
|
|
1322
|
+
options = {};
|
|
1323
|
+
}
|
|
1228
1324
|
if (newValue !== _value) {
|
|
1229
1325
|
//this.logger.info(_value + "=" + newValue);
|
|
1230
1326
|
_value = newValue;
|
|
@@ -1232,7 +1328,7 @@ class StableBrowser {
|
|
|
1232
1328
|
try {
|
|
1233
1329
|
await _preCommand(state, this);
|
|
1234
1330
|
state.info.value = _value;
|
|
1235
|
-
if (
|
|
1331
|
+
if (!options.press) {
|
|
1236
1332
|
try {
|
|
1237
1333
|
let currentValue = await state.element.inputValue();
|
|
1238
1334
|
if (currentValue) {
|
|
@@ -1243,13 +1339,9 @@ class StableBrowser {
|
|
|
1243
1339
|
this.logger.info("unable to clear input value");
|
|
1244
1340
|
}
|
|
1245
1341
|
}
|
|
1246
|
-
if (options
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
catch (e) {
|
|
1251
|
-
await state.element.dispatchEvent("click");
|
|
1252
|
-
}
|
|
1342
|
+
if (options.press) {
|
|
1343
|
+
options.timeout = 5000;
|
|
1344
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1253
1345
|
}
|
|
1254
1346
|
else {
|
|
1255
1347
|
try {
|
|
@@ -1287,7 +1379,12 @@ class StableBrowser {
|
|
|
1287
1379
|
await this.waitForPageLoad();
|
|
1288
1380
|
}
|
|
1289
1381
|
else if (enter === false) {
|
|
1290
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1384
|
+
}
|
|
1385
|
+
catch (e) {
|
|
1386
|
+
// ignore
|
|
1387
|
+
}
|
|
1291
1388
|
//await this.page.keyboard.press("Tab");
|
|
1292
1389
|
}
|
|
1293
1390
|
else {
|
|
@@ -1339,6 +1436,7 @@ class StableBrowser {
|
|
|
1339
1436
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1340
1437
|
}
|
|
1341
1438
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1439
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1342
1440
|
_validateSelectors(selectors);
|
|
1343
1441
|
let screenshotId = null;
|
|
1344
1442
|
let screenshotPath = null;
|
|
@@ -1348,7 +1446,7 @@ class StableBrowser {
|
|
|
1348
1446
|
}
|
|
1349
1447
|
info.operation = "getText";
|
|
1350
1448
|
info.selectors = selectors;
|
|
1351
|
-
let element = await this._locate(selectors, info, _params);
|
|
1449
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1352
1450
|
if (climb > 0) {
|
|
1353
1451
|
const climbArray = [];
|
|
1354
1452
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1367,6 +1465,18 @@ class StableBrowser {
|
|
|
1367
1465
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1368
1466
|
try {
|
|
1369
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
|
+
// }
|
|
1370
1480
|
const elementText = await element.innerText();
|
|
1371
1481
|
return {
|
|
1372
1482
|
text: elementText,
|
|
@@ -1378,7 +1488,7 @@ class StableBrowser {
|
|
|
1378
1488
|
}
|
|
1379
1489
|
catch (e) {
|
|
1380
1490
|
//await this.closeUnexpectedPopups();
|
|
1381
|
-
this.logger.info("no innerText will use textContent");
|
|
1491
|
+
this.logger.info("no innerText, will use textContent");
|
|
1382
1492
|
const elementText = await element.textContent();
|
|
1383
1493
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1384
1494
|
}
|
|
@@ -1403,6 +1513,7 @@ class StableBrowser {
|
|
|
1403
1513
|
highlight: false,
|
|
1404
1514
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1405
1515
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1516
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1406
1517
|
operation: "containsPattern",
|
|
1407
1518
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1408
1519
|
};
|
|
@@ -1438,6 +1549,8 @@ class StableBrowser {
|
|
|
1438
1549
|
}
|
|
1439
1550
|
}
|
|
1440
1551
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1552
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1553
|
+
const startTime = Date.now();
|
|
1441
1554
|
const state = {
|
|
1442
1555
|
selectors,
|
|
1443
1556
|
_params,
|
|
@@ -1464,62 +1577,54 @@ class StableBrowser {
|
|
|
1464
1577
|
}
|
|
1465
1578
|
let foundObj = null;
|
|
1466
1579
|
try {
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1474
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1475
|
-
if (dateAlternatives.date) {
|
|
1476
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1477
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1478
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1479
|
-
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);
|
|
1480
1586
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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)) {
|
|
1488
1607
|
return state.info;
|
|
1489
1608
|
}
|
|
1490
1609
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
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
|
|
1497
1615
|
}
|
|
1498
|
-
|
|
1616
|
+
state.info.foundText = foundObj?.text;
|
|
1617
|
+
state.info.value = foundObj?.value;
|
|
1618
|
+
throw new Error("element doesn't contain text " + text);
|
|
1499
1619
|
}
|
|
1500
1620
|
catch (e) {
|
|
1501
1621
|
await _commandError(state, e, this);
|
|
1622
|
+
throw e;
|
|
1502
1623
|
}
|
|
1503
1624
|
finally {
|
|
1504
1625
|
_commandFinally(state, this);
|
|
1505
1626
|
}
|
|
1506
1627
|
}
|
|
1507
|
-
_getDataFile(world = null) {
|
|
1508
|
-
let dataFile = null;
|
|
1509
|
-
if (world && world.reportFolder) {
|
|
1510
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1511
|
-
}
|
|
1512
|
-
else if (this.reportFolder) {
|
|
1513
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1514
|
-
}
|
|
1515
|
-
else if (this.context && this.context.reportFolder) {
|
|
1516
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1517
|
-
}
|
|
1518
|
-
else {
|
|
1519
|
-
dataFile = "data.json";
|
|
1520
|
-
}
|
|
1521
|
-
return dataFile;
|
|
1522
|
-
}
|
|
1523
1628
|
async waitForUserInput(message, world = null) {
|
|
1524
1629
|
if (!message) {
|
|
1525
1630
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1548,13 +1653,22 @@ class StableBrowser {
|
|
|
1548
1653
|
return;
|
|
1549
1654
|
}
|
|
1550
1655
|
// if data file exists, load it
|
|
1551
|
-
const dataFile =
|
|
1656
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1552
1657
|
let data = this.getTestData(world);
|
|
1553
1658
|
// merge the testData with the existing data
|
|
1554
1659
|
Object.assign(data, testData);
|
|
1555
1660
|
// save the data to the file
|
|
1556
1661
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1557
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
|
+
}
|
|
1558
1672
|
_getDataFilePath(fileName) {
|
|
1559
1673
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1560
1674
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1651,7 +1765,7 @@ class StableBrowser {
|
|
|
1651
1765
|
}
|
|
1652
1766
|
}
|
|
1653
1767
|
getTestData(world = null) {
|
|
1654
|
-
const dataFile =
|
|
1768
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1655
1769
|
let data = {};
|
|
1656
1770
|
if (fs.existsSync(dataFile)) {
|
|
1657
1771
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1738,6 +1852,15 @@ class StableBrowser {
|
|
|
1738
1852
|
document.documentElement.clientWidth,
|
|
1739
1853
|
])));
|
|
1740
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
|
+
// }
|
|
1741
1864
|
if (this.context.browserName === "chromium") {
|
|
1742
1865
|
const client = await playContext.newCDPSession(this.page);
|
|
1743
1866
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1759,6 +1882,10 @@ class StableBrowser {
|
|
|
1759
1882
|
else {
|
|
1760
1883
|
screenshotBuffer = await this.page.screenshot();
|
|
1761
1884
|
}
|
|
1885
|
+
// if (focusedElement) {
|
|
1886
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1887
|
+
// await this._unhighlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1762
1889
|
let image = await Jimp.read(screenshotBuffer);
|
|
1763
1890
|
// Get the image dimensions
|
|
1764
1891
|
const { width, height } = image.bitmap;
|
|
@@ -1771,6 +1898,7 @@ class StableBrowser {
|
|
|
1771
1898
|
else {
|
|
1772
1899
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1773
1900
|
}
|
|
1901
|
+
return screenshotBuffer;
|
|
1774
1902
|
}
|
|
1775
1903
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1776
1904
|
const state = {
|
|
@@ -1806,8 +1934,10 @@ class StableBrowser {
|
|
|
1806
1934
|
world,
|
|
1807
1935
|
type: Types.EXTRACT,
|
|
1808
1936
|
text: `Extract attribute from element`,
|
|
1937
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1809
1938
|
operation: "extractAttribute",
|
|
1810
1939
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1940
|
+
allowDisabled: true,
|
|
1811
1941
|
};
|
|
1812
1942
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1813
1943
|
try {
|
|
@@ -1829,6 +1959,7 @@ class StableBrowser {
|
|
|
1829
1959
|
state.info.value = state.value;
|
|
1830
1960
|
this.setTestData({ [variable]: state.value }, world);
|
|
1831
1961
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1962
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1832
1963
|
return state.info;
|
|
1833
1964
|
}
|
|
1834
1965
|
catch (e) {
|
|
@@ -1847,14 +1978,21 @@ class StableBrowser {
|
|
|
1847
1978
|
options,
|
|
1848
1979
|
world,
|
|
1849
1980
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1981
|
+
highlight: true,
|
|
1982
|
+
screenshot: true,
|
|
1850
1983
|
text: `Verify element attribute`,
|
|
1984
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1851
1985
|
operation: "verifyAttribute",
|
|
1852
1986
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1987
|
+
allowDisabled: true,
|
|
1853
1988
|
};
|
|
1854
1989
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1855
1990
|
let val;
|
|
1991
|
+
let expectedValue;
|
|
1856
1992
|
try {
|
|
1857
1993
|
await _preCommand(state, this);
|
|
1994
|
+
expectedValue = state.value;
|
|
1995
|
+
state.info.expectedValue = expectedValue;
|
|
1858
1996
|
switch (attribute) {
|
|
1859
1997
|
case "innerText":
|
|
1860
1998
|
val = String(await state.element.innerText());
|
|
@@ -1876,19 +2014,22 @@ class StableBrowser {
|
|
|
1876
2014
|
val = String(await state.element.getAttribute(attribute));
|
|
1877
2015
|
break;
|
|
1878
2016
|
}
|
|
2017
|
+
state.info.value = val;
|
|
1879
2018
|
let regex;
|
|
1880
|
-
if (
|
|
1881
|
-
const patternBody =
|
|
2019
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2020
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1882
2021
|
regex = new RegExp(patternBody, "g");
|
|
1883
2022
|
}
|
|
1884
2023
|
else {
|
|
1885
|
-
const escapedPattern =
|
|
2024
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1886
2025
|
regex = new RegExp(escapedPattern, "g");
|
|
1887
2026
|
}
|
|
1888
2027
|
if (!val.match(regex)) {
|
|
1889
|
-
|
|
2028
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2029
|
+
state.info.failCause.assertionFailed = true;
|
|
2030
|
+
state.info.failCause.lastError = errorMessage;
|
|
2031
|
+
throw new Error(errorMessage);
|
|
1890
2032
|
}
|
|
1891
|
-
state.info.expectedValue = val;
|
|
1892
2033
|
return state.info;
|
|
1893
2034
|
}
|
|
1894
2035
|
catch (e) {
|
|
@@ -1987,27 +2128,32 @@ class StableBrowser {
|
|
|
1987
2128
|
async _highlightElements(scope, css) {
|
|
1988
2129
|
try {
|
|
1989
2130
|
if (!scope) {
|
|
2131
|
+
// console.log(`Scope is not defined`);
|
|
1990
2132
|
return;
|
|
1991
2133
|
}
|
|
1992
2134
|
if (!css) {
|
|
1993
2135
|
scope
|
|
1994
2136
|
.evaluate((node) => {
|
|
1995
2137
|
if (node && node.style) {
|
|
1996
|
-
let
|
|
1997
|
-
|
|
2138
|
+
let originalOutline = node.style.outline;
|
|
2139
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2140
|
+
// node.__previousOutline = originalOutline;
|
|
2141
|
+
node.style.outline = "2px solid red";
|
|
2142
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1998
2143
|
if (window) {
|
|
1999
2144
|
window.addEventListener("beforeunload", function (e) {
|
|
2000
|
-
node.style.
|
|
2145
|
+
node.style.outline = originalOutline;
|
|
2001
2146
|
});
|
|
2002
2147
|
}
|
|
2003
2148
|
setTimeout(function () {
|
|
2004
|
-
node.style.
|
|
2149
|
+
node.style.outline = originalOutline;
|
|
2005
2150
|
}, 2000);
|
|
2006
2151
|
}
|
|
2007
2152
|
})
|
|
2008
2153
|
.then(() => { })
|
|
2009
2154
|
.catch((e) => {
|
|
2010
2155
|
// ignore
|
|
2156
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2011
2157
|
});
|
|
2012
2158
|
}
|
|
2013
2159
|
else {
|
|
@@ -2023,17 +2169,18 @@ class StableBrowser {
|
|
|
2023
2169
|
if (!element.style) {
|
|
2024
2170
|
return;
|
|
2025
2171
|
}
|
|
2026
|
-
|
|
2172
|
+
let originalOutline = element.style.outline;
|
|
2173
|
+
element.__previousOutline = originalOutline;
|
|
2027
2174
|
// Set the new border to be red and 2px solid
|
|
2028
|
-
element.style.
|
|
2175
|
+
element.style.outline = "2px solid red";
|
|
2029
2176
|
if (window) {
|
|
2030
2177
|
window.addEventListener("beforeunload", function (e) {
|
|
2031
|
-
element.style.
|
|
2178
|
+
element.style.outline = originalOutline;
|
|
2032
2179
|
});
|
|
2033
2180
|
}
|
|
2034
2181
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2035
2182
|
setTimeout(function () {
|
|
2036
|
-
element.style.
|
|
2183
|
+
element.style.outline = originalOutline;
|
|
2037
2184
|
}, 2000);
|
|
2038
2185
|
}
|
|
2039
2186
|
return;
|
|
@@ -2041,6 +2188,7 @@ class StableBrowser {
|
|
|
2041
2188
|
.then(() => { })
|
|
2042
2189
|
.catch((e) => {
|
|
2043
2190
|
// ignore
|
|
2191
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2044
2192
|
});
|
|
2045
2193
|
}
|
|
2046
2194
|
}
|
|
@@ -2048,6 +2196,54 @@ class StableBrowser {
|
|
|
2048
2196
|
console.debug(error);
|
|
2049
2197
|
}
|
|
2050
2198
|
}
|
|
2199
|
+
// async _unhighlightElements(scope, css) {
|
|
2200
|
+
// try {
|
|
2201
|
+
// if (!scope) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// if (!css) {
|
|
2205
|
+
// scope
|
|
2206
|
+
// .evaluate((node) => {
|
|
2207
|
+
// if (node && node.style) {
|
|
2208
|
+
// if (!node.__previousOutline) {
|
|
2209
|
+
// node.style.outline = "";
|
|
2210
|
+
// } else {
|
|
2211
|
+
// node.style.outline = node.__previousOutline;
|
|
2212
|
+
// }
|
|
2213
|
+
// }
|
|
2214
|
+
// })
|
|
2215
|
+
// .then(() => {})
|
|
2216
|
+
// .catch((e) => {
|
|
2217
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2218
|
+
// });
|
|
2219
|
+
// } else {
|
|
2220
|
+
// scope
|
|
2221
|
+
// .evaluate(([css]) => {
|
|
2222
|
+
// if (!css) {
|
|
2223
|
+
// return;
|
|
2224
|
+
// }
|
|
2225
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2226
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2227
|
+
// let element = elements[i];
|
|
2228
|
+
// if (!element.style) {
|
|
2229
|
+
// return;
|
|
2230
|
+
// }
|
|
2231
|
+
// if (!element.__previousOutline) {
|
|
2232
|
+
// element.style.outline = "";
|
|
2233
|
+
// } else {
|
|
2234
|
+
// element.style.outline = element.__previousOutline;
|
|
2235
|
+
// }
|
|
2236
|
+
// }
|
|
2237
|
+
// })
|
|
2238
|
+
// .then(() => {})
|
|
2239
|
+
// .catch((e) => {
|
|
2240
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2241
|
+
// });
|
|
2242
|
+
// }
|
|
2243
|
+
// } catch (error) {
|
|
2244
|
+
// // console.debug(error);
|
|
2245
|
+
// }
|
|
2246
|
+
// }
|
|
2051
2247
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2052
2248
|
const startTime = Date.now();
|
|
2053
2249
|
let error = null;
|
|
@@ -2092,6 +2288,7 @@ class StableBrowser {
|
|
|
2092
2288
|
_reportToWorld(world, {
|
|
2093
2289
|
type: Types.VERIFY_PAGE_PATH,
|
|
2094
2290
|
text: "Verify page path",
|
|
2291
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2095
2292
|
screenshotId,
|
|
2096
2293
|
result: error
|
|
2097
2294
|
? {
|
|
@@ -2109,26 +2306,89 @@ class StableBrowser {
|
|
|
2109
2306
|
});
|
|
2110
2307
|
}
|
|
2111
2308
|
}
|
|
2112
|
-
async
|
|
2309
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2310
|
+
const startTime = Date.now();
|
|
2311
|
+
let error = null;
|
|
2312
|
+
let screenshotId = null;
|
|
2313
|
+
let screenshotPath = null;
|
|
2314
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2315
|
+
const info = {};
|
|
2316
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2317
|
+
info.operation = "verifyPageTitle";
|
|
2318
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2319
|
+
if (newValue !== title) {
|
|
2320
|
+
this.logger.info(title + "=" + newValue);
|
|
2321
|
+
title = newValue;
|
|
2322
|
+
}
|
|
2323
|
+
info.title = title;
|
|
2324
|
+
try {
|
|
2325
|
+
for (let i = 0; i < 30; i++) {
|
|
2326
|
+
const foundTitle = await this.page.title();
|
|
2327
|
+
if (!foundTitle.includes(title)) {
|
|
2328
|
+
if (i === 29) {
|
|
2329
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2330
|
+
}
|
|
2331
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2335
|
+
return info;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
catch (e) {
|
|
2339
|
+
//await this.closeUnexpectedPopups();
|
|
2340
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2341
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2342
|
+
info.screenshotPath = screenshotPath;
|
|
2343
|
+
Object.assign(e, { info: info });
|
|
2344
|
+
error = e;
|
|
2345
|
+
// throw e;
|
|
2346
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2347
|
+
}
|
|
2348
|
+
finally {
|
|
2349
|
+
const endTime = Date.now();
|
|
2350
|
+
_reportToWorld(world, {
|
|
2351
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2352
|
+
text: "Verify page title",
|
|
2353
|
+
_text: "Verify the page title contains " + title,
|
|
2354
|
+
screenshotId,
|
|
2355
|
+
result: error
|
|
2356
|
+
? {
|
|
2357
|
+
status: "FAILED",
|
|
2358
|
+
startTime,
|
|
2359
|
+
endTime,
|
|
2360
|
+
message: error?.message,
|
|
2361
|
+
}
|
|
2362
|
+
: {
|
|
2363
|
+
status: "PASSED",
|
|
2364
|
+
startTime,
|
|
2365
|
+
endTime,
|
|
2366
|
+
},
|
|
2367
|
+
info: info,
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2113
2372
|
const frames = this.page.frames();
|
|
2114
2373
|
let results = [];
|
|
2374
|
+
// let ignoreCase = false;
|
|
2115
2375
|
for (let i = 0; i < frames.length; i++) {
|
|
2116
2376
|
if (dateAlternatives.date) {
|
|
2117
2377
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2118
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2378
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2119
2379
|
result.frame = frames[i];
|
|
2120
2380
|
results.push(result);
|
|
2121
2381
|
}
|
|
2122
2382
|
}
|
|
2123
2383
|
else if (numberAlternatives.number) {
|
|
2124
2384
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2125
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2385
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2126
2386
|
result.frame = frames[i];
|
|
2127
2387
|
results.push(result);
|
|
2128
2388
|
}
|
|
2129
2389
|
}
|
|
2130
2390
|
else {
|
|
2131
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2391
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2132
2392
|
result.frame = frames[i];
|
|
2133
2393
|
results.push(result);
|
|
2134
2394
|
}
|
|
@@ -2147,11 +2407,15 @@ class StableBrowser {
|
|
|
2147
2407
|
scroll: false,
|
|
2148
2408
|
highlight: false,
|
|
2149
2409
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2150
|
-
text: `Verify text exists in page`,
|
|
2410
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2411
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2151
2412
|
operation: "verifyTextExistInPage",
|
|
2152
2413
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2153
2414
|
};
|
|
2154
|
-
|
|
2415
|
+
if (testForRegex(text)) {
|
|
2416
|
+
text = text.replace(/\\"/g, '"');
|
|
2417
|
+
}
|
|
2418
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2155
2419
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2156
2420
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2157
2421
|
if (newValue !== text) {
|
|
@@ -2164,7 +2428,15 @@ class StableBrowser {
|
|
|
2164
2428
|
await _preCommand(state, this);
|
|
2165
2429
|
state.info.text = text;
|
|
2166
2430
|
while (true) {
|
|
2167
|
-
|
|
2431
|
+
let resultWithElementsFound = {
|
|
2432
|
+
length: 0,
|
|
2433
|
+
};
|
|
2434
|
+
try {
|
|
2435
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2436
|
+
}
|
|
2437
|
+
catch (error) {
|
|
2438
|
+
// ignore
|
|
2439
|
+
}
|
|
2168
2440
|
if (resultWithElementsFound.length === 0) {
|
|
2169
2441
|
if (Date.now() - state.startTime > timeout) {
|
|
2170
2442
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2172,18 +2444,40 @@ class StableBrowser {
|
|
|
2172
2444
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2173
2445
|
continue;
|
|
2174
2446
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2447
|
+
try {
|
|
2448
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2449
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2450
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2451
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2452
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2453
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2454
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2455
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2456
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2457
|
+
// .then(async () => {
|
|
2458
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2459
|
+
// })
|
|
2460
|
+
// .catch(
|
|
2461
|
+
// (e) => {}
|
|
2462
|
+
// console.error(e)
|
|
2463
|
+
// );
|
|
2464
|
+
// });
|
|
2465
|
+
// }
|
|
2466
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2467
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2468
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2469
|
+
if (element) {
|
|
2470
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2471
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2472
|
+
// await _screenshot(state, this, element);
|
|
2473
|
+
}
|
|
2183
2474
|
}
|
|
2475
|
+
await _screenshot(state, this);
|
|
2476
|
+
return state.info;
|
|
2477
|
+
}
|
|
2478
|
+
catch (error) {
|
|
2479
|
+
console.error(error);
|
|
2184
2480
|
}
|
|
2185
|
-
await _screenshot(state, this);
|
|
2186
|
-
return state.info;
|
|
2187
2481
|
}
|
|
2188
2482
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2189
2483
|
}
|
|
@@ -2204,11 +2498,15 @@ class StableBrowser {
|
|
|
2204
2498
|
scroll: false,
|
|
2205
2499
|
highlight: false,
|
|
2206
2500
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2207
|
-
text: `Verify text does not exist in page`,
|
|
2501
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2502
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2208
2503
|
operation: "verifyTextNotExistInPage",
|
|
2209
2504
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2210
2505
|
};
|
|
2211
|
-
|
|
2506
|
+
if (testForRegex(text)) {
|
|
2507
|
+
text = text.replace(/\\"/g, '"');
|
|
2508
|
+
}
|
|
2509
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2212
2510
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2213
2511
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2214
2512
|
if (newValue !== text) {
|
|
@@ -2220,8 +2518,16 @@ class StableBrowser {
|
|
|
2220
2518
|
try {
|
|
2221
2519
|
await _preCommand(state, this);
|
|
2222
2520
|
state.info.text = text;
|
|
2521
|
+
let resultWithElementsFound = {
|
|
2522
|
+
length: null, // initial cannot be 0
|
|
2523
|
+
};
|
|
2223
2524
|
while (true) {
|
|
2224
|
-
|
|
2525
|
+
try {
|
|
2526
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2527
|
+
}
|
|
2528
|
+
catch (error) {
|
|
2529
|
+
// ignore
|
|
2530
|
+
}
|
|
2225
2531
|
if (resultWithElementsFound.length === 0) {
|
|
2226
2532
|
await _screenshot(state, this);
|
|
2227
2533
|
return state.info;
|
|
@@ -2251,10 +2557,11 @@ class StableBrowser {
|
|
|
2251
2557
|
highlight: false,
|
|
2252
2558
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2253
2559
|
text: `Verify text with relation to another text`,
|
|
2560
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2254
2561
|
operation: "verify_text_with_relation",
|
|
2255
2562
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2256
2563
|
};
|
|
2257
|
-
const timeout = this.
|
|
2564
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2258
2565
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2259
2566
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2260
2567
|
if (newValue !== textAnchor) {
|
|
@@ -2272,8 +2579,16 @@ class StableBrowser {
|
|
|
2272
2579
|
try {
|
|
2273
2580
|
await _preCommand(state, this);
|
|
2274
2581
|
state.info.text = textToVerify;
|
|
2582
|
+
let resultWithElementsFound = {
|
|
2583
|
+
length: 0,
|
|
2584
|
+
};
|
|
2275
2585
|
while (true) {
|
|
2276
|
-
|
|
2586
|
+
try {
|
|
2587
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2588
|
+
}
|
|
2589
|
+
catch (error) {
|
|
2590
|
+
// ignore
|
|
2591
|
+
}
|
|
2277
2592
|
if (resultWithElementsFound.length === 0) {
|
|
2278
2593
|
if (Date.now() - state.startTime > timeout) {
|
|
2279
2594
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2281,51 +2596,56 @@ class StableBrowser {
|
|
|
2281
2596
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2282
2597
|
continue;
|
|
2283
2598
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
const
|
|
2292
|
-
for (let i = 0; i <
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2599
|
+
try {
|
|
2600
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2601
|
+
foundAncore = true;
|
|
2602
|
+
const result = resultWithElementsFound[i];
|
|
2603
|
+
const token = result.randomToken;
|
|
2604
|
+
const frame = result.frame;
|
|
2605
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2606
|
+
const climbArray1 = [];
|
|
2607
|
+
for (let i = 0; i < climb; i++) {
|
|
2608
|
+
climbArray1.push("..");
|
|
2609
|
+
}
|
|
2610
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2611
|
+
css = css + " >> " + climbXpath;
|
|
2612
|
+
const count = await frame.locator(css).count();
|
|
2613
|
+
for (let j = 0; j < count; j++) {
|
|
2614
|
+
const continer = await frame.locator(css).nth(j);
|
|
2615
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2616
|
+
if (result.elementCount > 0) {
|
|
2617
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2618
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2619
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2620
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2621
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2622
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2623
|
+
// .then(async () => {
|
|
2624
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2625
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2626
|
+
// () => {}
|
|
2627
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2628
|
+
// );
|
|
2629
|
+
// })
|
|
2630
|
+
// .catch(e);
|
|
2631
|
+
// }
|
|
2632
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2633
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2634
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2635
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2636
|
+
if (element) {
|
|
2637
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2638
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2299
2639
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
continue;
|
|
2303
|
-
}
|
|
2304
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2305
|
-
if (foundElements.length > 0) {
|
|
2306
|
-
// set the container element attribute
|
|
2307
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2308
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2309
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2310
|
-
return { found: true };
|
|
2640
|
+
await _screenshot(state, this);
|
|
2641
|
+
return state.info;
|
|
2311
2642
|
}
|
|
2312
2643
|
}
|
|
2313
|
-
return { found: false };
|
|
2314
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2315
|
-
if (findResult.found === true) {
|
|
2316
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2317
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2318
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2319
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2320
|
-
const element = await frame.$(dataAttribute);
|
|
2321
|
-
if (element) {
|
|
2322
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2323
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2324
|
-
}
|
|
2325
|
-
await _screenshot(state, this);
|
|
2326
|
-
return state.info;
|
|
2327
2644
|
}
|
|
2328
2645
|
}
|
|
2646
|
+
catch (error) {
|
|
2647
|
+
console.error(error);
|
|
2648
|
+
}
|
|
2329
2649
|
}
|
|
2330
2650
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2331
2651
|
}
|
|
@@ -2336,6 +2656,30 @@ class StableBrowser {
|
|
|
2336
2656
|
_commandFinally(state, this);
|
|
2337
2657
|
}
|
|
2338
2658
|
}
|
|
2659
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2660
|
+
const frames = this.page.frames();
|
|
2661
|
+
let results = [];
|
|
2662
|
+
let ignoreCase = false;
|
|
2663
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2664
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2665
|
+
result.frame = frames[i];
|
|
2666
|
+
const climbArray = [];
|
|
2667
|
+
for (let i = 0; i < climb; i++) {
|
|
2668
|
+
climbArray.push("..");
|
|
2669
|
+
}
|
|
2670
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2671
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2672
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2673
|
+
if (count > 0) {
|
|
2674
|
+
result.elementCount = count;
|
|
2675
|
+
result.locator = newLocator;
|
|
2676
|
+
results.push(result);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
// state.info.results = results;
|
|
2680
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2681
|
+
return resultWithElementsFound;
|
|
2682
|
+
}
|
|
2339
2683
|
async visualVerification(text, options = {}, world = null) {
|
|
2340
2684
|
const startTime = Date.now();
|
|
2341
2685
|
let error = null;
|
|
@@ -2354,10 +2698,13 @@ class StableBrowser {
|
|
|
2354
2698
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2355
2699
|
info.screenshotPath = screenshotPath;
|
|
2356
2700
|
const screenshot = await this.takeScreenshot();
|
|
2357
|
-
|
|
2358
|
-
method: "
|
|
2701
|
+
let request = {
|
|
2702
|
+
method: "post",
|
|
2703
|
+
maxBodyLength: Infinity,
|
|
2359
2704
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2360
2705
|
headers: {
|
|
2706
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2707
|
+
"x-source": "aaa",
|
|
2361
2708
|
"Content-Type": "application/json",
|
|
2362
2709
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2363
2710
|
},
|
|
@@ -2366,7 +2713,7 @@ class StableBrowser {
|
|
|
2366
2713
|
screenshot: screenshot,
|
|
2367
2714
|
}),
|
|
2368
2715
|
};
|
|
2369
|
-
|
|
2716
|
+
const result = await axios.request(request);
|
|
2370
2717
|
if (result.data.status !== true) {
|
|
2371
2718
|
throw new Error("Visual validation failed");
|
|
2372
2719
|
}
|
|
@@ -2394,6 +2741,7 @@ class StableBrowser {
|
|
|
2394
2741
|
_reportToWorld(world, {
|
|
2395
2742
|
type: Types.VERIFY_VISUAL,
|
|
2396
2743
|
text: "Visual verification",
|
|
2744
|
+
_text: "Visual verification of " + text,
|
|
2397
2745
|
screenshotId,
|
|
2398
2746
|
result: error
|
|
2399
2747
|
? {
|
|
@@ -2660,6 +3008,32 @@ class StableBrowser {
|
|
|
2660
3008
|
}
|
|
2661
3009
|
return timeout;
|
|
2662
3010
|
}
|
|
3011
|
+
_getFindElementTimeout(options) {
|
|
3012
|
+
if (options && options.timeout) {
|
|
3013
|
+
return options.timeout;
|
|
3014
|
+
}
|
|
3015
|
+
if (this.configuration.find_element_timeout) {
|
|
3016
|
+
return this.configuration.find_element_timeout;
|
|
3017
|
+
}
|
|
3018
|
+
return 30000;
|
|
3019
|
+
}
|
|
3020
|
+
async saveStoreState(path = null, world = null) {
|
|
3021
|
+
const storageState = await this.page.context().storageState();
|
|
3022
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3023
|
+
if (path) {
|
|
3024
|
+
// save { storageState: storageState } into the path
|
|
3025
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3026
|
+
}
|
|
3027
|
+
else {
|
|
3028
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
async restoreSaveState(path = null, world = null) {
|
|
3032
|
+
await refreshBrowser(this, path, world);
|
|
3033
|
+
this.registerEventListeners(this.context);
|
|
3034
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3035
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3036
|
+
}
|
|
2663
3037
|
async waitForPageLoad(options = {}, world = null) {
|
|
2664
3038
|
let timeout = this._getLoadTimeout(options);
|
|
2665
3039
|
const promiseArray = [];
|
|
@@ -2727,6 +3101,7 @@ class StableBrowser {
|
|
|
2727
3101
|
highlight: false,
|
|
2728
3102
|
type: Types.CLOSE_PAGE,
|
|
2729
3103
|
text: `Close page`,
|
|
3104
|
+
_text: `Close the page`,
|
|
2730
3105
|
operation: "closePage",
|
|
2731
3106
|
log: "***** close page *****\n",
|
|
2732
3107
|
throwError: false,
|
|
@@ -2743,8 +3118,95 @@ class StableBrowser {
|
|
|
2743
3118
|
_commandFinally(state, this);
|
|
2744
3119
|
}
|
|
2745
3120
|
}
|
|
3121
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3122
|
+
let operation = null;
|
|
3123
|
+
if (!options || !options.operation) {
|
|
3124
|
+
throw new Error("operation is not defined");
|
|
3125
|
+
}
|
|
3126
|
+
operation = options.operation;
|
|
3127
|
+
// validate operation is one of the supported operations
|
|
3128
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3129
|
+
throw new Error("operation is not supported");
|
|
3130
|
+
}
|
|
3131
|
+
const state = {
|
|
3132
|
+
options,
|
|
3133
|
+
world,
|
|
3134
|
+
locate: false,
|
|
3135
|
+
scroll: false,
|
|
3136
|
+
highlight: false,
|
|
3137
|
+
type: Types.TABLE_OPERATION,
|
|
3138
|
+
text: `Table operation`,
|
|
3139
|
+
_text: `Table ${operation} operation`,
|
|
3140
|
+
operation: operation,
|
|
3141
|
+
log: "***** Table operation *****\n",
|
|
3142
|
+
};
|
|
3143
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3144
|
+
try {
|
|
3145
|
+
await _preCommand(state, this);
|
|
3146
|
+
const start = Date.now();
|
|
3147
|
+
let cellArea = null;
|
|
3148
|
+
while (true) {
|
|
3149
|
+
try {
|
|
3150
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3151
|
+
if (cellArea) {
|
|
3152
|
+
break;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
catch (e) {
|
|
3156
|
+
// ignore
|
|
3157
|
+
}
|
|
3158
|
+
if (Date.now() - start > timeout) {
|
|
3159
|
+
throw new Error(`Cell not found in table`);
|
|
3160
|
+
}
|
|
3161
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3162
|
+
}
|
|
3163
|
+
switch (operation) {
|
|
3164
|
+
case "click":
|
|
3165
|
+
if (!options.css) {
|
|
3166
|
+
// will click in the center of the cell
|
|
3167
|
+
let xOffset = 0;
|
|
3168
|
+
let yOffset = 0;
|
|
3169
|
+
if (options.xOffset) {
|
|
3170
|
+
xOffset = options.xOffset;
|
|
3171
|
+
}
|
|
3172
|
+
if (options.yOffset) {
|
|
3173
|
+
yOffset = options.yOffset;
|
|
3174
|
+
}
|
|
3175
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3176
|
+
}
|
|
3177
|
+
else {
|
|
3178
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3179
|
+
if (results.length === 0) {
|
|
3180
|
+
throw new Error(`Element not found in cell area`);
|
|
3181
|
+
}
|
|
3182
|
+
state.element = results[0];
|
|
3183
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3184
|
+
}
|
|
3185
|
+
break;
|
|
3186
|
+
case "hover+click":
|
|
3187
|
+
if (!options.css) {
|
|
3188
|
+
throw new Error("css is not defined");
|
|
3189
|
+
}
|
|
3190
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3191
|
+
if (results.length === 0) {
|
|
3192
|
+
throw new Error(`Element not found in cell area`);
|
|
3193
|
+
}
|
|
3194
|
+
state.element = results[0];
|
|
3195
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3196
|
+
break;
|
|
3197
|
+
default:
|
|
3198
|
+
throw new Error("operation is not supported");
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
catch (e) {
|
|
3202
|
+
await _commandError(state, e, this);
|
|
3203
|
+
}
|
|
3204
|
+
finally {
|
|
3205
|
+
_commandFinally(state, this);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
2746
3208
|
saveTestDataAsGlobal(options, world) {
|
|
2747
|
-
const dataFile =
|
|
3209
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2748
3210
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2749
3211
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2750
3212
|
}
|
|
@@ -2774,6 +3236,7 @@ class StableBrowser {
|
|
|
2774
3236
|
_reportToWorld(world, {
|
|
2775
3237
|
type: Types.SET_VIEWPORT,
|
|
2776
3238
|
text: "set viewport size to " + width + "x" + hight,
|
|
3239
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2777
3240
|
screenshotId,
|
|
2778
3241
|
result: error
|
|
2779
3242
|
? {
|
|
@@ -2861,6 +3324,9 @@ class StableBrowser {
|
|
|
2861
3324
|
else {
|
|
2862
3325
|
this.stepName = "step " + this.stepIndex;
|
|
2863
3326
|
}
|
|
3327
|
+
if (this.context) {
|
|
3328
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3329
|
+
}
|
|
2864
3330
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2865
3331
|
if (this.context.browserObject.context) {
|
|
2866
3332
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2873,6 +3339,41 @@ class StableBrowser {
|
|
|
2873
3339
|
this.saveTestDataAsGlobal({}, world);
|
|
2874
3340
|
}
|
|
2875
3341
|
}
|
|
3342
|
+
if (this.initSnapshotTaken === false) {
|
|
3343
|
+
this.initSnapshotTaken = true;
|
|
3344
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3345
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3346
|
+
if (snapshot) {
|
|
3347
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
async getAriaSnapshot() {
|
|
3353
|
+
try {
|
|
3354
|
+
// find the page url
|
|
3355
|
+
const url = await this.page.url();
|
|
3356
|
+
// extract the path from the url
|
|
3357
|
+
const path = new URL(url).pathname;
|
|
3358
|
+
// get the page title
|
|
3359
|
+
const title = await this.page.title();
|
|
3360
|
+
// go over other frams
|
|
3361
|
+
const frames = this.page.frames();
|
|
3362
|
+
const snapshots = [];
|
|
3363
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3364
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3365
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3366
|
+
content.push(`- frame: ${i}`);
|
|
3367
|
+
const frame = frames[i];
|
|
3368
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3369
|
+
content.push(snapshot);
|
|
3370
|
+
}
|
|
3371
|
+
return content.join("\n");
|
|
3372
|
+
}
|
|
3373
|
+
catch (e) {
|
|
3374
|
+
console.error(e);
|
|
3375
|
+
}
|
|
3376
|
+
return null;
|
|
2876
3377
|
}
|
|
2877
3378
|
async afterStep(world, step) {
|
|
2878
3379
|
this.stepName = null;
|
|
@@ -2881,6 +3382,21 @@ class StableBrowser {
|
|
|
2881
3382
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2882
3383
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2883
3384
|
});
|
|
3385
|
+
await world.attach(JSON.stringify({
|
|
3386
|
+
type: "trace",
|
|
3387
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3388
|
+
}), "application/json+trace");
|
|
3389
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (this.context) {
|
|
3393
|
+
this.context.examplesRow = null;
|
|
3394
|
+
}
|
|
3395
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3396
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3397
|
+
if (snapshot) {
|
|
3398
|
+
const obj = {};
|
|
3399
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
2884
3400
|
}
|
|
2885
3401
|
}
|
|
2886
3402
|
}
|