automation_model 1.0.564-dev → 1.0.564-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 +720 -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,30 @@ 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._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);
|
|
514
589
|
}
|
|
515
590
|
}
|
|
516
591
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -583,7 +658,7 @@ class StableBrowser {
|
|
|
583
658
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
584
659
|
if (Date.now() - startTime > timeout) {
|
|
585
660
|
info.failCause.iframeNotFound = true;
|
|
586
|
-
info.failCause.lastError =
|
|
661
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
587
662
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
588
663
|
}
|
|
589
664
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -608,7 +683,7 @@ class StableBrowser {
|
|
|
608
683
|
return bodyContent;
|
|
609
684
|
});
|
|
610
685
|
}
|
|
611
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
686
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
612
687
|
if (!info) {
|
|
613
688
|
info = {};
|
|
614
689
|
info.failCause = {};
|
|
@@ -657,17 +732,17 @@ class StableBrowser {
|
|
|
657
732
|
}
|
|
658
733
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
659
734
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
660
|
-
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);
|
|
661
736
|
if (result.foundElements.length === 0) {
|
|
662
737
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
663
|
-
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);
|
|
664
739
|
}
|
|
665
740
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
666
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
741
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
667
742
|
}
|
|
668
743
|
else {
|
|
669
744
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
670
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
745
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
671
746
|
}
|
|
672
747
|
}
|
|
673
748
|
let foundElements = result.foundElements;
|
|
@@ -712,7 +787,7 @@ class StableBrowser {
|
|
|
712
787
|
break;
|
|
713
788
|
}
|
|
714
789
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
715
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
790
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
716
791
|
highPriorityOnly = false;
|
|
717
792
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
718
793
|
lazy_scroll = true;
|
|
@@ -720,7 +795,7 @@ class StableBrowser {
|
|
|
720
795
|
}
|
|
721
796
|
}
|
|
722
797
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
723
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
798
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
724
799
|
visibleOnly = false;
|
|
725
800
|
}
|
|
726
801
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -734,10 +809,12 @@ class StableBrowser {
|
|
|
734
809
|
// }
|
|
735
810
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
736
811
|
info.failCause.locatorNotFound = true;
|
|
737
|
-
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
|
+
}
|
|
738
815
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
739
816
|
}
|
|
740
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
817
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
741
818
|
let foundElements = [];
|
|
742
819
|
const result = {
|
|
743
820
|
foundElements: foundElements,
|
|
@@ -745,7 +822,7 @@ class StableBrowser {
|
|
|
745
822
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
746
823
|
let foundLocators = [];
|
|
747
824
|
try {
|
|
748
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
825
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
749
826
|
}
|
|
750
827
|
catch (e) {
|
|
751
828
|
// this call can fail it the browser is navigating
|
|
@@ -753,7 +830,7 @@ class StableBrowser {
|
|
|
753
830
|
// this.logger.debug(e);
|
|
754
831
|
foundLocators = [];
|
|
755
832
|
try {
|
|
756
|
-
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);
|
|
757
834
|
}
|
|
758
835
|
catch (e) {
|
|
759
836
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -768,9 +845,40 @@ class StableBrowser {
|
|
|
768
845
|
result.locatorIndex = i;
|
|
769
846
|
}
|
|
770
847
|
if (foundLocators.length > 1) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
+
}
|
|
774
882
|
}
|
|
775
883
|
}
|
|
776
884
|
}
|
|
@@ -881,25 +989,14 @@ class StableBrowser {
|
|
|
881
989
|
options,
|
|
882
990
|
world,
|
|
883
991
|
text: "Click element",
|
|
992
|
+
_text: "Click on " + selectors.element_name,
|
|
884
993
|
type: Types.CLICK,
|
|
885
994
|
operation: "click",
|
|
886
995
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
887
996
|
};
|
|
888
997
|
try {
|
|
889
998
|
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
|
-
}
|
|
999
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
903
1000
|
await this.waitForPageLoad();
|
|
904
1001
|
return state.info;
|
|
905
1002
|
}
|
|
@@ -910,6 +1007,38 @@ class StableBrowser {
|
|
|
910
1007
|
_commandFinally(state, this);
|
|
911
1008
|
}
|
|
912
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
|
+
}
|
|
913
1042
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
914
1043
|
const state = {
|
|
915
1044
|
selectors,
|
|
@@ -918,6 +1047,7 @@ class StableBrowser {
|
|
|
918
1047
|
world,
|
|
919
1048
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
920
1049
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1050
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
921
1051
|
operation: "setCheck",
|
|
922
1052
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
923
1053
|
};
|
|
@@ -927,9 +1057,15 @@ class StableBrowser {
|
|
|
927
1057
|
// let element = await this._locate(selectors, info, _params);
|
|
928
1058
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
929
1059
|
try {
|
|
930
|
-
//
|
|
1060
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1061
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1062
|
+
await this._highlightElements(state.element);
|
|
931
1063
|
await state.element.setChecked(checked);
|
|
932
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);
|
|
933
1069
|
}
|
|
934
1070
|
catch (e) {
|
|
935
1071
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -961,22 +1097,13 @@ class StableBrowser {
|
|
|
961
1097
|
world,
|
|
962
1098
|
type: Types.HOVER,
|
|
963
1099
|
text: `Hover element`,
|
|
1100
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
964
1101
|
operation: "hover",
|
|
965
1102
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
966
1103
|
};
|
|
967
1104
|
try {
|
|
968
1105
|
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
|
-
}
|
|
1106
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
980
1107
|
await _screenshot(state, this);
|
|
981
1108
|
await this.waitForPageLoad();
|
|
982
1109
|
return state.info;
|
|
@@ -1000,6 +1127,7 @@ class StableBrowser {
|
|
|
1000
1127
|
value: values.toString(),
|
|
1001
1128
|
type: Types.SELECT,
|
|
1002
1129
|
text: `Select option: ${values}`,
|
|
1130
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1003
1131
|
operation: "selectOption",
|
|
1004
1132
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1005
1133
|
};
|
|
@@ -1034,6 +1162,7 @@ class StableBrowser {
|
|
|
1034
1162
|
highlight: false,
|
|
1035
1163
|
type: Types.TYPE_PRESS,
|
|
1036
1164
|
text: `Type value: ${_value}`,
|
|
1165
|
+
_text: `Type value: ${_value}`,
|
|
1037
1166
|
operation: "type",
|
|
1038
1167
|
log: "",
|
|
1039
1168
|
};
|
|
@@ -1113,6 +1242,7 @@ class StableBrowser {
|
|
|
1113
1242
|
world,
|
|
1114
1243
|
type: Types.SET_DATE_TIME,
|
|
1115
1244
|
text: `Set date time value: ${value}`,
|
|
1245
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1116
1246
|
operation: "setDateTime",
|
|
1117
1247
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1118
1248
|
throwError: false,
|
|
@@ -1120,7 +1250,7 @@ class StableBrowser {
|
|
|
1120
1250
|
try {
|
|
1121
1251
|
await _preCommand(state, this);
|
|
1122
1252
|
try {
|
|
1123
|
-
await state.element
|
|
1253
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1124
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1125
1255
|
if (format) {
|
|
1126
1256
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1184,9 +1314,13 @@ class StableBrowser {
|
|
|
1184
1314
|
world,
|
|
1185
1315
|
type: Types.FILL,
|
|
1186
1316
|
text: `Click type input with value: ${_value}`,
|
|
1317
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1187
1318
|
operation: "clickType",
|
|
1188
1319
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1189
1320
|
};
|
|
1321
|
+
if (!options) {
|
|
1322
|
+
options = {};
|
|
1323
|
+
}
|
|
1190
1324
|
if (newValue !== _value) {
|
|
1191
1325
|
//this.logger.info(_value + "=" + newValue);
|
|
1192
1326
|
_value = newValue;
|
|
@@ -1194,7 +1328,7 @@ class StableBrowser {
|
|
|
1194
1328
|
try {
|
|
1195
1329
|
await _preCommand(state, this);
|
|
1196
1330
|
state.info.value = _value;
|
|
1197
|
-
if (
|
|
1331
|
+
if (!options.press) {
|
|
1198
1332
|
try {
|
|
1199
1333
|
let currentValue = await state.element.inputValue();
|
|
1200
1334
|
if (currentValue) {
|
|
@@ -1205,13 +1339,9 @@ class StableBrowser {
|
|
|
1205
1339
|
this.logger.info("unable to clear input value");
|
|
1206
1340
|
}
|
|
1207
1341
|
}
|
|
1208
|
-
if (options
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
catch (e) {
|
|
1213
|
-
await state.element.dispatchEvent("click");
|
|
1214
|
-
}
|
|
1342
|
+
if (options.press) {
|
|
1343
|
+
options.timeout = 5000;
|
|
1344
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1215
1345
|
}
|
|
1216
1346
|
else {
|
|
1217
1347
|
try {
|
|
@@ -1249,7 +1379,12 @@ class StableBrowser {
|
|
|
1249
1379
|
await this.waitForPageLoad();
|
|
1250
1380
|
}
|
|
1251
1381
|
else if (enter === false) {
|
|
1252
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1384
|
+
}
|
|
1385
|
+
catch (e) {
|
|
1386
|
+
// ignore
|
|
1387
|
+
}
|
|
1253
1388
|
//await this.page.keyboard.press("Tab");
|
|
1254
1389
|
}
|
|
1255
1390
|
else {
|
|
@@ -1301,6 +1436,7 @@ class StableBrowser {
|
|
|
1301
1436
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1302
1437
|
}
|
|
1303
1438
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1439
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1304
1440
|
_validateSelectors(selectors);
|
|
1305
1441
|
let screenshotId = null;
|
|
1306
1442
|
let screenshotPath = null;
|
|
@@ -1310,7 +1446,7 @@ class StableBrowser {
|
|
|
1310
1446
|
}
|
|
1311
1447
|
info.operation = "getText";
|
|
1312
1448
|
info.selectors = selectors;
|
|
1313
|
-
let element = await this._locate(selectors, info, _params);
|
|
1449
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1314
1450
|
if (climb > 0) {
|
|
1315
1451
|
const climbArray = [];
|
|
1316
1452
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1329,6 +1465,18 @@ class StableBrowser {
|
|
|
1329
1465
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1330
1466
|
try {
|
|
1331
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
|
+
// }
|
|
1332
1480
|
const elementText = await element.innerText();
|
|
1333
1481
|
return {
|
|
1334
1482
|
text: elementText,
|
|
@@ -1340,7 +1488,7 @@ class StableBrowser {
|
|
|
1340
1488
|
}
|
|
1341
1489
|
catch (e) {
|
|
1342
1490
|
//await this.closeUnexpectedPopups();
|
|
1343
|
-
this.logger.info("no innerText will use textContent");
|
|
1491
|
+
this.logger.info("no innerText, will use textContent");
|
|
1344
1492
|
const elementText = await element.textContent();
|
|
1345
1493
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1346
1494
|
}
|
|
@@ -1365,6 +1513,7 @@ class StableBrowser {
|
|
|
1365
1513
|
highlight: false,
|
|
1366
1514
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1367
1515
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1516
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1368
1517
|
operation: "containsPattern",
|
|
1369
1518
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1370
1519
|
};
|
|
@@ -1400,6 +1549,8 @@ class StableBrowser {
|
|
|
1400
1549
|
}
|
|
1401
1550
|
}
|
|
1402
1551
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1552
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1553
|
+
const startTime = Date.now();
|
|
1403
1554
|
const state = {
|
|
1404
1555
|
selectors,
|
|
1405
1556
|
_params,
|
|
@@ -1426,62 +1577,54 @@ class StableBrowser {
|
|
|
1426
1577
|
}
|
|
1427
1578
|
let foundObj = null;
|
|
1428
1579
|
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;
|
|
1580
|
+
while (Date.now() - startTime < timeout) {
|
|
1581
|
+
try {
|
|
1582
|
+
await _preCommand(state, this);
|
|
1583
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1584
|
+
if (foundObj && foundObj.element) {
|
|
1585
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1442
1586
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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)) {
|
|
1450
1607
|
return state.info;
|
|
1451
1608
|
}
|
|
1452
1609
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
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
|
|
1459
1615
|
}
|
|
1460
|
-
|
|
1616
|
+
state.info.foundText = foundObj?.text;
|
|
1617
|
+
state.info.value = foundObj?.value;
|
|
1618
|
+
throw new Error("element doesn't contain text " + text);
|
|
1461
1619
|
}
|
|
1462
1620
|
catch (e) {
|
|
1463
1621
|
await _commandError(state, e, this);
|
|
1622
|
+
throw e;
|
|
1464
1623
|
}
|
|
1465
1624
|
finally {
|
|
1466
1625
|
_commandFinally(state, this);
|
|
1467
1626
|
}
|
|
1468
1627
|
}
|
|
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
1628
|
async waitForUserInput(message, world = null) {
|
|
1486
1629
|
if (!message) {
|
|
1487
1630
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1510,7 +1653,7 @@ class StableBrowser {
|
|
|
1510
1653
|
return;
|
|
1511
1654
|
}
|
|
1512
1655
|
// if data file exists, load it
|
|
1513
|
-
const dataFile =
|
|
1656
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1514
1657
|
let data = this.getTestData(world);
|
|
1515
1658
|
// merge the testData with the existing data
|
|
1516
1659
|
Object.assign(data, testData);
|
|
@@ -1613,7 +1756,7 @@ class StableBrowser {
|
|
|
1613
1756
|
}
|
|
1614
1757
|
}
|
|
1615
1758
|
getTestData(world = null) {
|
|
1616
|
-
const dataFile =
|
|
1759
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1617
1760
|
let data = {};
|
|
1618
1761
|
if (fs.existsSync(dataFile)) {
|
|
1619
1762
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1700,6 +1843,15 @@ class StableBrowser {
|
|
|
1700
1843
|
document.documentElement.clientWidth,
|
|
1701
1844
|
])));
|
|
1702
1845
|
let screenshotBuffer = null;
|
|
1846
|
+
// if (focusedElement) {
|
|
1847
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1848
|
+
// await this._unhighlightElements(focusedElement);
|
|
1849
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1850
|
+
// console.log(`Unhighlighted previous element`);
|
|
1851
|
+
// }
|
|
1852
|
+
// if (focusedElement) {
|
|
1853
|
+
// await this._highlightElements(focusedElement);
|
|
1854
|
+
// }
|
|
1703
1855
|
if (this.context.browserName === "chromium") {
|
|
1704
1856
|
const client = await playContext.newCDPSession(this.page);
|
|
1705
1857
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1721,6 +1873,10 @@ class StableBrowser {
|
|
|
1721
1873
|
else {
|
|
1722
1874
|
screenshotBuffer = await this.page.screenshot();
|
|
1723
1875
|
}
|
|
1876
|
+
// if (focusedElement) {
|
|
1877
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1878
|
+
// await this._unhighlightElements(focusedElement);
|
|
1879
|
+
// }
|
|
1724
1880
|
let image = await Jimp.read(screenshotBuffer);
|
|
1725
1881
|
// Get the image dimensions
|
|
1726
1882
|
const { width, height } = image.bitmap;
|
|
@@ -1733,6 +1889,7 @@ class StableBrowser {
|
|
|
1733
1889
|
else {
|
|
1734
1890
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1735
1891
|
}
|
|
1892
|
+
return screenshotBuffer;
|
|
1736
1893
|
}
|
|
1737
1894
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1738
1895
|
const state = {
|
|
@@ -1768,8 +1925,10 @@ class StableBrowser {
|
|
|
1768
1925
|
world,
|
|
1769
1926
|
type: Types.EXTRACT,
|
|
1770
1927
|
text: `Extract attribute from element`,
|
|
1928
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1771
1929
|
operation: "extractAttribute",
|
|
1772
1930
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1931
|
+
allowDisabled: true,
|
|
1773
1932
|
};
|
|
1774
1933
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1775
1934
|
try {
|
|
@@ -1791,6 +1950,7 @@ class StableBrowser {
|
|
|
1791
1950
|
state.info.value = state.value;
|
|
1792
1951
|
this.setTestData({ [variable]: state.value }, world);
|
|
1793
1952
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1953
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1794
1954
|
return state.info;
|
|
1795
1955
|
}
|
|
1796
1956
|
catch (e) {
|
|
@@ -1809,14 +1969,21 @@ class StableBrowser {
|
|
|
1809
1969
|
options,
|
|
1810
1970
|
world,
|
|
1811
1971
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1972
|
+
highlight: true,
|
|
1973
|
+
screenshot: true,
|
|
1812
1974
|
text: `Verify element attribute`,
|
|
1975
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1813
1976
|
operation: "verifyAttribute",
|
|
1814
1977
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1978
|
+
allowDisabled: true,
|
|
1815
1979
|
};
|
|
1816
1980
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1817
1981
|
let val;
|
|
1982
|
+
let expectedValue;
|
|
1818
1983
|
try {
|
|
1819
1984
|
await _preCommand(state, this);
|
|
1985
|
+
expectedValue = state.value;
|
|
1986
|
+
state.info.expectedValue = expectedValue;
|
|
1820
1987
|
switch (attribute) {
|
|
1821
1988
|
case "innerText":
|
|
1822
1989
|
val = String(await state.element.innerText());
|
|
@@ -1830,23 +1997,30 @@ class StableBrowser {
|
|
|
1830
1997
|
case "disabled":
|
|
1831
1998
|
val = String(await state.element.isDisabled());
|
|
1832
1999
|
break;
|
|
2000
|
+
case "readOnly":
|
|
2001
|
+
const isEditable = await state.element.isEditable();
|
|
2002
|
+
val = String(!isEditable);
|
|
2003
|
+
break;
|
|
1833
2004
|
default:
|
|
1834
2005
|
val = String(await state.element.getAttribute(attribute));
|
|
1835
2006
|
break;
|
|
1836
2007
|
}
|
|
2008
|
+
state.info.value = val;
|
|
1837
2009
|
let regex;
|
|
1838
|
-
if (
|
|
1839
|
-
const patternBody =
|
|
2010
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2011
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1840
2012
|
regex = new RegExp(patternBody, "g");
|
|
1841
2013
|
}
|
|
1842
2014
|
else {
|
|
1843
|
-
const escapedPattern =
|
|
2015
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1844
2016
|
regex = new RegExp(escapedPattern, "g");
|
|
1845
2017
|
}
|
|
1846
2018
|
if (!val.match(regex)) {
|
|
1847
|
-
|
|
2019
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2020
|
+
state.info.failCause.assertionFailed = true;
|
|
2021
|
+
state.info.failCause.lastError = errorMessage;
|
|
2022
|
+
throw new Error(errorMessage);
|
|
1848
2023
|
}
|
|
1849
|
-
state.info.value = val;
|
|
1850
2024
|
return state.info;
|
|
1851
2025
|
}
|
|
1852
2026
|
catch (e) {
|
|
@@ -1945,27 +2119,32 @@ class StableBrowser {
|
|
|
1945
2119
|
async _highlightElements(scope, css) {
|
|
1946
2120
|
try {
|
|
1947
2121
|
if (!scope) {
|
|
2122
|
+
// console.log(`Scope is not defined`);
|
|
1948
2123
|
return;
|
|
1949
2124
|
}
|
|
1950
2125
|
if (!css) {
|
|
1951
2126
|
scope
|
|
1952
2127
|
.evaluate((node) => {
|
|
1953
2128
|
if (node && node.style) {
|
|
1954
|
-
let
|
|
1955
|
-
|
|
2129
|
+
let originalOutline = node.style.outline;
|
|
2130
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2131
|
+
// node.__previousOutline = originalOutline;
|
|
2132
|
+
node.style.outline = "2px solid red";
|
|
2133
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1956
2134
|
if (window) {
|
|
1957
2135
|
window.addEventListener("beforeunload", function (e) {
|
|
1958
|
-
node.style.
|
|
2136
|
+
node.style.outline = originalOutline;
|
|
1959
2137
|
});
|
|
1960
2138
|
}
|
|
1961
2139
|
setTimeout(function () {
|
|
1962
|
-
node.style.
|
|
2140
|
+
node.style.outline = originalOutline;
|
|
1963
2141
|
}, 2000);
|
|
1964
2142
|
}
|
|
1965
2143
|
})
|
|
1966
2144
|
.then(() => { })
|
|
1967
2145
|
.catch((e) => {
|
|
1968
2146
|
// ignore
|
|
2147
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1969
2148
|
});
|
|
1970
2149
|
}
|
|
1971
2150
|
else {
|
|
@@ -1981,17 +2160,18 @@ class StableBrowser {
|
|
|
1981
2160
|
if (!element.style) {
|
|
1982
2161
|
return;
|
|
1983
2162
|
}
|
|
1984
|
-
|
|
2163
|
+
let originalOutline = element.style.outline;
|
|
2164
|
+
element.__previousOutline = originalOutline;
|
|
1985
2165
|
// Set the new border to be red and 2px solid
|
|
1986
|
-
element.style.
|
|
2166
|
+
element.style.outline = "2px solid red";
|
|
1987
2167
|
if (window) {
|
|
1988
2168
|
window.addEventListener("beforeunload", function (e) {
|
|
1989
|
-
element.style.
|
|
2169
|
+
element.style.outline = originalOutline;
|
|
1990
2170
|
});
|
|
1991
2171
|
}
|
|
1992
2172
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1993
2173
|
setTimeout(function () {
|
|
1994
|
-
element.style.
|
|
2174
|
+
element.style.outline = originalOutline;
|
|
1995
2175
|
}, 2000);
|
|
1996
2176
|
}
|
|
1997
2177
|
return;
|
|
@@ -1999,6 +2179,7 @@ class StableBrowser {
|
|
|
1999
2179
|
.then(() => { })
|
|
2000
2180
|
.catch((e) => {
|
|
2001
2181
|
// ignore
|
|
2182
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2002
2183
|
});
|
|
2003
2184
|
}
|
|
2004
2185
|
}
|
|
@@ -2006,6 +2187,54 @@ class StableBrowser {
|
|
|
2006
2187
|
console.debug(error);
|
|
2007
2188
|
}
|
|
2008
2189
|
}
|
|
2190
|
+
// async _unhighlightElements(scope, css) {
|
|
2191
|
+
// try {
|
|
2192
|
+
// if (!scope) {
|
|
2193
|
+
// return;
|
|
2194
|
+
// }
|
|
2195
|
+
// if (!css) {
|
|
2196
|
+
// scope
|
|
2197
|
+
// .evaluate((node) => {
|
|
2198
|
+
// if (node && node.style) {
|
|
2199
|
+
// if (!node.__previousOutline) {
|
|
2200
|
+
// node.style.outline = "";
|
|
2201
|
+
// } else {
|
|
2202
|
+
// node.style.outline = node.__previousOutline;
|
|
2203
|
+
// }
|
|
2204
|
+
// }
|
|
2205
|
+
// })
|
|
2206
|
+
// .then(() => {})
|
|
2207
|
+
// .catch((e) => {
|
|
2208
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2209
|
+
// });
|
|
2210
|
+
// } else {
|
|
2211
|
+
// scope
|
|
2212
|
+
// .evaluate(([css]) => {
|
|
2213
|
+
// if (!css) {
|
|
2214
|
+
// return;
|
|
2215
|
+
// }
|
|
2216
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2217
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2218
|
+
// let element = elements[i];
|
|
2219
|
+
// if (!element.style) {
|
|
2220
|
+
// return;
|
|
2221
|
+
// }
|
|
2222
|
+
// if (!element.__previousOutline) {
|
|
2223
|
+
// element.style.outline = "";
|
|
2224
|
+
// } else {
|
|
2225
|
+
// element.style.outline = element.__previousOutline;
|
|
2226
|
+
// }
|
|
2227
|
+
// }
|
|
2228
|
+
// })
|
|
2229
|
+
// .then(() => {})
|
|
2230
|
+
// .catch((e) => {
|
|
2231
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2232
|
+
// });
|
|
2233
|
+
// }
|
|
2234
|
+
// } catch (error) {
|
|
2235
|
+
// // console.debug(error);
|
|
2236
|
+
// }
|
|
2237
|
+
// }
|
|
2009
2238
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2010
2239
|
const startTime = Date.now();
|
|
2011
2240
|
let error = null;
|
|
@@ -2050,6 +2279,7 @@ class StableBrowser {
|
|
|
2050
2279
|
_reportToWorld(world, {
|
|
2051
2280
|
type: Types.VERIFY_PAGE_PATH,
|
|
2052
2281
|
text: "Verify page path",
|
|
2282
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2053
2283
|
screenshotId,
|
|
2054
2284
|
result: error
|
|
2055
2285
|
? {
|
|
@@ -2067,26 +2297,27 @@ class StableBrowser {
|
|
|
2067
2297
|
});
|
|
2068
2298
|
}
|
|
2069
2299
|
}
|
|
2070
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2300
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2071
2301
|
const frames = this.page.frames();
|
|
2072
2302
|
let results = [];
|
|
2303
|
+
// let ignoreCase = false;
|
|
2073
2304
|
for (let i = 0; i < frames.length; i++) {
|
|
2074
2305
|
if (dateAlternatives.date) {
|
|
2075
2306
|
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,
|
|
2307
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2077
2308
|
result.frame = frames[i];
|
|
2078
2309
|
results.push(result);
|
|
2079
2310
|
}
|
|
2080
2311
|
}
|
|
2081
2312
|
else if (numberAlternatives.number) {
|
|
2082
2313
|
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,
|
|
2314
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2084
2315
|
result.frame = frames[i];
|
|
2085
2316
|
results.push(result);
|
|
2086
2317
|
}
|
|
2087
2318
|
}
|
|
2088
2319
|
else {
|
|
2089
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2320
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2090
2321
|
result.frame = frames[i];
|
|
2091
2322
|
results.push(result);
|
|
2092
2323
|
}
|
|
@@ -2105,11 +2336,15 @@ class StableBrowser {
|
|
|
2105
2336
|
scroll: false,
|
|
2106
2337
|
highlight: false,
|
|
2107
2338
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2108
|
-
text: `Verify text exists in page`,
|
|
2339
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2340
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2109
2341
|
operation: "verifyTextExistInPage",
|
|
2110
2342
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2111
2343
|
};
|
|
2112
|
-
|
|
2344
|
+
if (testForRegex(text)) {
|
|
2345
|
+
text = text.replace(/\\"/g, '"');
|
|
2346
|
+
}
|
|
2347
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2113
2348
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2114
2349
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2115
2350
|
if (newValue !== text) {
|
|
@@ -2122,7 +2357,15 @@ class StableBrowser {
|
|
|
2122
2357
|
await _preCommand(state, this);
|
|
2123
2358
|
state.info.text = text;
|
|
2124
2359
|
while (true) {
|
|
2125
|
-
|
|
2360
|
+
let resultWithElementsFound = {
|
|
2361
|
+
length: 0,
|
|
2362
|
+
};
|
|
2363
|
+
try {
|
|
2364
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2365
|
+
}
|
|
2366
|
+
catch (error) {
|
|
2367
|
+
// ignore
|
|
2368
|
+
}
|
|
2126
2369
|
if (resultWithElementsFound.length === 0) {
|
|
2127
2370
|
if (Date.now() - state.startTime > timeout) {
|
|
2128
2371
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2130,18 +2373,40 @@ class StableBrowser {
|
|
|
2130
2373
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2131
2374
|
continue;
|
|
2132
2375
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2376
|
+
try {
|
|
2377
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2378
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2379
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2380
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2381
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2382
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2383
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2384
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2385
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2386
|
+
// .then(async () => {
|
|
2387
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2388
|
+
// })
|
|
2389
|
+
// .catch(
|
|
2390
|
+
// (e) => {}
|
|
2391
|
+
// console.error(e)
|
|
2392
|
+
// );
|
|
2393
|
+
// });
|
|
2394
|
+
// }
|
|
2395
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2396
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2397
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2398
|
+
if (element) {
|
|
2399
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2400
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2401
|
+
// await _screenshot(state, this, element);
|
|
2402
|
+
}
|
|
2141
2403
|
}
|
|
2404
|
+
await _screenshot(state, this);
|
|
2405
|
+
return state.info;
|
|
2406
|
+
}
|
|
2407
|
+
catch (error) {
|
|
2408
|
+
console.error(error);
|
|
2142
2409
|
}
|
|
2143
|
-
await _screenshot(state, this);
|
|
2144
|
-
return state.info;
|
|
2145
2410
|
}
|
|
2146
2411
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2147
2412
|
}
|
|
@@ -2162,11 +2427,15 @@ class StableBrowser {
|
|
|
2162
2427
|
scroll: false,
|
|
2163
2428
|
highlight: false,
|
|
2164
2429
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2165
|
-
text: `Verify text does not exist in page`,
|
|
2430
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2431
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2166
2432
|
operation: "verifyTextNotExistInPage",
|
|
2167
2433
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2168
2434
|
};
|
|
2169
|
-
|
|
2435
|
+
if (testForRegex(text)) {
|
|
2436
|
+
text = text.replace(/\\"/g, '"');
|
|
2437
|
+
}
|
|
2438
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2170
2439
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2171
2440
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2172
2441
|
if (newValue !== text) {
|
|
@@ -2178,8 +2447,16 @@ class StableBrowser {
|
|
|
2178
2447
|
try {
|
|
2179
2448
|
await _preCommand(state, this);
|
|
2180
2449
|
state.info.text = text;
|
|
2450
|
+
let resultWithElementsFound = {
|
|
2451
|
+
length: null, // initial cannot be 0
|
|
2452
|
+
};
|
|
2181
2453
|
while (true) {
|
|
2182
|
-
|
|
2454
|
+
try {
|
|
2455
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2456
|
+
}
|
|
2457
|
+
catch (error) {
|
|
2458
|
+
// ignore
|
|
2459
|
+
}
|
|
2183
2460
|
if (resultWithElementsFound.length === 0) {
|
|
2184
2461
|
await _screenshot(state, this);
|
|
2185
2462
|
return state.info;
|
|
@@ -2209,10 +2486,11 @@ class StableBrowser {
|
|
|
2209
2486
|
highlight: false,
|
|
2210
2487
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2211
2488
|
text: `Verify text with relation to another text`,
|
|
2489
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2212
2490
|
operation: "verify_text_with_relation",
|
|
2213
2491
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2214
2492
|
};
|
|
2215
|
-
const timeout = this.
|
|
2493
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2216
2494
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2217
2495
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2218
2496
|
if (newValue !== textAnchor) {
|
|
@@ -2230,8 +2508,16 @@ class StableBrowser {
|
|
|
2230
2508
|
try {
|
|
2231
2509
|
await _preCommand(state, this);
|
|
2232
2510
|
state.info.text = textToVerify;
|
|
2511
|
+
let resultWithElementsFound = {
|
|
2512
|
+
length: 0,
|
|
2513
|
+
};
|
|
2233
2514
|
while (true) {
|
|
2234
|
-
|
|
2515
|
+
try {
|
|
2516
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2517
|
+
}
|
|
2518
|
+
catch (error) {
|
|
2519
|
+
// ignore
|
|
2520
|
+
}
|
|
2235
2521
|
if (resultWithElementsFound.length === 0) {
|
|
2236
2522
|
if (Date.now() - state.startTime > timeout) {
|
|
2237
2523
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2239,51 +2525,56 @@ class StableBrowser {
|
|
|
2239
2525
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2240
2526
|
continue;
|
|
2241
2527
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
const
|
|
2250
|
-
for (let i = 0; i <
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2528
|
+
try {
|
|
2529
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2530
|
+
foundAncore = true;
|
|
2531
|
+
const result = resultWithElementsFound[i];
|
|
2532
|
+
const token = result.randomToken;
|
|
2533
|
+
const frame = result.frame;
|
|
2534
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2535
|
+
const climbArray1 = [];
|
|
2536
|
+
for (let i = 0; i < climb; i++) {
|
|
2537
|
+
climbArray1.push("..");
|
|
2538
|
+
}
|
|
2539
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2540
|
+
css = css + " >> " + climbXpath;
|
|
2541
|
+
const count = await frame.locator(css).count();
|
|
2542
|
+
for (let j = 0; j < count; j++) {
|
|
2543
|
+
const continer = await frame.locator(css).nth(j);
|
|
2544
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
|
|
2545
|
+
if (result.elementCount > 0) {
|
|
2546
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2547
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2548
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2549
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2550
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2551
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2552
|
+
// .then(async () => {
|
|
2553
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2554
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2555
|
+
// () => {}
|
|
2556
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2557
|
+
// );
|
|
2558
|
+
// })
|
|
2559
|
+
// .catch(e);
|
|
2560
|
+
// }
|
|
2561
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2562
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2563
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2564
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2565
|
+
if (element) {
|
|
2566
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2567
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2257
2568
|
}
|
|
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 };
|
|
2569
|
+
await _screenshot(state, this);
|
|
2570
|
+
return state.info;
|
|
2269
2571
|
}
|
|
2270
2572
|
}
|
|
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
2573
|
}
|
|
2286
2574
|
}
|
|
2575
|
+
catch (error) {
|
|
2576
|
+
console.error(error);
|
|
2577
|
+
}
|
|
2287
2578
|
}
|
|
2288
2579
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2289
2580
|
}
|
|
@@ -2294,6 +2585,30 @@ class StableBrowser {
|
|
|
2294
2585
|
_commandFinally(state, this);
|
|
2295
2586
|
}
|
|
2296
2587
|
}
|
|
2588
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2589
|
+
const frames = this.page.frames();
|
|
2590
|
+
let results = [];
|
|
2591
|
+
let ignoreCase = false;
|
|
2592
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2593
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2594
|
+
result.frame = frames[i];
|
|
2595
|
+
const climbArray = [];
|
|
2596
|
+
for (let i = 0; i < climb; i++) {
|
|
2597
|
+
climbArray.push("..");
|
|
2598
|
+
}
|
|
2599
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2600
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2601
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2602
|
+
if (count > 0) {
|
|
2603
|
+
result.elementCount = count;
|
|
2604
|
+
result.locator = newLocator;
|
|
2605
|
+
results.push(result);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
// state.info.results = results;
|
|
2609
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2610
|
+
return resultWithElementsFound;
|
|
2611
|
+
}
|
|
2297
2612
|
async visualVerification(text, options = {}, world = null) {
|
|
2298
2613
|
const startTime = Date.now();
|
|
2299
2614
|
let error = null;
|
|
@@ -2312,10 +2627,13 @@ class StableBrowser {
|
|
|
2312
2627
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2313
2628
|
info.screenshotPath = screenshotPath;
|
|
2314
2629
|
const screenshot = await this.takeScreenshot();
|
|
2315
|
-
|
|
2316
|
-
method: "
|
|
2630
|
+
let request = {
|
|
2631
|
+
method: "post",
|
|
2632
|
+
maxBodyLength: Infinity,
|
|
2317
2633
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2318
2634
|
headers: {
|
|
2635
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2636
|
+
"x-source": "aaa",
|
|
2319
2637
|
"Content-Type": "application/json",
|
|
2320
2638
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2321
2639
|
},
|
|
@@ -2324,7 +2642,7 @@ class StableBrowser {
|
|
|
2324
2642
|
screenshot: screenshot,
|
|
2325
2643
|
}),
|
|
2326
2644
|
};
|
|
2327
|
-
|
|
2645
|
+
const result = await axios.request(request);
|
|
2328
2646
|
if (result.data.status !== true) {
|
|
2329
2647
|
throw new Error("Visual validation failed");
|
|
2330
2648
|
}
|
|
@@ -2352,6 +2670,7 @@ class StableBrowser {
|
|
|
2352
2670
|
_reportToWorld(world, {
|
|
2353
2671
|
type: Types.VERIFY_VISUAL,
|
|
2354
2672
|
text: "Visual verification",
|
|
2673
|
+
_text: "Visual verification of " + text,
|
|
2355
2674
|
screenshotId,
|
|
2356
2675
|
result: error
|
|
2357
2676
|
? {
|
|
@@ -2618,6 +2937,32 @@ class StableBrowser {
|
|
|
2618
2937
|
}
|
|
2619
2938
|
return timeout;
|
|
2620
2939
|
}
|
|
2940
|
+
_getFindElementTimeout(options) {
|
|
2941
|
+
if (options && options.timeout) {
|
|
2942
|
+
return options.timeout;
|
|
2943
|
+
}
|
|
2944
|
+
if (this.configuration.find_element_timeout) {
|
|
2945
|
+
return this.configuration.find_element_timeout;
|
|
2946
|
+
}
|
|
2947
|
+
return 30000;
|
|
2948
|
+
}
|
|
2949
|
+
async saveStoreState(path = null, world = null) {
|
|
2950
|
+
const storageState = await this.page.context().storageState();
|
|
2951
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2952
|
+
if (path) {
|
|
2953
|
+
// save { storageState: storageState } into the path
|
|
2954
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2955
|
+
}
|
|
2956
|
+
else {
|
|
2957
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
async restoreSaveState(path = null, world = null) {
|
|
2961
|
+
await refreshBrowser(this, path, world);
|
|
2962
|
+
this.registerEventListeners(this.context);
|
|
2963
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2964
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2965
|
+
}
|
|
2621
2966
|
async waitForPageLoad(options = {}, world = null) {
|
|
2622
2967
|
let timeout = this._getLoadTimeout(options);
|
|
2623
2968
|
const promiseArray = [];
|
|
@@ -2685,6 +3030,7 @@ class StableBrowser {
|
|
|
2685
3030
|
highlight: false,
|
|
2686
3031
|
type: Types.CLOSE_PAGE,
|
|
2687
3032
|
text: `Close page`,
|
|
3033
|
+
_text: `Close the page`,
|
|
2688
3034
|
operation: "closePage",
|
|
2689
3035
|
log: "***** close page *****\n",
|
|
2690
3036
|
throwError: false,
|
|
@@ -2701,8 +3047,95 @@ class StableBrowser {
|
|
|
2701
3047
|
_commandFinally(state, this);
|
|
2702
3048
|
}
|
|
2703
3049
|
}
|
|
3050
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3051
|
+
let operation = null;
|
|
3052
|
+
if (!options || !options.operation) {
|
|
3053
|
+
throw new Error("operation is not defined");
|
|
3054
|
+
}
|
|
3055
|
+
operation = options.operation;
|
|
3056
|
+
// validate operation is one of the supported operations
|
|
3057
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3058
|
+
throw new Error("operation is not supported");
|
|
3059
|
+
}
|
|
3060
|
+
const state = {
|
|
3061
|
+
options,
|
|
3062
|
+
world,
|
|
3063
|
+
locate: false,
|
|
3064
|
+
scroll: false,
|
|
3065
|
+
highlight: false,
|
|
3066
|
+
type: Types.TABLE_OPERATION,
|
|
3067
|
+
text: `Table operation`,
|
|
3068
|
+
_text: `Table ${operation} operation`,
|
|
3069
|
+
operation: operation,
|
|
3070
|
+
log: "***** Table operation *****\n",
|
|
3071
|
+
};
|
|
3072
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3073
|
+
try {
|
|
3074
|
+
await _preCommand(state, this);
|
|
3075
|
+
const start = Date.now();
|
|
3076
|
+
let cellArea = null;
|
|
3077
|
+
while (true) {
|
|
3078
|
+
try {
|
|
3079
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3080
|
+
if (cellArea) {
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
catch (e) {
|
|
3085
|
+
// ignore
|
|
3086
|
+
}
|
|
3087
|
+
if (Date.now() - start > timeout) {
|
|
3088
|
+
throw new Error(`Cell not found in table`);
|
|
3089
|
+
}
|
|
3090
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3091
|
+
}
|
|
3092
|
+
switch (operation) {
|
|
3093
|
+
case "click":
|
|
3094
|
+
if (!options.css) {
|
|
3095
|
+
// will click in the center of the cell
|
|
3096
|
+
let xOffset = 0;
|
|
3097
|
+
let yOffset = 0;
|
|
3098
|
+
if (options.xOffset) {
|
|
3099
|
+
xOffset = options.xOffset;
|
|
3100
|
+
}
|
|
3101
|
+
if (options.yOffset) {
|
|
3102
|
+
yOffset = options.yOffset;
|
|
3103
|
+
}
|
|
3104
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3105
|
+
}
|
|
3106
|
+
else {
|
|
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("click", state.element, options, this, state, _params);
|
|
3113
|
+
}
|
|
3114
|
+
break;
|
|
3115
|
+
case "hover+click":
|
|
3116
|
+
if (!options.css) {
|
|
3117
|
+
throw new Error("css is not defined");
|
|
3118
|
+
}
|
|
3119
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3120
|
+
if (results.length === 0) {
|
|
3121
|
+
throw new Error(`Element not found in cell area`);
|
|
3122
|
+
}
|
|
3123
|
+
state.element = results[0];
|
|
3124
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3125
|
+
break;
|
|
3126
|
+
default:
|
|
3127
|
+
throw new Error("operation is not supported");
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
catch (e) {
|
|
3131
|
+
await _commandError(state, e, this);
|
|
3132
|
+
}
|
|
3133
|
+
finally {
|
|
3134
|
+
_commandFinally(state, this);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
2704
3137
|
saveTestDataAsGlobal(options, world) {
|
|
2705
|
-
const dataFile =
|
|
3138
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2706
3139
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2707
3140
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2708
3141
|
}
|
|
@@ -2732,6 +3165,7 @@ class StableBrowser {
|
|
|
2732
3165
|
_reportToWorld(world, {
|
|
2733
3166
|
type: Types.SET_VIEWPORT,
|
|
2734
3167
|
text: "set viewport size to " + width + "x" + hight,
|
|
3168
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2735
3169
|
screenshotId,
|
|
2736
3170
|
result: error
|
|
2737
3171
|
? {
|
|
@@ -2803,14 +3237,25 @@ class StableBrowser {
|
|
|
2803
3237
|
}
|
|
2804
3238
|
}
|
|
2805
3239
|
async beforeStep(world, step) {
|
|
2806
|
-
this.stepName = step.pickleStep.text;
|
|
2807
|
-
this.logger.info("step: " + this.stepName);
|
|
2808
3240
|
if (this.stepIndex === undefined) {
|
|
2809
3241
|
this.stepIndex = 0;
|
|
2810
3242
|
}
|
|
2811
3243
|
else {
|
|
2812
3244
|
this.stepIndex++;
|
|
2813
3245
|
}
|
|
3246
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3247
|
+
this.stepName = step.pickleStep.text;
|
|
3248
|
+
this.logger.info("step: " + this.stepName);
|
|
3249
|
+
}
|
|
3250
|
+
else if (step && step.text) {
|
|
3251
|
+
this.stepName = step.text;
|
|
3252
|
+
}
|
|
3253
|
+
else {
|
|
3254
|
+
this.stepName = "step " + this.stepIndex;
|
|
3255
|
+
}
|
|
3256
|
+
if (this.context) {
|
|
3257
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3258
|
+
}
|
|
2814
3259
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2815
3260
|
if (this.context.browserObject.context) {
|
|
2816
3261
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2823,6 +3268,41 @@ class StableBrowser {
|
|
|
2823
3268
|
this.saveTestDataAsGlobal({}, world);
|
|
2824
3269
|
}
|
|
2825
3270
|
}
|
|
3271
|
+
if (this.initSnapshotTaken === false) {
|
|
3272
|
+
this.initSnapshotTaken = true;
|
|
3273
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3274
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3275
|
+
if (snapshot) {
|
|
3276
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
async getAriaSnapshot() {
|
|
3282
|
+
try {
|
|
3283
|
+
// find the page url
|
|
3284
|
+
const url = await this.page.url();
|
|
3285
|
+
// extract the path from the url
|
|
3286
|
+
const path = new URL(url).pathname;
|
|
3287
|
+
// get the page title
|
|
3288
|
+
const title = await this.page.title();
|
|
3289
|
+
// go over other frams
|
|
3290
|
+
const frames = this.page.frames();
|
|
3291
|
+
const snapshots = [];
|
|
3292
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3293
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3294
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3295
|
+
content.push(`- frame: ${i}`);
|
|
3296
|
+
const frame = frames[i];
|
|
3297
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3298
|
+
content.push(snapshot);
|
|
3299
|
+
}
|
|
3300
|
+
return content.join("\n");
|
|
3301
|
+
}
|
|
3302
|
+
catch (e) {
|
|
3303
|
+
console.error(e);
|
|
3304
|
+
}
|
|
3305
|
+
return null;
|
|
2826
3306
|
}
|
|
2827
3307
|
async afterStep(world, step) {
|
|
2828
3308
|
this.stepName = null;
|
|
@@ -2833,6 +3313,16 @@ class StableBrowser {
|
|
|
2833
3313
|
});
|
|
2834
3314
|
}
|
|
2835
3315
|
}
|
|
3316
|
+
if (this.context) {
|
|
3317
|
+
this.context.examplesRow = null;
|
|
3318
|
+
}
|
|
3319
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3320
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3321
|
+
if (snapshot) {
|
|
3322
|
+
const obj = {};
|
|
3323
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
2836
3326
|
}
|
|
2837
3327
|
}
|
|
2838
3328
|
function createTimedPromise(promise, label) {
|