automation_model 1.0.563-dev → 1.0.563-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 +46 -18
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +27 -9
- package/lib/stable_browser.js +708 -230
- 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;
|
|
@@ -235,16 +243,51 @@ class StableBrowser {
|
|
|
235
243
|
// async closeUnexpectedPopups() {
|
|
236
244
|
// await closeUnexpectedPopups(this.page);
|
|
237
245
|
// }
|
|
238
|
-
async goto(url) {
|
|
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
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
const state = {
|
|
254
|
+
value: url,
|
|
255
|
+
world: world,
|
|
256
|
+
type: Types.NAVIGATE,
|
|
257
|
+
text: `Navigate Page to: ${url}`,
|
|
258
|
+
operation: "goto",
|
|
259
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
260
|
+
info: {},
|
|
261
|
+
locate: false,
|
|
262
|
+
scroll: false,
|
|
263
|
+
screenshot: false,
|
|
264
|
+
highlight: false,
|
|
265
|
+
};
|
|
266
|
+
try {
|
|
267
|
+
await _preCommand(state, this);
|
|
268
|
+
await this.page.goto(url, {
|
|
269
|
+
timeout: 60000,
|
|
270
|
+
});
|
|
271
|
+
await _screenshot(state, this);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error("Error on goto", error);
|
|
275
|
+
_commandError(state, error, this);
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
_commandFinally(state, this);
|
|
279
|
+
}
|
|
245
280
|
}
|
|
246
|
-
_getLocator(locator, scope, _params) {
|
|
281
|
+
async _getLocator(locator, scope, _params) {
|
|
247
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
|
+
}
|
|
248
291
|
let locatorReturn;
|
|
249
292
|
if (locator.role) {
|
|
250
293
|
if (locator.role[1].nameReg) {
|
|
@@ -295,52 +338,60 @@ class StableBrowser {
|
|
|
295
338
|
if (css && css.locator) {
|
|
296
339
|
css = css.locator;
|
|
297
340
|
}
|
|
298
|
-
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);
|
|
299
342
|
if (result.elementCount === 0) {
|
|
300
343
|
return;
|
|
301
344
|
}
|
|
302
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
303
346
|
// css climb to parent element
|
|
304
347
|
const climbArray = [];
|
|
305
348
|
for (let i = 0; i < climb; i++) {
|
|
306
349
|
climbArray.push("..");
|
|
307
350
|
}
|
|
308
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
for (let i = 0; i < elements.length; i++) {
|
|
332
|
-
if (randomToken === null) {
|
|
333
|
-
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;
|
|
334
374
|
}
|
|
335
|
-
let element = elements[i];
|
|
336
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
337
|
-
elementCount++;
|
|
338
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;
|
|
339
389
|
}
|
|
340
|
-
|
|
341
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
342
393
|
}
|
|
343
|
-
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) {
|
|
344
395
|
if (!info) {
|
|
345
396
|
info = {};
|
|
346
397
|
}
|
|
@@ -363,30 +414,31 @@ class StableBrowser {
|
|
|
363
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
364
415
|
let locator = null;
|
|
365
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
366
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
367
419
|
if (!locatorString) {
|
|
368
420
|
info.failCause.textNotFound = true;
|
|
369
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
370
422
|
return;
|
|
371
423
|
}
|
|
372
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
373
425
|
}
|
|
374
426
|
else if (locatorSearch.text) {
|
|
375
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
376
|
-
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);
|
|
377
429
|
if (result.elementCount === 0) {
|
|
378
430
|
info.failCause.textNotFound = true;
|
|
379
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
380
432
|
return;
|
|
381
433
|
}
|
|
382
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
383
435
|
if (locatorSearch.childCss) {
|
|
384
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
385
437
|
}
|
|
386
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
387
439
|
}
|
|
388
440
|
else {
|
|
389
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
390
442
|
}
|
|
391
443
|
// let cssHref = false;
|
|
392
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -415,7 +467,7 @@ class StableBrowser {
|
|
|
415
467
|
if (!visibleOnly) {
|
|
416
468
|
visible = true;
|
|
417
469
|
}
|
|
418
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
419
471
|
foundLocators.push(locator.nth(j));
|
|
420
472
|
if (info.locatorLog) {
|
|
421
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -428,9 +480,11 @@ class StableBrowser {
|
|
|
428
480
|
info.printMessages = {};
|
|
429
481
|
}
|
|
430
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
431
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
432
485
|
}
|
|
433
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
434
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
435
489
|
}
|
|
436
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -498,7 +552,7 @@ class StableBrowser {
|
|
|
498
552
|
}
|
|
499
553
|
return { rerun: false };
|
|
500
554
|
}
|
|
501
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
502
556
|
if (!timeout) {
|
|
503
557
|
timeout = 30000;
|
|
504
558
|
}
|
|
@@ -508,9 +562,18 @@ class StableBrowser {
|
|
|
508
562
|
let selector = selectors.locators[j];
|
|
509
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
510
564
|
}
|
|
511
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
512
566
|
if (!element.rerun) {
|
|
513
|
-
|
|
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.page();
|
|
575
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
576
|
+
return newSelector;
|
|
514
577
|
}
|
|
515
578
|
}
|
|
516
579
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -583,7 +646,7 @@ class StableBrowser {
|
|
|
583
646
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
584
647
|
if (Date.now() - startTime > timeout) {
|
|
585
648
|
info.failCause.iframeNotFound = true;
|
|
586
|
-
info.failCause.lastError =
|
|
649
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
587
650
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
588
651
|
}
|
|
589
652
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -608,7 +671,7 @@ class StableBrowser {
|
|
|
608
671
|
return bodyContent;
|
|
609
672
|
});
|
|
610
673
|
}
|
|
611
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
674
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
612
675
|
if (!info) {
|
|
613
676
|
info = {};
|
|
614
677
|
info.failCause = {};
|
|
@@ -657,17 +720,17 @@ class StableBrowser {
|
|
|
657
720
|
}
|
|
658
721
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
659
722
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
660
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
723
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
661
724
|
if (result.foundElements.length === 0) {
|
|
662
725
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
663
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
726
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
664
727
|
}
|
|
665
728
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
666
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
729
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
667
730
|
}
|
|
668
731
|
else {
|
|
669
732
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
670
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
733
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
671
734
|
}
|
|
672
735
|
}
|
|
673
736
|
let foundElements = result.foundElements;
|
|
@@ -712,7 +775,7 @@ class StableBrowser {
|
|
|
712
775
|
break;
|
|
713
776
|
}
|
|
714
777
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
715
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
778
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
716
779
|
highPriorityOnly = false;
|
|
717
780
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
718
781
|
lazy_scroll = true;
|
|
@@ -720,7 +783,7 @@ class StableBrowser {
|
|
|
720
783
|
}
|
|
721
784
|
}
|
|
722
785
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
723
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
786
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
724
787
|
visibleOnly = false;
|
|
725
788
|
}
|
|
726
789
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -734,10 +797,12 @@ class StableBrowser {
|
|
|
734
797
|
// }
|
|
735
798
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
736
799
|
info.failCause.locatorNotFound = true;
|
|
737
|
-
info
|
|
800
|
+
if (!info?.failCause?.lastError) {
|
|
801
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
802
|
+
}
|
|
738
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
739
804
|
}
|
|
740
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
741
806
|
let foundElements = [];
|
|
742
807
|
const result = {
|
|
743
808
|
foundElements: foundElements,
|
|
@@ -745,7 +810,7 @@ class StableBrowser {
|
|
|
745
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
746
811
|
let foundLocators = [];
|
|
747
812
|
try {
|
|
748
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
749
814
|
}
|
|
750
815
|
catch (e) {
|
|
751
816
|
// this call can fail it the browser is navigating
|
|
@@ -753,7 +818,7 @@ class StableBrowser {
|
|
|
753
818
|
// this.logger.debug(e);
|
|
754
819
|
foundLocators = [];
|
|
755
820
|
try {
|
|
756
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
821
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
757
822
|
}
|
|
758
823
|
catch (e) {
|
|
759
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -768,9 +833,40 @@ class StableBrowser {
|
|
|
768
833
|
result.locatorIndex = i;
|
|
769
834
|
}
|
|
770
835
|
if (foundLocators.length > 1) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
836
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
837
|
+
const boxes = [];
|
|
838
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
839
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
840
|
+
}
|
|
841
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
842
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
843
|
+
if (j === k) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
847
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
848
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
849
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
850
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
851
|
+
// as the element is not unique, will remove it
|
|
852
|
+
boxes.splice(k, 1);
|
|
853
|
+
k--;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (boxes.length === 1) {
|
|
858
|
+
result.foundElements.push({
|
|
859
|
+
locator: boxes[0].locator.first(),
|
|
860
|
+
box: boxes[0].box,
|
|
861
|
+
unique: true,
|
|
862
|
+
});
|
|
863
|
+
result.locatorIndex = i;
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
info.failCause.foundMultiple = true;
|
|
867
|
+
if (info.locatorLog) {
|
|
868
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
869
|
+
}
|
|
774
870
|
}
|
|
775
871
|
}
|
|
776
872
|
}
|
|
@@ -881,25 +977,14 @@ class StableBrowser {
|
|
|
881
977
|
options,
|
|
882
978
|
world,
|
|
883
979
|
text: "Click element",
|
|
980
|
+
_text: "Click on " + selectors.element_name,
|
|
884
981
|
type: Types.CLICK,
|
|
885
982
|
operation: "click",
|
|
886
983
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
887
984
|
};
|
|
888
985
|
try {
|
|
889
986
|
await _preCommand(state, this);
|
|
890
|
-
|
|
891
|
-
state.selectors.locators[0].text = state.options.context;
|
|
892
|
-
}
|
|
893
|
-
try {
|
|
894
|
-
await state.element.click();
|
|
895
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
896
|
-
}
|
|
897
|
-
catch (e) {
|
|
898
|
-
// await this.closeUnexpectedPopups();
|
|
899
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
900
|
-
await state.element.dispatchEvent("click");
|
|
901
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
902
|
-
}
|
|
987
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
903
988
|
await this.waitForPageLoad();
|
|
904
989
|
return state.info;
|
|
905
990
|
}
|
|
@@ -910,6 +995,38 @@ class StableBrowser {
|
|
|
910
995
|
_commandFinally(state, this);
|
|
911
996
|
}
|
|
912
997
|
}
|
|
998
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
999
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1000
|
+
const state = {
|
|
1001
|
+
selectors,
|
|
1002
|
+
_params,
|
|
1003
|
+
options,
|
|
1004
|
+
world,
|
|
1005
|
+
text: "Wait for element",
|
|
1006
|
+
_text: "Wait for " + selectors.element_name,
|
|
1007
|
+
type: Types.WAIT_ELEMENT,
|
|
1008
|
+
operation: "waitForElement",
|
|
1009
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1010
|
+
};
|
|
1011
|
+
let found = false;
|
|
1012
|
+
try {
|
|
1013
|
+
await _preCommand(state, this);
|
|
1014
|
+
// if (state.options && state.options.context) {
|
|
1015
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1016
|
+
// }
|
|
1017
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1018
|
+
found = true;
|
|
1019
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
console.error("Error on waitForElement", e);
|
|
1023
|
+
// await _commandError(state, e, this);
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
_commandFinally(state, this);
|
|
1027
|
+
}
|
|
1028
|
+
return found;
|
|
1029
|
+
}
|
|
913
1030
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
914
1031
|
const state = {
|
|
915
1032
|
selectors,
|
|
@@ -918,6 +1035,7 @@ class StableBrowser {
|
|
|
918
1035
|
world,
|
|
919
1036
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
920
1037
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1038
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
921
1039
|
operation: "setCheck",
|
|
922
1040
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
923
1041
|
};
|
|
@@ -927,9 +1045,15 @@ class StableBrowser {
|
|
|
927
1045
|
// let element = await this._locate(selectors, info, _params);
|
|
928
1046
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
929
1047
|
try {
|
|
930
|
-
//
|
|
1048
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1050
|
+
await this._highlightElements(state.element);
|
|
931
1051
|
await state.element.setChecked(checked);
|
|
932
1052
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
|
+
// await this._unHighlightElements(element);
|
|
1054
|
+
// }
|
|
1055
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1056
|
+
// await this._unHighlightElements(element);
|
|
933
1057
|
}
|
|
934
1058
|
catch (e) {
|
|
935
1059
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -961,22 +1085,13 @@ class StableBrowser {
|
|
|
961
1085
|
world,
|
|
962
1086
|
type: Types.HOVER,
|
|
963
1087
|
text: `Hover element`,
|
|
1088
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
964
1089
|
operation: "hover",
|
|
965
1090
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
966
1091
|
};
|
|
967
1092
|
try {
|
|
968
1093
|
await _preCommand(state, this);
|
|
969
|
-
|
|
970
|
-
await state.element.hover();
|
|
971
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
972
|
-
}
|
|
973
|
-
catch (e) {
|
|
974
|
-
//await this.closeUnexpectedPopups();
|
|
975
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
976
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
977
|
-
await state.element.hover({ timeout: 10000 });
|
|
978
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
979
|
-
}
|
|
1094
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
980
1095
|
await _screenshot(state, this);
|
|
981
1096
|
await this.waitForPageLoad();
|
|
982
1097
|
return state.info;
|
|
@@ -1000,6 +1115,7 @@ class StableBrowser {
|
|
|
1000
1115
|
value: values.toString(),
|
|
1001
1116
|
type: Types.SELECT,
|
|
1002
1117
|
text: `Select option: ${values}`,
|
|
1118
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1003
1119
|
operation: "selectOption",
|
|
1004
1120
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1005
1121
|
};
|
|
@@ -1034,6 +1150,7 @@ class StableBrowser {
|
|
|
1034
1150
|
highlight: false,
|
|
1035
1151
|
type: Types.TYPE_PRESS,
|
|
1036
1152
|
text: `Type value: ${_value}`,
|
|
1153
|
+
_text: `Type value: ${_value}`,
|
|
1037
1154
|
operation: "type",
|
|
1038
1155
|
log: "",
|
|
1039
1156
|
};
|
|
@@ -1113,6 +1230,7 @@ class StableBrowser {
|
|
|
1113
1230
|
world,
|
|
1114
1231
|
type: Types.SET_DATE_TIME,
|
|
1115
1232
|
text: `Set date time value: ${value}`,
|
|
1233
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1116
1234
|
operation: "setDateTime",
|
|
1117
1235
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1118
1236
|
throwError: false,
|
|
@@ -1120,7 +1238,7 @@ class StableBrowser {
|
|
|
1120
1238
|
try {
|
|
1121
1239
|
await _preCommand(state, this);
|
|
1122
1240
|
try {
|
|
1123
|
-
await state.element
|
|
1241
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1124
1242
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1125
1243
|
if (format) {
|
|
1126
1244
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1184,9 +1302,13 @@ class StableBrowser {
|
|
|
1184
1302
|
world,
|
|
1185
1303
|
type: Types.FILL,
|
|
1186
1304
|
text: `Click type input with value: ${_value}`,
|
|
1305
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1187
1306
|
operation: "clickType",
|
|
1188
1307
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1189
1308
|
};
|
|
1309
|
+
if (!options) {
|
|
1310
|
+
options = {};
|
|
1311
|
+
}
|
|
1190
1312
|
if (newValue !== _value) {
|
|
1191
1313
|
//this.logger.info(_value + "=" + newValue);
|
|
1192
1314
|
_value = newValue;
|
|
@@ -1194,7 +1316,7 @@ class StableBrowser {
|
|
|
1194
1316
|
try {
|
|
1195
1317
|
await _preCommand(state, this);
|
|
1196
1318
|
state.info.value = _value;
|
|
1197
|
-
if (
|
|
1319
|
+
if (!options.press) {
|
|
1198
1320
|
try {
|
|
1199
1321
|
let currentValue = await state.element.inputValue();
|
|
1200
1322
|
if (currentValue) {
|
|
@@ -1205,13 +1327,9 @@ class StableBrowser {
|
|
|
1205
1327
|
this.logger.info("unable to clear input value");
|
|
1206
1328
|
}
|
|
1207
1329
|
}
|
|
1208
|
-
if (options
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
catch (e) {
|
|
1213
|
-
await state.element.dispatchEvent("click");
|
|
1214
|
-
}
|
|
1330
|
+
if (options.press) {
|
|
1331
|
+
options.timeout = 5000;
|
|
1332
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1215
1333
|
}
|
|
1216
1334
|
else {
|
|
1217
1335
|
try {
|
|
@@ -1249,7 +1367,12 @@ class StableBrowser {
|
|
|
1249
1367
|
await this.waitForPageLoad();
|
|
1250
1368
|
}
|
|
1251
1369
|
else if (enter === false) {
|
|
1252
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
// ignore
|
|
1375
|
+
}
|
|
1253
1376
|
//await this.page.keyboard.press("Tab");
|
|
1254
1377
|
}
|
|
1255
1378
|
else {
|
|
@@ -1301,6 +1424,7 @@ class StableBrowser {
|
|
|
1301
1424
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1302
1425
|
}
|
|
1303
1426
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1427
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1304
1428
|
_validateSelectors(selectors);
|
|
1305
1429
|
let screenshotId = null;
|
|
1306
1430
|
let screenshotPath = null;
|
|
@@ -1310,7 +1434,7 @@ class StableBrowser {
|
|
|
1310
1434
|
}
|
|
1311
1435
|
info.operation = "getText";
|
|
1312
1436
|
info.selectors = selectors;
|
|
1313
|
-
let element = await this._locate(selectors, info, _params);
|
|
1437
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1314
1438
|
if (climb > 0) {
|
|
1315
1439
|
const climbArray = [];
|
|
1316
1440
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1329,6 +1453,18 @@ class StableBrowser {
|
|
|
1329
1453
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1330
1454
|
try {
|
|
1331
1455
|
await this._highlightElements(element);
|
|
1456
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1457
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1458
|
+
// this._highlightElements(element)
|
|
1459
|
+
// .then(async () => {
|
|
1460
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1461
|
+
// this._unhighlightElements(element).then(
|
|
1462
|
+
// () => {}
|
|
1463
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1464
|
+
// );
|
|
1465
|
+
// })
|
|
1466
|
+
// .catch(e);
|
|
1467
|
+
// }
|
|
1332
1468
|
const elementText = await element.innerText();
|
|
1333
1469
|
return {
|
|
1334
1470
|
text: elementText,
|
|
@@ -1340,7 +1476,7 @@ class StableBrowser {
|
|
|
1340
1476
|
}
|
|
1341
1477
|
catch (e) {
|
|
1342
1478
|
//await this.closeUnexpectedPopups();
|
|
1343
|
-
this.logger.info("no innerText will use textContent");
|
|
1479
|
+
this.logger.info("no innerText, will use textContent");
|
|
1344
1480
|
const elementText = await element.textContent();
|
|
1345
1481
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1346
1482
|
}
|
|
@@ -1365,6 +1501,7 @@ class StableBrowser {
|
|
|
1365
1501
|
highlight: false,
|
|
1366
1502
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1367
1503
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1504
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1368
1505
|
operation: "containsPattern",
|
|
1369
1506
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1370
1507
|
};
|
|
@@ -1400,6 +1537,8 @@ class StableBrowser {
|
|
|
1400
1537
|
}
|
|
1401
1538
|
}
|
|
1402
1539
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1540
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1541
|
+
const startTime = Date.now();
|
|
1403
1542
|
const state = {
|
|
1404
1543
|
selectors,
|
|
1405
1544
|
_params,
|
|
@@ -1426,62 +1565,54 @@ class StableBrowser {
|
|
|
1426
1565
|
}
|
|
1427
1566
|
let foundObj = null;
|
|
1428
1567
|
try {
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1436
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1437
|
-
if (dateAlternatives.date) {
|
|
1438
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1439
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1440
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1441
|
-
return state.info;
|
|
1568
|
+
while (Date.now() - startTime < timeout) {
|
|
1569
|
+
try {
|
|
1570
|
+
await _preCommand(state, this);
|
|
1571
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1572
|
+
if (foundObj && foundObj.element) {
|
|
1573
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1442
1574
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1575
|
+
await _screenshot(state, this);
|
|
1576
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1577
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1578
|
+
if (dateAlternatives.date) {
|
|
1579
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1580
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1581
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1582
|
+
return state.info;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
else if (numberAlternatives.number) {
|
|
1587
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1588
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1589
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1590
|
+
return state.info;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1450
1595
|
return state.info;
|
|
1451
1596
|
}
|
|
1452
1597
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
throw new Error("element doesn't contain text " + text);
|
|
1598
|
+
catch (e) {
|
|
1599
|
+
// Log error but continue retrying until timeout is reached
|
|
1600
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1601
|
+
}
|
|
1602
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1459
1603
|
}
|
|
1460
|
-
|
|
1604
|
+
state.info.foundText = foundObj?.text;
|
|
1605
|
+
state.info.value = foundObj?.value;
|
|
1606
|
+
throw new Error("element doesn't contain text " + text);
|
|
1461
1607
|
}
|
|
1462
1608
|
catch (e) {
|
|
1463
1609
|
await _commandError(state, e, this);
|
|
1610
|
+
throw e;
|
|
1464
1611
|
}
|
|
1465
1612
|
finally {
|
|
1466
1613
|
_commandFinally(state, this);
|
|
1467
1614
|
}
|
|
1468
1615
|
}
|
|
1469
|
-
_getDataFile(world = null) {
|
|
1470
|
-
let dataFile = null;
|
|
1471
|
-
if (world && world.reportFolder) {
|
|
1472
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1473
|
-
}
|
|
1474
|
-
else if (this.reportFolder) {
|
|
1475
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1476
|
-
}
|
|
1477
|
-
else if (this.context && this.context.reportFolder) {
|
|
1478
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1479
|
-
}
|
|
1480
|
-
else {
|
|
1481
|
-
dataFile = "data.json";
|
|
1482
|
-
}
|
|
1483
|
-
return dataFile;
|
|
1484
|
-
}
|
|
1485
1616
|
async waitForUserInput(message, world = null) {
|
|
1486
1617
|
if (!message) {
|
|
1487
1618
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1510,7 +1641,7 @@ class StableBrowser {
|
|
|
1510
1641
|
return;
|
|
1511
1642
|
}
|
|
1512
1643
|
// if data file exists, load it
|
|
1513
|
-
const dataFile =
|
|
1644
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1514
1645
|
let data = this.getTestData(world);
|
|
1515
1646
|
// merge the testData with the existing data
|
|
1516
1647
|
Object.assign(data, testData);
|
|
@@ -1613,7 +1744,7 @@ class StableBrowser {
|
|
|
1613
1744
|
}
|
|
1614
1745
|
}
|
|
1615
1746
|
getTestData(world = null) {
|
|
1616
|
-
const dataFile =
|
|
1747
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1617
1748
|
let data = {};
|
|
1618
1749
|
if (fs.existsSync(dataFile)) {
|
|
1619
1750
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1700,6 +1831,15 @@ class StableBrowser {
|
|
|
1700
1831
|
document.documentElement.clientWidth,
|
|
1701
1832
|
])));
|
|
1702
1833
|
let screenshotBuffer = null;
|
|
1834
|
+
// if (focusedElement) {
|
|
1835
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1836
|
+
// await this._unhighlightElements(focusedElement);
|
|
1837
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1838
|
+
// console.log(`Unhighlighted previous element`);
|
|
1839
|
+
// }
|
|
1840
|
+
// if (focusedElement) {
|
|
1841
|
+
// await this._highlightElements(focusedElement);
|
|
1842
|
+
// }
|
|
1703
1843
|
if (this.context.browserName === "chromium") {
|
|
1704
1844
|
const client = await playContext.newCDPSession(this.page);
|
|
1705
1845
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1721,6 +1861,10 @@ class StableBrowser {
|
|
|
1721
1861
|
else {
|
|
1722
1862
|
screenshotBuffer = await this.page.screenshot();
|
|
1723
1863
|
}
|
|
1864
|
+
// if (focusedElement) {
|
|
1865
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1866
|
+
// await this._unhighlightElements(focusedElement);
|
|
1867
|
+
// }
|
|
1724
1868
|
let image = await Jimp.read(screenshotBuffer);
|
|
1725
1869
|
// Get the image dimensions
|
|
1726
1870
|
const { width, height } = image.bitmap;
|
|
@@ -1733,6 +1877,7 @@ class StableBrowser {
|
|
|
1733
1877
|
else {
|
|
1734
1878
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1735
1879
|
}
|
|
1880
|
+
return screenshotBuffer;
|
|
1736
1881
|
}
|
|
1737
1882
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1738
1883
|
const state = {
|
|
@@ -1768,8 +1913,10 @@ class StableBrowser {
|
|
|
1768
1913
|
world,
|
|
1769
1914
|
type: Types.EXTRACT,
|
|
1770
1915
|
text: `Extract attribute from element`,
|
|
1916
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1771
1917
|
operation: "extractAttribute",
|
|
1772
1918
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1919
|
+
allowDisabled: true,
|
|
1773
1920
|
};
|
|
1774
1921
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1775
1922
|
try {
|
|
@@ -1791,6 +1938,7 @@ class StableBrowser {
|
|
|
1791
1938
|
state.info.value = state.value;
|
|
1792
1939
|
this.setTestData({ [variable]: state.value }, world);
|
|
1793
1940
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1941
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1794
1942
|
return state.info;
|
|
1795
1943
|
}
|
|
1796
1944
|
catch (e) {
|
|
@@ -1809,14 +1957,21 @@ class StableBrowser {
|
|
|
1809
1957
|
options,
|
|
1810
1958
|
world,
|
|
1811
1959
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1960
|
+
highlight: true,
|
|
1961
|
+
screenshot: true,
|
|
1812
1962
|
text: `Verify element attribute`,
|
|
1963
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1813
1964
|
operation: "verifyAttribute",
|
|
1814
1965
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1815
1967
|
};
|
|
1816
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1817
1969
|
let val;
|
|
1970
|
+
let expectedValue;
|
|
1818
1971
|
try {
|
|
1819
1972
|
await _preCommand(state, this);
|
|
1973
|
+
expectedValue = state.value;
|
|
1974
|
+
state.info.expectedValue = expectedValue;
|
|
1820
1975
|
switch (attribute) {
|
|
1821
1976
|
case "innerText":
|
|
1822
1977
|
val = String(await state.element.innerText());
|
|
@@ -1830,23 +1985,30 @@ class StableBrowser {
|
|
|
1830
1985
|
case "disabled":
|
|
1831
1986
|
val = String(await state.element.isDisabled());
|
|
1832
1987
|
break;
|
|
1988
|
+
case "readOnly":
|
|
1989
|
+
const isEditable = await state.element.isEditable();
|
|
1990
|
+
val = String(!isEditable);
|
|
1991
|
+
break;
|
|
1833
1992
|
default:
|
|
1834
1993
|
val = String(await state.element.getAttribute(attribute));
|
|
1835
1994
|
break;
|
|
1836
1995
|
}
|
|
1996
|
+
state.info.value = val;
|
|
1837
1997
|
let regex;
|
|
1838
|
-
if (
|
|
1839
|
-
const patternBody =
|
|
1998
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1999
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1840
2000
|
regex = new RegExp(patternBody, "g");
|
|
1841
2001
|
}
|
|
1842
2002
|
else {
|
|
1843
|
-
const escapedPattern =
|
|
2003
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1844
2004
|
regex = new RegExp(escapedPattern, "g");
|
|
1845
2005
|
}
|
|
1846
2006
|
if (!val.match(regex)) {
|
|
1847
|
-
|
|
2007
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2008
|
+
state.info.failCause.assertionFailed = true;
|
|
2009
|
+
state.info.failCause.lastError = errorMessage;
|
|
2010
|
+
throw new Error(errorMessage);
|
|
1848
2011
|
}
|
|
1849
|
-
state.info.value = val;
|
|
1850
2012
|
return state.info;
|
|
1851
2013
|
}
|
|
1852
2014
|
catch (e) {
|
|
@@ -1945,27 +2107,32 @@ class StableBrowser {
|
|
|
1945
2107
|
async _highlightElements(scope, css) {
|
|
1946
2108
|
try {
|
|
1947
2109
|
if (!scope) {
|
|
2110
|
+
// console.log(`Scope is not defined`);
|
|
1948
2111
|
return;
|
|
1949
2112
|
}
|
|
1950
2113
|
if (!css) {
|
|
1951
2114
|
scope
|
|
1952
2115
|
.evaluate((node) => {
|
|
1953
2116
|
if (node && node.style) {
|
|
1954
|
-
let
|
|
1955
|
-
|
|
2117
|
+
let originalOutline = node.style.outline;
|
|
2118
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2119
|
+
// node.__previousOutline = originalOutline;
|
|
2120
|
+
node.style.outline = "2px solid red";
|
|
2121
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1956
2122
|
if (window) {
|
|
1957
2123
|
window.addEventListener("beforeunload", function (e) {
|
|
1958
|
-
node.style.
|
|
2124
|
+
node.style.outline = originalOutline;
|
|
1959
2125
|
});
|
|
1960
2126
|
}
|
|
1961
2127
|
setTimeout(function () {
|
|
1962
|
-
node.style.
|
|
2128
|
+
node.style.outline = originalOutline;
|
|
1963
2129
|
}, 2000);
|
|
1964
2130
|
}
|
|
1965
2131
|
})
|
|
1966
2132
|
.then(() => { })
|
|
1967
2133
|
.catch((e) => {
|
|
1968
2134
|
// ignore
|
|
2135
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1969
2136
|
});
|
|
1970
2137
|
}
|
|
1971
2138
|
else {
|
|
@@ -1981,17 +2148,18 @@ class StableBrowser {
|
|
|
1981
2148
|
if (!element.style) {
|
|
1982
2149
|
return;
|
|
1983
2150
|
}
|
|
1984
|
-
|
|
2151
|
+
let originalOutline = element.style.outline;
|
|
2152
|
+
element.__previousOutline = originalOutline;
|
|
1985
2153
|
// Set the new border to be red and 2px solid
|
|
1986
|
-
element.style.
|
|
2154
|
+
element.style.outline = "2px solid red";
|
|
1987
2155
|
if (window) {
|
|
1988
2156
|
window.addEventListener("beforeunload", function (e) {
|
|
1989
|
-
element.style.
|
|
2157
|
+
element.style.outline = originalOutline;
|
|
1990
2158
|
});
|
|
1991
2159
|
}
|
|
1992
2160
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1993
2161
|
setTimeout(function () {
|
|
1994
|
-
element.style.
|
|
2162
|
+
element.style.outline = originalOutline;
|
|
1995
2163
|
}, 2000);
|
|
1996
2164
|
}
|
|
1997
2165
|
return;
|
|
@@ -1999,6 +2167,7 @@ class StableBrowser {
|
|
|
1999
2167
|
.then(() => { })
|
|
2000
2168
|
.catch((e) => {
|
|
2001
2169
|
// ignore
|
|
2170
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2002
2171
|
});
|
|
2003
2172
|
}
|
|
2004
2173
|
}
|
|
@@ -2006,6 +2175,54 @@ class StableBrowser {
|
|
|
2006
2175
|
console.debug(error);
|
|
2007
2176
|
}
|
|
2008
2177
|
}
|
|
2178
|
+
// async _unhighlightElements(scope, css) {
|
|
2179
|
+
// try {
|
|
2180
|
+
// if (!scope) {
|
|
2181
|
+
// return;
|
|
2182
|
+
// }
|
|
2183
|
+
// if (!css) {
|
|
2184
|
+
// scope
|
|
2185
|
+
// .evaluate((node) => {
|
|
2186
|
+
// if (node && node.style) {
|
|
2187
|
+
// if (!node.__previousOutline) {
|
|
2188
|
+
// node.style.outline = "";
|
|
2189
|
+
// } else {
|
|
2190
|
+
// node.style.outline = node.__previousOutline;
|
|
2191
|
+
// }
|
|
2192
|
+
// }
|
|
2193
|
+
// })
|
|
2194
|
+
// .then(() => {})
|
|
2195
|
+
// .catch((e) => {
|
|
2196
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2197
|
+
// });
|
|
2198
|
+
// } else {
|
|
2199
|
+
// scope
|
|
2200
|
+
// .evaluate(([css]) => {
|
|
2201
|
+
// if (!css) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2205
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2206
|
+
// let element = elements[i];
|
|
2207
|
+
// if (!element.style) {
|
|
2208
|
+
// return;
|
|
2209
|
+
// }
|
|
2210
|
+
// if (!element.__previousOutline) {
|
|
2211
|
+
// element.style.outline = "";
|
|
2212
|
+
// } else {
|
|
2213
|
+
// element.style.outline = element.__previousOutline;
|
|
2214
|
+
// }
|
|
2215
|
+
// }
|
|
2216
|
+
// })
|
|
2217
|
+
// .then(() => {})
|
|
2218
|
+
// .catch((e) => {
|
|
2219
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2220
|
+
// });
|
|
2221
|
+
// }
|
|
2222
|
+
// } catch (error) {
|
|
2223
|
+
// // console.debug(error);
|
|
2224
|
+
// }
|
|
2225
|
+
// }
|
|
2009
2226
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2010
2227
|
const startTime = Date.now();
|
|
2011
2228
|
let error = null;
|
|
@@ -2050,6 +2267,7 @@ class StableBrowser {
|
|
|
2050
2267
|
_reportToWorld(world, {
|
|
2051
2268
|
type: Types.VERIFY_PAGE_PATH,
|
|
2052
2269
|
text: "Verify page path",
|
|
2270
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2053
2271
|
screenshotId,
|
|
2054
2272
|
result: error
|
|
2055
2273
|
? {
|
|
@@ -2067,26 +2285,27 @@ class StableBrowser {
|
|
|
2067
2285
|
});
|
|
2068
2286
|
}
|
|
2069
2287
|
}
|
|
2070
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2288
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2071
2289
|
const frames = this.page.frames();
|
|
2072
2290
|
let results = [];
|
|
2291
|
+
// let ignoreCase = false;
|
|
2073
2292
|
for (let i = 0; i < frames.length; i++) {
|
|
2074
2293
|
if (dateAlternatives.date) {
|
|
2075
2294
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2076
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2295
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2077
2296
|
result.frame = frames[i];
|
|
2078
2297
|
results.push(result);
|
|
2079
2298
|
}
|
|
2080
2299
|
}
|
|
2081
2300
|
else if (numberAlternatives.number) {
|
|
2082
2301
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2083
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2302
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2084
2303
|
result.frame = frames[i];
|
|
2085
2304
|
results.push(result);
|
|
2086
2305
|
}
|
|
2087
2306
|
}
|
|
2088
2307
|
else {
|
|
2089
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2308
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2090
2309
|
result.frame = frames[i];
|
|
2091
2310
|
results.push(result);
|
|
2092
2311
|
}
|
|
@@ -2105,11 +2324,15 @@ class StableBrowser {
|
|
|
2105
2324
|
scroll: false,
|
|
2106
2325
|
highlight: false,
|
|
2107
2326
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2108
|
-
text: `Verify text exists in page`,
|
|
2327
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2328
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2109
2329
|
operation: "verifyTextExistInPage",
|
|
2110
2330
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2111
2331
|
};
|
|
2112
|
-
|
|
2332
|
+
if (testForRegex(text)) {
|
|
2333
|
+
text = text.replace(/\\"/g, '"');
|
|
2334
|
+
}
|
|
2335
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2113
2336
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2114
2337
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2115
2338
|
if (newValue !== text) {
|
|
@@ -2122,7 +2345,15 @@ class StableBrowser {
|
|
|
2122
2345
|
await _preCommand(state, this);
|
|
2123
2346
|
state.info.text = text;
|
|
2124
2347
|
while (true) {
|
|
2125
|
-
|
|
2348
|
+
let resultWithElementsFound = {
|
|
2349
|
+
length: 0,
|
|
2350
|
+
};
|
|
2351
|
+
try {
|
|
2352
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2353
|
+
}
|
|
2354
|
+
catch (error) {
|
|
2355
|
+
// ignore
|
|
2356
|
+
}
|
|
2126
2357
|
if (resultWithElementsFound.length === 0) {
|
|
2127
2358
|
if (Date.now() - state.startTime > timeout) {
|
|
2128
2359
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2130,18 +2361,40 @@ class StableBrowser {
|
|
|
2130
2361
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2131
2362
|
continue;
|
|
2132
2363
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2364
|
+
try {
|
|
2365
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2366
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2367
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2368
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2369
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2370
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2371
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2372
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2373
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2374
|
+
// .then(async () => {
|
|
2375
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2376
|
+
// })
|
|
2377
|
+
// .catch(
|
|
2378
|
+
// (e) => {}
|
|
2379
|
+
// console.error(e)
|
|
2380
|
+
// );
|
|
2381
|
+
// });
|
|
2382
|
+
// }
|
|
2383
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2384
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2385
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2386
|
+
if (element) {
|
|
2387
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2388
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2389
|
+
// await _screenshot(state, this, element);
|
|
2390
|
+
}
|
|
2141
2391
|
}
|
|
2392
|
+
await _screenshot(state, this);
|
|
2393
|
+
return state.info;
|
|
2394
|
+
}
|
|
2395
|
+
catch (error) {
|
|
2396
|
+
console.error(error);
|
|
2142
2397
|
}
|
|
2143
|
-
await _screenshot(state, this);
|
|
2144
|
-
return state.info;
|
|
2145
2398
|
}
|
|
2146
2399
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2147
2400
|
}
|
|
@@ -2162,11 +2415,15 @@ class StableBrowser {
|
|
|
2162
2415
|
scroll: false,
|
|
2163
2416
|
highlight: false,
|
|
2164
2417
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2165
|
-
text: `Verify text does not exist in page`,
|
|
2418
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2419
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2166
2420
|
operation: "verifyTextNotExistInPage",
|
|
2167
2421
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2168
2422
|
};
|
|
2169
|
-
|
|
2423
|
+
if (testForRegex(text)) {
|
|
2424
|
+
text = text.replace(/\\"/g, '"');
|
|
2425
|
+
}
|
|
2426
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2170
2427
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2171
2428
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2172
2429
|
if (newValue !== text) {
|
|
@@ -2178,8 +2435,16 @@ class StableBrowser {
|
|
|
2178
2435
|
try {
|
|
2179
2436
|
await _preCommand(state, this);
|
|
2180
2437
|
state.info.text = text;
|
|
2438
|
+
let resultWithElementsFound = {
|
|
2439
|
+
length: null, // initial cannot be 0
|
|
2440
|
+
};
|
|
2181
2441
|
while (true) {
|
|
2182
|
-
|
|
2442
|
+
try {
|
|
2443
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2444
|
+
}
|
|
2445
|
+
catch (error) {
|
|
2446
|
+
// ignore
|
|
2447
|
+
}
|
|
2183
2448
|
if (resultWithElementsFound.length === 0) {
|
|
2184
2449
|
await _screenshot(state, this);
|
|
2185
2450
|
return state.info;
|
|
@@ -2209,10 +2474,11 @@ class StableBrowser {
|
|
|
2209
2474
|
highlight: false,
|
|
2210
2475
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2211
2476
|
text: `Verify text with relation to another text`,
|
|
2477
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2212
2478
|
operation: "verify_text_with_relation",
|
|
2213
2479
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2214
2480
|
};
|
|
2215
|
-
const timeout = this.
|
|
2481
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2216
2482
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2217
2483
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2218
2484
|
if (newValue !== textAnchor) {
|
|
@@ -2230,8 +2496,16 @@ class StableBrowser {
|
|
|
2230
2496
|
try {
|
|
2231
2497
|
await _preCommand(state, this);
|
|
2232
2498
|
state.info.text = textToVerify;
|
|
2499
|
+
let resultWithElementsFound = {
|
|
2500
|
+
length: 0,
|
|
2501
|
+
};
|
|
2233
2502
|
while (true) {
|
|
2234
|
-
|
|
2503
|
+
try {
|
|
2504
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2505
|
+
}
|
|
2506
|
+
catch (error) {
|
|
2507
|
+
// ignore
|
|
2508
|
+
}
|
|
2235
2509
|
if (resultWithElementsFound.length === 0) {
|
|
2236
2510
|
if (Date.now() - state.startTime > timeout) {
|
|
2237
2511
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2239,51 +2513,56 @@ class StableBrowser {
|
|
|
2239
2513
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2240
2514
|
continue;
|
|
2241
2515
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
const
|
|
2250
|
-
for (let i = 0; i <
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2516
|
+
try {
|
|
2517
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2518
|
+
foundAncore = true;
|
|
2519
|
+
const result = resultWithElementsFound[i];
|
|
2520
|
+
const token = result.randomToken;
|
|
2521
|
+
const frame = result.frame;
|
|
2522
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2523
|
+
const climbArray1 = [];
|
|
2524
|
+
for (let i = 0; i < climb; i++) {
|
|
2525
|
+
climbArray1.push("..");
|
|
2526
|
+
}
|
|
2527
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2528
|
+
css = css + " >> " + climbXpath;
|
|
2529
|
+
const count = await frame.locator(css).count();
|
|
2530
|
+
for (let j = 0; j < count; j++) {
|
|
2531
|
+
const continer = await frame.locator(css).nth(j);
|
|
2532
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
|
|
2533
|
+
if (result.elementCount > 0) {
|
|
2534
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2535
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2536
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2537
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2538
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2539
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2540
|
+
// .then(async () => {
|
|
2541
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2542
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2543
|
+
// () => {}
|
|
2544
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2545
|
+
// );
|
|
2546
|
+
// })
|
|
2547
|
+
// .catch(e);
|
|
2548
|
+
// }
|
|
2549
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2550
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2551
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2552
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2553
|
+
if (element) {
|
|
2554
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2555
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2257
2556
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
continue;
|
|
2261
|
-
}
|
|
2262
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2263
|
-
if (foundElements.length > 0) {
|
|
2264
|
-
// set the container element attribute
|
|
2265
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2266
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2267
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2268
|
-
return { found: true };
|
|
2557
|
+
await _screenshot(state, this);
|
|
2558
|
+
return state.info;
|
|
2269
2559
|
}
|
|
2270
2560
|
}
|
|
2271
|
-
return { found: false };
|
|
2272
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2273
|
-
if (findResult.found === true) {
|
|
2274
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2275
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2276
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2277
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2278
|
-
const element = await frame.$(dataAttribute);
|
|
2279
|
-
if (element) {
|
|
2280
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2281
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2282
|
-
}
|
|
2283
|
-
await _screenshot(state, this);
|
|
2284
|
-
return state.info;
|
|
2285
2561
|
}
|
|
2286
2562
|
}
|
|
2563
|
+
catch (error) {
|
|
2564
|
+
console.error(error);
|
|
2565
|
+
}
|
|
2287
2566
|
}
|
|
2288
2567
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2289
2568
|
}
|
|
@@ -2294,6 +2573,30 @@ class StableBrowser {
|
|
|
2294
2573
|
_commandFinally(state, this);
|
|
2295
2574
|
}
|
|
2296
2575
|
}
|
|
2576
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2577
|
+
const frames = this.page.frames();
|
|
2578
|
+
let results = [];
|
|
2579
|
+
let ignoreCase = false;
|
|
2580
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2581
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2582
|
+
result.frame = frames[i];
|
|
2583
|
+
const climbArray = [];
|
|
2584
|
+
for (let i = 0; i < climb; i++) {
|
|
2585
|
+
climbArray.push("..");
|
|
2586
|
+
}
|
|
2587
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2588
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2589
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2590
|
+
if (count > 0) {
|
|
2591
|
+
result.elementCount = count;
|
|
2592
|
+
result.locator = newLocator;
|
|
2593
|
+
results.push(result);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
// state.info.results = results;
|
|
2597
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2598
|
+
return resultWithElementsFound;
|
|
2599
|
+
}
|
|
2297
2600
|
async visualVerification(text, options = {}, world = null) {
|
|
2298
2601
|
const startTime = Date.now();
|
|
2299
2602
|
let error = null;
|
|
@@ -2312,10 +2615,13 @@ class StableBrowser {
|
|
|
2312
2615
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2313
2616
|
info.screenshotPath = screenshotPath;
|
|
2314
2617
|
const screenshot = await this.takeScreenshot();
|
|
2315
|
-
|
|
2316
|
-
method: "
|
|
2618
|
+
let request = {
|
|
2619
|
+
method: "post",
|
|
2620
|
+
maxBodyLength: Infinity,
|
|
2317
2621
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2318
2622
|
headers: {
|
|
2623
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2624
|
+
"x-source": "aaa",
|
|
2319
2625
|
"Content-Type": "application/json",
|
|
2320
2626
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2321
2627
|
},
|
|
@@ -2324,7 +2630,7 @@ class StableBrowser {
|
|
|
2324
2630
|
screenshot: screenshot,
|
|
2325
2631
|
}),
|
|
2326
2632
|
};
|
|
2327
|
-
|
|
2633
|
+
const result = await axios.request(request);
|
|
2328
2634
|
if (result.data.status !== true) {
|
|
2329
2635
|
throw new Error("Visual validation failed");
|
|
2330
2636
|
}
|
|
@@ -2352,6 +2658,7 @@ class StableBrowser {
|
|
|
2352
2658
|
_reportToWorld(world, {
|
|
2353
2659
|
type: Types.VERIFY_VISUAL,
|
|
2354
2660
|
text: "Visual verification",
|
|
2661
|
+
_text: "Visual verification of " + text,
|
|
2355
2662
|
screenshotId,
|
|
2356
2663
|
result: error
|
|
2357
2664
|
? {
|
|
@@ -2618,6 +2925,32 @@ class StableBrowser {
|
|
|
2618
2925
|
}
|
|
2619
2926
|
return timeout;
|
|
2620
2927
|
}
|
|
2928
|
+
_getFindElementTimeout(options) {
|
|
2929
|
+
if (options && options.timeout) {
|
|
2930
|
+
return options.timeout;
|
|
2931
|
+
}
|
|
2932
|
+
if (this.configuration.find_element_timeout) {
|
|
2933
|
+
return this.configuration.find_element_timeout;
|
|
2934
|
+
}
|
|
2935
|
+
return 30000;
|
|
2936
|
+
}
|
|
2937
|
+
async saveStoreState(path = null, world = null) {
|
|
2938
|
+
const storageState = await this.page.context().storageState();
|
|
2939
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2940
|
+
if (path) {
|
|
2941
|
+
// save { storageState: storageState } into the path
|
|
2942
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2943
|
+
}
|
|
2944
|
+
else {
|
|
2945
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
async restoreSaveState(path = null, world = null) {
|
|
2949
|
+
await refreshBrowser(this, path, world);
|
|
2950
|
+
this.registerEventListeners(this.context);
|
|
2951
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2952
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2953
|
+
}
|
|
2621
2954
|
async waitForPageLoad(options = {}, world = null) {
|
|
2622
2955
|
let timeout = this._getLoadTimeout(options);
|
|
2623
2956
|
const promiseArray = [];
|
|
@@ -2685,6 +3018,7 @@ class StableBrowser {
|
|
|
2685
3018
|
highlight: false,
|
|
2686
3019
|
type: Types.CLOSE_PAGE,
|
|
2687
3020
|
text: `Close page`,
|
|
3021
|
+
_text: `Close the page`,
|
|
2688
3022
|
operation: "closePage",
|
|
2689
3023
|
log: "***** close page *****\n",
|
|
2690
3024
|
throwError: false,
|
|
@@ -2701,8 +3035,95 @@ class StableBrowser {
|
|
|
2701
3035
|
_commandFinally(state, this);
|
|
2702
3036
|
}
|
|
2703
3037
|
}
|
|
3038
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3039
|
+
let operation = null;
|
|
3040
|
+
if (!options || !options.operation) {
|
|
3041
|
+
throw new Error("operation is not defined");
|
|
3042
|
+
}
|
|
3043
|
+
operation = options.operation;
|
|
3044
|
+
// validate operation is one of the supported operations
|
|
3045
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3046
|
+
throw new Error("operation is not supported");
|
|
3047
|
+
}
|
|
3048
|
+
const state = {
|
|
3049
|
+
options,
|
|
3050
|
+
world,
|
|
3051
|
+
locate: false,
|
|
3052
|
+
scroll: false,
|
|
3053
|
+
highlight: false,
|
|
3054
|
+
type: Types.TABLE_OPERATION,
|
|
3055
|
+
text: `Table operation`,
|
|
3056
|
+
_text: `Table ${operation} operation`,
|
|
3057
|
+
operation: operation,
|
|
3058
|
+
log: "***** Table operation *****\n",
|
|
3059
|
+
};
|
|
3060
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3061
|
+
try {
|
|
3062
|
+
await _preCommand(state, this);
|
|
3063
|
+
const start = Date.now();
|
|
3064
|
+
let cellArea = null;
|
|
3065
|
+
while (true) {
|
|
3066
|
+
try {
|
|
3067
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3068
|
+
if (cellArea) {
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
catch (e) {
|
|
3073
|
+
// ignore
|
|
3074
|
+
}
|
|
3075
|
+
if (Date.now() - start > timeout) {
|
|
3076
|
+
throw new Error(`Cell not found in table`);
|
|
3077
|
+
}
|
|
3078
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3079
|
+
}
|
|
3080
|
+
switch (operation) {
|
|
3081
|
+
case "click":
|
|
3082
|
+
if (!options.css) {
|
|
3083
|
+
// will click in the center of the cell
|
|
3084
|
+
let xOffset = 0;
|
|
3085
|
+
let yOffset = 0;
|
|
3086
|
+
if (options.xOffset) {
|
|
3087
|
+
xOffset = options.xOffset;
|
|
3088
|
+
}
|
|
3089
|
+
if (options.yOffset) {
|
|
3090
|
+
yOffset = options.yOffset;
|
|
3091
|
+
}
|
|
3092
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3093
|
+
}
|
|
3094
|
+
else {
|
|
3095
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3096
|
+
if (results.length === 0) {
|
|
3097
|
+
throw new Error(`Element not found in cell area`);
|
|
3098
|
+
}
|
|
3099
|
+
state.element = results[0];
|
|
3100
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3101
|
+
}
|
|
3102
|
+
break;
|
|
3103
|
+
case "hover+click":
|
|
3104
|
+
if (!options.css) {
|
|
3105
|
+
throw new Error("css is not defined");
|
|
3106
|
+
}
|
|
3107
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3108
|
+
if (results.length === 0) {
|
|
3109
|
+
throw new Error(`Element not found in cell area`);
|
|
3110
|
+
}
|
|
3111
|
+
state.element = results[0];
|
|
3112
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3113
|
+
break;
|
|
3114
|
+
default:
|
|
3115
|
+
throw new Error("operation is not supported");
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
catch (e) {
|
|
3119
|
+
await _commandError(state, e, this);
|
|
3120
|
+
}
|
|
3121
|
+
finally {
|
|
3122
|
+
_commandFinally(state, this);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
2704
3125
|
saveTestDataAsGlobal(options, world) {
|
|
2705
|
-
const dataFile =
|
|
3126
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2706
3127
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2707
3128
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2708
3129
|
}
|
|
@@ -2732,6 +3153,7 @@ class StableBrowser {
|
|
|
2732
3153
|
_reportToWorld(world, {
|
|
2733
3154
|
type: Types.SET_VIEWPORT,
|
|
2734
3155
|
text: "set viewport size to " + width + "x" + hight,
|
|
3156
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2735
3157
|
screenshotId,
|
|
2736
3158
|
result: error
|
|
2737
3159
|
? {
|
|
@@ -2803,14 +3225,25 @@ class StableBrowser {
|
|
|
2803
3225
|
}
|
|
2804
3226
|
}
|
|
2805
3227
|
async beforeStep(world, step) {
|
|
2806
|
-
this.stepName = step.pickleStep.text;
|
|
2807
|
-
this.logger.info("step: " + this.stepName);
|
|
2808
3228
|
if (this.stepIndex === undefined) {
|
|
2809
3229
|
this.stepIndex = 0;
|
|
2810
3230
|
}
|
|
2811
3231
|
else {
|
|
2812
3232
|
this.stepIndex++;
|
|
2813
3233
|
}
|
|
3234
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3235
|
+
this.stepName = step.pickleStep.text;
|
|
3236
|
+
this.logger.info("step: " + this.stepName);
|
|
3237
|
+
}
|
|
3238
|
+
else if (step && step.text) {
|
|
3239
|
+
this.stepName = step.text;
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
this.stepName = "step " + this.stepIndex;
|
|
3243
|
+
}
|
|
3244
|
+
if (this.context) {
|
|
3245
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3246
|
+
}
|
|
2814
3247
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2815
3248
|
if (this.context.browserObject.context) {
|
|
2816
3249
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2823,6 +3256,41 @@ class StableBrowser {
|
|
|
2823
3256
|
this.saveTestDataAsGlobal({}, world);
|
|
2824
3257
|
}
|
|
2825
3258
|
}
|
|
3259
|
+
if (this.initSnapshotTaken === false) {
|
|
3260
|
+
this.initSnapshotTaken = true;
|
|
3261
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3262
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3263
|
+
if (snapshot) {
|
|
3264
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async getAriaSnapshot() {
|
|
3270
|
+
try {
|
|
3271
|
+
// find the page url
|
|
3272
|
+
const url = await this.page.url();
|
|
3273
|
+
// extract the path from the url
|
|
3274
|
+
const path = new URL(url).pathname;
|
|
3275
|
+
// get the page title
|
|
3276
|
+
const title = await this.page.title();
|
|
3277
|
+
// go over other frams
|
|
3278
|
+
const frames = this.page.frames();
|
|
3279
|
+
const snapshots = [];
|
|
3280
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3281
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3282
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3283
|
+
content.push(`- frame: ${i}`);
|
|
3284
|
+
const frame = frames[i];
|
|
3285
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3286
|
+
content.push(snapshot);
|
|
3287
|
+
}
|
|
3288
|
+
return content.join("\n");
|
|
3289
|
+
}
|
|
3290
|
+
catch (e) {
|
|
3291
|
+
console.error(e);
|
|
3292
|
+
}
|
|
3293
|
+
return null;
|
|
2826
3294
|
}
|
|
2827
3295
|
async afterStep(world, step) {
|
|
2828
3296
|
this.stepName = null;
|
|
@@ -2833,6 +3301,16 @@ class StableBrowser {
|
|
|
2833
3301
|
});
|
|
2834
3302
|
}
|
|
2835
3303
|
}
|
|
3304
|
+
if (this.context) {
|
|
3305
|
+
this.context.examplesRow = null;
|
|
3306
|
+
}
|
|
3307
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3308
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3309
|
+
if (snapshot) {
|
|
3310
|
+
const obj = {};
|
|
3311
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
2836
3314
|
}
|
|
2837
3315
|
}
|
|
2838
3316
|
function createTimedPromise(promise, label) {
|