automation_model 1.0.544-dev → 1.0.544-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 +722 -533
- 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
|
-
}
|
|
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
|
-
}
|
|
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
373
|
}
|
|
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++;
|
|
374
|
+
if (!el.setAttribute) {
|
|
375
|
+
el = el.parentElement;
|
|
495
376
|
}
|
|
377
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
378
|
+
return true;
|
|
379
|
+
}, [tag1, randomToken]))) {
|
|
380
|
+
continue;
|
|
496
381
|
}
|
|
497
|
-
|
|
498
|
-
}
|
|
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,38 +1943,58 @@ 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
|
-
val = await state.element.innerText();
|
|
1963
|
+
val = String(await state.element.innerText());
|
|
1904
1964
|
break;
|
|
1905
1965
|
case "value":
|
|
1906
|
-
val = await state.element.inputValue();
|
|
1966
|
+
val = String(await state.element.inputValue());
|
|
1967
|
+
break;
|
|
1968
|
+
case "checked":
|
|
1969
|
+
val = String(await state.element.isChecked());
|
|
1970
|
+
break;
|
|
1971
|
+
case "disabled":
|
|
1972
|
+
val = String(await state.element.isDisabled());
|
|
1973
|
+
break;
|
|
1974
|
+
case "readOnly":
|
|
1975
|
+
const isEditable = await state.element.isEditable();
|
|
1976
|
+
val = String(!isEditable);
|
|
1907
1977
|
break;
|
|
1908
1978
|
default:
|
|
1909
|
-
val = await state.element.getAttribute(attribute);
|
|
1979
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1910
1980
|
break;
|
|
1911
1981
|
}
|
|
1982
|
+
state.info.value = val;
|
|
1912
1983
|
let regex;
|
|
1913
|
-
if (
|
|
1914
|
-
const patternBody =
|
|
1984
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1985
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1915
1986
|
regex = new RegExp(patternBody, "g");
|
|
1916
1987
|
}
|
|
1917
1988
|
else {
|
|
1918
|
-
const escapedPattern =
|
|
1989
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1919
1990
|
regex = new RegExp(escapedPattern, "g");
|
|
1920
1991
|
}
|
|
1921
1992
|
if (!val.match(regex)) {
|
|
1922
|
-
|
|
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);
|
|
1923
1997
|
}
|
|
1924
|
-
state.info.value = val;
|
|
1925
1998
|
return state.info;
|
|
1926
1999
|
}
|
|
1927
2000
|
catch (e) {
|
|
@@ -1949,7 +2022,7 @@ class StableBrowser {
|
|
|
1949
2022
|
if (options && options.timeout) {
|
|
1950
2023
|
timeout = options.timeout;
|
|
1951
2024
|
}
|
|
1952
|
-
const serviceUrl =
|
|
2025
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1953
2026
|
const request = {
|
|
1954
2027
|
method: "POST",
|
|
1955
2028
|
url: serviceUrl,
|
|
@@ -2020,27 +2093,32 @@ class StableBrowser {
|
|
|
2020
2093
|
async _highlightElements(scope, css) {
|
|
2021
2094
|
try {
|
|
2022
2095
|
if (!scope) {
|
|
2096
|
+
// console.log(`Scope is not defined`);
|
|
2023
2097
|
return;
|
|
2024
2098
|
}
|
|
2025
2099
|
if (!css) {
|
|
2026
2100
|
scope
|
|
2027
2101
|
.evaluate((node) => {
|
|
2028
2102
|
if (node && node.style) {
|
|
2029
|
-
let
|
|
2030
|
-
|
|
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}`);
|
|
2031
2108
|
if (window) {
|
|
2032
2109
|
window.addEventListener("beforeunload", function (e) {
|
|
2033
|
-
node.style.
|
|
2110
|
+
node.style.outline = originalOutline;
|
|
2034
2111
|
});
|
|
2035
2112
|
}
|
|
2036
2113
|
setTimeout(function () {
|
|
2037
|
-
node.style.
|
|
2114
|
+
node.style.outline = originalOutline;
|
|
2038
2115
|
}, 2000);
|
|
2039
2116
|
}
|
|
2040
2117
|
})
|
|
2041
2118
|
.then(() => { })
|
|
2042
2119
|
.catch((e) => {
|
|
2043
2120
|
// ignore
|
|
2121
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2044
2122
|
});
|
|
2045
2123
|
}
|
|
2046
2124
|
else {
|
|
@@ -2056,17 +2134,18 @@ class StableBrowser {
|
|
|
2056
2134
|
if (!element.style) {
|
|
2057
2135
|
return;
|
|
2058
2136
|
}
|
|
2059
|
-
|
|
2137
|
+
let originalOutline = element.style.outline;
|
|
2138
|
+
element.__previousOutline = originalOutline;
|
|
2060
2139
|
// Set the new border to be red and 2px solid
|
|
2061
|
-
element.style.
|
|
2140
|
+
element.style.outline = "2px solid red";
|
|
2062
2141
|
if (window) {
|
|
2063
2142
|
window.addEventListener("beforeunload", function (e) {
|
|
2064
|
-
element.style.
|
|
2143
|
+
element.style.outline = originalOutline;
|
|
2065
2144
|
});
|
|
2066
2145
|
}
|
|
2067
2146
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2068
2147
|
setTimeout(function () {
|
|
2069
|
-
element.style.
|
|
2148
|
+
element.style.outline = originalOutline;
|
|
2070
2149
|
}, 2000);
|
|
2071
2150
|
}
|
|
2072
2151
|
return;
|
|
@@ -2074,6 +2153,7 @@ class StableBrowser {
|
|
|
2074
2153
|
.then(() => { })
|
|
2075
2154
|
.catch((e) => {
|
|
2076
2155
|
// ignore
|
|
2156
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2077
2157
|
});
|
|
2078
2158
|
}
|
|
2079
2159
|
}
|
|
@@ -2081,6 +2161,54 @@ class StableBrowser {
|
|
|
2081
2161
|
console.debug(error);
|
|
2082
2162
|
}
|
|
2083
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
|
+
// }
|
|
2084
2212
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2085
2213
|
const startTime = Date.now();
|
|
2086
2214
|
let error = null;
|
|
@@ -2125,6 +2253,7 @@ class StableBrowser {
|
|
|
2125
2253
|
_reportToWorld(world, {
|
|
2126
2254
|
type: Types.VERIFY_PAGE_PATH,
|
|
2127
2255
|
text: "Verify page path",
|
|
2256
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2128
2257
|
screenshotId,
|
|
2129
2258
|
result: error
|
|
2130
2259
|
? {
|
|
@@ -2142,6 +2271,35 @@ class StableBrowser {
|
|
|
2142
2271
|
});
|
|
2143
2272
|
}
|
|
2144
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
|
+
}
|
|
2145
2303
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2146
2304
|
text = unEscapeString(text);
|
|
2147
2305
|
const state = {
|
|
@@ -2151,12 +2309,13 @@ class StableBrowser {
|
|
|
2151
2309
|
locate: false,
|
|
2152
2310
|
scroll: false,
|
|
2153
2311
|
highlight: false,
|
|
2154
|
-
type: Types.
|
|
2312
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2155
2313
|
text: `Verify text exists in page`,
|
|
2314
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2156
2315
|
operation: "verifyTextExistInPage",
|
|
2157
2316
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2158
2317
|
};
|
|
2159
|
-
const timeout = this.
|
|
2318
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2160
2319
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2161
2320
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2162
2321
|
if (newValue !== text) {
|
|
@@ -2169,31 +2328,15 @@ class StableBrowser {
|
|
|
2169
2328
|
await _preCommand(state, this);
|
|
2170
2329
|
state.info.text = text;
|
|
2171
2330
|
while (true) {
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
else if (numberAlternatives.number) {
|
|
2183
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2184
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2185
|
-
result.frame = frames[i];
|
|
2186
|
-
results.push(result);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
else {
|
|
2190
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2191
|
-
result.frame = frames[i];
|
|
2192
|
-
results.push(result);
|
|
2193
|
-
}
|
|
2331
|
+
let resultWithElementsFound = {
|
|
2332
|
+
length: 0,
|
|
2333
|
+
};
|
|
2334
|
+
try {
|
|
2335
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
// ignore
|
|
2194
2339
|
}
|
|
2195
|
-
state.info.results = results;
|
|
2196
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2197
2340
|
if (resultWithElementsFound.length === 0) {
|
|
2198
2341
|
if (Date.now() - state.startTime > timeout) {
|
|
2199
2342
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2201,18 +2344,40 @@ class StableBrowser {
|
|
|
2201
2344
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2202
2345
|
continue;
|
|
2203
2346
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
+
}
|
|
2212
2374
|
}
|
|
2375
|
+
await _screenshot(state, this);
|
|
2376
|
+
return state.info;
|
|
2377
|
+
}
|
|
2378
|
+
catch (error) {
|
|
2379
|
+
console.error(error);
|
|
2213
2380
|
}
|
|
2214
|
-
await _screenshot(state, this);
|
|
2215
|
-
return state.info;
|
|
2216
2381
|
}
|
|
2217
2382
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2218
2383
|
}
|
|
@@ -2234,10 +2399,11 @@ class StableBrowser {
|
|
|
2234
2399
|
highlight: false,
|
|
2235
2400
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2236
2401
|
text: `Verify text does not exist in page`,
|
|
2402
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2237
2403
|
operation: "verifyTextNotExistInPage",
|
|
2238
2404
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2239
2405
|
};
|
|
2240
|
-
const timeout = this.
|
|
2406
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2241
2407
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2242
2408
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2243
2409
|
if (newValue !== text) {
|
|
@@ -2249,32 +2415,16 @@ class StableBrowser {
|
|
|
2249
2415
|
try {
|
|
2250
2416
|
await _preCommand(state, this);
|
|
2251
2417
|
state.info.text = text;
|
|
2418
|
+
let resultWithElementsFound = {
|
|
2419
|
+
length: null, // initial cannot be 0
|
|
2420
|
+
};
|
|
2252
2421
|
while (true) {
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2259
|
-
result.frame = frames[i];
|
|
2260
|
-
results.push(result);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
else if (numberAlternatives.number) {
|
|
2264
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2265
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2266
|
-
result.frame = frames[i];
|
|
2267
|
-
results.push(result);
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
else {
|
|
2271
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2272
|
-
result.frame = frames[i];
|
|
2273
|
-
results.push(result);
|
|
2274
|
-
}
|
|
2422
|
+
try {
|
|
2423
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
// ignore
|
|
2275
2427
|
}
|
|
2276
|
-
state.info.results = results;
|
|
2277
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2278
2428
|
if (resultWithElementsFound.length === 0) {
|
|
2279
2429
|
await _screenshot(state, this);
|
|
2280
2430
|
return state.info;
|
|
@@ -2292,15 +2442,116 @@ class StableBrowser {
|
|
|
2292
2442
|
_commandFinally(state, this);
|
|
2293
2443
|
}
|
|
2294
2444
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
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 });
|
|
2299
2548
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2549
|
+
catch (e) {
|
|
2550
|
+
await _commandError(state, e, this);
|
|
2551
|
+
}
|
|
2552
|
+
finally {
|
|
2553
|
+
_commandFinally(state, this);
|
|
2302
2554
|
}
|
|
2303
|
-
return serviceUrl;
|
|
2304
2555
|
}
|
|
2305
2556
|
async visualVerification(text, options = {}, world = null) {
|
|
2306
2557
|
const startTime = Date.now();
|
|
@@ -2316,14 +2567,17 @@ class StableBrowser {
|
|
|
2316
2567
|
throw new Error("TOKEN is not set");
|
|
2317
2568
|
}
|
|
2318
2569
|
try {
|
|
2319
|
-
let serviceUrl =
|
|
2570
|
+
let serviceUrl = _getServerUrl();
|
|
2320
2571
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2321
2572
|
info.screenshotPath = screenshotPath;
|
|
2322
2573
|
const screenshot = await this.takeScreenshot();
|
|
2323
|
-
|
|
2324
|
-
method: "
|
|
2574
|
+
let request = {
|
|
2575
|
+
method: "post",
|
|
2576
|
+
maxBodyLength: Infinity,
|
|
2325
2577
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2326
2578
|
headers: {
|
|
2579
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2580
|
+
"x-source": "aaa",
|
|
2327
2581
|
"Content-Type": "application/json",
|
|
2328
2582
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2329
2583
|
},
|
|
@@ -2332,7 +2586,7 @@ class StableBrowser {
|
|
|
2332
2586
|
screenshot: screenshot,
|
|
2333
2587
|
}),
|
|
2334
2588
|
};
|
|
2335
|
-
|
|
2589
|
+
const result = await axios.request(request);
|
|
2336
2590
|
if (result.data.status !== true) {
|
|
2337
2591
|
throw new Error("Visual validation failed");
|
|
2338
2592
|
}
|
|
@@ -2360,6 +2614,7 @@ class StableBrowser {
|
|
|
2360
2614
|
_reportToWorld(world, {
|
|
2361
2615
|
type: Types.VERIFY_VISUAL,
|
|
2362
2616
|
text: "Visual verification",
|
|
2617
|
+
_text: "Visual verification of " + text,
|
|
2363
2618
|
screenshotId,
|
|
2364
2619
|
result: error
|
|
2365
2620
|
? {
|
|
@@ -2405,6 +2660,7 @@ class StableBrowser {
|
|
|
2405
2660
|
let screenshotPath = null;
|
|
2406
2661
|
const info = {};
|
|
2407
2662
|
info.log = "";
|
|
2663
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2408
2664
|
info.operation = "getTableData";
|
|
2409
2665
|
info.selectors = selectors;
|
|
2410
2666
|
try {
|
|
@@ -2480,7 +2736,7 @@ class StableBrowser {
|
|
|
2480
2736
|
info.operation = "analyzeTable";
|
|
2481
2737
|
info.selectors = selectors;
|
|
2482
2738
|
info.query = query;
|
|
2483
|
-
query =
|
|
2739
|
+
query = _fixUsingParams(query, _params);
|
|
2484
2740
|
info.query_fixed = query;
|
|
2485
2741
|
info.operator = operator;
|
|
2486
2742
|
info.value = value;
|
|
@@ -2625,6 +2881,32 @@ class StableBrowser {
|
|
|
2625
2881
|
}
|
|
2626
2882
|
return timeout;
|
|
2627
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
|
+
}
|
|
2628
2910
|
async waitForPageLoad(options = {}, world = null) {
|
|
2629
2911
|
let timeout = this._getLoadTimeout(options);
|
|
2630
2912
|
const promiseArray = [];
|
|
@@ -2692,6 +2974,7 @@ class StableBrowser {
|
|
|
2692
2974
|
highlight: false,
|
|
2693
2975
|
type: Types.CLOSE_PAGE,
|
|
2694
2976
|
text: `Close page`,
|
|
2977
|
+
_text: `Close the page`,
|
|
2695
2978
|
operation: "closePage",
|
|
2696
2979
|
log: "***** close page *****\n",
|
|
2697
2980
|
throwError: false,
|
|
@@ -2739,6 +3022,7 @@ class StableBrowser {
|
|
|
2739
3022
|
_reportToWorld(world, {
|
|
2740
3023
|
type: Types.SET_VIEWPORT,
|
|
2741
3024
|
text: "set viewport size to " + width + "x" + hight,
|
|
3025
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2742
3026
|
screenshotId,
|
|
2743
3027
|
result: error
|
|
2744
3028
|
? {
|
|
@@ -2810,14 +3094,25 @@ class StableBrowser {
|
|
|
2810
3094
|
}
|
|
2811
3095
|
}
|
|
2812
3096
|
async beforeStep(world, step) {
|
|
2813
|
-
this.stepName = step.pickleStep.text;
|
|
2814
|
-
this.logger.info("step: " + this.stepName);
|
|
2815
3097
|
if (this.stepIndex === undefined) {
|
|
2816
3098
|
this.stepIndex = 0;
|
|
2817
3099
|
}
|
|
2818
3100
|
else {
|
|
2819
3101
|
this.stepIndex++;
|
|
2820
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
|
+
}
|
|
2821
3116
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2822
3117
|
if (this.context.browserObject.context) {
|
|
2823
3118
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2830,6 +3125,41 @@ class StableBrowser {
|
|
|
2830
3125
|
this.saveTestDataAsGlobal({}, world);
|
|
2831
3126
|
}
|
|
2832
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;
|
|
2833
3163
|
}
|
|
2834
3164
|
async afterStep(world, step) {
|
|
2835
3165
|
this.stepName = null;
|
|
@@ -2840,6 +3170,16 @@ class StableBrowser {
|
|
|
2840
3170
|
});
|
|
2841
3171
|
}
|
|
2842
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
|
+
}
|
|
2843
3183
|
}
|
|
2844
3184
|
}
|
|
2845
3185
|
function createTimedPromise(promise, label) {
|
|
@@ -2847,156 +3187,5 @@ function createTimedPromise(promise, label) {
|
|
|
2847
3187
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2848
3188
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2849
3189
|
}
|
|
2850
|
-
const KEYBOARD_EVENTS = [
|
|
2851
|
-
"ALT",
|
|
2852
|
-
"AltGraph",
|
|
2853
|
-
"CapsLock",
|
|
2854
|
-
"Control",
|
|
2855
|
-
"Fn",
|
|
2856
|
-
"FnLock",
|
|
2857
|
-
"Hyper",
|
|
2858
|
-
"Meta",
|
|
2859
|
-
"NumLock",
|
|
2860
|
-
"ScrollLock",
|
|
2861
|
-
"Shift",
|
|
2862
|
-
"Super",
|
|
2863
|
-
"Symbol",
|
|
2864
|
-
"SymbolLock",
|
|
2865
|
-
"Enter",
|
|
2866
|
-
"Tab",
|
|
2867
|
-
"ArrowDown",
|
|
2868
|
-
"ArrowLeft",
|
|
2869
|
-
"ArrowRight",
|
|
2870
|
-
"ArrowUp",
|
|
2871
|
-
"End",
|
|
2872
|
-
"Home",
|
|
2873
|
-
"PageDown",
|
|
2874
|
-
"PageUp",
|
|
2875
|
-
"Backspace",
|
|
2876
|
-
"Clear",
|
|
2877
|
-
"Copy",
|
|
2878
|
-
"CrSel",
|
|
2879
|
-
"Cut",
|
|
2880
|
-
"Delete",
|
|
2881
|
-
"EraseEof",
|
|
2882
|
-
"ExSel",
|
|
2883
|
-
"Insert",
|
|
2884
|
-
"Paste",
|
|
2885
|
-
"Redo",
|
|
2886
|
-
"Undo",
|
|
2887
|
-
"Accept",
|
|
2888
|
-
"Again",
|
|
2889
|
-
"Attn",
|
|
2890
|
-
"Cancel",
|
|
2891
|
-
"ContextMenu",
|
|
2892
|
-
"Escape",
|
|
2893
|
-
"Execute",
|
|
2894
|
-
"Find",
|
|
2895
|
-
"Finish",
|
|
2896
|
-
"Help",
|
|
2897
|
-
"Pause",
|
|
2898
|
-
"Play",
|
|
2899
|
-
"Props",
|
|
2900
|
-
"Select",
|
|
2901
|
-
"ZoomIn",
|
|
2902
|
-
"ZoomOut",
|
|
2903
|
-
"BrightnessDown",
|
|
2904
|
-
"BrightnessUp",
|
|
2905
|
-
"Eject",
|
|
2906
|
-
"LogOff",
|
|
2907
|
-
"Power",
|
|
2908
|
-
"PowerOff",
|
|
2909
|
-
"PrintScreen",
|
|
2910
|
-
"Hibernate",
|
|
2911
|
-
"Standby",
|
|
2912
|
-
"WakeUp",
|
|
2913
|
-
"AllCandidates",
|
|
2914
|
-
"Alphanumeric",
|
|
2915
|
-
"CodeInput",
|
|
2916
|
-
"Compose",
|
|
2917
|
-
"Convert",
|
|
2918
|
-
"Dead",
|
|
2919
|
-
"FinalMode",
|
|
2920
|
-
"GroupFirst",
|
|
2921
|
-
"GroupLast",
|
|
2922
|
-
"GroupNext",
|
|
2923
|
-
"GroupPrevious",
|
|
2924
|
-
"ModeChange",
|
|
2925
|
-
"NextCandidate",
|
|
2926
|
-
"NonConvert",
|
|
2927
|
-
"PreviousCandidate",
|
|
2928
|
-
"Process",
|
|
2929
|
-
"SingleCandidate",
|
|
2930
|
-
"HangulMode",
|
|
2931
|
-
"HanjaMode",
|
|
2932
|
-
"JunjaMode",
|
|
2933
|
-
"Eisu",
|
|
2934
|
-
"Hankaku",
|
|
2935
|
-
"Hiragana",
|
|
2936
|
-
"HiraganaKatakana",
|
|
2937
|
-
"KanaMode",
|
|
2938
|
-
"KanjiMode",
|
|
2939
|
-
"Katakana",
|
|
2940
|
-
"Romaji",
|
|
2941
|
-
"Zenkaku",
|
|
2942
|
-
"ZenkakuHanaku",
|
|
2943
|
-
"F1",
|
|
2944
|
-
"F2",
|
|
2945
|
-
"F3",
|
|
2946
|
-
"F4",
|
|
2947
|
-
"F5",
|
|
2948
|
-
"F6",
|
|
2949
|
-
"F7",
|
|
2950
|
-
"F8",
|
|
2951
|
-
"F9",
|
|
2952
|
-
"F10",
|
|
2953
|
-
"F11",
|
|
2954
|
-
"F12",
|
|
2955
|
-
"Soft1",
|
|
2956
|
-
"Soft2",
|
|
2957
|
-
"Soft3",
|
|
2958
|
-
"Soft4",
|
|
2959
|
-
"ChannelDown",
|
|
2960
|
-
"ChannelUp",
|
|
2961
|
-
"Close",
|
|
2962
|
-
"MailForward",
|
|
2963
|
-
"MailReply",
|
|
2964
|
-
"MailSend",
|
|
2965
|
-
"MediaFastForward",
|
|
2966
|
-
"MediaPause",
|
|
2967
|
-
"MediaPlay",
|
|
2968
|
-
"MediaPlayPause",
|
|
2969
|
-
"MediaRecord",
|
|
2970
|
-
"MediaRewind",
|
|
2971
|
-
"MediaStop",
|
|
2972
|
-
"MediaTrackNext",
|
|
2973
|
-
"MediaTrackPrevious",
|
|
2974
|
-
"AudioBalanceLeft",
|
|
2975
|
-
"AudioBalanceRight",
|
|
2976
|
-
"AudioBassBoostDown",
|
|
2977
|
-
"AudioBassBoostToggle",
|
|
2978
|
-
"AudioBassBoostUp",
|
|
2979
|
-
"AudioFaderFront",
|
|
2980
|
-
"AudioFaderRear",
|
|
2981
|
-
"AudioSurroundModeNext",
|
|
2982
|
-
"AudioTrebleDown",
|
|
2983
|
-
"AudioTrebleUp",
|
|
2984
|
-
"AudioVolumeDown",
|
|
2985
|
-
"AudioVolumeMute",
|
|
2986
|
-
"AudioVolumeUp",
|
|
2987
|
-
"MicrophoneToggle",
|
|
2988
|
-
"MicrophoneVolumeDown",
|
|
2989
|
-
"MicrophoneVolumeMute",
|
|
2990
|
-
"MicrophoneVolumeUp",
|
|
2991
|
-
"TV",
|
|
2992
|
-
"TV3DMode",
|
|
2993
|
-
"TVAntennaCable",
|
|
2994
|
-
"TVAudioDescription",
|
|
2995
|
-
];
|
|
2996
|
-
function unEscapeString(str) {
|
|
2997
|
-
const placeholder = "__NEWLINE__";
|
|
2998
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2999
|
-
return str;
|
|
3000
|
-
}
|
|
3001
3190
|
export { StableBrowser };
|
|
3002
3191
|
//# sourceMappingURL=stable_browser.js.map
|