automation_model 1.0.568-dev → 1.0.568-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 +3 -0
- 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 +49 -45
- 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 +765 -232
- 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 +7 -2
- package/lib/utils.js +319 -12
- 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,52 +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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
for (let i = 0; i < elements.length; i++) {
|
|
356
|
-
if (randomToken === null) {
|
|
357
|
-
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;
|
|
358
374
|
}
|
|
359
|
-
let element = elements[i];
|
|
360
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
361
|
-
elementCount++;
|
|
362
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;
|
|
363
389
|
}
|
|
364
|
-
|
|
365
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
366
393
|
}
|
|
367
|
-
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) {
|
|
368
395
|
if (!info) {
|
|
369
396
|
info = {};
|
|
370
397
|
}
|
|
@@ -387,30 +414,31 @@ class StableBrowser {
|
|
|
387
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
388
415
|
let locator = null;
|
|
389
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
390
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
391
419
|
if (!locatorString) {
|
|
392
420
|
info.failCause.textNotFound = true;
|
|
393
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
394
422
|
return;
|
|
395
423
|
}
|
|
396
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
397
425
|
}
|
|
398
426
|
else if (locatorSearch.text) {
|
|
399
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
400
|
-
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);
|
|
401
429
|
if (result.elementCount === 0) {
|
|
402
430
|
info.failCause.textNotFound = true;
|
|
403
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
404
432
|
return;
|
|
405
433
|
}
|
|
406
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
407
435
|
if (locatorSearch.childCss) {
|
|
408
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
409
437
|
}
|
|
410
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
411
439
|
}
|
|
412
440
|
else {
|
|
413
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
414
442
|
}
|
|
415
443
|
// let cssHref = false;
|
|
416
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -439,7 +467,7 @@ class StableBrowser {
|
|
|
439
467
|
if (!visibleOnly) {
|
|
440
468
|
visible = true;
|
|
441
469
|
}
|
|
442
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
443
471
|
foundLocators.push(locator.nth(j));
|
|
444
472
|
if (info.locatorLog) {
|
|
445
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -452,9 +480,11 @@ class StableBrowser {
|
|
|
452
480
|
info.printMessages = {};
|
|
453
481
|
}
|
|
454
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
455
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
456
485
|
}
|
|
457
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
458
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
459
489
|
}
|
|
460
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -522,7 +552,7 @@ class StableBrowser {
|
|
|
522
552
|
}
|
|
523
553
|
return { rerun: false };
|
|
524
554
|
}
|
|
525
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
526
556
|
if (!timeout) {
|
|
527
557
|
timeout = 30000;
|
|
528
558
|
}
|
|
@@ -532,9 +562,30 @@ class StableBrowser {
|
|
|
532
562
|
let selector = selectors.locators[j];
|
|
533
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
534
564
|
}
|
|
535
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
536
566
|
if (!element.rerun) {
|
|
537
|
-
|
|
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);
|
|
538
589
|
}
|
|
539
590
|
}
|
|
540
591
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -607,7 +658,7 @@ class StableBrowser {
|
|
|
607
658
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
608
659
|
if (Date.now() - startTime > timeout) {
|
|
609
660
|
info.failCause.iframeNotFound = true;
|
|
610
|
-
info.failCause.lastError =
|
|
661
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
611
662
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
612
663
|
}
|
|
613
664
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -632,7 +683,7 @@ class StableBrowser {
|
|
|
632
683
|
return bodyContent;
|
|
633
684
|
});
|
|
634
685
|
}
|
|
635
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
686
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
636
687
|
if (!info) {
|
|
637
688
|
info = {};
|
|
638
689
|
info.failCause = {};
|
|
@@ -681,18 +732,13 @@ class StableBrowser {
|
|
|
681
732
|
}
|
|
682
733
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
683
734
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
684
|
-
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);
|
|
685
736
|
if (result.foundElements.length === 0) {
|
|
686
737
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
687
|
-
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);
|
|
688
739
|
}
|
|
689
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
690
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
691
|
-
}
|
|
692
|
-
else {
|
|
693
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
694
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
695
|
-
}
|
|
740
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
741
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
696
742
|
}
|
|
697
743
|
let foundElements = result.foundElements;
|
|
698
744
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -736,7 +782,7 @@ class StableBrowser {
|
|
|
736
782
|
break;
|
|
737
783
|
}
|
|
738
784
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
739
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
785
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
740
786
|
highPriorityOnly = false;
|
|
741
787
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
742
788
|
lazy_scroll = true;
|
|
@@ -744,10 +790,15 @@ class StableBrowser {
|
|
|
744
790
|
}
|
|
745
791
|
}
|
|
746
792
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
747
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
793
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
748
794
|
visibleOnly = false;
|
|
749
795
|
}
|
|
750
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
|
+
}
|
|
751
802
|
}
|
|
752
803
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
753
804
|
// if (info.locatorLog) {
|
|
@@ -758,10 +809,12 @@ class StableBrowser {
|
|
|
758
809
|
// }
|
|
759
810
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
760
811
|
info.failCause.locatorNotFound = true;
|
|
761
|
-
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
|
+
}
|
|
762
815
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
763
816
|
}
|
|
764
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
817
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
765
818
|
let foundElements = [];
|
|
766
819
|
const result = {
|
|
767
820
|
foundElements: foundElements,
|
|
@@ -769,7 +822,7 @@ class StableBrowser {
|
|
|
769
822
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
770
823
|
let foundLocators = [];
|
|
771
824
|
try {
|
|
772
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
825
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
773
826
|
}
|
|
774
827
|
catch (e) {
|
|
775
828
|
// this call can fail it the browser is navigating
|
|
@@ -777,7 +830,7 @@ class StableBrowser {
|
|
|
777
830
|
// this.logger.debug(e);
|
|
778
831
|
foundLocators = [];
|
|
779
832
|
try {
|
|
780
|
-
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);
|
|
781
834
|
}
|
|
782
835
|
catch (e) {
|
|
783
836
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -792,9 +845,40 @@ class StableBrowser {
|
|
|
792
845
|
result.locatorIndex = i;
|
|
793
846
|
}
|
|
794
847
|
if (foundLocators.length > 1) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
}
|
|
798
882
|
}
|
|
799
883
|
}
|
|
800
884
|
}
|
|
@@ -905,25 +989,14 @@ class StableBrowser {
|
|
|
905
989
|
options,
|
|
906
990
|
world,
|
|
907
991
|
text: "Click element",
|
|
992
|
+
_text: "Click on " + selectors.element_name,
|
|
908
993
|
type: Types.CLICK,
|
|
909
994
|
operation: "click",
|
|
910
995
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
911
996
|
};
|
|
912
997
|
try {
|
|
913
998
|
await _preCommand(state, this);
|
|
914
|
-
|
|
915
|
-
state.selectors.locators[0].text = state.options.context;
|
|
916
|
-
}
|
|
917
|
-
try {
|
|
918
|
-
await state.element.click();
|
|
919
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
920
|
-
}
|
|
921
|
-
catch (e) {
|
|
922
|
-
// await this.closeUnexpectedPopups();
|
|
923
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
924
|
-
await state.element.dispatchEvent("click");
|
|
925
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
926
|
-
}
|
|
999
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
927
1000
|
await this.waitForPageLoad();
|
|
928
1001
|
return state.info;
|
|
929
1002
|
}
|
|
@@ -934,6 +1007,38 @@ class StableBrowser {
|
|
|
934
1007
|
_commandFinally(state, this);
|
|
935
1008
|
}
|
|
936
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
|
+
}
|
|
937
1042
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
938
1043
|
const state = {
|
|
939
1044
|
selectors,
|
|
@@ -942,6 +1047,7 @@ class StableBrowser {
|
|
|
942
1047
|
world,
|
|
943
1048
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
944
1049
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1050
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
945
1051
|
operation: "setCheck",
|
|
946
1052
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
947
1053
|
};
|
|
@@ -951,9 +1057,15 @@ class StableBrowser {
|
|
|
951
1057
|
// let element = await this._locate(selectors, info, _params);
|
|
952
1058
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
953
1059
|
try {
|
|
954
|
-
//
|
|
1060
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1061
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1062
|
+
await this._highlightElements(state.element);
|
|
955
1063
|
await state.element.setChecked(checked);
|
|
956
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);
|
|
957
1069
|
}
|
|
958
1070
|
catch (e) {
|
|
959
1071
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -985,22 +1097,13 @@ class StableBrowser {
|
|
|
985
1097
|
world,
|
|
986
1098
|
type: Types.HOVER,
|
|
987
1099
|
text: `Hover element`,
|
|
1100
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
988
1101
|
operation: "hover",
|
|
989
1102
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
990
1103
|
};
|
|
991
1104
|
try {
|
|
992
1105
|
await _preCommand(state, this);
|
|
993
|
-
|
|
994
|
-
await state.element.hover();
|
|
995
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
996
|
-
}
|
|
997
|
-
catch (e) {
|
|
998
|
-
//await this.closeUnexpectedPopups();
|
|
999
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1000
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1001
|
-
await state.element.hover({ timeout: 10000 });
|
|
1002
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1003
|
-
}
|
|
1106
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1004
1107
|
await _screenshot(state, this);
|
|
1005
1108
|
await this.waitForPageLoad();
|
|
1006
1109
|
return state.info;
|
|
@@ -1024,6 +1127,7 @@ class StableBrowser {
|
|
|
1024
1127
|
value: values.toString(),
|
|
1025
1128
|
type: Types.SELECT,
|
|
1026
1129
|
text: `Select option: ${values}`,
|
|
1130
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1027
1131
|
operation: "selectOption",
|
|
1028
1132
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1029
1133
|
};
|
|
@@ -1058,6 +1162,7 @@ class StableBrowser {
|
|
|
1058
1162
|
highlight: false,
|
|
1059
1163
|
type: Types.TYPE_PRESS,
|
|
1060
1164
|
text: `Type value: ${_value}`,
|
|
1165
|
+
_text: `Type value: ${_value}`,
|
|
1061
1166
|
operation: "type",
|
|
1062
1167
|
log: "",
|
|
1063
1168
|
};
|
|
@@ -1137,6 +1242,7 @@ class StableBrowser {
|
|
|
1137
1242
|
world,
|
|
1138
1243
|
type: Types.SET_DATE_TIME,
|
|
1139
1244
|
text: `Set date time value: ${value}`,
|
|
1245
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1140
1246
|
operation: "setDateTime",
|
|
1141
1247
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1142
1248
|
throwError: false,
|
|
@@ -1144,7 +1250,7 @@ class StableBrowser {
|
|
|
1144
1250
|
try {
|
|
1145
1251
|
await _preCommand(state, this);
|
|
1146
1252
|
try {
|
|
1147
|
-
await state.element
|
|
1253
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1148
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1149
1255
|
if (format) {
|
|
1150
1256
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1208,9 +1314,13 @@ class StableBrowser {
|
|
|
1208
1314
|
world,
|
|
1209
1315
|
type: Types.FILL,
|
|
1210
1316
|
text: `Click type input with value: ${_value}`,
|
|
1317
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1211
1318
|
operation: "clickType",
|
|
1212
1319
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1213
1320
|
};
|
|
1321
|
+
if (!options) {
|
|
1322
|
+
options = {};
|
|
1323
|
+
}
|
|
1214
1324
|
if (newValue !== _value) {
|
|
1215
1325
|
//this.logger.info(_value + "=" + newValue);
|
|
1216
1326
|
_value = newValue;
|
|
@@ -1218,7 +1328,7 @@ class StableBrowser {
|
|
|
1218
1328
|
try {
|
|
1219
1329
|
await _preCommand(state, this);
|
|
1220
1330
|
state.info.value = _value;
|
|
1221
|
-
if (
|
|
1331
|
+
if (!options.press) {
|
|
1222
1332
|
try {
|
|
1223
1333
|
let currentValue = await state.element.inputValue();
|
|
1224
1334
|
if (currentValue) {
|
|
@@ -1229,13 +1339,9 @@ class StableBrowser {
|
|
|
1229
1339
|
this.logger.info("unable to clear input value");
|
|
1230
1340
|
}
|
|
1231
1341
|
}
|
|
1232
|
-
if (options
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
catch (e) {
|
|
1237
|
-
await state.element.dispatchEvent("click");
|
|
1238
|
-
}
|
|
1342
|
+
if (options.press) {
|
|
1343
|
+
options.timeout = 5000;
|
|
1344
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1239
1345
|
}
|
|
1240
1346
|
else {
|
|
1241
1347
|
try {
|
|
@@ -1273,7 +1379,12 @@ class StableBrowser {
|
|
|
1273
1379
|
await this.waitForPageLoad();
|
|
1274
1380
|
}
|
|
1275
1381
|
else if (enter === false) {
|
|
1276
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1384
|
+
}
|
|
1385
|
+
catch (e) {
|
|
1386
|
+
// ignore
|
|
1387
|
+
}
|
|
1277
1388
|
//await this.page.keyboard.press("Tab");
|
|
1278
1389
|
}
|
|
1279
1390
|
else {
|
|
@@ -1325,6 +1436,7 @@ class StableBrowser {
|
|
|
1325
1436
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1326
1437
|
}
|
|
1327
1438
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1439
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1328
1440
|
_validateSelectors(selectors);
|
|
1329
1441
|
let screenshotId = null;
|
|
1330
1442
|
let screenshotPath = null;
|
|
@@ -1334,7 +1446,7 @@ class StableBrowser {
|
|
|
1334
1446
|
}
|
|
1335
1447
|
info.operation = "getText";
|
|
1336
1448
|
info.selectors = selectors;
|
|
1337
|
-
let element = await this._locate(selectors, info, _params);
|
|
1449
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1338
1450
|
if (climb > 0) {
|
|
1339
1451
|
const climbArray = [];
|
|
1340
1452
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1353,6 +1465,18 @@ class StableBrowser {
|
|
|
1353
1465
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1354
1466
|
try {
|
|
1355
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
|
+
// }
|
|
1356
1480
|
const elementText = await element.innerText();
|
|
1357
1481
|
return {
|
|
1358
1482
|
text: elementText,
|
|
@@ -1364,7 +1488,7 @@ class StableBrowser {
|
|
|
1364
1488
|
}
|
|
1365
1489
|
catch (e) {
|
|
1366
1490
|
//await this.closeUnexpectedPopups();
|
|
1367
|
-
this.logger.info("no innerText will use textContent");
|
|
1491
|
+
this.logger.info("no innerText, will use textContent");
|
|
1368
1492
|
const elementText = await element.textContent();
|
|
1369
1493
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1370
1494
|
}
|
|
@@ -1389,6 +1513,7 @@ class StableBrowser {
|
|
|
1389
1513
|
highlight: false,
|
|
1390
1514
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1391
1515
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1516
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1392
1517
|
operation: "containsPattern",
|
|
1393
1518
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1394
1519
|
};
|
|
@@ -1424,6 +1549,8 @@ class StableBrowser {
|
|
|
1424
1549
|
}
|
|
1425
1550
|
}
|
|
1426
1551
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1552
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1553
|
+
const startTime = Date.now();
|
|
1427
1554
|
const state = {
|
|
1428
1555
|
selectors,
|
|
1429
1556
|
_params,
|
|
@@ -1450,62 +1577,54 @@ class StableBrowser {
|
|
|
1450
1577
|
}
|
|
1451
1578
|
let foundObj = null;
|
|
1452
1579
|
try {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1460
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1461
|
-
if (dateAlternatives.date) {
|
|
1462
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1463
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1464
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1465
|
-
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);
|
|
1466
1586
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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)) {
|
|
1474
1607
|
return state.info;
|
|
1475
1608
|
}
|
|
1476
1609
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
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
|
|
1483
1615
|
}
|
|
1484
|
-
|
|
1616
|
+
state.info.foundText = foundObj?.text;
|
|
1617
|
+
state.info.value = foundObj?.value;
|
|
1618
|
+
throw new Error("element doesn't contain text " + text);
|
|
1485
1619
|
}
|
|
1486
1620
|
catch (e) {
|
|
1487
1621
|
await _commandError(state, e, this);
|
|
1622
|
+
throw e;
|
|
1488
1623
|
}
|
|
1489
1624
|
finally {
|
|
1490
1625
|
_commandFinally(state, this);
|
|
1491
1626
|
}
|
|
1492
1627
|
}
|
|
1493
|
-
_getDataFile(world = null) {
|
|
1494
|
-
let dataFile = null;
|
|
1495
|
-
if (world && world.reportFolder) {
|
|
1496
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1497
|
-
}
|
|
1498
|
-
else if (this.reportFolder) {
|
|
1499
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1500
|
-
}
|
|
1501
|
-
else if (this.context && this.context.reportFolder) {
|
|
1502
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1503
|
-
}
|
|
1504
|
-
else {
|
|
1505
|
-
dataFile = "data.json";
|
|
1506
|
-
}
|
|
1507
|
-
return dataFile;
|
|
1508
|
-
}
|
|
1509
1628
|
async waitForUserInput(message, world = null) {
|
|
1510
1629
|
if (!message) {
|
|
1511
1630
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1534,13 +1653,22 @@ class StableBrowser {
|
|
|
1534
1653
|
return;
|
|
1535
1654
|
}
|
|
1536
1655
|
// if data file exists, load it
|
|
1537
|
-
const dataFile =
|
|
1656
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1538
1657
|
let data = this.getTestData(world);
|
|
1539
1658
|
// merge the testData with the existing data
|
|
1540
1659
|
Object.assign(data, testData);
|
|
1541
1660
|
// save the data to the file
|
|
1542
1661
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1543
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
|
+
}
|
|
1544
1672
|
_getDataFilePath(fileName) {
|
|
1545
1673
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1546
1674
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1637,7 +1765,7 @@ class StableBrowser {
|
|
|
1637
1765
|
}
|
|
1638
1766
|
}
|
|
1639
1767
|
getTestData(world = null) {
|
|
1640
|
-
const dataFile =
|
|
1768
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1641
1769
|
let data = {};
|
|
1642
1770
|
if (fs.existsSync(dataFile)) {
|
|
1643
1771
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1724,6 +1852,15 @@ class StableBrowser {
|
|
|
1724
1852
|
document.documentElement.clientWidth,
|
|
1725
1853
|
])));
|
|
1726
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
|
+
// }
|
|
1727
1864
|
if (this.context.browserName === "chromium") {
|
|
1728
1865
|
const client = await playContext.newCDPSession(this.page);
|
|
1729
1866
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1745,6 +1882,10 @@ class StableBrowser {
|
|
|
1745
1882
|
else {
|
|
1746
1883
|
screenshotBuffer = await this.page.screenshot();
|
|
1747
1884
|
}
|
|
1885
|
+
// if (focusedElement) {
|
|
1886
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1887
|
+
// await this._unhighlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1748
1889
|
let image = await Jimp.read(screenshotBuffer);
|
|
1749
1890
|
// Get the image dimensions
|
|
1750
1891
|
const { width, height } = image.bitmap;
|
|
@@ -1757,6 +1898,7 @@ class StableBrowser {
|
|
|
1757
1898
|
else {
|
|
1758
1899
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1759
1900
|
}
|
|
1901
|
+
return screenshotBuffer;
|
|
1760
1902
|
}
|
|
1761
1903
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1762
1904
|
const state = {
|
|
@@ -1792,8 +1934,10 @@ class StableBrowser {
|
|
|
1792
1934
|
world,
|
|
1793
1935
|
type: Types.EXTRACT,
|
|
1794
1936
|
text: `Extract attribute from element`,
|
|
1937
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1795
1938
|
operation: "extractAttribute",
|
|
1796
1939
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1940
|
+
allowDisabled: true,
|
|
1797
1941
|
};
|
|
1798
1942
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1799
1943
|
try {
|
|
@@ -1815,6 +1959,7 @@ class StableBrowser {
|
|
|
1815
1959
|
state.info.value = state.value;
|
|
1816
1960
|
this.setTestData({ [variable]: state.value }, world);
|
|
1817
1961
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1962
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1818
1963
|
return state.info;
|
|
1819
1964
|
}
|
|
1820
1965
|
catch (e) {
|
|
@@ -1833,14 +1978,21 @@ class StableBrowser {
|
|
|
1833
1978
|
options,
|
|
1834
1979
|
world,
|
|
1835
1980
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1981
|
+
highlight: true,
|
|
1982
|
+
screenshot: true,
|
|
1836
1983
|
text: `Verify element attribute`,
|
|
1984
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1837
1985
|
operation: "verifyAttribute",
|
|
1838
1986
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1987
|
+
allowDisabled: true,
|
|
1839
1988
|
};
|
|
1840
1989
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1841
1990
|
let val;
|
|
1991
|
+
let expectedValue;
|
|
1842
1992
|
try {
|
|
1843
1993
|
await _preCommand(state, this);
|
|
1994
|
+
expectedValue = state.value;
|
|
1995
|
+
state.info.expectedValue = expectedValue;
|
|
1844
1996
|
switch (attribute) {
|
|
1845
1997
|
case "innerText":
|
|
1846
1998
|
val = String(await state.element.innerText());
|
|
@@ -1854,7 +2006,7 @@ class StableBrowser {
|
|
|
1854
2006
|
case "disabled":
|
|
1855
2007
|
val = String(await state.element.isDisabled());
|
|
1856
2008
|
break;
|
|
1857
|
-
case "
|
|
2009
|
+
case "readOnly":
|
|
1858
2010
|
const isEditable = await state.element.isEditable();
|
|
1859
2011
|
val = String(!isEditable);
|
|
1860
2012
|
break;
|
|
@@ -1862,19 +2014,22 @@ class StableBrowser {
|
|
|
1862
2014
|
val = String(await state.element.getAttribute(attribute));
|
|
1863
2015
|
break;
|
|
1864
2016
|
}
|
|
2017
|
+
state.info.value = val;
|
|
1865
2018
|
let regex;
|
|
1866
|
-
if (
|
|
1867
|
-
const patternBody =
|
|
2019
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2020
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1868
2021
|
regex = new RegExp(patternBody, "g");
|
|
1869
2022
|
}
|
|
1870
2023
|
else {
|
|
1871
|
-
const escapedPattern =
|
|
2024
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1872
2025
|
regex = new RegExp(escapedPattern, "g");
|
|
1873
2026
|
}
|
|
1874
2027
|
if (!val.match(regex)) {
|
|
1875
|
-
|
|
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);
|
|
1876
2032
|
}
|
|
1877
|
-
state.info.value = val;
|
|
1878
2033
|
return state.info;
|
|
1879
2034
|
}
|
|
1880
2035
|
catch (e) {
|
|
@@ -1973,27 +2128,32 @@ class StableBrowser {
|
|
|
1973
2128
|
async _highlightElements(scope, css) {
|
|
1974
2129
|
try {
|
|
1975
2130
|
if (!scope) {
|
|
2131
|
+
// console.log(`Scope is not defined`);
|
|
1976
2132
|
return;
|
|
1977
2133
|
}
|
|
1978
2134
|
if (!css) {
|
|
1979
2135
|
scope
|
|
1980
2136
|
.evaluate((node) => {
|
|
1981
2137
|
if (node && node.style) {
|
|
1982
|
-
let
|
|
1983
|
-
|
|
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}`);
|
|
1984
2143
|
if (window) {
|
|
1985
2144
|
window.addEventListener("beforeunload", function (e) {
|
|
1986
|
-
node.style.
|
|
2145
|
+
node.style.outline = originalOutline;
|
|
1987
2146
|
});
|
|
1988
2147
|
}
|
|
1989
2148
|
setTimeout(function () {
|
|
1990
|
-
node.style.
|
|
2149
|
+
node.style.outline = originalOutline;
|
|
1991
2150
|
}, 2000);
|
|
1992
2151
|
}
|
|
1993
2152
|
})
|
|
1994
2153
|
.then(() => { })
|
|
1995
2154
|
.catch((e) => {
|
|
1996
2155
|
// ignore
|
|
2156
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1997
2157
|
});
|
|
1998
2158
|
}
|
|
1999
2159
|
else {
|
|
@@ -2009,17 +2169,18 @@ class StableBrowser {
|
|
|
2009
2169
|
if (!element.style) {
|
|
2010
2170
|
return;
|
|
2011
2171
|
}
|
|
2012
|
-
|
|
2172
|
+
let originalOutline = element.style.outline;
|
|
2173
|
+
element.__previousOutline = originalOutline;
|
|
2013
2174
|
// Set the new border to be red and 2px solid
|
|
2014
|
-
element.style.
|
|
2175
|
+
element.style.outline = "2px solid red";
|
|
2015
2176
|
if (window) {
|
|
2016
2177
|
window.addEventListener("beforeunload", function (e) {
|
|
2017
|
-
element.style.
|
|
2178
|
+
element.style.outline = originalOutline;
|
|
2018
2179
|
});
|
|
2019
2180
|
}
|
|
2020
2181
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2021
2182
|
setTimeout(function () {
|
|
2022
|
-
element.style.
|
|
2183
|
+
element.style.outline = originalOutline;
|
|
2023
2184
|
}, 2000);
|
|
2024
2185
|
}
|
|
2025
2186
|
return;
|
|
@@ -2027,6 +2188,7 @@ class StableBrowser {
|
|
|
2027
2188
|
.then(() => { })
|
|
2028
2189
|
.catch((e) => {
|
|
2029
2190
|
// ignore
|
|
2191
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2030
2192
|
});
|
|
2031
2193
|
}
|
|
2032
2194
|
}
|
|
@@ -2034,6 +2196,54 @@ class StableBrowser {
|
|
|
2034
2196
|
console.debug(error);
|
|
2035
2197
|
}
|
|
2036
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
|
+
// }
|
|
2037
2247
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2038
2248
|
const startTime = Date.now();
|
|
2039
2249
|
let error = null;
|
|
@@ -2078,6 +2288,7 @@ class StableBrowser {
|
|
|
2078
2288
|
_reportToWorld(world, {
|
|
2079
2289
|
type: Types.VERIFY_PAGE_PATH,
|
|
2080
2290
|
text: "Verify page path",
|
|
2291
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2081
2292
|
screenshotId,
|
|
2082
2293
|
result: error
|
|
2083
2294
|
? {
|
|
@@ -2095,26 +2306,89 @@ class StableBrowser {
|
|
|
2095
2306
|
});
|
|
2096
2307
|
}
|
|
2097
2308
|
}
|
|
2098
|
-
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) {
|
|
2099
2372
|
const frames = this.page.frames();
|
|
2100
2373
|
let results = [];
|
|
2374
|
+
// let ignoreCase = false;
|
|
2101
2375
|
for (let i = 0; i < frames.length; i++) {
|
|
2102
2376
|
if (dateAlternatives.date) {
|
|
2103
2377
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2104
|
-
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, {});
|
|
2105
2379
|
result.frame = frames[i];
|
|
2106
2380
|
results.push(result);
|
|
2107
2381
|
}
|
|
2108
2382
|
}
|
|
2109
2383
|
else if (numberAlternatives.number) {
|
|
2110
2384
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2111
|
-
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, {});
|
|
2112
2386
|
result.frame = frames[i];
|
|
2113
2387
|
results.push(result);
|
|
2114
2388
|
}
|
|
2115
2389
|
}
|
|
2116
2390
|
else {
|
|
2117
|
-
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, {});
|
|
2118
2392
|
result.frame = frames[i];
|
|
2119
2393
|
results.push(result);
|
|
2120
2394
|
}
|
|
@@ -2133,11 +2407,15 @@ class StableBrowser {
|
|
|
2133
2407
|
scroll: false,
|
|
2134
2408
|
highlight: false,
|
|
2135
2409
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2136
|
-
text: `Verify text exists in page`,
|
|
2410
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2411
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2137
2412
|
operation: "verifyTextExistInPage",
|
|
2138
2413
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2139
2414
|
};
|
|
2140
|
-
|
|
2415
|
+
if (testForRegex(text)) {
|
|
2416
|
+
text = text.replace(/\\"/g, '"');
|
|
2417
|
+
}
|
|
2418
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2141
2419
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2142
2420
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2143
2421
|
if (newValue !== text) {
|
|
@@ -2150,7 +2428,15 @@ class StableBrowser {
|
|
|
2150
2428
|
await _preCommand(state, this);
|
|
2151
2429
|
state.info.text = text;
|
|
2152
2430
|
while (true) {
|
|
2153
|
-
|
|
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
|
+
}
|
|
2154
2440
|
if (resultWithElementsFound.length === 0) {
|
|
2155
2441
|
if (Date.now() - state.startTime > timeout) {
|
|
2156
2442
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2158,18 +2444,40 @@ class StableBrowser {
|
|
|
2158
2444
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2159
2445
|
continue;
|
|
2160
2446
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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
|
+
}
|
|
2169
2474
|
}
|
|
2475
|
+
await _screenshot(state, this);
|
|
2476
|
+
return state.info;
|
|
2477
|
+
}
|
|
2478
|
+
catch (error) {
|
|
2479
|
+
console.error(error);
|
|
2170
2480
|
}
|
|
2171
|
-
await _screenshot(state, this);
|
|
2172
|
-
return state.info;
|
|
2173
2481
|
}
|
|
2174
2482
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2175
2483
|
}
|
|
@@ -2190,11 +2498,15 @@ class StableBrowser {
|
|
|
2190
2498
|
scroll: false,
|
|
2191
2499
|
highlight: false,
|
|
2192
2500
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2193
|
-
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`,
|
|
2194
2503
|
operation: "verifyTextNotExistInPage",
|
|
2195
2504
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2196
2505
|
};
|
|
2197
|
-
|
|
2506
|
+
if (testForRegex(text)) {
|
|
2507
|
+
text = text.replace(/\\"/g, '"');
|
|
2508
|
+
}
|
|
2509
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2198
2510
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2199
2511
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2200
2512
|
if (newValue !== text) {
|
|
@@ -2206,8 +2518,16 @@ class StableBrowser {
|
|
|
2206
2518
|
try {
|
|
2207
2519
|
await _preCommand(state, this);
|
|
2208
2520
|
state.info.text = text;
|
|
2521
|
+
let resultWithElementsFound = {
|
|
2522
|
+
length: null, // initial cannot be 0
|
|
2523
|
+
};
|
|
2209
2524
|
while (true) {
|
|
2210
|
-
|
|
2525
|
+
try {
|
|
2526
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2527
|
+
}
|
|
2528
|
+
catch (error) {
|
|
2529
|
+
// ignore
|
|
2530
|
+
}
|
|
2211
2531
|
if (resultWithElementsFound.length === 0) {
|
|
2212
2532
|
await _screenshot(state, this);
|
|
2213
2533
|
return state.info;
|
|
@@ -2237,10 +2557,11 @@ class StableBrowser {
|
|
|
2237
2557
|
highlight: false,
|
|
2238
2558
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2239
2559
|
text: `Verify text with relation to another text`,
|
|
2560
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2240
2561
|
operation: "verify_text_with_relation",
|
|
2241
2562
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2242
2563
|
};
|
|
2243
|
-
const timeout = this.
|
|
2564
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2244
2565
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2245
2566
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2246
2567
|
if (newValue !== textAnchor) {
|
|
@@ -2258,8 +2579,16 @@ class StableBrowser {
|
|
|
2258
2579
|
try {
|
|
2259
2580
|
await _preCommand(state, this);
|
|
2260
2581
|
state.info.text = textToVerify;
|
|
2582
|
+
let resultWithElementsFound = {
|
|
2583
|
+
length: 0,
|
|
2584
|
+
};
|
|
2261
2585
|
while (true) {
|
|
2262
|
-
|
|
2586
|
+
try {
|
|
2587
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2588
|
+
}
|
|
2589
|
+
catch (error) {
|
|
2590
|
+
// ignore
|
|
2591
|
+
}
|
|
2263
2592
|
if (resultWithElementsFound.length === 0) {
|
|
2264
2593
|
if (Date.now() - state.startTime > timeout) {
|
|
2265
2594
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2267,51 +2596,56 @@ class StableBrowser {
|
|
|
2267
2596
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2268
2597
|
continue;
|
|
2269
2598
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
const
|
|
2278
|
-
for (let i = 0; i <
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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, false, 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");
|
|
2285
2639
|
}
|
|
2640
|
+
await _screenshot(state, this);
|
|
2641
|
+
return state.info;
|
|
2286
2642
|
}
|
|
2287
|
-
if (!climbParent) {
|
|
2288
|
-
continue;
|
|
2289
|
-
}
|
|
2290
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2291
|
-
if (foundElements.length > 0) {
|
|
2292
|
-
// set the container element attribute
|
|
2293
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2294
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2295
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2296
|
-
return { found: true };
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
return { found: false };
|
|
2300
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2301
|
-
if (findResult.found === true) {
|
|
2302
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2303
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2304
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2305
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2306
|
-
const element = await frame.$(dataAttribute);
|
|
2307
|
-
if (element) {
|
|
2308
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2309
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2310
2643
|
}
|
|
2311
|
-
await _screenshot(state, this);
|
|
2312
|
-
return state.info;
|
|
2313
2644
|
}
|
|
2314
2645
|
}
|
|
2646
|
+
catch (error) {
|
|
2647
|
+
console.error(error);
|
|
2648
|
+
}
|
|
2315
2649
|
}
|
|
2316
2650
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2317
2651
|
}
|
|
@@ -2322,6 +2656,30 @@ class StableBrowser {
|
|
|
2322
2656
|
_commandFinally(state, this);
|
|
2323
2657
|
}
|
|
2324
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
|
+
}
|
|
2325
2683
|
async visualVerification(text, options = {}, world = null) {
|
|
2326
2684
|
const startTime = Date.now();
|
|
2327
2685
|
let error = null;
|
|
@@ -2340,10 +2698,13 @@ class StableBrowser {
|
|
|
2340
2698
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2341
2699
|
info.screenshotPath = screenshotPath;
|
|
2342
2700
|
const screenshot = await this.takeScreenshot();
|
|
2343
|
-
|
|
2344
|
-
method: "
|
|
2701
|
+
let request = {
|
|
2702
|
+
method: "post",
|
|
2703
|
+
maxBodyLength: Infinity,
|
|
2345
2704
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2346
2705
|
headers: {
|
|
2706
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2707
|
+
"x-source": "aaa",
|
|
2347
2708
|
"Content-Type": "application/json",
|
|
2348
2709
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2349
2710
|
},
|
|
@@ -2352,7 +2713,7 @@ class StableBrowser {
|
|
|
2352
2713
|
screenshot: screenshot,
|
|
2353
2714
|
}),
|
|
2354
2715
|
};
|
|
2355
|
-
|
|
2716
|
+
const result = await axios.request(request);
|
|
2356
2717
|
if (result.data.status !== true) {
|
|
2357
2718
|
throw new Error("Visual validation failed");
|
|
2358
2719
|
}
|
|
@@ -2380,6 +2741,7 @@ class StableBrowser {
|
|
|
2380
2741
|
_reportToWorld(world, {
|
|
2381
2742
|
type: Types.VERIFY_VISUAL,
|
|
2382
2743
|
text: "Visual verification",
|
|
2744
|
+
_text: "Visual verification of " + text,
|
|
2383
2745
|
screenshotId,
|
|
2384
2746
|
result: error
|
|
2385
2747
|
? {
|
|
@@ -2646,6 +3008,32 @@ class StableBrowser {
|
|
|
2646
3008
|
}
|
|
2647
3009
|
return timeout;
|
|
2648
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
|
+
}
|
|
2649
3037
|
async waitForPageLoad(options = {}, world = null) {
|
|
2650
3038
|
let timeout = this._getLoadTimeout(options);
|
|
2651
3039
|
const promiseArray = [];
|
|
@@ -2713,6 +3101,7 @@ class StableBrowser {
|
|
|
2713
3101
|
highlight: false,
|
|
2714
3102
|
type: Types.CLOSE_PAGE,
|
|
2715
3103
|
text: `Close page`,
|
|
3104
|
+
_text: `Close the page`,
|
|
2716
3105
|
operation: "closePage",
|
|
2717
3106
|
log: "***** close page *****\n",
|
|
2718
3107
|
throwError: false,
|
|
@@ -2729,8 +3118,95 @@ class StableBrowser {
|
|
|
2729
3118
|
_commandFinally(state, this);
|
|
2730
3119
|
}
|
|
2731
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
|
+
}
|
|
2732
3208
|
saveTestDataAsGlobal(options, world) {
|
|
2733
|
-
const dataFile =
|
|
3209
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2734
3210
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2735
3211
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2736
3212
|
}
|
|
@@ -2760,6 +3236,7 @@ class StableBrowser {
|
|
|
2760
3236
|
_reportToWorld(world, {
|
|
2761
3237
|
type: Types.SET_VIEWPORT,
|
|
2762
3238
|
text: "set viewport size to " + width + "x" + hight,
|
|
3239
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2763
3240
|
screenshotId,
|
|
2764
3241
|
result: error
|
|
2765
3242
|
? {
|
|
@@ -2831,14 +3308,25 @@ class StableBrowser {
|
|
|
2831
3308
|
}
|
|
2832
3309
|
}
|
|
2833
3310
|
async beforeStep(world, step) {
|
|
2834
|
-
this.stepName = step.pickleStep.text;
|
|
2835
|
-
this.logger.info("step: " + this.stepName);
|
|
2836
3311
|
if (this.stepIndex === undefined) {
|
|
2837
3312
|
this.stepIndex = 0;
|
|
2838
3313
|
}
|
|
2839
3314
|
else {
|
|
2840
3315
|
this.stepIndex++;
|
|
2841
3316
|
}
|
|
3317
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3318
|
+
this.stepName = step.pickleStep.text;
|
|
3319
|
+
this.logger.info("step: " + this.stepName);
|
|
3320
|
+
}
|
|
3321
|
+
else if (step && step.text) {
|
|
3322
|
+
this.stepName = step.text;
|
|
3323
|
+
}
|
|
3324
|
+
else {
|
|
3325
|
+
this.stepName = "step " + this.stepIndex;
|
|
3326
|
+
}
|
|
3327
|
+
if (this.context) {
|
|
3328
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3329
|
+
}
|
|
2842
3330
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2843
3331
|
if (this.context.browserObject.context) {
|
|
2844
3332
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2851,6 +3339,41 @@ class StableBrowser {
|
|
|
2851
3339
|
this.saveTestDataAsGlobal({}, world);
|
|
2852
3340
|
}
|
|
2853
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;
|
|
2854
3377
|
}
|
|
2855
3378
|
async afterStep(world, step) {
|
|
2856
3379
|
this.stepName = null;
|
|
@@ -2861,6 +3384,16 @@ class StableBrowser {
|
|
|
2861
3384
|
});
|
|
2862
3385
|
}
|
|
2863
3386
|
}
|
|
3387
|
+
if (this.context) {
|
|
3388
|
+
this.context.examplesRow = null;
|
|
3389
|
+
}
|
|
3390
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3391
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3392
|
+
if (snapshot) {
|
|
3393
|
+
const obj = {};
|
|
3394
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
2864
3397
|
}
|
|
2865
3398
|
}
|
|
2866
3399
|
function createTimedPromise(promise, label) {
|