automation_model 1.0.545-dev → 1.0.545-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 +55 -17
- package/lib/stable_browser.js +713 -530
- package/lib/stable_browser.js.map +1 -1
- 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 +15 -1
- package/lib/utils.js +399 -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,20 @@ 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, } 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
|
+
export const Types = {
|
|
23
25
|
CLICK: "click_element",
|
|
26
|
+
WAIT_ELEMENT: "wait_element",
|
|
24
27
|
NAVIGATE: "navigate",
|
|
25
28
|
FILL: "fill_element",
|
|
26
29
|
EXECUTE: "execute_page_method",
|
|
@@ -30,6 +33,8 @@ const Types = {
|
|
|
30
33
|
GET_PAGE_STATUS: "get_page_status",
|
|
31
34
|
CLICK_ROW_ACTION: "click_row_action",
|
|
32
35
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
36
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
37
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
33
38
|
ANALYZE_TABLE: "analyze_table",
|
|
34
39
|
SELECT: "select_combobox",
|
|
35
40
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -47,8 +52,12 @@ const Types = {
|
|
|
47
52
|
SET_INPUT: "set_input",
|
|
48
53
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
49
54
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
55
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
50
56
|
};
|
|
51
57
|
export const apps = {};
|
|
58
|
+
const formatElementName = (elementName) => {
|
|
59
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
60
|
+
};
|
|
52
61
|
class StableBrowser {
|
|
53
62
|
browser;
|
|
54
63
|
page;
|
|
@@ -62,6 +71,7 @@ class StableBrowser {
|
|
|
62
71
|
appName = "main";
|
|
63
72
|
tags = null;
|
|
64
73
|
isRecording = false;
|
|
74
|
+
initSnapshotTaken = false;
|
|
65
75
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
66
76
|
this.browser = browser;
|
|
67
77
|
this.page = page;
|
|
@@ -100,27 +110,9 @@ class StableBrowser {
|
|
|
100
110
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
101
111
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
102
112
|
}
|
|
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
113
|
registerEventListeners(context) {
|
|
122
114
|
this.registerConsoleLogListener(this.page, context);
|
|
123
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
115
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
124
116
|
if (!context.pageLoading) {
|
|
125
117
|
context.pageLoading = { status: false };
|
|
126
118
|
}
|
|
@@ -176,8 +168,8 @@ class StableBrowser {
|
|
|
176
168
|
};
|
|
177
169
|
}
|
|
178
170
|
const tempContext = {};
|
|
179
|
-
|
|
180
|
-
|
|
171
|
+
_copyContext(this, tempContext);
|
|
172
|
+
_copyContext(apps[appName], this);
|
|
181
173
|
apps[this.appName] = tempContext;
|
|
182
174
|
this.appName = appName;
|
|
183
175
|
if (newContextCreated) {
|
|
@@ -186,22 +178,6 @@ class StableBrowser {
|
|
|
186
178
|
await this.waitForPageLoad();
|
|
187
179
|
}
|
|
188
180
|
}
|
|
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
181
|
registerConsoleLogListener(page, context) {
|
|
206
182
|
if (!this.context.webLogger) {
|
|
207
183
|
this.context.webLogger = [];
|
|
@@ -265,55 +241,51 @@ class StableBrowser {
|
|
|
265
241
|
// async closeUnexpectedPopups() {
|
|
266
242
|
// await closeUnexpectedPopups(this.page);
|
|
267
243
|
// }
|
|
268
|
-
async goto(url) {
|
|
244
|
+
async goto(url, world = null) {
|
|
245
|
+
if (!url) {
|
|
246
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
247
|
+
}
|
|
269
248
|
if (!url.startsWith("http")) {
|
|
270
249
|
url = "https://" + url;
|
|
271
250
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
251
|
+
const state = {
|
|
252
|
+
value: url,
|
|
253
|
+
world: world,
|
|
254
|
+
type: Types.NAVIGATE,
|
|
255
|
+
text: `Navigate Page to: ${url}`,
|
|
256
|
+
operation: "goto",
|
|
257
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
258
|
+
info: {},
|
|
259
|
+
locate: false,
|
|
260
|
+
scroll: false,
|
|
261
|
+
screenshot: false,
|
|
262
|
+
highlight: false,
|
|
263
|
+
};
|
|
264
|
+
try {
|
|
265
|
+
await _preCommand(state, this);
|
|
266
|
+
await this.page.goto(url, {
|
|
267
|
+
timeout: 60000,
|
|
268
|
+
});
|
|
269
|
+
await _screenshot(state, this);
|
|
279
270
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// remove the _ prefix
|
|
284
|
-
regValue = key.substring(1);
|
|
285
|
-
}
|
|
286
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
271
|
+
catch (error) {
|
|
272
|
+
console.error("Error on goto", error);
|
|
273
|
+
_commandError(state, error, this);
|
|
287
274
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
291
|
-
// check if not null
|
|
292
|
-
if (!locator) {
|
|
293
|
-
return locator;
|
|
275
|
+
finally {
|
|
276
|
+
_commandFinally(state, this);
|
|
294
277
|
}
|
|
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
278
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
279
|
+
async _getLocator(locator, scope, _params) {
|
|
280
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
281
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
282
|
+
for (let key in locator) {
|
|
283
|
+
if (typeof locator[key] !== "string")
|
|
284
|
+
continue;
|
|
285
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
286
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
312
287
|
}
|
|
313
288
|
}
|
|
314
|
-
}
|
|
315
|
-
_getLocator(locator, scope, _params) {
|
|
316
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
317
289
|
let locatorReturn;
|
|
318
290
|
if (locator.role) {
|
|
319
291
|
if (locator.role[1].nameReg) {
|
|
@@ -321,7 +293,7 @@ class StableBrowser {
|
|
|
321
293
|
delete locator.role[1].nameReg;
|
|
322
294
|
}
|
|
323
295
|
// if (locator.role[1].name) {
|
|
324
|
-
// locator.role[1].name =
|
|
296
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
325
297
|
// }
|
|
326
298
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
327
299
|
}
|
|
@@ -364,140 +336,54 @@ class StableBrowser {
|
|
|
364
336
|
if (css && css.locator) {
|
|
365
337
|
css = css.locator;
|
|
366
338
|
}
|
|
367
|
-
let result = await this._locateElementByText(scope,
|
|
339
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
368
340
|
if (result.elementCount === 0) {
|
|
369
341
|
return;
|
|
370
342
|
}
|
|
371
|
-
let textElementCss = "[data-blinq-id
|
|
343
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
372
344
|
// css climb to parent element
|
|
373
345
|
const climbArray = [];
|
|
374
346
|
for (let i = 0; i < climb; i++) {
|
|
375
347
|
climbArray.push("..");
|
|
376
348
|
}
|
|
377
349
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
350
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
351
|
+
if (css) {
|
|
352
|
+
resultCss = resultCss + " >> " + css;
|
|
353
|
+
}
|
|
354
|
+
return resultCss;
|
|
355
|
+
}
|
|
356
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
357
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
358
|
+
const locator = scope.locator(query);
|
|
359
|
+
const count = await locator.count();
|
|
360
|
+
if (!tag1) {
|
|
361
|
+
tag1 = "*";
|
|
362
|
+
}
|
|
363
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
364
|
+
let tagCount = 0;
|
|
365
|
+
for (let i = 0; i < count; i++) {
|
|
366
|
+
const element = locator.nth(i);
|
|
367
|
+
// check if the tag matches
|
|
368
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
369
|
+
if (!tag.startsWith("*")) {
|
|
370
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
371
|
+
return false;
|
|
388
372
|
}
|
|
389
|
-
currentNode = currentNode.parentNode;
|
|
390
|
-
}
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
document.isParent = isParent;
|
|
394
|
-
function getRegex(str) {
|
|
395
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
396
|
-
if (!match) {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
let [_, pattern, flags] = match;
|
|
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
373
|
}
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
}
|
|
374
|
+
if (!el.setAttribute) {
|
|
375
|
+
el = el.parentElement;
|
|
449
376
|
}
|
|
377
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
378
|
+
return true;
|
|
379
|
+
}, [tag1, randomToken]))) {
|
|
380
|
+
continue;
|
|
450
381
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
}
|
|
460
|
-
}
|
|
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
|
-
}
|
|
485
|
-
}
|
|
486
|
-
let elementCount = 0;
|
|
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++;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
498
|
-
}, [text1, tag1, regex1, partial1]);
|
|
382
|
+
tagCount++;
|
|
383
|
+
}
|
|
384
|
+
return { elementCount: tagCount, randomToken };
|
|
499
385
|
}
|
|
500
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
386
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
501
387
|
if (!info) {
|
|
502
388
|
info = {};
|
|
503
389
|
}
|
|
@@ -506,10 +392,13 @@ class StableBrowser {
|
|
|
506
392
|
}
|
|
507
393
|
if (!info.log) {
|
|
508
394
|
info.log = "";
|
|
395
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
509
396
|
}
|
|
510
397
|
let locatorSearch = selectorHierarchy[index];
|
|
398
|
+
let originalLocatorSearch = "";
|
|
511
399
|
try {
|
|
512
|
-
|
|
400
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
401
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
513
402
|
}
|
|
514
403
|
catch (e) {
|
|
515
404
|
console.error(e);
|
|
@@ -517,30 +406,31 @@ class StableBrowser {
|
|
|
517
406
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
518
407
|
let locator = null;
|
|
519
408
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
520
|
-
|
|
409
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
410
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
521
411
|
if (!locatorString) {
|
|
522
412
|
info.failCause.textNotFound = true;
|
|
523
|
-
info.failCause.lastError =
|
|
413
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
524
414
|
return;
|
|
525
415
|
}
|
|
526
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
416
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
527
417
|
}
|
|
528
418
|
else if (locatorSearch.text) {
|
|
529
|
-
let text =
|
|
530
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
419
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
420
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
531
421
|
if (result.elementCount === 0) {
|
|
532
422
|
info.failCause.textNotFound = true;
|
|
533
|
-
info.failCause.lastError =
|
|
423
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
534
424
|
return;
|
|
535
425
|
}
|
|
536
|
-
locatorSearch.css = "[data-blinq-id
|
|
426
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
537
427
|
if (locatorSearch.childCss) {
|
|
538
428
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
539
429
|
}
|
|
540
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
430
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
541
431
|
}
|
|
542
432
|
else {
|
|
543
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
433
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
544
434
|
}
|
|
545
435
|
// let cssHref = false;
|
|
546
436
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -555,16 +445,25 @@ class StableBrowser {
|
|
|
555
445
|
let visibleLocator = null;
|
|
556
446
|
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
557
447
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
448
|
+
if (info.locatorLog) {
|
|
449
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
450
|
+
}
|
|
558
451
|
return;
|
|
559
452
|
}
|
|
453
|
+
if (info.locatorLog && count === 0) {
|
|
454
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
455
|
+
}
|
|
560
456
|
for (let j = 0; j < count; j++) {
|
|
561
457
|
let visible = await locator.nth(j).isVisible();
|
|
562
458
|
const enabled = await locator.nth(j).isEnabled();
|
|
563
459
|
if (!visibleOnly) {
|
|
564
460
|
visible = true;
|
|
565
461
|
}
|
|
566
|
-
if (visible && enabled) {
|
|
462
|
+
if (visible && (allowDisabled || enabled)) {
|
|
567
463
|
foundLocators.push(locator.nth(j));
|
|
464
|
+
if (info.locatorLog) {
|
|
465
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
466
|
+
}
|
|
568
467
|
}
|
|
569
468
|
else {
|
|
570
469
|
info.failCause.visible = visible;
|
|
@@ -572,8 +471,16 @@ class StableBrowser {
|
|
|
572
471
|
if (!info.printMessages) {
|
|
573
472
|
info.printMessages = {};
|
|
574
473
|
}
|
|
474
|
+
if (info.locatorLog && !visible) {
|
|
475
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
476
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
477
|
+
}
|
|
478
|
+
if (info.locatorLog && !enabled) {
|
|
479
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
480
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
481
|
+
}
|
|
575
482
|
if (!info.printMessages[j.toString()]) {
|
|
576
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
483
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
577
484
|
info.printMessages[j.toString()] = true;
|
|
578
485
|
}
|
|
579
486
|
}
|
|
@@ -589,7 +496,7 @@ class StableBrowser {
|
|
|
589
496
|
if (!info) {
|
|
590
497
|
info = {};
|
|
591
498
|
}
|
|
592
|
-
info.log += "scan for popup handlers" + "\n";
|
|
499
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
593
500
|
const handlerGroup = [];
|
|
594
501
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
595
502
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -637,7 +544,7 @@ class StableBrowser {
|
|
|
637
544
|
}
|
|
638
545
|
return { rerun: false };
|
|
639
546
|
}
|
|
640
|
-
async _locate(selectors, info, _params, timeout) {
|
|
547
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
641
548
|
if (!timeout) {
|
|
642
549
|
timeout = 30000;
|
|
643
550
|
}
|
|
@@ -647,9 +554,18 @@ class StableBrowser {
|
|
|
647
554
|
let selector = selectors.locators[j];
|
|
648
555
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
649
556
|
}
|
|
650
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
557
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
651
558
|
if (!element.rerun) {
|
|
652
|
-
|
|
559
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
560
|
+
element.evaluate((el, randomToken) => {
|
|
561
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
562
|
+
}, randomToken);
|
|
563
|
+
if (element._frame) {
|
|
564
|
+
return element;
|
|
565
|
+
}
|
|
566
|
+
const scope = element.page();
|
|
567
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
568
|
+
return newSelector;
|
|
653
569
|
}
|
|
654
570
|
}
|
|
655
571
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -691,9 +607,11 @@ class StableBrowser {
|
|
|
691
607
|
}
|
|
692
608
|
return framescope;
|
|
693
609
|
};
|
|
610
|
+
let fLocator = null;
|
|
694
611
|
while (true) {
|
|
695
612
|
let frameFound = false;
|
|
696
613
|
if (selectors.nestFrmLoc) {
|
|
614
|
+
fLocator = selectors.nestFrmLoc;
|
|
697
615
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
698
616
|
frameFound = true;
|
|
699
617
|
break;
|
|
@@ -702,6 +620,7 @@ class StableBrowser {
|
|
|
702
620
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
703
621
|
let frameLocator = selectors.frameLocators[i];
|
|
704
622
|
if (frameLocator.css) {
|
|
623
|
+
fLocator = frameLocator.css;
|
|
705
624
|
scope = scope.frameLocator(frameLocator.css);
|
|
706
625
|
frameFound = true;
|
|
707
626
|
break;
|
|
@@ -709,18 +628,25 @@ class StableBrowser {
|
|
|
709
628
|
}
|
|
710
629
|
}
|
|
711
630
|
if (!frameFound && selectors.iframe_src) {
|
|
631
|
+
fLocator = selectors.iframe_src;
|
|
712
632
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
713
633
|
}
|
|
714
634
|
if (!scope) {
|
|
715
|
-
info
|
|
635
|
+
if (info && info.locatorLog) {
|
|
636
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
637
|
+
}
|
|
638
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
716
639
|
if (Date.now() - startTime > timeout) {
|
|
717
640
|
info.failCause.iframeNotFound = true;
|
|
718
|
-
info.failCause.lastError =
|
|
641
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
719
642
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
720
643
|
}
|
|
721
644
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
722
645
|
}
|
|
723
646
|
else {
|
|
647
|
+
if (info && info.locatorLog) {
|
|
648
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
649
|
+
}
|
|
724
650
|
break;
|
|
725
651
|
}
|
|
726
652
|
}
|
|
@@ -737,11 +663,12 @@ class StableBrowser {
|
|
|
737
663
|
return bodyContent;
|
|
738
664
|
});
|
|
739
665
|
}
|
|
740
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
666
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
741
667
|
if (!info) {
|
|
742
668
|
info = {};
|
|
743
669
|
info.failCause = {};
|
|
744
670
|
info.log = "";
|
|
671
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
745
672
|
}
|
|
746
673
|
let highPriorityTimeout = 5000;
|
|
747
674
|
let visibleOnlyTimeout = 6000;
|
|
@@ -785,17 +712,17 @@ class StableBrowser {
|
|
|
785
712
|
}
|
|
786
713
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
787
714
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
788
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
715
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
789
716
|
if (result.foundElements.length === 0) {
|
|
790
717
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
791
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
718
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
792
719
|
}
|
|
793
720
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
794
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
721
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
795
722
|
}
|
|
796
723
|
else {
|
|
797
724
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
798
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
725
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
799
726
|
}
|
|
800
727
|
}
|
|
801
728
|
let foundElements = result.foundElements;
|
|
@@ -840,26 +767,34 @@ class StableBrowser {
|
|
|
840
767
|
break;
|
|
841
768
|
}
|
|
842
769
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
843
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
770
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
844
771
|
highPriorityOnly = false;
|
|
845
772
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
846
773
|
lazy_scroll = true;
|
|
847
|
-
await this.
|
|
774
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
848
775
|
}
|
|
849
776
|
}
|
|
850
777
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
851
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
778
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
852
779
|
visibleOnly = false;
|
|
853
780
|
}
|
|
854
781
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
855
782
|
}
|
|
856
783
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
857
|
-
info.
|
|
784
|
+
// if (info.locatorLog) {
|
|
785
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
786
|
+
// for (let line of lines) {
|
|
787
|
+
// this.logger.debug(line);
|
|
788
|
+
// }
|
|
789
|
+
// }
|
|
790
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
858
791
|
info.failCause.locatorNotFound = true;
|
|
859
|
-
info
|
|
792
|
+
if (!info?.failCause?.lastError) {
|
|
793
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
794
|
+
}
|
|
860
795
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
861
796
|
}
|
|
862
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
797
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
863
798
|
let foundElements = [];
|
|
864
799
|
const result = {
|
|
865
800
|
foundElements: foundElements,
|
|
@@ -867,14 +802,15 @@ class StableBrowser {
|
|
|
867
802
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
868
803
|
let foundLocators = [];
|
|
869
804
|
try {
|
|
870
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
805
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
871
806
|
}
|
|
872
807
|
catch (e) {
|
|
873
|
-
this
|
|
874
|
-
this.logger.debug(
|
|
808
|
+
// this call can fail it the browser is navigating
|
|
809
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
810
|
+
// this.logger.debug(e);
|
|
875
811
|
foundLocators = [];
|
|
876
812
|
try {
|
|
877
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
878
814
|
}
|
|
879
815
|
catch (e) {
|
|
880
816
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -890,11 +826,27 @@ class StableBrowser {
|
|
|
890
826
|
}
|
|
891
827
|
if (foundLocators.length > 1) {
|
|
892
828
|
info.failCause.foundMultiple = true;
|
|
829
|
+
if (info.locatorLog) {
|
|
830
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
831
|
+
}
|
|
893
832
|
}
|
|
894
833
|
}
|
|
895
834
|
return result;
|
|
896
835
|
}
|
|
897
836
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
837
|
+
const state = {
|
|
838
|
+
locate: false,
|
|
839
|
+
scroll: false,
|
|
840
|
+
highlight: false,
|
|
841
|
+
_params,
|
|
842
|
+
options,
|
|
843
|
+
world,
|
|
844
|
+
type: Types.CLICK,
|
|
845
|
+
text: "Click element",
|
|
846
|
+
operation: "simpleClick",
|
|
847
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
848
|
+
};
|
|
849
|
+
_preCommand(state, this);
|
|
898
850
|
const startTime = Date.now();
|
|
899
851
|
let timeout = 30000;
|
|
900
852
|
if (options && options.timeout) {
|
|
@@ -917,15 +869,33 @@ class StableBrowser {
|
|
|
917
869
|
}
|
|
918
870
|
}
|
|
919
871
|
catch (e) {
|
|
920
|
-
if (
|
|
872
|
+
if (performance.now() - startTime > timeout) {
|
|
921
873
|
// throw e;
|
|
922
|
-
|
|
874
|
+
try {
|
|
875
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
876
|
+
}
|
|
877
|
+
finally {
|
|
878
|
+
_commandFinally(state, this);
|
|
879
|
+
}
|
|
923
880
|
}
|
|
924
881
|
}
|
|
925
882
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
926
883
|
}
|
|
927
884
|
}
|
|
928
885
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
886
|
+
const state = {
|
|
887
|
+
locate: false,
|
|
888
|
+
scroll: false,
|
|
889
|
+
highlight: false,
|
|
890
|
+
_params,
|
|
891
|
+
options,
|
|
892
|
+
world,
|
|
893
|
+
type: Types.FILL,
|
|
894
|
+
text: "Fill element",
|
|
895
|
+
operation: "simpleClickType",
|
|
896
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
897
|
+
};
|
|
898
|
+
_preCommand(state, this);
|
|
929
899
|
const startTime = Date.now();
|
|
930
900
|
let timeout = 30000;
|
|
931
901
|
if (options && options.timeout) {
|
|
@@ -948,9 +918,14 @@ class StableBrowser {
|
|
|
948
918
|
}
|
|
949
919
|
}
|
|
950
920
|
catch (e) {
|
|
951
|
-
if (
|
|
921
|
+
if (performance.now() - startTime > timeout) {
|
|
952
922
|
// throw e;
|
|
953
|
-
|
|
923
|
+
try {
|
|
924
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
925
|
+
}
|
|
926
|
+
finally {
|
|
927
|
+
_commandFinally(state, this);
|
|
928
|
+
}
|
|
954
929
|
}
|
|
955
930
|
}
|
|
956
931
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -963,15 +938,16 @@ class StableBrowser {
|
|
|
963
938
|
options,
|
|
964
939
|
world,
|
|
965
940
|
text: "Click element",
|
|
941
|
+
_text: "Click on " + selectors.element_name,
|
|
966
942
|
type: Types.CLICK,
|
|
967
943
|
operation: "click",
|
|
968
944
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
969
945
|
};
|
|
970
946
|
try {
|
|
971
947
|
await _preCommand(state, this);
|
|
972
|
-
if (state.options && state.options.context) {
|
|
973
|
-
|
|
974
|
-
}
|
|
948
|
+
// if (state.options && state.options.context) {
|
|
949
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
950
|
+
// }
|
|
975
951
|
try {
|
|
976
952
|
await state.element.click();
|
|
977
953
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -992,6 +968,38 @@ class StableBrowser {
|
|
|
992
968
|
_commandFinally(state, this);
|
|
993
969
|
}
|
|
994
970
|
}
|
|
971
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
972
|
+
const timeout = this._getFindElementTimeout(options);
|
|
973
|
+
const state = {
|
|
974
|
+
selectors,
|
|
975
|
+
_params,
|
|
976
|
+
options,
|
|
977
|
+
world,
|
|
978
|
+
text: "Wait for element",
|
|
979
|
+
_text: "Wait for " + selectors.element_name,
|
|
980
|
+
type: Types.WAIT_ELEMENT,
|
|
981
|
+
operation: "waitForElement",
|
|
982
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
983
|
+
};
|
|
984
|
+
let found = false;
|
|
985
|
+
try {
|
|
986
|
+
await _preCommand(state, this);
|
|
987
|
+
// if (state.options && state.options.context) {
|
|
988
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
989
|
+
// }
|
|
990
|
+
await state.element.waitFor({ timeout: timeout });
|
|
991
|
+
found = true;
|
|
992
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
993
|
+
}
|
|
994
|
+
catch (e) {
|
|
995
|
+
console.error("Error on waitForElement", e);
|
|
996
|
+
// await _commandError(state, e, this);
|
|
997
|
+
}
|
|
998
|
+
finally {
|
|
999
|
+
_commandFinally(state, this);
|
|
1000
|
+
}
|
|
1001
|
+
return found;
|
|
1002
|
+
}
|
|
995
1003
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
996
1004
|
const state = {
|
|
997
1005
|
selectors,
|
|
@@ -1000,6 +1008,7 @@ class StableBrowser {
|
|
|
1000
1008
|
world,
|
|
1001
1009
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1002
1010
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1011
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1003
1012
|
operation: "setCheck",
|
|
1004
1013
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1005
1014
|
};
|
|
@@ -1009,9 +1018,15 @@ class StableBrowser {
|
|
|
1009
1018
|
// let element = await this._locate(selectors, info, _params);
|
|
1010
1019
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1011
1020
|
try {
|
|
1012
|
-
//
|
|
1021
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1022
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1023
|
+
await this._highlightElements(element);
|
|
1013
1024
|
await state.element.setChecked(checked);
|
|
1014
1025
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1026
|
+
// await this._unHighlightElements(element);
|
|
1027
|
+
// }
|
|
1028
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1029
|
+
// await this._unHighlightElements(element);
|
|
1015
1030
|
}
|
|
1016
1031
|
catch (e) {
|
|
1017
1032
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1043,6 +1058,7 @@ class StableBrowser {
|
|
|
1043
1058
|
world,
|
|
1044
1059
|
type: Types.HOVER,
|
|
1045
1060
|
text: `Hover element`,
|
|
1061
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1046
1062
|
operation: "hover",
|
|
1047
1063
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1048
1064
|
};
|
|
@@ -1050,6 +1066,7 @@ class StableBrowser {
|
|
|
1050
1066
|
await _preCommand(state, this);
|
|
1051
1067
|
try {
|
|
1052
1068
|
await state.element.hover();
|
|
1069
|
+
// await _screenshot(state, this);
|
|
1053
1070
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1054
1071
|
}
|
|
1055
1072
|
catch (e) {
|
|
@@ -1057,6 +1074,7 @@ class StableBrowser {
|
|
|
1057
1074
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1058
1075
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1059
1076
|
await state.element.hover({ timeout: 10000 });
|
|
1077
|
+
// await _screenshot(state, this);
|
|
1060
1078
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1061
1079
|
}
|
|
1062
1080
|
await _screenshot(state, this);
|
|
@@ -1082,6 +1100,7 @@ class StableBrowser {
|
|
|
1082
1100
|
value: values.toString(),
|
|
1083
1101
|
type: Types.SELECT,
|
|
1084
1102
|
text: `Select option: ${values}`,
|
|
1103
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1085
1104
|
operation: "selectOption",
|
|
1086
1105
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1087
1106
|
};
|
|
@@ -1116,6 +1135,7 @@ class StableBrowser {
|
|
|
1116
1135
|
highlight: false,
|
|
1117
1136
|
type: Types.TYPE_PRESS,
|
|
1118
1137
|
text: `Type value: ${_value}`,
|
|
1138
|
+
_text: `Type value: ${_value}`,
|
|
1119
1139
|
operation: "type",
|
|
1120
1140
|
log: "",
|
|
1121
1141
|
};
|
|
@@ -1195,6 +1215,7 @@ class StableBrowser {
|
|
|
1195
1215
|
world,
|
|
1196
1216
|
type: Types.SET_DATE_TIME,
|
|
1197
1217
|
text: `Set date time value: ${value}`,
|
|
1218
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1198
1219
|
operation: "setDateTime",
|
|
1199
1220
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1200
1221
|
throwError: false,
|
|
@@ -1266,6 +1287,7 @@ class StableBrowser {
|
|
|
1266
1287
|
world,
|
|
1267
1288
|
type: Types.FILL,
|
|
1268
1289
|
text: `Click type input with value: ${_value}`,
|
|
1290
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1269
1291
|
operation: "clickType",
|
|
1270
1292
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1271
1293
|
};
|
|
@@ -1331,7 +1353,12 @@ class StableBrowser {
|
|
|
1331
1353
|
await this.waitForPageLoad();
|
|
1332
1354
|
}
|
|
1333
1355
|
else if (enter === false) {
|
|
1334
|
-
|
|
1356
|
+
try {
|
|
1357
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1358
|
+
}
|
|
1359
|
+
catch (e) {
|
|
1360
|
+
// ignore
|
|
1361
|
+
}
|
|
1335
1362
|
//await this.page.keyboard.press("Tab");
|
|
1336
1363
|
}
|
|
1337
1364
|
else {
|
|
@@ -1383,15 +1410,17 @@ class StableBrowser {
|
|
|
1383
1410
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1384
1411
|
}
|
|
1385
1412
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1413
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1386
1414
|
_validateSelectors(selectors);
|
|
1387
1415
|
let screenshotId = null;
|
|
1388
1416
|
let screenshotPath = null;
|
|
1389
1417
|
if (!info.log) {
|
|
1390
1418
|
info.log = "";
|
|
1419
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1391
1420
|
}
|
|
1392
1421
|
info.operation = "getText";
|
|
1393
1422
|
info.selectors = selectors;
|
|
1394
|
-
let element = await this._locate(selectors, info, _params);
|
|
1423
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1395
1424
|
if (climb > 0) {
|
|
1396
1425
|
const climbArray = [];
|
|
1397
1426
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1410,6 +1439,18 @@ class StableBrowser {
|
|
|
1410
1439
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1411
1440
|
try {
|
|
1412
1441
|
await this._highlightElements(element);
|
|
1442
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1443
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1444
|
+
// this._highlightElements(element)
|
|
1445
|
+
// .then(async () => {
|
|
1446
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1447
|
+
// this._unhighlightElements(element).then(
|
|
1448
|
+
// () => {}
|
|
1449
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1450
|
+
// );
|
|
1451
|
+
// })
|
|
1452
|
+
// .catch(e);
|
|
1453
|
+
// }
|
|
1413
1454
|
const elementText = await element.innerText();
|
|
1414
1455
|
return {
|
|
1415
1456
|
text: elementText,
|
|
@@ -1421,7 +1462,7 @@ class StableBrowser {
|
|
|
1421
1462
|
}
|
|
1422
1463
|
catch (e) {
|
|
1423
1464
|
//await this.closeUnexpectedPopups();
|
|
1424
|
-
this.logger.info("no innerText will use textContent");
|
|
1465
|
+
this.logger.info("no innerText, will use textContent");
|
|
1425
1466
|
const elementText = await element.textContent();
|
|
1426
1467
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1427
1468
|
}
|
|
@@ -1446,6 +1487,7 @@ class StableBrowser {
|
|
|
1446
1487
|
highlight: false,
|
|
1447
1488
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1448
1489
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1490
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1449
1491
|
operation: "containsPattern",
|
|
1450
1492
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1451
1493
|
};
|
|
@@ -1481,6 +1523,8 @@ class StableBrowser {
|
|
|
1481
1523
|
}
|
|
1482
1524
|
}
|
|
1483
1525
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1526
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1527
|
+
const startTime = Date.now();
|
|
1484
1528
|
const state = {
|
|
1485
1529
|
selectors,
|
|
1486
1530
|
_params,
|
|
@@ -1507,62 +1551,54 @@ class StableBrowser {
|
|
|
1507
1551
|
}
|
|
1508
1552
|
let foundObj = null;
|
|
1509
1553
|
try {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1517
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1518
|
-
if (dateAlternatives.date) {
|
|
1519
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1520
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1521
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1522
|
-
return state.info;
|
|
1554
|
+
while (Date.now() - startTime < timeout) {
|
|
1555
|
+
try {
|
|
1556
|
+
await _preCommand(state, this);
|
|
1557
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1558
|
+
if (foundObj && foundObj.element) {
|
|
1559
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1523
1560
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1561
|
+
await _screenshot(state, this);
|
|
1562
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1563
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1564
|
+
if (dateAlternatives.date) {
|
|
1565
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1566
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1567
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1568
|
+
return state.info;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
else if (numberAlternatives.number) {
|
|
1573
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1574
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1575
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1576
|
+
return state.info;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1531
1581
|
return state.info;
|
|
1532
1582
|
}
|
|
1533
1583
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
throw new Error("element doesn't contain text " + text);
|
|
1584
|
+
catch (e) {
|
|
1585
|
+
// Log error but continue retrying until timeout is reached
|
|
1586
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1587
|
+
}
|
|
1588
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1540
1589
|
}
|
|
1541
|
-
|
|
1590
|
+
state.info.foundText = foundObj?.text;
|
|
1591
|
+
state.info.value = foundObj?.value;
|
|
1592
|
+
throw new Error("element doesn't contain text " + text);
|
|
1542
1593
|
}
|
|
1543
1594
|
catch (e) {
|
|
1544
1595
|
await _commandError(state, e, this);
|
|
1596
|
+
throw e;
|
|
1545
1597
|
}
|
|
1546
1598
|
finally {
|
|
1547
1599
|
_commandFinally(state, this);
|
|
1548
1600
|
}
|
|
1549
1601
|
}
|
|
1550
|
-
_getDataFile(world = null) {
|
|
1551
|
-
let dataFile = null;
|
|
1552
|
-
if (world && world.reportFolder) {
|
|
1553
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1554
|
-
}
|
|
1555
|
-
else if (this.reportFolder) {
|
|
1556
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1557
|
-
}
|
|
1558
|
-
else if (this.context && this.context.reportFolder) {
|
|
1559
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1560
|
-
}
|
|
1561
|
-
else {
|
|
1562
|
-
dataFile = "data.json";
|
|
1563
|
-
}
|
|
1564
|
-
return dataFile;
|
|
1565
|
-
}
|
|
1566
1602
|
async waitForUserInput(message, world = null) {
|
|
1567
1603
|
if (!message) {
|
|
1568
1604
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1591,7 +1627,7 @@ class StableBrowser {
|
|
|
1591
1627
|
return;
|
|
1592
1628
|
}
|
|
1593
1629
|
// if data file exists, load it
|
|
1594
|
-
const dataFile =
|
|
1630
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1595
1631
|
let data = this.getTestData(world);
|
|
1596
1632
|
// merge the testData with the existing data
|
|
1597
1633
|
Object.assign(data, testData);
|
|
@@ -1694,7 +1730,7 @@ class StableBrowser {
|
|
|
1694
1730
|
}
|
|
1695
1731
|
}
|
|
1696
1732
|
getTestData(world = null) {
|
|
1697
|
-
const dataFile =
|
|
1733
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1698
1734
|
let data = {};
|
|
1699
1735
|
if (fs.existsSync(dataFile)) {
|
|
1700
1736
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1781,6 +1817,15 @@ class StableBrowser {
|
|
|
1781
1817
|
document.documentElement.clientWidth,
|
|
1782
1818
|
])));
|
|
1783
1819
|
let screenshotBuffer = null;
|
|
1820
|
+
// if (focusedElement) {
|
|
1821
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1822
|
+
// await this._unhighlightElements(focusedElement);
|
|
1823
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1824
|
+
// console.log(`Unhighlighted previous element`);
|
|
1825
|
+
// }
|
|
1826
|
+
// if (focusedElement) {
|
|
1827
|
+
// await this._highlightElements(focusedElement);
|
|
1828
|
+
// }
|
|
1784
1829
|
if (this.context.browserName === "chromium") {
|
|
1785
1830
|
const client = await playContext.newCDPSession(this.page);
|
|
1786
1831
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1802,6 +1847,10 @@ class StableBrowser {
|
|
|
1802
1847
|
else {
|
|
1803
1848
|
screenshotBuffer = await this.page.screenshot();
|
|
1804
1849
|
}
|
|
1850
|
+
// if (focusedElement) {
|
|
1851
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1852
|
+
// await this._unhighlightElements(focusedElement);
|
|
1853
|
+
// }
|
|
1805
1854
|
let image = await Jimp.read(screenshotBuffer);
|
|
1806
1855
|
// Get the image dimensions
|
|
1807
1856
|
const { width, height } = image.bitmap;
|
|
@@ -1814,6 +1863,7 @@ class StableBrowser {
|
|
|
1814
1863
|
else {
|
|
1815
1864
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1816
1865
|
}
|
|
1866
|
+
return screenshotBuffer;
|
|
1817
1867
|
}
|
|
1818
1868
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1819
1869
|
const state = {
|
|
@@ -1849,8 +1899,10 @@ class StableBrowser {
|
|
|
1849
1899
|
world,
|
|
1850
1900
|
type: Types.EXTRACT,
|
|
1851
1901
|
text: `Extract attribute from element`,
|
|
1902
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1852
1903
|
operation: "extractAttribute",
|
|
1853
1904
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1905
|
+
allowDisabled: true,
|
|
1854
1906
|
};
|
|
1855
1907
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1856
1908
|
try {
|
|
@@ -1872,6 +1924,7 @@ class StableBrowser {
|
|
|
1872
1924
|
state.info.value = state.value;
|
|
1873
1925
|
this.setTestData({ [variable]: state.value }, world);
|
|
1874
1926
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1927
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1875
1928
|
return state.info;
|
|
1876
1929
|
}
|
|
1877
1930
|
catch (e) {
|
|
@@ -1890,14 +1943,21 @@ class StableBrowser {
|
|
|
1890
1943
|
options,
|
|
1891
1944
|
world,
|
|
1892
1945
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1946
|
+
highlight: true,
|
|
1947
|
+
screenshot: true,
|
|
1893
1948
|
text: `Verify element attribute`,
|
|
1949
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1894
1950
|
operation: "verifyAttribute",
|
|
1895
1951
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1952
|
+
allowDisabled: true,
|
|
1896
1953
|
};
|
|
1897
1954
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1898
1955
|
let val;
|
|
1956
|
+
let expectedValue;
|
|
1899
1957
|
try {
|
|
1900
1958
|
await _preCommand(state, this);
|
|
1959
|
+
expectedValue = state.value;
|
|
1960
|
+
state.info.expectedValue = expectedValue;
|
|
1901
1961
|
switch (attribute) {
|
|
1902
1962
|
case "innerText":
|
|
1903
1963
|
val = String(await state.element.innerText());
|
|
@@ -1911,23 +1971,30 @@ class StableBrowser {
|
|
|
1911
1971
|
case "disabled":
|
|
1912
1972
|
val = String(await state.element.isDisabled());
|
|
1913
1973
|
break;
|
|
1974
|
+
case "readOnly":
|
|
1975
|
+
const isEditable = await state.element.isEditable();
|
|
1976
|
+
val = String(!isEditable);
|
|
1977
|
+
break;
|
|
1914
1978
|
default:
|
|
1915
1979
|
val = String(await state.element.getAttribute(attribute));
|
|
1916
1980
|
break;
|
|
1917
1981
|
}
|
|
1982
|
+
state.info.value = val;
|
|
1918
1983
|
let regex;
|
|
1919
|
-
if (
|
|
1920
|
-
const patternBody =
|
|
1984
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1985
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1921
1986
|
regex = new RegExp(patternBody, "g");
|
|
1922
1987
|
}
|
|
1923
1988
|
else {
|
|
1924
|
-
const escapedPattern =
|
|
1989
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1925
1990
|
regex = new RegExp(escapedPattern, "g");
|
|
1926
1991
|
}
|
|
1927
1992
|
if (!val.match(regex)) {
|
|
1928
|
-
|
|
1993
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1994
|
+
state.info.failCause.assertionFailed = true;
|
|
1995
|
+
state.info.failCause.lastError = errorMessage;
|
|
1996
|
+
throw new Error(errorMessage);
|
|
1929
1997
|
}
|
|
1930
|
-
state.info.value = val;
|
|
1931
1998
|
return state.info;
|
|
1932
1999
|
}
|
|
1933
2000
|
catch (e) {
|
|
@@ -1955,7 +2022,7 @@ class StableBrowser {
|
|
|
1955
2022
|
if (options && options.timeout) {
|
|
1956
2023
|
timeout = options.timeout;
|
|
1957
2024
|
}
|
|
1958
|
-
const serviceUrl =
|
|
2025
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1959
2026
|
const request = {
|
|
1960
2027
|
method: "POST",
|
|
1961
2028
|
url: serviceUrl,
|
|
@@ -2026,27 +2093,32 @@ class StableBrowser {
|
|
|
2026
2093
|
async _highlightElements(scope, css) {
|
|
2027
2094
|
try {
|
|
2028
2095
|
if (!scope) {
|
|
2096
|
+
// console.log(`Scope is not defined`);
|
|
2029
2097
|
return;
|
|
2030
2098
|
}
|
|
2031
2099
|
if (!css) {
|
|
2032
2100
|
scope
|
|
2033
2101
|
.evaluate((node) => {
|
|
2034
2102
|
if (node && node.style) {
|
|
2035
|
-
let
|
|
2036
|
-
|
|
2103
|
+
let originalOutline = node.style.outline;
|
|
2104
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2105
|
+
// node.__previousOutline = originalOutline;
|
|
2106
|
+
node.style.outline = "2px solid red";
|
|
2107
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2037
2108
|
if (window) {
|
|
2038
2109
|
window.addEventListener("beforeunload", function (e) {
|
|
2039
|
-
node.style.
|
|
2110
|
+
node.style.outline = originalOutline;
|
|
2040
2111
|
});
|
|
2041
2112
|
}
|
|
2042
2113
|
setTimeout(function () {
|
|
2043
|
-
node.style.
|
|
2114
|
+
node.style.outline = originalOutline;
|
|
2044
2115
|
}, 2000);
|
|
2045
2116
|
}
|
|
2046
2117
|
})
|
|
2047
2118
|
.then(() => { })
|
|
2048
2119
|
.catch((e) => {
|
|
2049
2120
|
// ignore
|
|
2121
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2050
2122
|
});
|
|
2051
2123
|
}
|
|
2052
2124
|
else {
|
|
@@ -2062,17 +2134,18 @@ class StableBrowser {
|
|
|
2062
2134
|
if (!element.style) {
|
|
2063
2135
|
return;
|
|
2064
2136
|
}
|
|
2065
|
-
|
|
2137
|
+
let originalOutline = element.style.outline;
|
|
2138
|
+
element.__previousOutline = originalOutline;
|
|
2066
2139
|
// Set the new border to be red and 2px solid
|
|
2067
|
-
element.style.
|
|
2140
|
+
element.style.outline = "2px solid red";
|
|
2068
2141
|
if (window) {
|
|
2069
2142
|
window.addEventListener("beforeunload", function (e) {
|
|
2070
|
-
element.style.
|
|
2143
|
+
element.style.outline = originalOutline;
|
|
2071
2144
|
});
|
|
2072
2145
|
}
|
|
2073
2146
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2074
2147
|
setTimeout(function () {
|
|
2075
|
-
element.style.
|
|
2148
|
+
element.style.outline = originalOutline;
|
|
2076
2149
|
}, 2000);
|
|
2077
2150
|
}
|
|
2078
2151
|
return;
|
|
@@ -2080,6 +2153,7 @@ class StableBrowser {
|
|
|
2080
2153
|
.then(() => { })
|
|
2081
2154
|
.catch((e) => {
|
|
2082
2155
|
// ignore
|
|
2156
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2083
2157
|
});
|
|
2084
2158
|
}
|
|
2085
2159
|
}
|
|
@@ -2087,6 +2161,54 @@ class StableBrowser {
|
|
|
2087
2161
|
console.debug(error);
|
|
2088
2162
|
}
|
|
2089
2163
|
}
|
|
2164
|
+
// async _unhighlightElements(scope, css) {
|
|
2165
|
+
// try {
|
|
2166
|
+
// if (!scope) {
|
|
2167
|
+
// return;
|
|
2168
|
+
// }
|
|
2169
|
+
// if (!css) {
|
|
2170
|
+
// scope
|
|
2171
|
+
// .evaluate((node) => {
|
|
2172
|
+
// if (node && node.style) {
|
|
2173
|
+
// if (!node.__previousOutline) {
|
|
2174
|
+
// node.style.outline = "";
|
|
2175
|
+
// } else {
|
|
2176
|
+
// node.style.outline = node.__previousOutline;
|
|
2177
|
+
// }
|
|
2178
|
+
// }
|
|
2179
|
+
// })
|
|
2180
|
+
// .then(() => {})
|
|
2181
|
+
// .catch((e) => {
|
|
2182
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2183
|
+
// });
|
|
2184
|
+
// } else {
|
|
2185
|
+
// scope
|
|
2186
|
+
// .evaluate(([css]) => {
|
|
2187
|
+
// if (!css) {
|
|
2188
|
+
// return;
|
|
2189
|
+
// }
|
|
2190
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2191
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2192
|
+
// let element = elements[i];
|
|
2193
|
+
// if (!element.style) {
|
|
2194
|
+
// return;
|
|
2195
|
+
// }
|
|
2196
|
+
// if (!element.__previousOutline) {
|
|
2197
|
+
// element.style.outline = "";
|
|
2198
|
+
// } else {
|
|
2199
|
+
// element.style.outline = element.__previousOutline;
|
|
2200
|
+
// }
|
|
2201
|
+
// }
|
|
2202
|
+
// })
|
|
2203
|
+
// .then(() => {})
|
|
2204
|
+
// .catch((e) => {
|
|
2205
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2206
|
+
// });
|
|
2207
|
+
// }
|
|
2208
|
+
// } catch (error) {
|
|
2209
|
+
// // console.debug(error);
|
|
2210
|
+
// }
|
|
2211
|
+
// }
|
|
2090
2212
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2091
2213
|
const startTime = Date.now();
|
|
2092
2214
|
let error = null;
|
|
@@ -2131,6 +2253,7 @@ class StableBrowser {
|
|
|
2131
2253
|
_reportToWorld(world, {
|
|
2132
2254
|
type: Types.VERIFY_PAGE_PATH,
|
|
2133
2255
|
text: "Verify page path",
|
|
2256
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2134
2257
|
screenshotId,
|
|
2135
2258
|
result: error
|
|
2136
2259
|
? {
|
|
@@ -2148,6 +2271,35 @@ class StableBrowser {
|
|
|
2148
2271
|
});
|
|
2149
2272
|
}
|
|
2150
2273
|
}
|
|
2274
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2275
|
+
const frames = this.page.frames();
|
|
2276
|
+
let results = [];
|
|
2277
|
+
let ignoreCase = false;
|
|
2278
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2279
|
+
if (dateAlternatives.date) {
|
|
2280
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2281
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2282
|
+
result.frame = frames[i];
|
|
2283
|
+
results.push(result);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
else if (numberAlternatives.number) {
|
|
2287
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2288
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2289
|
+
result.frame = frames[i];
|
|
2290
|
+
results.push(result);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
else {
|
|
2294
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2295
|
+
result.frame = frames[i];
|
|
2296
|
+
results.push(result);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
state.info.results = results;
|
|
2300
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2301
|
+
return resultWithElementsFound;
|
|
2302
|
+
}
|
|
2151
2303
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2152
2304
|
text = unEscapeString(text);
|
|
2153
2305
|
const state = {
|
|
@@ -2157,12 +2309,13 @@ class StableBrowser {
|
|
|
2157
2309
|
locate: false,
|
|
2158
2310
|
scroll: false,
|
|
2159
2311
|
highlight: false,
|
|
2160
|
-
type: Types.
|
|
2312
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2161
2313
|
text: `Verify text exists in page`,
|
|
2314
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2162
2315
|
operation: "verifyTextExistInPage",
|
|
2163
2316
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2164
2317
|
};
|
|
2165
|
-
const timeout = this.
|
|
2318
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2166
2319
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2167
2320
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2168
2321
|
if (newValue !== text) {
|
|
@@ -2175,31 +2328,15 @@ class StableBrowser {
|
|
|
2175
2328
|
await _preCommand(state, this);
|
|
2176
2329
|
state.info.text = text;
|
|
2177
2330
|
while (true) {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
else if (numberAlternatives.number) {
|
|
2189
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2190
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2191
|
-
result.frame = frames[i];
|
|
2192
|
-
results.push(result);
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
else {
|
|
2196
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2197
|
-
result.frame = frames[i];
|
|
2198
|
-
results.push(result);
|
|
2199
|
-
}
|
|
2331
|
+
let resultWithElementsFound = {
|
|
2332
|
+
length: 0,
|
|
2333
|
+
};
|
|
2334
|
+
try {
|
|
2335
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
// ignore
|
|
2200
2339
|
}
|
|
2201
|
-
state.info.results = results;
|
|
2202
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2203
2340
|
if (resultWithElementsFound.length === 0) {
|
|
2204
2341
|
if (Date.now() - state.startTime > timeout) {
|
|
2205
2342
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2207,18 +2344,40 @@ class StableBrowser {
|
|
|
2207
2344
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2208
2345
|
continue;
|
|
2209
2346
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2347
|
+
try {
|
|
2348
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2349
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2350
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2351
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2352
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2353
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2354
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2355
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2356
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2357
|
+
// .then(async () => {
|
|
2358
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2359
|
+
// })
|
|
2360
|
+
// .catch(
|
|
2361
|
+
// (e) => {}
|
|
2362
|
+
// console.error(e)
|
|
2363
|
+
// );
|
|
2364
|
+
// });
|
|
2365
|
+
// }
|
|
2366
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2367
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2368
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2369
|
+
if (element) {
|
|
2370
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2371
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2372
|
+
// await _screenshot(state, this, element);
|
|
2373
|
+
}
|
|
2218
2374
|
}
|
|
2375
|
+
await _screenshot(state, this);
|
|
2376
|
+
return state.info;
|
|
2377
|
+
}
|
|
2378
|
+
catch (error) {
|
|
2379
|
+
console.error(error);
|
|
2219
2380
|
}
|
|
2220
|
-
await _screenshot(state, this);
|
|
2221
|
-
return state.info;
|
|
2222
2381
|
}
|
|
2223
2382
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2224
2383
|
}
|
|
@@ -2240,10 +2399,11 @@ class StableBrowser {
|
|
|
2240
2399
|
highlight: false,
|
|
2241
2400
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2242
2401
|
text: `Verify text does not exist in page`,
|
|
2402
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2243
2403
|
operation: "verifyTextNotExistInPage",
|
|
2244
2404
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2245
2405
|
};
|
|
2246
|
-
const timeout = this.
|
|
2406
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2247
2407
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2248
2408
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2249
2409
|
if (newValue !== text) {
|
|
@@ -2255,32 +2415,16 @@ class StableBrowser {
|
|
|
2255
2415
|
try {
|
|
2256
2416
|
await _preCommand(state, this);
|
|
2257
2417
|
state.info.text = text;
|
|
2418
|
+
let resultWithElementsFound = {
|
|
2419
|
+
length: null, // initial cannot be 0
|
|
2420
|
+
};
|
|
2258
2421
|
while (true) {
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2265
|
-
result.frame = frames[i];
|
|
2266
|
-
results.push(result);
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
else if (numberAlternatives.number) {
|
|
2270
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2271
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2272
|
-
result.frame = frames[i];
|
|
2273
|
-
results.push(result);
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
else {
|
|
2277
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2278
|
-
result.frame = frames[i];
|
|
2279
|
-
results.push(result);
|
|
2280
|
-
}
|
|
2422
|
+
try {
|
|
2423
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
// ignore
|
|
2281
2427
|
}
|
|
2282
|
-
state.info.results = results;
|
|
2283
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2284
2428
|
if (resultWithElementsFound.length === 0) {
|
|
2285
2429
|
await _screenshot(state, this);
|
|
2286
2430
|
return state.info;
|
|
@@ -2298,15 +2442,116 @@ class StableBrowser {
|
|
|
2298
2442
|
_commandFinally(state, this);
|
|
2299
2443
|
}
|
|
2300
2444
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2445
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2446
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2447
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2448
|
+
const state = {
|
|
2449
|
+
text_search: textToVerify,
|
|
2450
|
+
options,
|
|
2451
|
+
world,
|
|
2452
|
+
locate: false,
|
|
2453
|
+
scroll: false,
|
|
2454
|
+
highlight: false,
|
|
2455
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2456
|
+
text: `Verify text with relation to another text`,
|
|
2457
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2458
|
+
operation: "verify_text_with_relation",
|
|
2459
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2460
|
+
};
|
|
2461
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2462
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2463
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2464
|
+
if (newValue !== textAnchor) {
|
|
2465
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2466
|
+
textAnchor = newValue;
|
|
2467
|
+
}
|
|
2468
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2469
|
+
if (newValue !== textToVerify) {
|
|
2470
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2471
|
+
textToVerify = newValue;
|
|
2472
|
+
}
|
|
2473
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2474
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2475
|
+
let foundAncore = false;
|
|
2476
|
+
try {
|
|
2477
|
+
await _preCommand(state, this);
|
|
2478
|
+
state.info.text = textToVerify;
|
|
2479
|
+
let resultWithElementsFound = {
|
|
2480
|
+
length: 0,
|
|
2481
|
+
};
|
|
2482
|
+
while (true) {
|
|
2483
|
+
try {
|
|
2484
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2485
|
+
}
|
|
2486
|
+
catch (error) {
|
|
2487
|
+
// ignore
|
|
2488
|
+
}
|
|
2489
|
+
if (resultWithElementsFound.length === 0) {
|
|
2490
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2491
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2492
|
+
}
|
|
2493
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
try {
|
|
2497
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2498
|
+
foundAncore = true;
|
|
2499
|
+
const result = resultWithElementsFound[i];
|
|
2500
|
+
const token = result.randomToken;
|
|
2501
|
+
const frame = result.frame;
|
|
2502
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2503
|
+
const climbArray1 = [];
|
|
2504
|
+
for (let i = 0; i < climb; i++) {
|
|
2505
|
+
climbArray1.push("..");
|
|
2506
|
+
}
|
|
2507
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2508
|
+
css = css + " >> " + climbXpath;
|
|
2509
|
+
const count = await frame.locator(css).count();
|
|
2510
|
+
for (let j = 0; j < count; j++) {
|
|
2511
|
+
const continer = await frame.locator(css).nth(j);
|
|
2512
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2513
|
+
if (result.elementCount > 0) {
|
|
2514
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2515
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2516
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2517
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2518
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2519
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2520
|
+
// .then(async () => {
|
|
2521
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2522
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2523
|
+
// () => {}
|
|
2524
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2525
|
+
// );
|
|
2526
|
+
// })
|
|
2527
|
+
// .catch(e);
|
|
2528
|
+
// }
|
|
2529
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2530
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2531
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2532
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2533
|
+
if (element) {
|
|
2534
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2535
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2536
|
+
}
|
|
2537
|
+
await _screenshot(state, this);
|
|
2538
|
+
return state.info;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
catch (error) {
|
|
2544
|
+
console.error(error);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2305
2548
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2549
|
+
catch (e) {
|
|
2550
|
+
await _commandError(state, e, this);
|
|
2551
|
+
}
|
|
2552
|
+
finally {
|
|
2553
|
+
_commandFinally(state, this);
|
|
2308
2554
|
}
|
|
2309
|
-
return serviceUrl;
|
|
2310
2555
|
}
|
|
2311
2556
|
async visualVerification(text, options = {}, world = null) {
|
|
2312
2557
|
const startTime = Date.now();
|
|
@@ -2322,14 +2567,17 @@ class StableBrowser {
|
|
|
2322
2567
|
throw new Error("TOKEN is not set");
|
|
2323
2568
|
}
|
|
2324
2569
|
try {
|
|
2325
|
-
let serviceUrl =
|
|
2570
|
+
let serviceUrl = _getServerUrl();
|
|
2326
2571
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2327
2572
|
info.screenshotPath = screenshotPath;
|
|
2328
2573
|
const screenshot = await this.takeScreenshot();
|
|
2329
|
-
|
|
2330
|
-
method: "
|
|
2574
|
+
let request = {
|
|
2575
|
+
method: "post",
|
|
2576
|
+
maxBodyLength: Infinity,
|
|
2331
2577
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2332
2578
|
headers: {
|
|
2579
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2580
|
+
"x-source": "aaa",
|
|
2333
2581
|
"Content-Type": "application/json",
|
|
2334
2582
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2335
2583
|
},
|
|
@@ -2338,7 +2586,7 @@ class StableBrowser {
|
|
|
2338
2586
|
screenshot: screenshot,
|
|
2339
2587
|
}),
|
|
2340
2588
|
};
|
|
2341
|
-
|
|
2589
|
+
const result = await axios.request(request);
|
|
2342
2590
|
if (result.data.status !== true) {
|
|
2343
2591
|
throw new Error("Visual validation failed");
|
|
2344
2592
|
}
|
|
@@ -2366,6 +2614,7 @@ class StableBrowser {
|
|
|
2366
2614
|
_reportToWorld(world, {
|
|
2367
2615
|
type: Types.VERIFY_VISUAL,
|
|
2368
2616
|
text: "Visual verification",
|
|
2617
|
+
_text: "Visual verification of " + text,
|
|
2369
2618
|
screenshotId,
|
|
2370
2619
|
result: error
|
|
2371
2620
|
? {
|
|
@@ -2411,6 +2660,7 @@ class StableBrowser {
|
|
|
2411
2660
|
let screenshotPath = null;
|
|
2412
2661
|
const info = {};
|
|
2413
2662
|
info.log = "";
|
|
2663
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2414
2664
|
info.operation = "getTableData";
|
|
2415
2665
|
info.selectors = selectors;
|
|
2416
2666
|
try {
|
|
@@ -2486,7 +2736,7 @@ class StableBrowser {
|
|
|
2486
2736
|
info.operation = "analyzeTable";
|
|
2487
2737
|
info.selectors = selectors;
|
|
2488
2738
|
info.query = query;
|
|
2489
|
-
query =
|
|
2739
|
+
query = _fixUsingParams(query, _params);
|
|
2490
2740
|
info.query_fixed = query;
|
|
2491
2741
|
info.operator = operator;
|
|
2492
2742
|
info.value = value;
|
|
@@ -2631,6 +2881,32 @@ class StableBrowser {
|
|
|
2631
2881
|
}
|
|
2632
2882
|
return timeout;
|
|
2633
2883
|
}
|
|
2884
|
+
_getFindElementTimeout(options) {
|
|
2885
|
+
if (options && options.timeout) {
|
|
2886
|
+
return options.timeout;
|
|
2887
|
+
}
|
|
2888
|
+
if (this.configuration.find_element_timeout) {
|
|
2889
|
+
return this.configuration.find_element_timeout;
|
|
2890
|
+
}
|
|
2891
|
+
return 30000;
|
|
2892
|
+
}
|
|
2893
|
+
async saveStoreState(path = null, world = null) {
|
|
2894
|
+
const storageState = await this.page.context().storageState();
|
|
2895
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2896
|
+
if (path) {
|
|
2897
|
+
// save { storageState: storageState } into the path
|
|
2898
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2899
|
+
}
|
|
2900
|
+
else {
|
|
2901
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
async restoreSaveState(path = null, world = null) {
|
|
2905
|
+
await refreshBrowser(this, path, world);
|
|
2906
|
+
this.registerEventListeners(this.context);
|
|
2907
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2908
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2909
|
+
}
|
|
2634
2910
|
async waitForPageLoad(options = {}, world = null) {
|
|
2635
2911
|
let timeout = this._getLoadTimeout(options);
|
|
2636
2912
|
const promiseArray = [];
|
|
@@ -2698,6 +2974,7 @@ class StableBrowser {
|
|
|
2698
2974
|
highlight: false,
|
|
2699
2975
|
type: Types.CLOSE_PAGE,
|
|
2700
2976
|
text: `Close page`,
|
|
2977
|
+
_text: `Close the page`,
|
|
2701
2978
|
operation: "closePage",
|
|
2702
2979
|
log: "***** close page *****\n",
|
|
2703
2980
|
throwError: false,
|
|
@@ -2745,6 +3022,7 @@ class StableBrowser {
|
|
|
2745
3022
|
_reportToWorld(world, {
|
|
2746
3023
|
type: Types.SET_VIEWPORT,
|
|
2747
3024
|
text: "set viewport size to " + width + "x" + hight,
|
|
3025
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2748
3026
|
screenshotId,
|
|
2749
3027
|
result: error
|
|
2750
3028
|
? {
|
|
@@ -2816,14 +3094,25 @@ class StableBrowser {
|
|
|
2816
3094
|
}
|
|
2817
3095
|
}
|
|
2818
3096
|
async beforeStep(world, step) {
|
|
2819
|
-
this.stepName = step.pickleStep.text;
|
|
2820
|
-
this.logger.info("step: " + this.stepName);
|
|
2821
3097
|
if (this.stepIndex === undefined) {
|
|
2822
3098
|
this.stepIndex = 0;
|
|
2823
3099
|
}
|
|
2824
3100
|
else {
|
|
2825
3101
|
this.stepIndex++;
|
|
2826
3102
|
}
|
|
3103
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3104
|
+
this.stepName = step.pickleStep.text;
|
|
3105
|
+
this.logger.info("step: " + this.stepName);
|
|
3106
|
+
}
|
|
3107
|
+
else if (step && step.text) {
|
|
3108
|
+
this.stepName = step.text;
|
|
3109
|
+
}
|
|
3110
|
+
else {
|
|
3111
|
+
this.stepName = "step " + this.stepIndex;
|
|
3112
|
+
}
|
|
3113
|
+
if (this.context) {
|
|
3114
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3115
|
+
}
|
|
2827
3116
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2828
3117
|
if (this.context.browserObject.context) {
|
|
2829
3118
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2836,6 +3125,41 @@ class StableBrowser {
|
|
|
2836
3125
|
this.saveTestDataAsGlobal({}, world);
|
|
2837
3126
|
}
|
|
2838
3127
|
}
|
|
3128
|
+
if (this.initSnapshotTaken === false) {
|
|
3129
|
+
this.initSnapshotTaken = true;
|
|
3130
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3131
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3132
|
+
if (snapshot) {
|
|
3133
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
async getAriaSnapshot() {
|
|
3139
|
+
try {
|
|
3140
|
+
// find the page url
|
|
3141
|
+
const url = await this.page.url();
|
|
3142
|
+
// extract the path from the url
|
|
3143
|
+
const path = new URL(url).pathname;
|
|
3144
|
+
// get the page title
|
|
3145
|
+
const title = await this.page.title();
|
|
3146
|
+
// go over other frams
|
|
3147
|
+
const frames = this.page.frames();
|
|
3148
|
+
const snapshots = [];
|
|
3149
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3150
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3151
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3152
|
+
content.push(`- frame: ${i}`);
|
|
3153
|
+
const frame = frames[i];
|
|
3154
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3155
|
+
content.push(snapshot);
|
|
3156
|
+
}
|
|
3157
|
+
return content.join("\n");
|
|
3158
|
+
}
|
|
3159
|
+
catch (e) {
|
|
3160
|
+
console.error(e);
|
|
3161
|
+
}
|
|
3162
|
+
return null;
|
|
2839
3163
|
}
|
|
2840
3164
|
async afterStep(world, step) {
|
|
2841
3165
|
this.stepName = null;
|
|
@@ -2846,6 +3170,16 @@ class StableBrowser {
|
|
|
2846
3170
|
});
|
|
2847
3171
|
}
|
|
2848
3172
|
}
|
|
3173
|
+
if (this.context) {
|
|
3174
|
+
this.context.examplesRow = null;
|
|
3175
|
+
}
|
|
3176
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3177
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3178
|
+
if (snapshot) {
|
|
3179
|
+
const obj = {};
|
|
3180
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
2849
3183
|
}
|
|
2850
3184
|
}
|
|
2851
3185
|
function createTimedPromise(promise, label) {
|
|
@@ -2853,156 +3187,5 @@ function createTimedPromise(promise, label) {
|
|
|
2853
3187
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2854
3188
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2855
3189
|
}
|
|
2856
|
-
const KEYBOARD_EVENTS = [
|
|
2857
|
-
"ALT",
|
|
2858
|
-
"AltGraph",
|
|
2859
|
-
"CapsLock",
|
|
2860
|
-
"Control",
|
|
2861
|
-
"Fn",
|
|
2862
|
-
"FnLock",
|
|
2863
|
-
"Hyper",
|
|
2864
|
-
"Meta",
|
|
2865
|
-
"NumLock",
|
|
2866
|
-
"ScrollLock",
|
|
2867
|
-
"Shift",
|
|
2868
|
-
"Super",
|
|
2869
|
-
"Symbol",
|
|
2870
|
-
"SymbolLock",
|
|
2871
|
-
"Enter",
|
|
2872
|
-
"Tab",
|
|
2873
|
-
"ArrowDown",
|
|
2874
|
-
"ArrowLeft",
|
|
2875
|
-
"ArrowRight",
|
|
2876
|
-
"ArrowUp",
|
|
2877
|
-
"End",
|
|
2878
|
-
"Home",
|
|
2879
|
-
"PageDown",
|
|
2880
|
-
"PageUp",
|
|
2881
|
-
"Backspace",
|
|
2882
|
-
"Clear",
|
|
2883
|
-
"Copy",
|
|
2884
|
-
"CrSel",
|
|
2885
|
-
"Cut",
|
|
2886
|
-
"Delete",
|
|
2887
|
-
"EraseEof",
|
|
2888
|
-
"ExSel",
|
|
2889
|
-
"Insert",
|
|
2890
|
-
"Paste",
|
|
2891
|
-
"Redo",
|
|
2892
|
-
"Undo",
|
|
2893
|
-
"Accept",
|
|
2894
|
-
"Again",
|
|
2895
|
-
"Attn",
|
|
2896
|
-
"Cancel",
|
|
2897
|
-
"ContextMenu",
|
|
2898
|
-
"Escape",
|
|
2899
|
-
"Execute",
|
|
2900
|
-
"Find",
|
|
2901
|
-
"Finish",
|
|
2902
|
-
"Help",
|
|
2903
|
-
"Pause",
|
|
2904
|
-
"Play",
|
|
2905
|
-
"Props",
|
|
2906
|
-
"Select",
|
|
2907
|
-
"ZoomIn",
|
|
2908
|
-
"ZoomOut",
|
|
2909
|
-
"BrightnessDown",
|
|
2910
|
-
"BrightnessUp",
|
|
2911
|
-
"Eject",
|
|
2912
|
-
"LogOff",
|
|
2913
|
-
"Power",
|
|
2914
|
-
"PowerOff",
|
|
2915
|
-
"PrintScreen",
|
|
2916
|
-
"Hibernate",
|
|
2917
|
-
"Standby",
|
|
2918
|
-
"WakeUp",
|
|
2919
|
-
"AllCandidates",
|
|
2920
|
-
"Alphanumeric",
|
|
2921
|
-
"CodeInput",
|
|
2922
|
-
"Compose",
|
|
2923
|
-
"Convert",
|
|
2924
|
-
"Dead",
|
|
2925
|
-
"FinalMode",
|
|
2926
|
-
"GroupFirst",
|
|
2927
|
-
"GroupLast",
|
|
2928
|
-
"GroupNext",
|
|
2929
|
-
"GroupPrevious",
|
|
2930
|
-
"ModeChange",
|
|
2931
|
-
"NextCandidate",
|
|
2932
|
-
"NonConvert",
|
|
2933
|
-
"PreviousCandidate",
|
|
2934
|
-
"Process",
|
|
2935
|
-
"SingleCandidate",
|
|
2936
|
-
"HangulMode",
|
|
2937
|
-
"HanjaMode",
|
|
2938
|
-
"JunjaMode",
|
|
2939
|
-
"Eisu",
|
|
2940
|
-
"Hankaku",
|
|
2941
|
-
"Hiragana",
|
|
2942
|
-
"HiraganaKatakana",
|
|
2943
|
-
"KanaMode",
|
|
2944
|
-
"KanjiMode",
|
|
2945
|
-
"Katakana",
|
|
2946
|
-
"Romaji",
|
|
2947
|
-
"Zenkaku",
|
|
2948
|
-
"ZenkakuHanaku",
|
|
2949
|
-
"F1",
|
|
2950
|
-
"F2",
|
|
2951
|
-
"F3",
|
|
2952
|
-
"F4",
|
|
2953
|
-
"F5",
|
|
2954
|
-
"F6",
|
|
2955
|
-
"F7",
|
|
2956
|
-
"F8",
|
|
2957
|
-
"F9",
|
|
2958
|
-
"F10",
|
|
2959
|
-
"F11",
|
|
2960
|
-
"F12",
|
|
2961
|
-
"Soft1",
|
|
2962
|
-
"Soft2",
|
|
2963
|
-
"Soft3",
|
|
2964
|
-
"Soft4",
|
|
2965
|
-
"ChannelDown",
|
|
2966
|
-
"ChannelUp",
|
|
2967
|
-
"Close",
|
|
2968
|
-
"MailForward",
|
|
2969
|
-
"MailReply",
|
|
2970
|
-
"MailSend",
|
|
2971
|
-
"MediaFastForward",
|
|
2972
|
-
"MediaPause",
|
|
2973
|
-
"MediaPlay",
|
|
2974
|
-
"MediaPlayPause",
|
|
2975
|
-
"MediaRecord",
|
|
2976
|
-
"MediaRewind",
|
|
2977
|
-
"MediaStop",
|
|
2978
|
-
"MediaTrackNext",
|
|
2979
|
-
"MediaTrackPrevious",
|
|
2980
|
-
"AudioBalanceLeft",
|
|
2981
|
-
"AudioBalanceRight",
|
|
2982
|
-
"AudioBassBoostDown",
|
|
2983
|
-
"AudioBassBoostToggle",
|
|
2984
|
-
"AudioBassBoostUp",
|
|
2985
|
-
"AudioFaderFront",
|
|
2986
|
-
"AudioFaderRear",
|
|
2987
|
-
"AudioSurroundModeNext",
|
|
2988
|
-
"AudioTrebleDown",
|
|
2989
|
-
"AudioTrebleUp",
|
|
2990
|
-
"AudioVolumeDown",
|
|
2991
|
-
"AudioVolumeMute",
|
|
2992
|
-
"AudioVolumeUp",
|
|
2993
|
-
"MicrophoneToggle",
|
|
2994
|
-
"MicrophoneVolumeDown",
|
|
2995
|
-
"MicrophoneVolumeMute",
|
|
2996
|
-
"MicrophoneVolumeUp",
|
|
2997
|
-
"TV",
|
|
2998
|
-
"TV3DMode",
|
|
2999
|
-
"TVAntennaCable",
|
|
3000
|
-
"TVAudioDescription",
|
|
3001
|
-
];
|
|
3002
|
-
function unEscapeString(str) {
|
|
3003
|
-
const placeholder = "__NEWLINE__";
|
|
3004
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
3005
|
-
return str;
|
|
3006
|
-
}
|
|
3007
3190
|
export { StableBrowser };
|
|
3008
3191
|
//# sourceMappingURL=stable_browser.js.map
|