automation_model 1.0.549-dev → 1.0.549-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.d.ts +1 -0
- package/lib/api.js +8 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +101 -5
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +85 -39
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +47 -13
- 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/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/init_browser.d.ts +4 -2
- package/lib/init_browser.js +52 -5
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +10 -10
- package/lib/locate_element.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +51 -12
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +57 -17
- package/lib/stable_browser.js +777 -527
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +95 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +16 -1
- package/lib/utils.js +416 -6
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,17 +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 { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, } 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";
|
|
25
|
+
export const Types = {
|
|
23
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
24
28
|
NAVIGATE: "navigate",
|
|
25
29
|
FILL: "fill_element",
|
|
26
30
|
EXECUTE: "execute_page_method",
|
|
@@ -30,6 +34,8 @@ const Types = {
|
|
|
30
34
|
GET_PAGE_STATUS: "get_page_status",
|
|
31
35
|
CLICK_ROW_ACTION: "click_row_action",
|
|
32
36
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
37
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
38
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
33
39
|
ANALYZE_TABLE: "analyze_table",
|
|
34
40
|
SELECT: "select_combobox",
|
|
35
41
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -40,6 +46,7 @@ const Types = {
|
|
|
40
46
|
UNCHECK: "uncheck_element",
|
|
41
47
|
EXTRACT: "extract_attribute",
|
|
42
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
43
50
|
SET_DATE_TIME: "set_date_time",
|
|
44
51
|
SET_VIEWPORT: "set_viewport",
|
|
45
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -47,8 +54,12 @@ const Types = {
|
|
|
47
54
|
SET_INPUT: "set_input",
|
|
48
55
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
49
56
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
57
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
50
58
|
};
|
|
51
59
|
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
62
|
+
};
|
|
52
63
|
class StableBrowser {
|
|
53
64
|
browser;
|
|
54
65
|
page;
|
|
@@ -62,6 +73,7 @@ class StableBrowser {
|
|
|
62
73
|
appName = "main";
|
|
63
74
|
tags = null;
|
|
64
75
|
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
65
77
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
66
78
|
this.browser = browser;
|
|
67
79
|
this.page = page;
|
|
@@ -100,27 +112,9 @@ class StableBrowser {
|
|
|
100
112
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
101
113
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
102
114
|
}
|
|
103
|
-
async scrollPageToLoadLazyElements() {
|
|
104
|
-
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
105
|
-
let retry = 0;
|
|
106
|
-
while (true) {
|
|
107
|
-
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
108
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
109
|
-
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
110
|
-
if (newHeight === lastHeight) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
lastHeight = newHeight;
|
|
114
|
-
retry++;
|
|
115
|
-
if (retry > 10) {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
120
|
-
}
|
|
121
115
|
registerEventListeners(context) {
|
|
122
116
|
this.registerConsoleLogListener(this.page, context);
|
|
123
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
117
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
124
118
|
if (!context.pageLoading) {
|
|
125
119
|
context.pageLoading = { status: false };
|
|
126
120
|
}
|
|
@@ -176,8 +170,8 @@ class StableBrowser {
|
|
|
176
170
|
};
|
|
177
171
|
}
|
|
178
172
|
const tempContext = {};
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
_copyContext(this, tempContext);
|
|
174
|
+
_copyContext(apps[appName], this);
|
|
181
175
|
apps[this.appName] = tempContext;
|
|
182
176
|
this.appName = appName;
|
|
183
177
|
if (newContextCreated) {
|
|
@@ -186,22 +180,6 @@ class StableBrowser {
|
|
|
186
180
|
await this.waitForPageLoad();
|
|
187
181
|
}
|
|
188
182
|
}
|
|
189
|
-
_copyContext(from, to) {
|
|
190
|
-
to.browser = from.browser;
|
|
191
|
-
to.page = from.page;
|
|
192
|
-
to.context = from.context;
|
|
193
|
-
}
|
|
194
|
-
getWebLogFile(logFolder) {
|
|
195
|
-
if (!fs.existsSync(logFolder)) {
|
|
196
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
197
|
-
}
|
|
198
|
-
let nextIndex = 1;
|
|
199
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
200
|
-
nextIndex++;
|
|
201
|
-
}
|
|
202
|
-
const fileName = nextIndex + ".json";
|
|
203
|
-
return path.join(logFolder, fileName);
|
|
204
|
-
}
|
|
205
183
|
registerConsoleLogListener(page, context) {
|
|
206
184
|
if (!this.context.webLogger) {
|
|
207
185
|
this.context.webLogger = [];
|
|
@@ -265,55 +243,51 @@ class StableBrowser {
|
|
|
265
243
|
// async closeUnexpectedPopups() {
|
|
266
244
|
// await closeUnexpectedPopups(this.page);
|
|
267
245
|
// }
|
|
268
|
-
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
|
+
}
|
|
269
250
|
if (!url.startsWith("http")) {
|
|
270
251
|
url = "https://" + url;
|
|
271
252
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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);
|
|
279
272
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// remove the _ prefix
|
|
284
|
-
regValue = key.substring(1);
|
|
285
|
-
}
|
|
286
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error("Error on goto", error);
|
|
275
|
+
_commandError(state, error, this);
|
|
287
276
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
291
|
-
// check if not null
|
|
292
|
-
if (!locator) {
|
|
293
|
-
return locator;
|
|
277
|
+
finally {
|
|
278
|
+
_commandFinally(state, this);
|
|
294
279
|
}
|
|
295
|
-
// clone the locator
|
|
296
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
297
|
-
this.scanAndManipulate(locator, _params);
|
|
298
|
-
return locator;
|
|
299
|
-
}
|
|
300
|
-
_isObject(value) {
|
|
301
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
302
280
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
281
|
+
async _getLocator(locator, scope, _params) {
|
|
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);
|
|
312
289
|
}
|
|
313
290
|
}
|
|
314
|
-
}
|
|
315
|
-
_getLocator(locator, scope, _params) {
|
|
316
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
317
291
|
let locatorReturn;
|
|
318
292
|
if (locator.role) {
|
|
319
293
|
if (locator.role[1].nameReg) {
|
|
@@ -321,7 +295,7 @@ class StableBrowser {
|
|
|
321
295
|
delete locator.role[1].nameReg;
|
|
322
296
|
}
|
|
323
297
|
// if (locator.role[1].name) {
|
|
324
|
-
// locator.role[1].name =
|
|
298
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
325
299
|
// }
|
|
326
300
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
327
301
|
}
|
|
@@ -364,140 +338,60 @@ class StableBrowser {
|
|
|
364
338
|
if (css && css.locator) {
|
|
365
339
|
css = css.locator;
|
|
366
340
|
}
|
|
367
|
-
let result = await this._locateElementByText(scope,
|
|
341
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
368
342
|
if (result.elementCount === 0) {
|
|
369
343
|
return;
|
|
370
344
|
}
|
|
371
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
372
346
|
// css climb to parent element
|
|
373
347
|
const climbArray = [];
|
|
374
348
|
for (let i = 0; i < climb; i++) {
|
|
375
349
|
climbArray.push("..");
|
|
376
350
|
}
|
|
377
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return new RegExp(pattern, flags);
|
|
401
|
-
}
|
|
402
|
-
document.getRegex = getRegex;
|
|
403
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
404
|
-
// Check and add the element if it has a shadow root
|
|
405
|
-
if (element.shadowRoot) {
|
|
406
|
-
result.push(element);
|
|
407
|
-
// Also search within the shadow root
|
|
408
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
409
|
-
}
|
|
410
|
-
// Iterate over child nodes
|
|
411
|
-
element.childNodes.forEach((child) => {
|
|
412
|
-
// Recursively call the function for each child node
|
|
413
|
-
document.collectAllShadowDomElements(child, result);
|
|
414
|
-
});
|
|
415
|
-
return result;
|
|
416
|
-
}
|
|
417
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
418
|
-
if (!tag) {
|
|
419
|
-
tag = "*:not(script, style, head)";
|
|
420
|
-
}
|
|
421
|
-
let regexpSearch = document.getRegex(text);
|
|
422
|
-
if (regexpSearch) {
|
|
423
|
-
regex = true;
|
|
424
|
-
}
|
|
425
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
426
|
-
let shadowHosts = [];
|
|
427
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
428
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
429
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
430
|
-
if (!shadowElement) {
|
|
431
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
435
|
-
elements = elements.concat(shadowElements);
|
|
436
|
-
}
|
|
437
|
-
let randomToken = null;
|
|
438
|
-
const foundElements = [];
|
|
439
|
-
if (regex) {
|
|
440
|
-
if (!regexpSearch) {
|
|
441
|
-
regexpSearch = new RegExp(text, "im");
|
|
442
|
-
}
|
|
443
|
-
for (let i = 0; i < elements.length; i++) {
|
|
444
|
-
const element = elements[i];
|
|
445
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
446
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
447
|
-
foundElements.push(element);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
text = text.trim();
|
|
453
|
-
for (let i = 0; i < elements.length; i++) {
|
|
454
|
-
const element = elements[i];
|
|
455
|
-
if (partial) {
|
|
456
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
457
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
458
|
-
foundElements.push(element);
|
|
459
|
-
}
|
|
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;
|
|
460
374
|
}
|
|
461
|
-
else {
|
|
462
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
463
|
-
(element.value && element.value === text)) {
|
|
464
|
-
foundElements.push(element);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
let noChildElements = [];
|
|
470
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
471
|
-
let element = foundElements[i];
|
|
472
|
-
let hasChild = false;
|
|
473
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
474
|
-
if (i === j) {
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (isParent(element, foundElements[j])) {
|
|
478
|
-
hasChild = true;
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (!hasChild) {
|
|
483
|
-
noChildElements.push(element);
|
|
484
375
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (noChildElements.length > 0) {
|
|
488
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
489
|
-
if (randomToken === null) {
|
|
490
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
491
|
-
}
|
|
492
|
-
let element = noChildElements[i];
|
|
493
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
494
|
-
elementCount++;
|
|
376
|
+
if (!el.setAttribute) {
|
|
377
|
+
el = el.parentElement;
|
|
495
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;
|
|
496
389
|
}
|
|
497
|
-
|
|
498
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
499
393
|
}
|
|
500
|
-
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) {
|
|
501
395
|
if (!info) {
|
|
502
396
|
info = {};
|
|
503
397
|
}
|
|
@@ -506,10 +400,13 @@ class StableBrowser {
|
|
|
506
400
|
}
|
|
507
401
|
if (!info.log) {
|
|
508
402
|
info.log = "";
|
|
403
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
509
404
|
}
|
|
510
405
|
let locatorSearch = selectorHierarchy[index];
|
|
406
|
+
let originalLocatorSearch = "";
|
|
511
407
|
try {
|
|
512
|
-
|
|
408
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
409
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
513
410
|
}
|
|
514
411
|
catch (e) {
|
|
515
412
|
console.error(e);
|
|
@@ -517,30 +414,31 @@ class StableBrowser {
|
|
|
517
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
518
415
|
let locator = null;
|
|
519
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
520
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
521
419
|
if (!locatorString) {
|
|
522
420
|
info.failCause.textNotFound = true;
|
|
523
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
524
422
|
return;
|
|
525
423
|
}
|
|
526
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
527
425
|
}
|
|
528
426
|
else if (locatorSearch.text) {
|
|
529
|
-
let text =
|
|
530
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
427
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
428
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
531
429
|
if (result.elementCount === 0) {
|
|
532
430
|
info.failCause.textNotFound = true;
|
|
533
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
534
432
|
return;
|
|
535
433
|
}
|
|
536
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
537
435
|
if (locatorSearch.childCss) {
|
|
538
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
539
437
|
}
|
|
540
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
541
439
|
}
|
|
542
440
|
else {
|
|
543
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
544
442
|
}
|
|
545
443
|
// let cssHref = false;
|
|
546
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -555,16 +453,25 @@ class StableBrowser {
|
|
|
555
453
|
let visibleLocator = null;
|
|
556
454
|
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
557
455
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
456
|
+
if (info.locatorLog) {
|
|
457
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
458
|
+
}
|
|
558
459
|
return;
|
|
559
460
|
}
|
|
461
|
+
if (info.locatorLog && count === 0) {
|
|
462
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
|
+
}
|
|
560
464
|
for (let j = 0; j < count; j++) {
|
|
561
465
|
let visible = await locator.nth(j).isVisible();
|
|
562
466
|
const enabled = await locator.nth(j).isEnabled();
|
|
563
467
|
if (!visibleOnly) {
|
|
564
468
|
visible = true;
|
|
565
469
|
}
|
|
566
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
567
471
|
foundLocators.push(locator.nth(j));
|
|
472
|
+
if (info.locatorLog) {
|
|
473
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
|
+
}
|
|
568
475
|
}
|
|
569
476
|
else {
|
|
570
477
|
info.failCause.visible = visible;
|
|
@@ -572,8 +479,16 @@ class StableBrowser {
|
|
|
572
479
|
if (!info.printMessages) {
|
|
573
480
|
info.printMessages = {};
|
|
574
481
|
}
|
|
482
|
+
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
484
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
485
|
+
}
|
|
486
|
+
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
488
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
489
|
+
}
|
|
575
490
|
if (!info.printMessages[j.toString()]) {
|
|
576
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
491
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
577
492
|
info.printMessages[j.toString()] = true;
|
|
578
493
|
}
|
|
579
494
|
}
|
|
@@ -589,7 +504,7 @@ class StableBrowser {
|
|
|
589
504
|
if (!info) {
|
|
590
505
|
info = {};
|
|
591
506
|
}
|
|
592
|
-
info.log += "scan for popup handlers" + "\n";
|
|
507
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
593
508
|
const handlerGroup = [];
|
|
594
509
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
595
510
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -637,7 +552,7 @@ class StableBrowser {
|
|
|
637
552
|
}
|
|
638
553
|
return { rerun: false };
|
|
639
554
|
}
|
|
640
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
641
556
|
if (!timeout) {
|
|
642
557
|
timeout = 30000;
|
|
643
558
|
}
|
|
@@ -647,9 +562,18 @@ class StableBrowser {
|
|
|
647
562
|
let selector = selectors.locators[j];
|
|
648
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
649
564
|
}
|
|
650
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
651
566
|
if (!element.rerun) {
|
|
652
|
-
|
|
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;
|
|
653
577
|
}
|
|
654
578
|
}
|
|
655
579
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -691,9 +615,11 @@ class StableBrowser {
|
|
|
691
615
|
}
|
|
692
616
|
return framescope;
|
|
693
617
|
};
|
|
618
|
+
let fLocator = null;
|
|
694
619
|
while (true) {
|
|
695
620
|
let frameFound = false;
|
|
696
621
|
if (selectors.nestFrmLoc) {
|
|
622
|
+
fLocator = selectors.nestFrmLoc;
|
|
697
623
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
698
624
|
frameFound = true;
|
|
699
625
|
break;
|
|
@@ -702,6 +628,7 @@ class StableBrowser {
|
|
|
702
628
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
703
629
|
let frameLocator = selectors.frameLocators[i];
|
|
704
630
|
if (frameLocator.css) {
|
|
631
|
+
fLocator = frameLocator.css;
|
|
705
632
|
scope = scope.frameLocator(frameLocator.css);
|
|
706
633
|
frameFound = true;
|
|
707
634
|
break;
|
|
@@ -709,18 +636,25 @@ class StableBrowser {
|
|
|
709
636
|
}
|
|
710
637
|
}
|
|
711
638
|
if (!frameFound && selectors.iframe_src) {
|
|
639
|
+
fLocator = selectors.iframe_src;
|
|
712
640
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
713
641
|
}
|
|
714
642
|
if (!scope) {
|
|
715
|
-
info
|
|
643
|
+
if (info && info.locatorLog) {
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
645
|
+
}
|
|
646
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
716
647
|
if (Date.now() - startTime > timeout) {
|
|
717
648
|
info.failCause.iframeNotFound = true;
|
|
718
|
-
info.failCause.lastError =
|
|
649
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
719
650
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
720
651
|
}
|
|
721
652
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
722
653
|
}
|
|
723
654
|
else {
|
|
655
|
+
if (info && info.locatorLog) {
|
|
656
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
657
|
+
}
|
|
724
658
|
break;
|
|
725
659
|
}
|
|
726
660
|
}
|
|
@@ -737,11 +671,12 @@ class StableBrowser {
|
|
|
737
671
|
return bodyContent;
|
|
738
672
|
});
|
|
739
673
|
}
|
|
740
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
674
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
741
675
|
if (!info) {
|
|
742
676
|
info = {};
|
|
743
677
|
info.failCause = {};
|
|
744
678
|
info.log = "";
|
|
679
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
745
680
|
}
|
|
746
681
|
let highPriorityTimeout = 5000;
|
|
747
682
|
let visibleOnlyTimeout = 6000;
|
|
@@ -785,17 +720,17 @@ class StableBrowser {
|
|
|
785
720
|
}
|
|
786
721
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
787
722
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
788
|
-
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);
|
|
789
724
|
if (result.foundElements.length === 0) {
|
|
790
725
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
791
|
-
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);
|
|
792
727
|
}
|
|
793
728
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
794
|
-
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);
|
|
795
730
|
}
|
|
796
731
|
else {
|
|
797
732
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
798
|
-
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);
|
|
799
734
|
}
|
|
800
735
|
}
|
|
801
736
|
let foundElements = result.foundElements;
|
|
@@ -840,26 +775,34 @@ class StableBrowser {
|
|
|
840
775
|
break;
|
|
841
776
|
}
|
|
842
777
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
843
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
778
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
844
779
|
highPriorityOnly = false;
|
|
845
780
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
846
781
|
lazy_scroll = true;
|
|
847
|
-
await this.
|
|
782
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
848
783
|
}
|
|
849
784
|
}
|
|
850
785
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
851
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
786
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
852
787
|
visibleOnly = false;
|
|
853
788
|
}
|
|
854
789
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
855
790
|
}
|
|
856
791
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
857
|
-
info.
|
|
792
|
+
// if (info.locatorLog) {
|
|
793
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
794
|
+
// for (let line of lines) {
|
|
795
|
+
// this.logger.debug(line);
|
|
796
|
+
// }
|
|
797
|
+
// }
|
|
798
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
858
799
|
info.failCause.locatorNotFound = true;
|
|
859
|
-
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
|
+
}
|
|
860
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
861
804
|
}
|
|
862
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
863
806
|
let foundElements = [];
|
|
864
807
|
const result = {
|
|
865
808
|
foundElements: foundElements,
|
|
@@ -867,14 +810,15 @@ class StableBrowser {
|
|
|
867
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
868
811
|
let foundLocators = [];
|
|
869
812
|
try {
|
|
870
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
871
814
|
}
|
|
872
815
|
catch (e) {
|
|
873
|
-
this
|
|
874
|
-
this.logger.debug(
|
|
816
|
+
// this call can fail it the browser is navigating
|
|
817
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
818
|
+
// this.logger.debug(e);
|
|
875
819
|
foundLocators = [];
|
|
876
820
|
try {
|
|
877
|
-
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);
|
|
878
822
|
}
|
|
879
823
|
catch (e) {
|
|
880
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -890,6 +834,9 @@ class StableBrowser {
|
|
|
890
834
|
}
|
|
891
835
|
if (foundLocators.length > 1) {
|
|
892
836
|
info.failCause.foundMultiple = true;
|
|
837
|
+
if (info.locatorLog) {
|
|
838
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
839
|
+
}
|
|
893
840
|
}
|
|
894
841
|
}
|
|
895
842
|
return result;
|
|
@@ -999,15 +946,16 @@ class StableBrowser {
|
|
|
999
946
|
options,
|
|
1000
947
|
world,
|
|
1001
948
|
text: "Click element",
|
|
949
|
+
_text: "Click on " + selectors.element_name,
|
|
1002
950
|
type: Types.CLICK,
|
|
1003
951
|
operation: "click",
|
|
1004
952
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1005
953
|
};
|
|
1006
954
|
try {
|
|
1007
955
|
await _preCommand(state, this);
|
|
1008
|
-
if (state.options && state.options.context) {
|
|
1009
|
-
|
|
1010
|
-
}
|
|
956
|
+
// if (state.options && state.options.context) {
|
|
957
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
958
|
+
// }
|
|
1011
959
|
try {
|
|
1012
960
|
await state.element.click();
|
|
1013
961
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1028,6 +976,38 @@ class StableBrowser {
|
|
|
1028
976
|
_commandFinally(state, this);
|
|
1029
977
|
}
|
|
1030
978
|
}
|
|
979
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
980
|
+
const timeout = this._getFindElementTimeout(options);
|
|
981
|
+
const state = {
|
|
982
|
+
selectors,
|
|
983
|
+
_params,
|
|
984
|
+
options,
|
|
985
|
+
world,
|
|
986
|
+
text: "Wait for element",
|
|
987
|
+
_text: "Wait for " + selectors.element_name,
|
|
988
|
+
type: Types.WAIT_ELEMENT,
|
|
989
|
+
operation: "waitForElement",
|
|
990
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
991
|
+
};
|
|
992
|
+
let found = false;
|
|
993
|
+
try {
|
|
994
|
+
await _preCommand(state, this);
|
|
995
|
+
// if (state.options && state.options.context) {
|
|
996
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
997
|
+
// }
|
|
998
|
+
await state.element.waitFor({ timeout: timeout });
|
|
999
|
+
found = true;
|
|
1000
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1001
|
+
}
|
|
1002
|
+
catch (e) {
|
|
1003
|
+
console.error("Error on waitForElement", e);
|
|
1004
|
+
// await _commandError(state, e, this);
|
|
1005
|
+
}
|
|
1006
|
+
finally {
|
|
1007
|
+
_commandFinally(state, this);
|
|
1008
|
+
}
|
|
1009
|
+
return found;
|
|
1010
|
+
}
|
|
1031
1011
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
1032
1012
|
const state = {
|
|
1033
1013
|
selectors,
|
|
@@ -1036,6 +1016,7 @@ class StableBrowser {
|
|
|
1036
1016
|
world,
|
|
1037
1017
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1038
1018
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1019
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1039
1020
|
operation: "setCheck",
|
|
1040
1021
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1041
1022
|
};
|
|
@@ -1045,9 +1026,15 @@ class StableBrowser {
|
|
|
1045
1026
|
// let element = await this._locate(selectors, info, _params);
|
|
1046
1027
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1047
1028
|
try {
|
|
1048
|
-
//
|
|
1029
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1030
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1031
|
+
await this._highlightElements(element);
|
|
1049
1032
|
await state.element.setChecked(checked);
|
|
1050
1033
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1034
|
+
// await this._unHighlightElements(element);
|
|
1035
|
+
// }
|
|
1036
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1037
|
+
// await this._unHighlightElements(element);
|
|
1051
1038
|
}
|
|
1052
1039
|
catch (e) {
|
|
1053
1040
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1079,6 +1066,7 @@ class StableBrowser {
|
|
|
1079
1066
|
world,
|
|
1080
1067
|
type: Types.HOVER,
|
|
1081
1068
|
text: `Hover element`,
|
|
1069
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1082
1070
|
operation: "hover",
|
|
1083
1071
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1084
1072
|
};
|
|
@@ -1086,6 +1074,7 @@ class StableBrowser {
|
|
|
1086
1074
|
await _preCommand(state, this);
|
|
1087
1075
|
try {
|
|
1088
1076
|
await state.element.hover();
|
|
1077
|
+
// await _screenshot(state, this);
|
|
1089
1078
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1090
1079
|
}
|
|
1091
1080
|
catch (e) {
|
|
@@ -1093,6 +1082,7 @@ class StableBrowser {
|
|
|
1093
1082
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1094
1083
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1095
1084
|
await state.element.hover({ timeout: 10000 });
|
|
1085
|
+
// await _screenshot(state, this);
|
|
1096
1086
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1097
1087
|
}
|
|
1098
1088
|
await _screenshot(state, this);
|
|
@@ -1118,6 +1108,7 @@ class StableBrowser {
|
|
|
1118
1108
|
value: values.toString(),
|
|
1119
1109
|
type: Types.SELECT,
|
|
1120
1110
|
text: `Select option: ${values}`,
|
|
1111
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1121
1112
|
operation: "selectOption",
|
|
1122
1113
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1123
1114
|
};
|
|
@@ -1152,6 +1143,7 @@ class StableBrowser {
|
|
|
1152
1143
|
highlight: false,
|
|
1153
1144
|
type: Types.TYPE_PRESS,
|
|
1154
1145
|
text: `Type value: ${_value}`,
|
|
1146
|
+
_text: `Type value: ${_value}`,
|
|
1155
1147
|
operation: "type",
|
|
1156
1148
|
log: "",
|
|
1157
1149
|
};
|
|
@@ -1231,6 +1223,7 @@ class StableBrowser {
|
|
|
1231
1223
|
world,
|
|
1232
1224
|
type: Types.SET_DATE_TIME,
|
|
1233
1225
|
text: `Set date time value: ${value}`,
|
|
1226
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1234
1227
|
operation: "setDateTime",
|
|
1235
1228
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1236
1229
|
throwError: false,
|
|
@@ -1302,6 +1295,7 @@ class StableBrowser {
|
|
|
1302
1295
|
world,
|
|
1303
1296
|
type: Types.FILL,
|
|
1304
1297
|
text: `Click type input with value: ${_value}`,
|
|
1298
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1305
1299
|
operation: "clickType",
|
|
1306
1300
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1307
1301
|
};
|
|
@@ -1367,7 +1361,12 @@ class StableBrowser {
|
|
|
1367
1361
|
await this.waitForPageLoad();
|
|
1368
1362
|
}
|
|
1369
1363
|
else if (enter === false) {
|
|
1370
|
-
|
|
1364
|
+
try {
|
|
1365
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1366
|
+
}
|
|
1367
|
+
catch (e) {
|
|
1368
|
+
// ignore
|
|
1369
|
+
}
|
|
1371
1370
|
//await this.page.keyboard.press("Tab");
|
|
1372
1371
|
}
|
|
1373
1372
|
else {
|
|
@@ -1419,15 +1418,17 @@ class StableBrowser {
|
|
|
1419
1418
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1420
1419
|
}
|
|
1421
1420
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1421
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1422
1422
|
_validateSelectors(selectors);
|
|
1423
1423
|
let screenshotId = null;
|
|
1424
1424
|
let screenshotPath = null;
|
|
1425
1425
|
if (!info.log) {
|
|
1426
1426
|
info.log = "";
|
|
1427
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1427
1428
|
}
|
|
1428
1429
|
info.operation = "getText";
|
|
1429
1430
|
info.selectors = selectors;
|
|
1430
|
-
let element = await this._locate(selectors, info, _params);
|
|
1431
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1431
1432
|
if (climb > 0) {
|
|
1432
1433
|
const climbArray = [];
|
|
1433
1434
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1446,6 +1447,18 @@ class StableBrowser {
|
|
|
1446
1447
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1447
1448
|
try {
|
|
1448
1449
|
await this._highlightElements(element);
|
|
1450
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1451
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1452
|
+
// this._highlightElements(element)
|
|
1453
|
+
// .then(async () => {
|
|
1454
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1455
|
+
// this._unhighlightElements(element).then(
|
|
1456
|
+
// () => {}
|
|
1457
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1458
|
+
// );
|
|
1459
|
+
// })
|
|
1460
|
+
// .catch(e);
|
|
1461
|
+
// }
|
|
1449
1462
|
const elementText = await element.innerText();
|
|
1450
1463
|
return {
|
|
1451
1464
|
text: elementText,
|
|
@@ -1457,7 +1470,7 @@ class StableBrowser {
|
|
|
1457
1470
|
}
|
|
1458
1471
|
catch (e) {
|
|
1459
1472
|
//await this.closeUnexpectedPopups();
|
|
1460
|
-
this.logger.info("no innerText will use textContent");
|
|
1473
|
+
this.logger.info("no innerText, will use textContent");
|
|
1461
1474
|
const elementText = await element.textContent();
|
|
1462
1475
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1463
1476
|
}
|
|
@@ -1482,6 +1495,7 @@ class StableBrowser {
|
|
|
1482
1495
|
highlight: false,
|
|
1483
1496
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1484
1497
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1498
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1485
1499
|
operation: "containsPattern",
|
|
1486
1500
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1487
1501
|
};
|
|
@@ -1517,6 +1531,8 @@ class StableBrowser {
|
|
|
1517
1531
|
}
|
|
1518
1532
|
}
|
|
1519
1533
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1534
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1535
|
+
const startTime = Date.now();
|
|
1520
1536
|
const state = {
|
|
1521
1537
|
selectors,
|
|
1522
1538
|
_params,
|
|
@@ -1543,62 +1559,54 @@ class StableBrowser {
|
|
|
1543
1559
|
}
|
|
1544
1560
|
let foundObj = null;
|
|
1545
1561
|
try {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1553
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1554
|
-
if (dateAlternatives.date) {
|
|
1555
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1556
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1557
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1558
|
-
return state.info;
|
|
1562
|
+
while (Date.now() - startTime < timeout) {
|
|
1563
|
+
try {
|
|
1564
|
+
await _preCommand(state, this);
|
|
1565
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1566
|
+
if (foundObj && foundObj.element) {
|
|
1567
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1559
1568
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1569
|
+
await _screenshot(state, this);
|
|
1570
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1571
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1572
|
+
if (dateAlternatives.date) {
|
|
1573
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1574
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1575
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1576
|
+
return state.info;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
else if (numberAlternatives.number) {
|
|
1581
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1582
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1583
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1584
|
+
return state.info;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1567
1589
|
return state.info;
|
|
1568
1590
|
}
|
|
1569
1591
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
throw new Error("element doesn't contain text " + text);
|
|
1592
|
+
catch (e) {
|
|
1593
|
+
// Log error but continue retrying until timeout is reached
|
|
1594
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1595
|
+
}
|
|
1596
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1576
1597
|
}
|
|
1577
|
-
|
|
1598
|
+
state.info.foundText = foundObj?.text;
|
|
1599
|
+
state.info.value = foundObj?.value;
|
|
1600
|
+
throw new Error("element doesn't contain text " + text);
|
|
1578
1601
|
}
|
|
1579
1602
|
catch (e) {
|
|
1580
1603
|
await _commandError(state, e, this);
|
|
1604
|
+
throw e;
|
|
1581
1605
|
}
|
|
1582
1606
|
finally {
|
|
1583
1607
|
_commandFinally(state, this);
|
|
1584
1608
|
}
|
|
1585
1609
|
}
|
|
1586
|
-
_getDataFile(world = null) {
|
|
1587
|
-
let dataFile = null;
|
|
1588
|
-
if (world && world.reportFolder) {
|
|
1589
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1590
|
-
}
|
|
1591
|
-
else if (this.reportFolder) {
|
|
1592
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1593
|
-
}
|
|
1594
|
-
else if (this.context && this.context.reportFolder) {
|
|
1595
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1596
|
-
}
|
|
1597
|
-
else {
|
|
1598
|
-
dataFile = "data.json";
|
|
1599
|
-
}
|
|
1600
|
-
return dataFile;
|
|
1601
|
-
}
|
|
1602
1610
|
async waitForUserInput(message, world = null) {
|
|
1603
1611
|
if (!message) {
|
|
1604
1612
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1627,7 +1635,7 @@ class StableBrowser {
|
|
|
1627
1635
|
return;
|
|
1628
1636
|
}
|
|
1629
1637
|
// if data file exists, load it
|
|
1630
|
-
const dataFile =
|
|
1638
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1631
1639
|
let data = this.getTestData(world);
|
|
1632
1640
|
// merge the testData with the existing data
|
|
1633
1641
|
Object.assign(data, testData);
|
|
@@ -1730,7 +1738,7 @@ class StableBrowser {
|
|
|
1730
1738
|
}
|
|
1731
1739
|
}
|
|
1732
1740
|
getTestData(world = null) {
|
|
1733
|
-
const dataFile =
|
|
1741
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1734
1742
|
let data = {};
|
|
1735
1743
|
if (fs.existsSync(dataFile)) {
|
|
1736
1744
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1817,6 +1825,15 @@ class StableBrowser {
|
|
|
1817
1825
|
document.documentElement.clientWidth,
|
|
1818
1826
|
])));
|
|
1819
1827
|
let screenshotBuffer = null;
|
|
1828
|
+
// if (focusedElement) {
|
|
1829
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1830
|
+
// await this._unhighlightElements(focusedElement);
|
|
1831
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1832
|
+
// console.log(`Unhighlighted previous element`);
|
|
1833
|
+
// }
|
|
1834
|
+
// if (focusedElement) {
|
|
1835
|
+
// await this._highlightElements(focusedElement);
|
|
1836
|
+
// }
|
|
1820
1837
|
if (this.context.browserName === "chromium") {
|
|
1821
1838
|
const client = await playContext.newCDPSession(this.page);
|
|
1822
1839
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1838,6 +1855,10 @@ class StableBrowser {
|
|
|
1838
1855
|
else {
|
|
1839
1856
|
screenshotBuffer = await this.page.screenshot();
|
|
1840
1857
|
}
|
|
1858
|
+
// if (focusedElement) {
|
|
1859
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1860
|
+
// await this._unhighlightElements(focusedElement);
|
|
1861
|
+
// }
|
|
1841
1862
|
let image = await Jimp.read(screenshotBuffer);
|
|
1842
1863
|
// Get the image dimensions
|
|
1843
1864
|
const { width, height } = image.bitmap;
|
|
@@ -1850,6 +1871,7 @@ class StableBrowser {
|
|
|
1850
1871
|
else {
|
|
1851
1872
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1852
1873
|
}
|
|
1874
|
+
return screenshotBuffer;
|
|
1853
1875
|
}
|
|
1854
1876
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1855
1877
|
const state = {
|
|
@@ -1885,8 +1907,10 @@ class StableBrowser {
|
|
|
1885
1907
|
world,
|
|
1886
1908
|
type: Types.EXTRACT,
|
|
1887
1909
|
text: `Extract attribute from element`,
|
|
1910
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1888
1911
|
operation: "extractAttribute",
|
|
1889
1912
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1913
|
+
allowDisabled: true,
|
|
1890
1914
|
};
|
|
1891
1915
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1892
1916
|
try {
|
|
@@ -1908,6 +1932,7 @@ class StableBrowser {
|
|
|
1908
1932
|
state.info.value = state.value;
|
|
1909
1933
|
this.setTestData({ [variable]: state.value }, world);
|
|
1910
1934
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1935
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1911
1936
|
return state.info;
|
|
1912
1937
|
}
|
|
1913
1938
|
catch (e) {
|
|
@@ -1926,14 +1951,21 @@ class StableBrowser {
|
|
|
1926
1951
|
options,
|
|
1927
1952
|
world,
|
|
1928
1953
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1954
|
+
highlight: true,
|
|
1955
|
+
screenshot: true,
|
|
1929
1956
|
text: `Verify element attribute`,
|
|
1957
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1930
1958
|
operation: "verifyAttribute",
|
|
1931
1959
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1960
|
+
allowDisabled: true,
|
|
1932
1961
|
};
|
|
1933
1962
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1934
1963
|
let val;
|
|
1964
|
+
let expectedValue;
|
|
1935
1965
|
try {
|
|
1936
1966
|
await _preCommand(state, this);
|
|
1967
|
+
expectedValue = state.value;
|
|
1968
|
+
state.info.expectedValue = expectedValue;
|
|
1937
1969
|
switch (attribute) {
|
|
1938
1970
|
case "innerText":
|
|
1939
1971
|
val = String(await state.element.innerText());
|
|
@@ -1947,23 +1979,30 @@ class StableBrowser {
|
|
|
1947
1979
|
case "disabled":
|
|
1948
1980
|
val = String(await state.element.isDisabled());
|
|
1949
1981
|
break;
|
|
1982
|
+
case "readOnly":
|
|
1983
|
+
const isEditable = await state.element.isEditable();
|
|
1984
|
+
val = String(!isEditable);
|
|
1985
|
+
break;
|
|
1950
1986
|
default:
|
|
1951
1987
|
val = String(await state.element.getAttribute(attribute));
|
|
1952
1988
|
break;
|
|
1953
1989
|
}
|
|
1990
|
+
state.info.value = val;
|
|
1954
1991
|
let regex;
|
|
1955
|
-
if (
|
|
1956
|
-
const patternBody =
|
|
1992
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1993
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1957
1994
|
regex = new RegExp(patternBody, "g");
|
|
1958
1995
|
}
|
|
1959
1996
|
else {
|
|
1960
|
-
const escapedPattern =
|
|
1997
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1961
1998
|
regex = new RegExp(escapedPattern, "g");
|
|
1962
1999
|
}
|
|
1963
2000
|
if (!val.match(regex)) {
|
|
1964
|
-
|
|
2001
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2002
|
+
state.info.failCause.assertionFailed = true;
|
|
2003
|
+
state.info.failCause.lastError = errorMessage;
|
|
2004
|
+
throw new Error(errorMessage);
|
|
1965
2005
|
}
|
|
1966
|
-
state.info.value = val;
|
|
1967
2006
|
return state.info;
|
|
1968
2007
|
}
|
|
1969
2008
|
catch (e) {
|
|
@@ -1991,7 +2030,7 @@ class StableBrowser {
|
|
|
1991
2030
|
if (options && options.timeout) {
|
|
1992
2031
|
timeout = options.timeout;
|
|
1993
2032
|
}
|
|
1994
|
-
const serviceUrl =
|
|
2033
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1995
2034
|
const request = {
|
|
1996
2035
|
method: "POST",
|
|
1997
2036
|
url: serviceUrl,
|
|
@@ -2062,27 +2101,32 @@ class StableBrowser {
|
|
|
2062
2101
|
async _highlightElements(scope, css) {
|
|
2063
2102
|
try {
|
|
2064
2103
|
if (!scope) {
|
|
2104
|
+
// console.log(`Scope is not defined`);
|
|
2065
2105
|
return;
|
|
2066
2106
|
}
|
|
2067
2107
|
if (!css) {
|
|
2068
2108
|
scope
|
|
2069
2109
|
.evaluate((node) => {
|
|
2070
2110
|
if (node && node.style) {
|
|
2071
|
-
let
|
|
2072
|
-
|
|
2111
|
+
let originalOutline = node.style.outline;
|
|
2112
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2113
|
+
// node.__previousOutline = originalOutline;
|
|
2114
|
+
node.style.outline = "2px solid red";
|
|
2115
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2073
2116
|
if (window) {
|
|
2074
2117
|
window.addEventListener("beforeunload", function (e) {
|
|
2075
|
-
node.style.
|
|
2118
|
+
node.style.outline = originalOutline;
|
|
2076
2119
|
});
|
|
2077
2120
|
}
|
|
2078
2121
|
setTimeout(function () {
|
|
2079
|
-
node.style.
|
|
2122
|
+
node.style.outline = originalOutline;
|
|
2080
2123
|
}, 2000);
|
|
2081
2124
|
}
|
|
2082
2125
|
})
|
|
2083
2126
|
.then(() => { })
|
|
2084
2127
|
.catch((e) => {
|
|
2085
2128
|
// ignore
|
|
2129
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2086
2130
|
});
|
|
2087
2131
|
}
|
|
2088
2132
|
else {
|
|
@@ -2098,17 +2142,18 @@ class StableBrowser {
|
|
|
2098
2142
|
if (!element.style) {
|
|
2099
2143
|
return;
|
|
2100
2144
|
}
|
|
2101
|
-
|
|
2145
|
+
let originalOutline = element.style.outline;
|
|
2146
|
+
element.__previousOutline = originalOutline;
|
|
2102
2147
|
// Set the new border to be red and 2px solid
|
|
2103
|
-
element.style.
|
|
2148
|
+
element.style.outline = "2px solid red";
|
|
2104
2149
|
if (window) {
|
|
2105
2150
|
window.addEventListener("beforeunload", function (e) {
|
|
2106
|
-
element.style.
|
|
2151
|
+
element.style.outline = originalOutline;
|
|
2107
2152
|
});
|
|
2108
2153
|
}
|
|
2109
2154
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2110
2155
|
setTimeout(function () {
|
|
2111
|
-
element.style.
|
|
2156
|
+
element.style.outline = originalOutline;
|
|
2112
2157
|
}, 2000);
|
|
2113
2158
|
}
|
|
2114
2159
|
return;
|
|
@@ -2116,6 +2161,7 @@ class StableBrowser {
|
|
|
2116
2161
|
.then(() => { })
|
|
2117
2162
|
.catch((e) => {
|
|
2118
2163
|
// ignore
|
|
2164
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2119
2165
|
});
|
|
2120
2166
|
}
|
|
2121
2167
|
}
|
|
@@ -2123,6 +2169,54 @@ class StableBrowser {
|
|
|
2123
2169
|
console.debug(error);
|
|
2124
2170
|
}
|
|
2125
2171
|
}
|
|
2172
|
+
// async _unhighlightElements(scope, css) {
|
|
2173
|
+
// try {
|
|
2174
|
+
// if (!scope) {
|
|
2175
|
+
// return;
|
|
2176
|
+
// }
|
|
2177
|
+
// if (!css) {
|
|
2178
|
+
// scope
|
|
2179
|
+
// .evaluate((node) => {
|
|
2180
|
+
// if (node && node.style) {
|
|
2181
|
+
// if (!node.__previousOutline) {
|
|
2182
|
+
// node.style.outline = "";
|
|
2183
|
+
// } else {
|
|
2184
|
+
// node.style.outline = node.__previousOutline;
|
|
2185
|
+
// }
|
|
2186
|
+
// }
|
|
2187
|
+
// })
|
|
2188
|
+
// .then(() => {})
|
|
2189
|
+
// .catch((e) => {
|
|
2190
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2191
|
+
// });
|
|
2192
|
+
// } else {
|
|
2193
|
+
// scope
|
|
2194
|
+
// .evaluate(([css]) => {
|
|
2195
|
+
// if (!css) {
|
|
2196
|
+
// return;
|
|
2197
|
+
// }
|
|
2198
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2199
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2200
|
+
// let element = elements[i];
|
|
2201
|
+
// if (!element.style) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// if (!element.__previousOutline) {
|
|
2205
|
+
// element.style.outline = "";
|
|
2206
|
+
// } else {
|
|
2207
|
+
// element.style.outline = element.__previousOutline;
|
|
2208
|
+
// }
|
|
2209
|
+
// }
|
|
2210
|
+
// })
|
|
2211
|
+
// .then(() => {})
|
|
2212
|
+
// .catch((e) => {
|
|
2213
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2214
|
+
// });
|
|
2215
|
+
// }
|
|
2216
|
+
// } catch (error) {
|
|
2217
|
+
// // console.debug(error);
|
|
2218
|
+
// }
|
|
2219
|
+
// }
|
|
2126
2220
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2127
2221
|
const startTime = Date.now();
|
|
2128
2222
|
let error = null;
|
|
@@ -2167,6 +2261,7 @@ class StableBrowser {
|
|
|
2167
2261
|
_reportToWorld(world, {
|
|
2168
2262
|
type: Types.VERIFY_PAGE_PATH,
|
|
2169
2263
|
text: "Verify page path",
|
|
2264
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2170
2265
|
screenshotId,
|
|
2171
2266
|
result: error
|
|
2172
2267
|
? {
|
|
@@ -2184,6 +2279,35 @@ class StableBrowser {
|
|
|
2184
2279
|
});
|
|
2185
2280
|
}
|
|
2186
2281
|
}
|
|
2282
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2283
|
+
const frames = this.page.frames();
|
|
2284
|
+
let results = [];
|
|
2285
|
+
let ignoreCase = false;
|
|
2286
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2287
|
+
if (dateAlternatives.date) {
|
|
2288
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2289
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2290
|
+
result.frame = frames[i];
|
|
2291
|
+
results.push(result);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
else if (numberAlternatives.number) {
|
|
2295
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2296
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2297
|
+
result.frame = frames[i];
|
|
2298
|
+
results.push(result);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
else {
|
|
2302
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2303
|
+
result.frame = frames[i];
|
|
2304
|
+
results.push(result);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
state.info.results = results;
|
|
2308
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2309
|
+
return resultWithElementsFound;
|
|
2310
|
+
}
|
|
2187
2311
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2188
2312
|
text = unEscapeString(text);
|
|
2189
2313
|
const state = {
|
|
@@ -2193,12 +2317,16 @@ class StableBrowser {
|
|
|
2193
2317
|
locate: false,
|
|
2194
2318
|
scroll: false,
|
|
2195
2319
|
highlight: false,
|
|
2196
|
-
type: Types.
|
|
2320
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2197
2321
|
text: `Verify text exists in page`,
|
|
2322
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2198
2323
|
operation: "verifyTextExistInPage",
|
|
2199
2324
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2200
2325
|
};
|
|
2201
|
-
|
|
2326
|
+
if (testForRegex(text)) {
|
|
2327
|
+
text = text.replace(/\\"/g, '"');
|
|
2328
|
+
}
|
|
2329
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2202
2330
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2203
2331
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2204
2332
|
if (newValue !== text) {
|
|
@@ -2211,31 +2339,15 @@ class StableBrowser {
|
|
|
2211
2339
|
await _preCommand(state, this);
|
|
2212
2340
|
state.info.text = text;
|
|
2213
2341
|
while (true) {
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
else if (numberAlternatives.number) {
|
|
2225
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2226
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2227
|
-
result.frame = frames[i];
|
|
2228
|
-
results.push(result);
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
else {
|
|
2232
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2233
|
-
result.frame = frames[i];
|
|
2234
|
-
results.push(result);
|
|
2235
|
-
}
|
|
2342
|
+
let resultWithElementsFound = {
|
|
2343
|
+
length: 0,
|
|
2344
|
+
};
|
|
2345
|
+
try {
|
|
2346
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
// ignore
|
|
2236
2350
|
}
|
|
2237
|
-
state.info.results = results;
|
|
2238
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2239
2351
|
if (resultWithElementsFound.length === 0) {
|
|
2240
2352
|
if (Date.now() - state.startTime > timeout) {
|
|
2241
2353
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2243,18 +2355,40 @@ class StableBrowser {
|
|
|
2243
2355
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2244
2356
|
continue;
|
|
2245
2357
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2358
|
+
try {
|
|
2359
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2360
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2361
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2362
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2363
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2364
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2365
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2366
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2367
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2368
|
+
// .then(async () => {
|
|
2369
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2370
|
+
// })
|
|
2371
|
+
// .catch(
|
|
2372
|
+
// (e) => {}
|
|
2373
|
+
// console.error(e)
|
|
2374
|
+
// );
|
|
2375
|
+
// });
|
|
2376
|
+
// }
|
|
2377
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2378
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2379
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2380
|
+
if (element) {
|
|
2381
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2382
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2383
|
+
// await _screenshot(state, this, element);
|
|
2384
|
+
}
|
|
2254
2385
|
}
|
|
2386
|
+
await _screenshot(state, this);
|
|
2387
|
+
return state.info;
|
|
2388
|
+
}
|
|
2389
|
+
catch (error) {
|
|
2390
|
+
console.error(error);
|
|
2255
2391
|
}
|
|
2256
|
-
await _screenshot(state, this);
|
|
2257
|
-
return state.info;
|
|
2258
2392
|
}
|
|
2259
2393
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2260
2394
|
}
|
|
@@ -2276,10 +2410,14 @@ class StableBrowser {
|
|
|
2276
2410
|
highlight: false,
|
|
2277
2411
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2278
2412
|
text: `Verify text does not exist in page`,
|
|
2413
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2279
2414
|
operation: "verifyTextNotExistInPage",
|
|
2280
2415
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2281
2416
|
};
|
|
2282
|
-
|
|
2417
|
+
if (testForRegex(text)) {
|
|
2418
|
+
text = text.replace(/\\"/g, '"');
|
|
2419
|
+
}
|
|
2420
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2283
2421
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2284
2422
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2285
2423
|
if (newValue !== text) {
|
|
@@ -2291,32 +2429,16 @@ class StableBrowser {
|
|
|
2291
2429
|
try {
|
|
2292
2430
|
await _preCommand(state, this);
|
|
2293
2431
|
state.info.text = text;
|
|
2432
|
+
let resultWithElementsFound = {
|
|
2433
|
+
length: null, // initial cannot be 0
|
|
2434
|
+
};
|
|
2294
2435
|
while (true) {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2301
|
-
result.frame = frames[i];
|
|
2302
|
-
results.push(result);
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
else if (numberAlternatives.number) {
|
|
2306
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2307
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2308
|
-
result.frame = frames[i];
|
|
2309
|
-
results.push(result);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
else {
|
|
2313
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2314
|
-
result.frame = frames[i];
|
|
2315
|
-
results.push(result);
|
|
2316
|
-
}
|
|
2436
|
+
try {
|
|
2437
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2438
|
+
}
|
|
2439
|
+
catch (error) {
|
|
2440
|
+
// ignore
|
|
2317
2441
|
}
|
|
2318
|
-
state.info.results = results;
|
|
2319
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2320
2442
|
if (resultWithElementsFound.length === 0) {
|
|
2321
2443
|
await _screenshot(state, this);
|
|
2322
2444
|
return state.info;
|
|
@@ -2334,15 +2456,116 @@ class StableBrowser {
|
|
|
2334
2456
|
_commandFinally(state, this);
|
|
2335
2457
|
}
|
|
2336
2458
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2459
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2460
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2461
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2462
|
+
const state = {
|
|
2463
|
+
text_search: textToVerify,
|
|
2464
|
+
options,
|
|
2465
|
+
world,
|
|
2466
|
+
locate: false,
|
|
2467
|
+
scroll: false,
|
|
2468
|
+
highlight: false,
|
|
2469
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2470
|
+
text: `Verify text with relation to another text`,
|
|
2471
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2472
|
+
operation: "verify_text_with_relation",
|
|
2473
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2474
|
+
};
|
|
2475
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2476
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2477
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2478
|
+
if (newValue !== textAnchor) {
|
|
2479
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2480
|
+
textAnchor = newValue;
|
|
2481
|
+
}
|
|
2482
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2483
|
+
if (newValue !== textToVerify) {
|
|
2484
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2485
|
+
textToVerify = newValue;
|
|
2486
|
+
}
|
|
2487
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2488
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2489
|
+
let foundAncore = false;
|
|
2490
|
+
try {
|
|
2491
|
+
await _preCommand(state, this);
|
|
2492
|
+
state.info.text = textToVerify;
|
|
2493
|
+
let resultWithElementsFound = {
|
|
2494
|
+
length: 0,
|
|
2495
|
+
};
|
|
2496
|
+
while (true) {
|
|
2497
|
+
try {
|
|
2498
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2499
|
+
}
|
|
2500
|
+
catch (error) {
|
|
2501
|
+
// ignore
|
|
2502
|
+
}
|
|
2503
|
+
if (resultWithElementsFound.length === 0) {
|
|
2504
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2505
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2506
|
+
}
|
|
2507
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
try {
|
|
2511
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2512
|
+
foundAncore = true;
|
|
2513
|
+
const result = resultWithElementsFound[i];
|
|
2514
|
+
const token = result.randomToken;
|
|
2515
|
+
const frame = result.frame;
|
|
2516
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2517
|
+
const climbArray1 = [];
|
|
2518
|
+
for (let i = 0; i < climb; i++) {
|
|
2519
|
+
climbArray1.push("..");
|
|
2520
|
+
}
|
|
2521
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2522
|
+
css = css + " >> " + climbXpath;
|
|
2523
|
+
const count = await frame.locator(css).count();
|
|
2524
|
+
for (let j = 0; j < count; j++) {
|
|
2525
|
+
const continer = await frame.locator(css).nth(j);
|
|
2526
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2527
|
+
if (result.elementCount > 0) {
|
|
2528
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2529
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2530
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2531
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2532
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2533
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2534
|
+
// .then(async () => {
|
|
2535
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2536
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2537
|
+
// () => {}
|
|
2538
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2539
|
+
// );
|
|
2540
|
+
// })
|
|
2541
|
+
// .catch(e);
|
|
2542
|
+
// }
|
|
2543
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2544
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2545
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2546
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2547
|
+
if (element) {
|
|
2548
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2549
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2550
|
+
}
|
|
2551
|
+
await _screenshot(state, this);
|
|
2552
|
+
return state.info;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (error) {
|
|
2558
|
+
console.error(error);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2562
|
+
}
|
|
2563
|
+
catch (e) {
|
|
2564
|
+
await _commandError(state, e, this);
|
|
2341
2565
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2566
|
+
finally {
|
|
2567
|
+
_commandFinally(state, this);
|
|
2344
2568
|
}
|
|
2345
|
-
return serviceUrl;
|
|
2346
2569
|
}
|
|
2347
2570
|
async visualVerification(text, options = {}, world = null) {
|
|
2348
2571
|
const startTime = Date.now();
|
|
@@ -2358,14 +2581,17 @@ class StableBrowser {
|
|
|
2358
2581
|
throw new Error("TOKEN is not set");
|
|
2359
2582
|
}
|
|
2360
2583
|
try {
|
|
2361
|
-
let serviceUrl =
|
|
2584
|
+
let serviceUrl = _getServerUrl();
|
|
2362
2585
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2363
2586
|
info.screenshotPath = screenshotPath;
|
|
2364
2587
|
const screenshot = await this.takeScreenshot();
|
|
2365
|
-
|
|
2366
|
-
method: "
|
|
2588
|
+
let request = {
|
|
2589
|
+
method: "post",
|
|
2590
|
+
maxBodyLength: Infinity,
|
|
2367
2591
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2368
2592
|
headers: {
|
|
2593
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2594
|
+
"x-source": "aaa",
|
|
2369
2595
|
"Content-Type": "application/json",
|
|
2370
2596
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2371
2597
|
},
|
|
@@ -2374,7 +2600,7 @@ class StableBrowser {
|
|
|
2374
2600
|
screenshot: screenshot,
|
|
2375
2601
|
}),
|
|
2376
2602
|
};
|
|
2377
|
-
|
|
2603
|
+
const result = await axios.request(request);
|
|
2378
2604
|
if (result.data.status !== true) {
|
|
2379
2605
|
throw new Error("Visual validation failed");
|
|
2380
2606
|
}
|
|
@@ -2402,6 +2628,7 @@ class StableBrowser {
|
|
|
2402
2628
|
_reportToWorld(world, {
|
|
2403
2629
|
type: Types.VERIFY_VISUAL,
|
|
2404
2630
|
text: "Visual verification",
|
|
2631
|
+
_text: "Visual verification of " + text,
|
|
2405
2632
|
screenshotId,
|
|
2406
2633
|
result: error
|
|
2407
2634
|
? {
|
|
@@ -2447,6 +2674,7 @@ class StableBrowser {
|
|
|
2447
2674
|
let screenshotPath = null;
|
|
2448
2675
|
const info = {};
|
|
2449
2676
|
info.log = "";
|
|
2677
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2450
2678
|
info.operation = "getTableData";
|
|
2451
2679
|
info.selectors = selectors;
|
|
2452
2680
|
try {
|
|
@@ -2522,7 +2750,7 @@ class StableBrowser {
|
|
|
2522
2750
|
info.operation = "analyzeTable";
|
|
2523
2751
|
info.selectors = selectors;
|
|
2524
2752
|
info.query = query;
|
|
2525
|
-
query =
|
|
2753
|
+
query = _fixUsingParams(query, _params);
|
|
2526
2754
|
info.query_fixed = query;
|
|
2527
2755
|
info.operator = operator;
|
|
2528
2756
|
info.value = value;
|
|
@@ -2667,6 +2895,32 @@ class StableBrowser {
|
|
|
2667
2895
|
}
|
|
2668
2896
|
return timeout;
|
|
2669
2897
|
}
|
|
2898
|
+
_getFindElementTimeout(options) {
|
|
2899
|
+
if (options && options.timeout) {
|
|
2900
|
+
return options.timeout;
|
|
2901
|
+
}
|
|
2902
|
+
if (this.configuration.find_element_timeout) {
|
|
2903
|
+
return this.configuration.find_element_timeout;
|
|
2904
|
+
}
|
|
2905
|
+
return 30000;
|
|
2906
|
+
}
|
|
2907
|
+
async saveStoreState(path = null, world = null) {
|
|
2908
|
+
const storageState = await this.page.context().storageState();
|
|
2909
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2910
|
+
if (path) {
|
|
2911
|
+
// save { storageState: storageState } into the path
|
|
2912
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2913
|
+
}
|
|
2914
|
+
else {
|
|
2915
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
async restoreSaveState(path = null, world = null) {
|
|
2919
|
+
await refreshBrowser(this, path, world);
|
|
2920
|
+
this.registerEventListeners(this.context);
|
|
2921
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2922
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2923
|
+
}
|
|
2670
2924
|
async waitForPageLoad(options = {}, world = null) {
|
|
2671
2925
|
let timeout = this._getLoadTimeout(options);
|
|
2672
2926
|
const promiseArray = [];
|
|
@@ -2734,6 +2988,7 @@ class StableBrowser {
|
|
|
2734
2988
|
highlight: false,
|
|
2735
2989
|
type: Types.CLOSE_PAGE,
|
|
2736
2990
|
text: `Close page`,
|
|
2991
|
+
_text: `Close the page`,
|
|
2737
2992
|
operation: "closePage",
|
|
2738
2993
|
log: "***** close page *****\n",
|
|
2739
2994
|
throwError: false,
|
|
@@ -2750,8 +3005,97 @@ class StableBrowser {
|
|
|
2750
3005
|
_commandFinally(state, this);
|
|
2751
3006
|
}
|
|
2752
3007
|
}
|
|
3008
|
+
async tableCellOperation(headerText, rowText, options, world = null) {
|
|
3009
|
+
let operation = null;
|
|
3010
|
+
if (!options || !options.operation) {
|
|
3011
|
+
throw new Error("operation is not defined");
|
|
3012
|
+
}
|
|
3013
|
+
operation = options.operation;
|
|
3014
|
+
// validate operation is one of the supported operations
|
|
3015
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3016
|
+
throw new Error("operation is not supported");
|
|
3017
|
+
}
|
|
3018
|
+
const state = {
|
|
3019
|
+
options,
|
|
3020
|
+
world,
|
|
3021
|
+
locate: false,
|
|
3022
|
+
scroll: false,
|
|
3023
|
+
highlight: false,
|
|
3024
|
+
type: Types.TABLE_OPERATION,
|
|
3025
|
+
text: `Table operation`,
|
|
3026
|
+
_text: `Table ${operation} operation`,
|
|
3027
|
+
operation: operation,
|
|
3028
|
+
log: "***** Table operation *****\n",
|
|
3029
|
+
};
|
|
3030
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3031
|
+
try {
|
|
3032
|
+
await _preCommand(state, this);
|
|
3033
|
+
const start = Date.now();
|
|
3034
|
+
let cellArea = null;
|
|
3035
|
+
while (true) {
|
|
3036
|
+
try {
|
|
3037
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3038
|
+
if (cellArea) {
|
|
3039
|
+
break;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
catch (e) {
|
|
3043
|
+
// ignore
|
|
3044
|
+
}
|
|
3045
|
+
if (Date.now() - start > timeout) {
|
|
3046
|
+
throw new Error(`Cell not found in table`);
|
|
3047
|
+
}
|
|
3048
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3049
|
+
}
|
|
3050
|
+
switch (operation) {
|
|
3051
|
+
case "click":
|
|
3052
|
+
if (!options.css) {
|
|
3053
|
+
// will click in the center of the cell
|
|
3054
|
+
let xOffset = 0;
|
|
3055
|
+
let yOffset = 0;
|
|
3056
|
+
if (options.xOffset) {
|
|
3057
|
+
xOffset = options.xOffset;
|
|
3058
|
+
}
|
|
3059
|
+
if (options.yOffset) {
|
|
3060
|
+
yOffset = options.yOffset;
|
|
3061
|
+
}
|
|
3062
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3063
|
+
}
|
|
3064
|
+
else {
|
|
3065
|
+
const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
|
|
3066
|
+
if (results.length === 0) {
|
|
3067
|
+
throw new Error(`Element not found in cell area`);
|
|
3068
|
+
}
|
|
3069
|
+
state.element = results[0];
|
|
3070
|
+
await results[0].click();
|
|
3071
|
+
}
|
|
3072
|
+
break;
|
|
3073
|
+
case "hover+click":
|
|
3074
|
+
if (!options.css) {
|
|
3075
|
+
throw new Error("css is not defined");
|
|
3076
|
+
}
|
|
3077
|
+
const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
|
|
3078
|
+
if (results.length === 0) {
|
|
3079
|
+
throw new Error(`Element not found in cell area`);
|
|
3080
|
+
}
|
|
3081
|
+
state.element = results[0];
|
|
3082
|
+
await results[0].hover();
|
|
3083
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3084
|
+
await results[0].click();
|
|
3085
|
+
break;
|
|
3086
|
+
default:
|
|
3087
|
+
throw new Error("operation is not supported");
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
catch (e) {
|
|
3091
|
+
await _commandError(state, e, this);
|
|
3092
|
+
}
|
|
3093
|
+
finally {
|
|
3094
|
+
_commandFinally(state, this);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
2753
3097
|
saveTestDataAsGlobal(options, world) {
|
|
2754
|
-
const dataFile =
|
|
3098
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2755
3099
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2756
3100
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2757
3101
|
}
|
|
@@ -2781,6 +3125,7 @@ class StableBrowser {
|
|
|
2781
3125
|
_reportToWorld(world, {
|
|
2782
3126
|
type: Types.SET_VIEWPORT,
|
|
2783
3127
|
text: "set viewport size to " + width + "x" + hight,
|
|
3128
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2784
3129
|
screenshotId,
|
|
2785
3130
|
result: error
|
|
2786
3131
|
? {
|
|
@@ -2852,14 +3197,25 @@ class StableBrowser {
|
|
|
2852
3197
|
}
|
|
2853
3198
|
}
|
|
2854
3199
|
async beforeStep(world, step) {
|
|
2855
|
-
this.stepName = step.pickleStep.text;
|
|
2856
|
-
this.logger.info("step: " + this.stepName);
|
|
2857
3200
|
if (this.stepIndex === undefined) {
|
|
2858
3201
|
this.stepIndex = 0;
|
|
2859
3202
|
}
|
|
2860
3203
|
else {
|
|
2861
3204
|
this.stepIndex++;
|
|
2862
3205
|
}
|
|
3206
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3207
|
+
this.stepName = step.pickleStep.text;
|
|
3208
|
+
this.logger.info("step: " + this.stepName);
|
|
3209
|
+
}
|
|
3210
|
+
else if (step && step.text) {
|
|
3211
|
+
this.stepName = step.text;
|
|
3212
|
+
}
|
|
3213
|
+
else {
|
|
3214
|
+
this.stepName = "step " + this.stepIndex;
|
|
3215
|
+
}
|
|
3216
|
+
if (this.context) {
|
|
3217
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3218
|
+
}
|
|
2863
3219
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2864
3220
|
if (this.context.browserObject.context) {
|
|
2865
3221
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2872,6 +3228,41 @@ class StableBrowser {
|
|
|
2872
3228
|
this.saveTestDataAsGlobal({}, world);
|
|
2873
3229
|
}
|
|
2874
3230
|
}
|
|
3231
|
+
if (this.initSnapshotTaken === false) {
|
|
3232
|
+
this.initSnapshotTaken = true;
|
|
3233
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3234
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3235
|
+
if (snapshot) {
|
|
3236
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
async getAriaSnapshot() {
|
|
3242
|
+
try {
|
|
3243
|
+
// find the page url
|
|
3244
|
+
const url = await this.page.url();
|
|
3245
|
+
// extract the path from the url
|
|
3246
|
+
const path = new URL(url).pathname;
|
|
3247
|
+
// get the page title
|
|
3248
|
+
const title = await this.page.title();
|
|
3249
|
+
// go over other frams
|
|
3250
|
+
const frames = this.page.frames();
|
|
3251
|
+
const snapshots = [];
|
|
3252
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3253
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3254
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3255
|
+
content.push(`- frame: ${i}`);
|
|
3256
|
+
const frame = frames[i];
|
|
3257
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3258
|
+
content.push(snapshot);
|
|
3259
|
+
}
|
|
3260
|
+
return content.join("\n");
|
|
3261
|
+
}
|
|
3262
|
+
catch (e) {
|
|
3263
|
+
console.error(e);
|
|
3264
|
+
}
|
|
3265
|
+
return null;
|
|
2875
3266
|
}
|
|
2876
3267
|
async afterStep(world, step) {
|
|
2877
3268
|
this.stepName = null;
|
|
@@ -2882,6 +3273,16 @@ class StableBrowser {
|
|
|
2882
3273
|
});
|
|
2883
3274
|
}
|
|
2884
3275
|
}
|
|
3276
|
+
if (this.context) {
|
|
3277
|
+
this.context.examplesRow = null;
|
|
3278
|
+
}
|
|
3279
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3280
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3281
|
+
if (snapshot) {
|
|
3282
|
+
const obj = {};
|
|
3283
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
2885
3286
|
}
|
|
2886
3287
|
}
|
|
2887
3288
|
function createTimedPromise(promise, label) {
|
|
@@ -2889,156 +3290,5 @@ function createTimedPromise(promise, label) {
|
|
|
2889
3290
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2890
3291
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2891
3292
|
}
|
|
2892
|
-
const KEYBOARD_EVENTS = [
|
|
2893
|
-
"ALT",
|
|
2894
|
-
"AltGraph",
|
|
2895
|
-
"CapsLock",
|
|
2896
|
-
"Control",
|
|
2897
|
-
"Fn",
|
|
2898
|
-
"FnLock",
|
|
2899
|
-
"Hyper",
|
|
2900
|
-
"Meta",
|
|
2901
|
-
"NumLock",
|
|
2902
|
-
"ScrollLock",
|
|
2903
|
-
"Shift",
|
|
2904
|
-
"Super",
|
|
2905
|
-
"Symbol",
|
|
2906
|
-
"SymbolLock",
|
|
2907
|
-
"Enter",
|
|
2908
|
-
"Tab",
|
|
2909
|
-
"ArrowDown",
|
|
2910
|
-
"ArrowLeft",
|
|
2911
|
-
"ArrowRight",
|
|
2912
|
-
"ArrowUp",
|
|
2913
|
-
"End",
|
|
2914
|
-
"Home",
|
|
2915
|
-
"PageDown",
|
|
2916
|
-
"PageUp",
|
|
2917
|
-
"Backspace",
|
|
2918
|
-
"Clear",
|
|
2919
|
-
"Copy",
|
|
2920
|
-
"CrSel",
|
|
2921
|
-
"Cut",
|
|
2922
|
-
"Delete",
|
|
2923
|
-
"EraseEof",
|
|
2924
|
-
"ExSel",
|
|
2925
|
-
"Insert",
|
|
2926
|
-
"Paste",
|
|
2927
|
-
"Redo",
|
|
2928
|
-
"Undo",
|
|
2929
|
-
"Accept",
|
|
2930
|
-
"Again",
|
|
2931
|
-
"Attn",
|
|
2932
|
-
"Cancel",
|
|
2933
|
-
"ContextMenu",
|
|
2934
|
-
"Escape",
|
|
2935
|
-
"Execute",
|
|
2936
|
-
"Find",
|
|
2937
|
-
"Finish",
|
|
2938
|
-
"Help",
|
|
2939
|
-
"Pause",
|
|
2940
|
-
"Play",
|
|
2941
|
-
"Props",
|
|
2942
|
-
"Select",
|
|
2943
|
-
"ZoomIn",
|
|
2944
|
-
"ZoomOut",
|
|
2945
|
-
"BrightnessDown",
|
|
2946
|
-
"BrightnessUp",
|
|
2947
|
-
"Eject",
|
|
2948
|
-
"LogOff",
|
|
2949
|
-
"Power",
|
|
2950
|
-
"PowerOff",
|
|
2951
|
-
"PrintScreen",
|
|
2952
|
-
"Hibernate",
|
|
2953
|
-
"Standby",
|
|
2954
|
-
"WakeUp",
|
|
2955
|
-
"AllCandidates",
|
|
2956
|
-
"Alphanumeric",
|
|
2957
|
-
"CodeInput",
|
|
2958
|
-
"Compose",
|
|
2959
|
-
"Convert",
|
|
2960
|
-
"Dead",
|
|
2961
|
-
"FinalMode",
|
|
2962
|
-
"GroupFirst",
|
|
2963
|
-
"GroupLast",
|
|
2964
|
-
"GroupNext",
|
|
2965
|
-
"GroupPrevious",
|
|
2966
|
-
"ModeChange",
|
|
2967
|
-
"NextCandidate",
|
|
2968
|
-
"NonConvert",
|
|
2969
|
-
"PreviousCandidate",
|
|
2970
|
-
"Process",
|
|
2971
|
-
"SingleCandidate",
|
|
2972
|
-
"HangulMode",
|
|
2973
|
-
"HanjaMode",
|
|
2974
|
-
"JunjaMode",
|
|
2975
|
-
"Eisu",
|
|
2976
|
-
"Hankaku",
|
|
2977
|
-
"Hiragana",
|
|
2978
|
-
"HiraganaKatakana",
|
|
2979
|
-
"KanaMode",
|
|
2980
|
-
"KanjiMode",
|
|
2981
|
-
"Katakana",
|
|
2982
|
-
"Romaji",
|
|
2983
|
-
"Zenkaku",
|
|
2984
|
-
"ZenkakuHanaku",
|
|
2985
|
-
"F1",
|
|
2986
|
-
"F2",
|
|
2987
|
-
"F3",
|
|
2988
|
-
"F4",
|
|
2989
|
-
"F5",
|
|
2990
|
-
"F6",
|
|
2991
|
-
"F7",
|
|
2992
|
-
"F8",
|
|
2993
|
-
"F9",
|
|
2994
|
-
"F10",
|
|
2995
|
-
"F11",
|
|
2996
|
-
"F12",
|
|
2997
|
-
"Soft1",
|
|
2998
|
-
"Soft2",
|
|
2999
|
-
"Soft3",
|
|
3000
|
-
"Soft4",
|
|
3001
|
-
"ChannelDown",
|
|
3002
|
-
"ChannelUp",
|
|
3003
|
-
"Close",
|
|
3004
|
-
"MailForward",
|
|
3005
|
-
"MailReply",
|
|
3006
|
-
"MailSend",
|
|
3007
|
-
"MediaFastForward",
|
|
3008
|
-
"MediaPause",
|
|
3009
|
-
"MediaPlay",
|
|
3010
|
-
"MediaPlayPause",
|
|
3011
|
-
"MediaRecord",
|
|
3012
|
-
"MediaRewind",
|
|
3013
|
-
"MediaStop",
|
|
3014
|
-
"MediaTrackNext",
|
|
3015
|
-
"MediaTrackPrevious",
|
|
3016
|
-
"AudioBalanceLeft",
|
|
3017
|
-
"AudioBalanceRight",
|
|
3018
|
-
"AudioBassBoostDown",
|
|
3019
|
-
"AudioBassBoostToggle",
|
|
3020
|
-
"AudioBassBoostUp",
|
|
3021
|
-
"AudioFaderFront",
|
|
3022
|
-
"AudioFaderRear",
|
|
3023
|
-
"AudioSurroundModeNext",
|
|
3024
|
-
"AudioTrebleDown",
|
|
3025
|
-
"AudioTrebleUp",
|
|
3026
|
-
"AudioVolumeDown",
|
|
3027
|
-
"AudioVolumeMute",
|
|
3028
|
-
"AudioVolumeUp",
|
|
3029
|
-
"MicrophoneToggle",
|
|
3030
|
-
"MicrophoneVolumeDown",
|
|
3031
|
-
"MicrophoneVolumeMute",
|
|
3032
|
-
"MicrophoneVolumeUp",
|
|
3033
|
-
"TV",
|
|
3034
|
-
"TV3DMode",
|
|
3035
|
-
"TVAntennaCable",
|
|
3036
|
-
"TVAudioDescription",
|
|
3037
|
-
];
|
|
3038
|
-
function unEscapeString(str) {
|
|
3039
|
-
const placeholder = "__NEWLINE__";
|
|
3040
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
3041
|
-
return str;
|
|
3042
|
-
}
|
|
3043
3293
|
export { StableBrowser };
|
|
3044
3294
|
//# sourceMappingURL=stable_browser.js.map
|