automation_model 1.0.548-dev → 1.0.548-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 +680 -527
- 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 +16 -1
- package/lib/utils.js +416 -6
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,17 +10,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, testForRegex, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
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
278
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
else if (this._isObject(currentObj[key])) {
|
|
310
|
-
// Recursively scan nested objects
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return new RegExp(pattern, flags);
|
|
401
|
-
}
|
|
402
|
-
document.getRegex = getRegex;
|
|
403
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
404
|
-
// Check and add the element if it has a shadow root
|
|
405
|
-
if (element.shadowRoot) {
|
|
406
|
-
result.push(element);
|
|
407
|
-
// Also search within the shadow root
|
|
408
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
409
|
-
}
|
|
410
|
-
// Iterate over child nodes
|
|
411
|
-
element.childNodes.forEach((child) => {
|
|
412
|
-
// Recursively call the function for each child node
|
|
413
|
-
document.collectAllShadowDomElements(child, result);
|
|
414
|
-
});
|
|
415
|
-
return result;
|
|
416
|
-
}
|
|
417
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
418
|
-
if (!tag) {
|
|
419
|
-
tag = "*:not(script, style, head)";
|
|
420
|
-
}
|
|
421
|
-
let regexpSearch = document.getRegex(text);
|
|
422
|
-
if (regexpSearch) {
|
|
423
|
-
regex = true;
|
|
424
|
-
}
|
|
425
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
426
|
-
let shadowHosts = [];
|
|
427
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
428
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
429
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
430
|
-
if (!shadowElement) {
|
|
431
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
435
|
-
elements = elements.concat(shadowElements);
|
|
436
|
-
}
|
|
437
|
-
let randomToken = null;
|
|
438
|
-
const foundElements = [];
|
|
439
|
-
if (regex) {
|
|
440
|
-
if (!regexpSearch) {
|
|
441
|
-
regexpSearch = new RegExp(text, "im");
|
|
442
|
-
}
|
|
443
|
-
for (let i = 0; i < elements.length; i++) {
|
|
444
|
-
const element = elements[i];
|
|
445
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
446
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
447
|
-
foundElements.push(element);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
text = text.trim();
|
|
453
|
-
for (let i = 0; i < elements.length; i++) {
|
|
454
|
-
const element = elements[i];
|
|
455
|
-
if (partial) {
|
|
456
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
457
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
458
|
-
foundElements.push(element);
|
|
459
|
-
}
|
|
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;
|
|
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;
|
|
480
372
|
}
|
|
481
373
|
}
|
|
482
|
-
if (!
|
|
483
|
-
|
|
374
|
+
if (!el.setAttribute) {
|
|
375
|
+
el = el.parentElement;
|
|
484
376
|
}
|
|
377
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
378
|
+
return true;
|
|
379
|
+
}, [tag1, randomToken]))) {
|
|
380
|
+
continue;
|
|
485
381
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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,6 +826,9 @@ 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;
|
|
@@ -999,15 +938,16 @@ class StableBrowser {
|
|
|
999
938
|
options,
|
|
1000
939
|
world,
|
|
1001
940
|
text: "Click element",
|
|
941
|
+
_text: "Click on " + selectors.element_name,
|
|
1002
942
|
type: Types.CLICK,
|
|
1003
943
|
operation: "click",
|
|
1004
944
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1005
945
|
};
|
|
1006
946
|
try {
|
|
1007
947
|
await _preCommand(state, this);
|
|
1008
|
-
if (state.options && state.options.context) {
|
|
1009
|
-
|
|
1010
|
-
}
|
|
948
|
+
// if (state.options && state.options.context) {
|
|
949
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
950
|
+
// }
|
|
1011
951
|
try {
|
|
1012
952
|
await state.element.click();
|
|
1013
953
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1028,6 +968,38 @@ class StableBrowser {
|
|
|
1028
968
|
_commandFinally(state, this);
|
|
1029
969
|
}
|
|
1030
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
|
+
}
|
|
1031
1003
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
1032
1004
|
const state = {
|
|
1033
1005
|
selectors,
|
|
@@ -1036,6 +1008,7 @@ class StableBrowser {
|
|
|
1036
1008
|
world,
|
|
1037
1009
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1038
1010
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1011
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1039
1012
|
operation: "setCheck",
|
|
1040
1013
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1041
1014
|
};
|
|
@@ -1045,9 +1018,15 @@ class StableBrowser {
|
|
|
1045
1018
|
// let element = await this._locate(selectors, info, _params);
|
|
1046
1019
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1047
1020
|
try {
|
|
1048
|
-
//
|
|
1021
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1022
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1023
|
+
await this._highlightElements(element);
|
|
1049
1024
|
await state.element.setChecked(checked);
|
|
1050
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);
|
|
1051
1030
|
}
|
|
1052
1031
|
catch (e) {
|
|
1053
1032
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1079,6 +1058,7 @@ class StableBrowser {
|
|
|
1079
1058
|
world,
|
|
1080
1059
|
type: Types.HOVER,
|
|
1081
1060
|
text: `Hover element`,
|
|
1061
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1082
1062
|
operation: "hover",
|
|
1083
1063
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1084
1064
|
};
|
|
@@ -1086,6 +1066,7 @@ class StableBrowser {
|
|
|
1086
1066
|
await _preCommand(state, this);
|
|
1087
1067
|
try {
|
|
1088
1068
|
await state.element.hover();
|
|
1069
|
+
// await _screenshot(state, this);
|
|
1089
1070
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1090
1071
|
}
|
|
1091
1072
|
catch (e) {
|
|
@@ -1093,6 +1074,7 @@ class StableBrowser {
|
|
|
1093
1074
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1094
1075
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1095
1076
|
await state.element.hover({ timeout: 10000 });
|
|
1077
|
+
// await _screenshot(state, this);
|
|
1096
1078
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1097
1079
|
}
|
|
1098
1080
|
await _screenshot(state, this);
|
|
@@ -1118,6 +1100,7 @@ class StableBrowser {
|
|
|
1118
1100
|
value: values.toString(),
|
|
1119
1101
|
type: Types.SELECT,
|
|
1120
1102
|
text: `Select option: ${values}`,
|
|
1103
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1121
1104
|
operation: "selectOption",
|
|
1122
1105
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1123
1106
|
};
|
|
@@ -1152,6 +1135,7 @@ class StableBrowser {
|
|
|
1152
1135
|
highlight: false,
|
|
1153
1136
|
type: Types.TYPE_PRESS,
|
|
1154
1137
|
text: `Type value: ${_value}`,
|
|
1138
|
+
_text: `Type value: ${_value}`,
|
|
1155
1139
|
operation: "type",
|
|
1156
1140
|
log: "",
|
|
1157
1141
|
};
|
|
@@ -1231,6 +1215,7 @@ class StableBrowser {
|
|
|
1231
1215
|
world,
|
|
1232
1216
|
type: Types.SET_DATE_TIME,
|
|
1233
1217
|
text: `Set date time value: ${value}`,
|
|
1218
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1234
1219
|
operation: "setDateTime",
|
|
1235
1220
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1236
1221
|
throwError: false,
|
|
@@ -1302,6 +1287,7 @@ class StableBrowser {
|
|
|
1302
1287
|
world,
|
|
1303
1288
|
type: Types.FILL,
|
|
1304
1289
|
text: `Click type input with value: ${_value}`,
|
|
1290
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1305
1291
|
operation: "clickType",
|
|
1306
1292
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1307
1293
|
};
|
|
@@ -1367,7 +1353,12 @@ class StableBrowser {
|
|
|
1367
1353
|
await this.waitForPageLoad();
|
|
1368
1354
|
}
|
|
1369
1355
|
else if (enter === false) {
|
|
1370
|
-
|
|
1356
|
+
try {
|
|
1357
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1358
|
+
}
|
|
1359
|
+
catch (e) {
|
|
1360
|
+
// ignore
|
|
1361
|
+
}
|
|
1371
1362
|
//await this.page.keyboard.press("Tab");
|
|
1372
1363
|
}
|
|
1373
1364
|
else {
|
|
@@ -1419,15 +1410,17 @@ class StableBrowser {
|
|
|
1419
1410
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1420
1411
|
}
|
|
1421
1412
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1413
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1422
1414
|
_validateSelectors(selectors);
|
|
1423
1415
|
let screenshotId = null;
|
|
1424
1416
|
let screenshotPath = null;
|
|
1425
1417
|
if (!info.log) {
|
|
1426
1418
|
info.log = "";
|
|
1419
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1427
1420
|
}
|
|
1428
1421
|
info.operation = "getText";
|
|
1429
1422
|
info.selectors = selectors;
|
|
1430
|
-
let element = await this._locate(selectors, info, _params);
|
|
1423
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1431
1424
|
if (climb > 0) {
|
|
1432
1425
|
const climbArray = [];
|
|
1433
1426
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1446,6 +1439,18 @@ class StableBrowser {
|
|
|
1446
1439
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1447
1440
|
try {
|
|
1448
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
|
+
// }
|
|
1449
1454
|
const elementText = await element.innerText();
|
|
1450
1455
|
return {
|
|
1451
1456
|
text: elementText,
|
|
@@ -1457,7 +1462,7 @@ class StableBrowser {
|
|
|
1457
1462
|
}
|
|
1458
1463
|
catch (e) {
|
|
1459
1464
|
//await this.closeUnexpectedPopups();
|
|
1460
|
-
this.logger.info("no innerText will use textContent");
|
|
1465
|
+
this.logger.info("no innerText, will use textContent");
|
|
1461
1466
|
const elementText = await element.textContent();
|
|
1462
1467
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1463
1468
|
}
|
|
@@ -1482,6 +1487,7 @@ class StableBrowser {
|
|
|
1482
1487
|
highlight: false,
|
|
1483
1488
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1484
1489
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1490
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1485
1491
|
operation: "containsPattern",
|
|
1486
1492
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1487
1493
|
};
|
|
@@ -1517,6 +1523,8 @@ class StableBrowser {
|
|
|
1517
1523
|
}
|
|
1518
1524
|
}
|
|
1519
1525
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1526
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1527
|
+
const startTime = Date.now();
|
|
1520
1528
|
const state = {
|
|
1521
1529
|
selectors,
|
|
1522
1530
|
_params,
|
|
@@ -1543,62 +1551,54 @@ class StableBrowser {
|
|
|
1543
1551
|
}
|
|
1544
1552
|
let foundObj = null;
|
|
1545
1553
|
try {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1553
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1554
|
-
if (dateAlternatives.date) {
|
|
1555
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1556
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1557
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1558
|
-
return state.info;
|
|
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);
|
|
1559
1560
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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)) {
|
|
1567
1581
|
return state.info;
|
|
1568
1582
|
}
|
|
1569
1583
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
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
|
|
1576
1589
|
}
|
|
1577
|
-
|
|
1590
|
+
state.info.foundText = foundObj?.text;
|
|
1591
|
+
state.info.value = foundObj?.value;
|
|
1592
|
+
throw new Error("element doesn't contain text " + text);
|
|
1578
1593
|
}
|
|
1579
1594
|
catch (e) {
|
|
1580
1595
|
await _commandError(state, e, this);
|
|
1596
|
+
throw e;
|
|
1581
1597
|
}
|
|
1582
1598
|
finally {
|
|
1583
1599
|
_commandFinally(state, this);
|
|
1584
1600
|
}
|
|
1585
1601
|
}
|
|
1586
|
-
_getDataFile(world = null) {
|
|
1587
|
-
let dataFile = null;
|
|
1588
|
-
if (world && world.reportFolder) {
|
|
1589
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1590
|
-
}
|
|
1591
|
-
else if (this.reportFolder) {
|
|
1592
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1593
|
-
}
|
|
1594
|
-
else if (this.context && this.context.reportFolder) {
|
|
1595
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1596
|
-
}
|
|
1597
|
-
else {
|
|
1598
|
-
dataFile = "data.json";
|
|
1599
|
-
}
|
|
1600
|
-
return dataFile;
|
|
1601
|
-
}
|
|
1602
1602
|
async waitForUserInput(message, world = null) {
|
|
1603
1603
|
if (!message) {
|
|
1604
1604
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1627,7 +1627,7 @@ class StableBrowser {
|
|
|
1627
1627
|
return;
|
|
1628
1628
|
}
|
|
1629
1629
|
// if data file exists, load it
|
|
1630
|
-
const dataFile =
|
|
1630
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1631
1631
|
let data = this.getTestData(world);
|
|
1632
1632
|
// merge the testData with the existing data
|
|
1633
1633
|
Object.assign(data, testData);
|
|
@@ -1730,7 +1730,7 @@ class StableBrowser {
|
|
|
1730
1730
|
}
|
|
1731
1731
|
}
|
|
1732
1732
|
getTestData(world = null) {
|
|
1733
|
-
const dataFile =
|
|
1733
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1734
1734
|
let data = {};
|
|
1735
1735
|
if (fs.existsSync(dataFile)) {
|
|
1736
1736
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1817,6 +1817,15 @@ class StableBrowser {
|
|
|
1817
1817
|
document.documentElement.clientWidth,
|
|
1818
1818
|
])));
|
|
1819
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
|
+
// }
|
|
1820
1829
|
if (this.context.browserName === "chromium") {
|
|
1821
1830
|
const client = await playContext.newCDPSession(this.page);
|
|
1822
1831
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1838,6 +1847,10 @@ class StableBrowser {
|
|
|
1838
1847
|
else {
|
|
1839
1848
|
screenshotBuffer = await this.page.screenshot();
|
|
1840
1849
|
}
|
|
1850
|
+
// if (focusedElement) {
|
|
1851
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1852
|
+
// await this._unhighlightElements(focusedElement);
|
|
1853
|
+
// }
|
|
1841
1854
|
let image = await Jimp.read(screenshotBuffer);
|
|
1842
1855
|
// Get the image dimensions
|
|
1843
1856
|
const { width, height } = image.bitmap;
|
|
@@ -1850,6 +1863,7 @@ class StableBrowser {
|
|
|
1850
1863
|
else {
|
|
1851
1864
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1852
1865
|
}
|
|
1866
|
+
return screenshotBuffer;
|
|
1853
1867
|
}
|
|
1854
1868
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1855
1869
|
const state = {
|
|
@@ -1885,8 +1899,10 @@ class StableBrowser {
|
|
|
1885
1899
|
world,
|
|
1886
1900
|
type: Types.EXTRACT,
|
|
1887
1901
|
text: `Extract attribute from element`,
|
|
1902
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1888
1903
|
operation: "extractAttribute",
|
|
1889
1904
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1905
|
+
allowDisabled: true,
|
|
1890
1906
|
};
|
|
1891
1907
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1892
1908
|
try {
|
|
@@ -1908,6 +1924,7 @@ class StableBrowser {
|
|
|
1908
1924
|
state.info.value = state.value;
|
|
1909
1925
|
this.setTestData({ [variable]: state.value }, world);
|
|
1910
1926
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1927
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1911
1928
|
return state.info;
|
|
1912
1929
|
}
|
|
1913
1930
|
catch (e) {
|
|
@@ -1926,14 +1943,21 @@ class StableBrowser {
|
|
|
1926
1943
|
options,
|
|
1927
1944
|
world,
|
|
1928
1945
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1946
|
+
highlight: true,
|
|
1947
|
+
screenshot: true,
|
|
1929
1948
|
text: `Verify element attribute`,
|
|
1949
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1930
1950
|
operation: "verifyAttribute",
|
|
1931
1951
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1952
|
+
allowDisabled: true,
|
|
1932
1953
|
};
|
|
1933
1954
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1934
1955
|
let val;
|
|
1956
|
+
let expectedValue;
|
|
1935
1957
|
try {
|
|
1936
1958
|
await _preCommand(state, this);
|
|
1959
|
+
expectedValue = state.value;
|
|
1960
|
+
state.info.expectedValue = expectedValue;
|
|
1937
1961
|
switch (attribute) {
|
|
1938
1962
|
case "innerText":
|
|
1939
1963
|
val = String(await state.element.innerText());
|
|
@@ -1947,23 +1971,30 @@ class StableBrowser {
|
|
|
1947
1971
|
case "disabled":
|
|
1948
1972
|
val = String(await state.element.isDisabled());
|
|
1949
1973
|
break;
|
|
1974
|
+
case "readOnly":
|
|
1975
|
+
const isEditable = await state.element.isEditable();
|
|
1976
|
+
val = String(!isEditable);
|
|
1977
|
+
break;
|
|
1950
1978
|
default:
|
|
1951
1979
|
val = String(await state.element.getAttribute(attribute));
|
|
1952
1980
|
break;
|
|
1953
1981
|
}
|
|
1982
|
+
state.info.value = val;
|
|
1954
1983
|
let regex;
|
|
1955
|
-
if (
|
|
1956
|
-
const patternBody =
|
|
1984
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1985
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1957
1986
|
regex = new RegExp(patternBody, "g");
|
|
1958
1987
|
}
|
|
1959
1988
|
else {
|
|
1960
|
-
const escapedPattern =
|
|
1989
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1961
1990
|
regex = new RegExp(escapedPattern, "g");
|
|
1962
1991
|
}
|
|
1963
1992
|
if (!val.match(regex)) {
|
|
1964
|
-
|
|
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);
|
|
1965
1997
|
}
|
|
1966
|
-
state.info.value = val;
|
|
1967
1998
|
return state.info;
|
|
1968
1999
|
}
|
|
1969
2000
|
catch (e) {
|
|
@@ -1991,7 +2022,7 @@ class StableBrowser {
|
|
|
1991
2022
|
if (options && options.timeout) {
|
|
1992
2023
|
timeout = options.timeout;
|
|
1993
2024
|
}
|
|
1994
|
-
const serviceUrl =
|
|
2025
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1995
2026
|
const request = {
|
|
1996
2027
|
method: "POST",
|
|
1997
2028
|
url: serviceUrl,
|
|
@@ -2062,27 +2093,32 @@ class StableBrowser {
|
|
|
2062
2093
|
async _highlightElements(scope, css) {
|
|
2063
2094
|
try {
|
|
2064
2095
|
if (!scope) {
|
|
2096
|
+
// console.log(`Scope is not defined`);
|
|
2065
2097
|
return;
|
|
2066
2098
|
}
|
|
2067
2099
|
if (!css) {
|
|
2068
2100
|
scope
|
|
2069
2101
|
.evaluate((node) => {
|
|
2070
2102
|
if (node && node.style) {
|
|
2071
|
-
let
|
|
2072
|
-
|
|
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}`);
|
|
2073
2108
|
if (window) {
|
|
2074
2109
|
window.addEventListener("beforeunload", function (e) {
|
|
2075
|
-
node.style.
|
|
2110
|
+
node.style.outline = originalOutline;
|
|
2076
2111
|
});
|
|
2077
2112
|
}
|
|
2078
2113
|
setTimeout(function () {
|
|
2079
|
-
node.style.
|
|
2114
|
+
node.style.outline = originalOutline;
|
|
2080
2115
|
}, 2000);
|
|
2081
2116
|
}
|
|
2082
2117
|
})
|
|
2083
2118
|
.then(() => { })
|
|
2084
2119
|
.catch((e) => {
|
|
2085
2120
|
// ignore
|
|
2121
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2086
2122
|
});
|
|
2087
2123
|
}
|
|
2088
2124
|
else {
|
|
@@ -2098,17 +2134,18 @@ class StableBrowser {
|
|
|
2098
2134
|
if (!element.style) {
|
|
2099
2135
|
return;
|
|
2100
2136
|
}
|
|
2101
|
-
|
|
2137
|
+
let originalOutline = element.style.outline;
|
|
2138
|
+
element.__previousOutline = originalOutline;
|
|
2102
2139
|
// Set the new border to be red and 2px solid
|
|
2103
|
-
element.style.
|
|
2140
|
+
element.style.outline = "2px solid red";
|
|
2104
2141
|
if (window) {
|
|
2105
2142
|
window.addEventListener("beforeunload", function (e) {
|
|
2106
|
-
element.style.
|
|
2143
|
+
element.style.outline = originalOutline;
|
|
2107
2144
|
});
|
|
2108
2145
|
}
|
|
2109
2146
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2110
2147
|
setTimeout(function () {
|
|
2111
|
-
element.style.
|
|
2148
|
+
element.style.outline = originalOutline;
|
|
2112
2149
|
}, 2000);
|
|
2113
2150
|
}
|
|
2114
2151
|
return;
|
|
@@ -2116,6 +2153,7 @@ class StableBrowser {
|
|
|
2116
2153
|
.then(() => { })
|
|
2117
2154
|
.catch((e) => {
|
|
2118
2155
|
// ignore
|
|
2156
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2119
2157
|
});
|
|
2120
2158
|
}
|
|
2121
2159
|
}
|
|
@@ -2123,6 +2161,54 @@ class StableBrowser {
|
|
|
2123
2161
|
console.debug(error);
|
|
2124
2162
|
}
|
|
2125
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
|
+
// }
|
|
2126
2212
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2127
2213
|
const startTime = Date.now();
|
|
2128
2214
|
let error = null;
|
|
@@ -2167,6 +2253,7 @@ class StableBrowser {
|
|
|
2167
2253
|
_reportToWorld(world, {
|
|
2168
2254
|
type: Types.VERIFY_PAGE_PATH,
|
|
2169
2255
|
text: "Verify page path",
|
|
2256
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2170
2257
|
screenshotId,
|
|
2171
2258
|
result: error
|
|
2172
2259
|
? {
|
|
@@ -2184,6 +2271,35 @@ class StableBrowser {
|
|
|
2184
2271
|
});
|
|
2185
2272
|
}
|
|
2186
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
|
+
}
|
|
2187
2303
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2188
2304
|
text = unEscapeString(text);
|
|
2189
2305
|
const state = {
|
|
@@ -2193,12 +2309,16 @@ class StableBrowser {
|
|
|
2193
2309
|
locate: false,
|
|
2194
2310
|
scroll: false,
|
|
2195
2311
|
highlight: false,
|
|
2196
|
-
type: Types.
|
|
2312
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2197
2313
|
text: `Verify text exists in page`,
|
|
2314
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2198
2315
|
operation: "verifyTextExistInPage",
|
|
2199
2316
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2200
2317
|
};
|
|
2201
|
-
|
|
2318
|
+
if (testForRegex(text)) {
|
|
2319
|
+
text = text.replace(/\\"/g, '"');
|
|
2320
|
+
}
|
|
2321
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2202
2322
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2203
2323
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2204
2324
|
if (newValue !== text) {
|
|
@@ -2211,31 +2331,15 @@ class StableBrowser {
|
|
|
2211
2331
|
await _preCommand(state, this);
|
|
2212
2332
|
state.info.text = text;
|
|
2213
2333
|
while (true) {
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
else if (numberAlternatives.number) {
|
|
2225
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2226
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2227
|
-
result.frame = frames[i];
|
|
2228
|
-
results.push(result);
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
else {
|
|
2232
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2233
|
-
result.frame = frames[i];
|
|
2234
|
-
results.push(result);
|
|
2235
|
-
}
|
|
2334
|
+
let resultWithElementsFound = {
|
|
2335
|
+
length: 0,
|
|
2336
|
+
};
|
|
2337
|
+
try {
|
|
2338
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2339
|
+
}
|
|
2340
|
+
catch (error) {
|
|
2341
|
+
// ignore
|
|
2236
2342
|
}
|
|
2237
|
-
state.info.results = results;
|
|
2238
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2239
2343
|
if (resultWithElementsFound.length === 0) {
|
|
2240
2344
|
if (Date.now() - state.startTime > timeout) {
|
|
2241
2345
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2243,18 +2347,40 @@ class StableBrowser {
|
|
|
2243
2347
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2244
2348
|
continue;
|
|
2245
2349
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2350
|
+
try {
|
|
2351
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2352
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2353
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2354
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2355
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2356
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2357
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2358
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2359
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2360
|
+
// .then(async () => {
|
|
2361
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2362
|
+
// })
|
|
2363
|
+
// .catch(
|
|
2364
|
+
// (e) => {}
|
|
2365
|
+
// console.error(e)
|
|
2366
|
+
// );
|
|
2367
|
+
// });
|
|
2368
|
+
// }
|
|
2369
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2370
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2371
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2372
|
+
if (element) {
|
|
2373
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2374
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2375
|
+
// await _screenshot(state, this, element);
|
|
2376
|
+
}
|
|
2254
2377
|
}
|
|
2378
|
+
await _screenshot(state, this);
|
|
2379
|
+
return state.info;
|
|
2380
|
+
}
|
|
2381
|
+
catch (error) {
|
|
2382
|
+
console.error(error);
|
|
2255
2383
|
}
|
|
2256
|
-
await _screenshot(state, this);
|
|
2257
|
-
return state.info;
|
|
2258
2384
|
}
|
|
2259
2385
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2260
2386
|
}
|
|
@@ -2276,10 +2402,14 @@ class StableBrowser {
|
|
|
2276
2402
|
highlight: false,
|
|
2277
2403
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2278
2404
|
text: `Verify text does not exist in page`,
|
|
2405
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2279
2406
|
operation: "verifyTextNotExistInPage",
|
|
2280
2407
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2281
2408
|
};
|
|
2282
|
-
|
|
2409
|
+
if (testForRegex(text)) {
|
|
2410
|
+
text = text.replace(/\\"/g, '"');
|
|
2411
|
+
}
|
|
2412
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2283
2413
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2284
2414
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2285
2415
|
if (newValue !== text) {
|
|
@@ -2291,32 +2421,16 @@ class StableBrowser {
|
|
|
2291
2421
|
try {
|
|
2292
2422
|
await _preCommand(state, this);
|
|
2293
2423
|
state.info.text = text;
|
|
2424
|
+
let resultWithElementsFound = {
|
|
2425
|
+
length: null, // initial cannot be 0
|
|
2426
|
+
};
|
|
2294
2427
|
while (true) {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2301
|
-
result.frame = frames[i];
|
|
2302
|
-
results.push(result);
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
else if (numberAlternatives.number) {
|
|
2306
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2307
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2308
|
-
result.frame = frames[i];
|
|
2309
|
-
results.push(result);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
else {
|
|
2313
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2314
|
-
result.frame = frames[i];
|
|
2315
|
-
results.push(result);
|
|
2316
|
-
}
|
|
2428
|
+
try {
|
|
2429
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2430
|
+
}
|
|
2431
|
+
catch (error) {
|
|
2432
|
+
// ignore
|
|
2317
2433
|
}
|
|
2318
|
-
state.info.results = results;
|
|
2319
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2320
2434
|
if (resultWithElementsFound.length === 0) {
|
|
2321
2435
|
await _screenshot(state, this);
|
|
2322
2436
|
return state.info;
|
|
@@ -2334,15 +2448,116 @@ class StableBrowser {
|
|
|
2334
2448
|
_commandFinally(state, this);
|
|
2335
2449
|
}
|
|
2336
2450
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2451
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2452
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2453
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2454
|
+
const state = {
|
|
2455
|
+
text_search: textToVerify,
|
|
2456
|
+
options,
|
|
2457
|
+
world,
|
|
2458
|
+
locate: false,
|
|
2459
|
+
scroll: false,
|
|
2460
|
+
highlight: false,
|
|
2461
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2462
|
+
text: `Verify text with relation to another text`,
|
|
2463
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2464
|
+
operation: "verify_text_with_relation",
|
|
2465
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2466
|
+
};
|
|
2467
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2468
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2469
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2470
|
+
if (newValue !== textAnchor) {
|
|
2471
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2472
|
+
textAnchor = newValue;
|
|
2473
|
+
}
|
|
2474
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2475
|
+
if (newValue !== textToVerify) {
|
|
2476
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2477
|
+
textToVerify = newValue;
|
|
2478
|
+
}
|
|
2479
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2480
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2481
|
+
let foundAncore = false;
|
|
2482
|
+
try {
|
|
2483
|
+
await _preCommand(state, this);
|
|
2484
|
+
state.info.text = textToVerify;
|
|
2485
|
+
let resultWithElementsFound = {
|
|
2486
|
+
length: 0,
|
|
2487
|
+
};
|
|
2488
|
+
while (true) {
|
|
2489
|
+
try {
|
|
2490
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2491
|
+
}
|
|
2492
|
+
catch (error) {
|
|
2493
|
+
// ignore
|
|
2494
|
+
}
|
|
2495
|
+
if (resultWithElementsFound.length === 0) {
|
|
2496
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2497
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2498
|
+
}
|
|
2499
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
try {
|
|
2503
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2504
|
+
foundAncore = true;
|
|
2505
|
+
const result = resultWithElementsFound[i];
|
|
2506
|
+
const token = result.randomToken;
|
|
2507
|
+
const frame = result.frame;
|
|
2508
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2509
|
+
const climbArray1 = [];
|
|
2510
|
+
for (let i = 0; i < climb; i++) {
|
|
2511
|
+
climbArray1.push("..");
|
|
2512
|
+
}
|
|
2513
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2514
|
+
css = css + " >> " + climbXpath;
|
|
2515
|
+
const count = await frame.locator(css).count();
|
|
2516
|
+
for (let j = 0; j < count; j++) {
|
|
2517
|
+
const continer = await frame.locator(css).nth(j);
|
|
2518
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2519
|
+
if (result.elementCount > 0) {
|
|
2520
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2521
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2522
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2523
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2524
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2525
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2526
|
+
// .then(async () => {
|
|
2527
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2528
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2529
|
+
// () => {}
|
|
2530
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2531
|
+
// );
|
|
2532
|
+
// })
|
|
2533
|
+
// .catch(e);
|
|
2534
|
+
// }
|
|
2535
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2536
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2537
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2538
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2539
|
+
if (element) {
|
|
2540
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2541
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2542
|
+
}
|
|
2543
|
+
await _screenshot(state, this);
|
|
2544
|
+
return state.info;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
catch (error) {
|
|
2550
|
+
console.error(error);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2341
2554
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2555
|
+
catch (e) {
|
|
2556
|
+
await _commandError(state, e, this);
|
|
2557
|
+
}
|
|
2558
|
+
finally {
|
|
2559
|
+
_commandFinally(state, this);
|
|
2344
2560
|
}
|
|
2345
|
-
return serviceUrl;
|
|
2346
2561
|
}
|
|
2347
2562
|
async visualVerification(text, options = {}, world = null) {
|
|
2348
2563
|
const startTime = Date.now();
|
|
@@ -2358,14 +2573,17 @@ class StableBrowser {
|
|
|
2358
2573
|
throw new Error("TOKEN is not set");
|
|
2359
2574
|
}
|
|
2360
2575
|
try {
|
|
2361
|
-
let serviceUrl =
|
|
2576
|
+
let serviceUrl = _getServerUrl();
|
|
2362
2577
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2363
2578
|
info.screenshotPath = screenshotPath;
|
|
2364
2579
|
const screenshot = await this.takeScreenshot();
|
|
2365
|
-
|
|
2366
|
-
method: "
|
|
2580
|
+
let request = {
|
|
2581
|
+
method: "post",
|
|
2582
|
+
maxBodyLength: Infinity,
|
|
2367
2583
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2368
2584
|
headers: {
|
|
2585
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2586
|
+
"x-source": "aaa",
|
|
2369
2587
|
"Content-Type": "application/json",
|
|
2370
2588
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2371
2589
|
},
|
|
@@ -2374,7 +2592,7 @@ class StableBrowser {
|
|
|
2374
2592
|
screenshot: screenshot,
|
|
2375
2593
|
}),
|
|
2376
2594
|
};
|
|
2377
|
-
|
|
2595
|
+
const result = await axios.request(request);
|
|
2378
2596
|
if (result.data.status !== true) {
|
|
2379
2597
|
throw new Error("Visual validation failed");
|
|
2380
2598
|
}
|
|
@@ -2402,6 +2620,7 @@ class StableBrowser {
|
|
|
2402
2620
|
_reportToWorld(world, {
|
|
2403
2621
|
type: Types.VERIFY_VISUAL,
|
|
2404
2622
|
text: "Visual verification",
|
|
2623
|
+
_text: "Visual verification of " + text,
|
|
2405
2624
|
screenshotId,
|
|
2406
2625
|
result: error
|
|
2407
2626
|
? {
|
|
@@ -2447,6 +2666,7 @@ class StableBrowser {
|
|
|
2447
2666
|
let screenshotPath = null;
|
|
2448
2667
|
const info = {};
|
|
2449
2668
|
info.log = "";
|
|
2669
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2450
2670
|
info.operation = "getTableData";
|
|
2451
2671
|
info.selectors = selectors;
|
|
2452
2672
|
try {
|
|
@@ -2522,7 +2742,7 @@ class StableBrowser {
|
|
|
2522
2742
|
info.operation = "analyzeTable";
|
|
2523
2743
|
info.selectors = selectors;
|
|
2524
2744
|
info.query = query;
|
|
2525
|
-
query =
|
|
2745
|
+
query = _fixUsingParams(query, _params);
|
|
2526
2746
|
info.query_fixed = query;
|
|
2527
2747
|
info.operator = operator;
|
|
2528
2748
|
info.value = value;
|
|
@@ -2667,6 +2887,32 @@ class StableBrowser {
|
|
|
2667
2887
|
}
|
|
2668
2888
|
return timeout;
|
|
2669
2889
|
}
|
|
2890
|
+
_getFindElementTimeout(options) {
|
|
2891
|
+
if (options && options.timeout) {
|
|
2892
|
+
return options.timeout;
|
|
2893
|
+
}
|
|
2894
|
+
if (this.configuration.find_element_timeout) {
|
|
2895
|
+
return this.configuration.find_element_timeout;
|
|
2896
|
+
}
|
|
2897
|
+
return 30000;
|
|
2898
|
+
}
|
|
2899
|
+
async saveStoreState(path = null, world = null) {
|
|
2900
|
+
const storageState = await this.page.context().storageState();
|
|
2901
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2902
|
+
if (path) {
|
|
2903
|
+
// save { storageState: storageState } into the path
|
|
2904
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2905
|
+
}
|
|
2906
|
+
else {
|
|
2907
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
async restoreSaveState(path = null, world = null) {
|
|
2911
|
+
await refreshBrowser(this, path, world);
|
|
2912
|
+
this.registerEventListeners(this.context);
|
|
2913
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2914
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2915
|
+
}
|
|
2670
2916
|
async waitForPageLoad(options = {}, world = null) {
|
|
2671
2917
|
let timeout = this._getLoadTimeout(options);
|
|
2672
2918
|
const promiseArray = [];
|
|
@@ -2734,6 +2980,7 @@ class StableBrowser {
|
|
|
2734
2980
|
highlight: false,
|
|
2735
2981
|
type: Types.CLOSE_PAGE,
|
|
2736
2982
|
text: `Close page`,
|
|
2983
|
+
_text: `Close the page`,
|
|
2737
2984
|
operation: "closePage",
|
|
2738
2985
|
log: "***** close page *****\n",
|
|
2739
2986
|
throwError: false,
|
|
@@ -2751,7 +2998,7 @@ class StableBrowser {
|
|
|
2751
2998
|
}
|
|
2752
2999
|
}
|
|
2753
3000
|
saveTestDataAsGlobal(options, world) {
|
|
2754
|
-
const dataFile =
|
|
3001
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2755
3002
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2756
3003
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2757
3004
|
}
|
|
@@ -2781,6 +3028,7 @@ class StableBrowser {
|
|
|
2781
3028
|
_reportToWorld(world, {
|
|
2782
3029
|
type: Types.SET_VIEWPORT,
|
|
2783
3030
|
text: "set viewport size to " + width + "x" + hight,
|
|
3031
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2784
3032
|
screenshotId,
|
|
2785
3033
|
result: error
|
|
2786
3034
|
? {
|
|
@@ -2852,14 +3100,25 @@ class StableBrowser {
|
|
|
2852
3100
|
}
|
|
2853
3101
|
}
|
|
2854
3102
|
async beforeStep(world, step) {
|
|
2855
|
-
this.stepName = step.pickleStep.text;
|
|
2856
|
-
this.logger.info("step: " + this.stepName);
|
|
2857
3103
|
if (this.stepIndex === undefined) {
|
|
2858
3104
|
this.stepIndex = 0;
|
|
2859
3105
|
}
|
|
2860
3106
|
else {
|
|
2861
3107
|
this.stepIndex++;
|
|
2862
3108
|
}
|
|
3109
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3110
|
+
this.stepName = step.pickleStep.text;
|
|
3111
|
+
this.logger.info("step: " + this.stepName);
|
|
3112
|
+
}
|
|
3113
|
+
else if (step && step.text) {
|
|
3114
|
+
this.stepName = step.text;
|
|
3115
|
+
}
|
|
3116
|
+
else {
|
|
3117
|
+
this.stepName = "step " + this.stepIndex;
|
|
3118
|
+
}
|
|
3119
|
+
if (this.context) {
|
|
3120
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3121
|
+
}
|
|
2863
3122
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2864
3123
|
if (this.context.browserObject.context) {
|
|
2865
3124
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2872,6 +3131,41 @@ class StableBrowser {
|
|
|
2872
3131
|
this.saveTestDataAsGlobal({}, world);
|
|
2873
3132
|
}
|
|
2874
3133
|
}
|
|
3134
|
+
if (this.initSnapshotTaken === false) {
|
|
3135
|
+
this.initSnapshotTaken = true;
|
|
3136
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3137
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3138
|
+
if (snapshot) {
|
|
3139
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
async getAriaSnapshot() {
|
|
3145
|
+
try {
|
|
3146
|
+
// find the page url
|
|
3147
|
+
const url = await this.page.url();
|
|
3148
|
+
// extract the path from the url
|
|
3149
|
+
const path = new URL(url).pathname;
|
|
3150
|
+
// get the page title
|
|
3151
|
+
const title = await this.page.title();
|
|
3152
|
+
// go over other frams
|
|
3153
|
+
const frames = this.page.frames();
|
|
3154
|
+
const snapshots = [];
|
|
3155
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3156
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3157
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3158
|
+
content.push(`- frame: ${i}`);
|
|
3159
|
+
const frame = frames[i];
|
|
3160
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3161
|
+
content.push(snapshot);
|
|
3162
|
+
}
|
|
3163
|
+
return content.join("\n");
|
|
3164
|
+
}
|
|
3165
|
+
catch (e) {
|
|
3166
|
+
console.error(e);
|
|
3167
|
+
}
|
|
3168
|
+
return null;
|
|
2875
3169
|
}
|
|
2876
3170
|
async afterStep(world, step) {
|
|
2877
3171
|
this.stepName = null;
|
|
@@ -2882,6 +3176,16 @@ class StableBrowser {
|
|
|
2882
3176
|
});
|
|
2883
3177
|
}
|
|
2884
3178
|
}
|
|
3179
|
+
if (this.context) {
|
|
3180
|
+
this.context.examplesRow = null;
|
|
3181
|
+
}
|
|
3182
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3183
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3184
|
+
if (snapshot) {
|
|
3185
|
+
const obj = {};
|
|
3186
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
2885
3189
|
}
|
|
2886
3190
|
}
|
|
2887
3191
|
function createTimedPromise(promise, label) {
|
|
@@ -2889,156 +3193,5 @@ function createTimedPromise(promise, label) {
|
|
|
2889
3193
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2890
3194
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2891
3195
|
}
|
|
2892
|
-
const KEYBOARD_EVENTS = [
|
|
2893
|
-
"ALT",
|
|
2894
|
-
"AltGraph",
|
|
2895
|
-
"CapsLock",
|
|
2896
|
-
"Control",
|
|
2897
|
-
"Fn",
|
|
2898
|
-
"FnLock",
|
|
2899
|
-
"Hyper",
|
|
2900
|
-
"Meta",
|
|
2901
|
-
"NumLock",
|
|
2902
|
-
"ScrollLock",
|
|
2903
|
-
"Shift",
|
|
2904
|
-
"Super",
|
|
2905
|
-
"Symbol",
|
|
2906
|
-
"SymbolLock",
|
|
2907
|
-
"Enter",
|
|
2908
|
-
"Tab",
|
|
2909
|
-
"ArrowDown",
|
|
2910
|
-
"ArrowLeft",
|
|
2911
|
-
"ArrowRight",
|
|
2912
|
-
"ArrowUp",
|
|
2913
|
-
"End",
|
|
2914
|
-
"Home",
|
|
2915
|
-
"PageDown",
|
|
2916
|
-
"PageUp",
|
|
2917
|
-
"Backspace",
|
|
2918
|
-
"Clear",
|
|
2919
|
-
"Copy",
|
|
2920
|
-
"CrSel",
|
|
2921
|
-
"Cut",
|
|
2922
|
-
"Delete",
|
|
2923
|
-
"EraseEof",
|
|
2924
|
-
"ExSel",
|
|
2925
|
-
"Insert",
|
|
2926
|
-
"Paste",
|
|
2927
|
-
"Redo",
|
|
2928
|
-
"Undo",
|
|
2929
|
-
"Accept",
|
|
2930
|
-
"Again",
|
|
2931
|
-
"Attn",
|
|
2932
|
-
"Cancel",
|
|
2933
|
-
"ContextMenu",
|
|
2934
|
-
"Escape",
|
|
2935
|
-
"Execute",
|
|
2936
|
-
"Find",
|
|
2937
|
-
"Finish",
|
|
2938
|
-
"Help",
|
|
2939
|
-
"Pause",
|
|
2940
|
-
"Play",
|
|
2941
|
-
"Props",
|
|
2942
|
-
"Select",
|
|
2943
|
-
"ZoomIn",
|
|
2944
|
-
"ZoomOut",
|
|
2945
|
-
"BrightnessDown",
|
|
2946
|
-
"BrightnessUp",
|
|
2947
|
-
"Eject",
|
|
2948
|
-
"LogOff",
|
|
2949
|
-
"Power",
|
|
2950
|
-
"PowerOff",
|
|
2951
|
-
"PrintScreen",
|
|
2952
|
-
"Hibernate",
|
|
2953
|
-
"Standby",
|
|
2954
|
-
"WakeUp",
|
|
2955
|
-
"AllCandidates",
|
|
2956
|
-
"Alphanumeric",
|
|
2957
|
-
"CodeInput",
|
|
2958
|
-
"Compose",
|
|
2959
|
-
"Convert",
|
|
2960
|
-
"Dead",
|
|
2961
|
-
"FinalMode",
|
|
2962
|
-
"GroupFirst",
|
|
2963
|
-
"GroupLast",
|
|
2964
|
-
"GroupNext",
|
|
2965
|
-
"GroupPrevious",
|
|
2966
|
-
"ModeChange",
|
|
2967
|
-
"NextCandidate",
|
|
2968
|
-
"NonConvert",
|
|
2969
|
-
"PreviousCandidate",
|
|
2970
|
-
"Process",
|
|
2971
|
-
"SingleCandidate",
|
|
2972
|
-
"HangulMode",
|
|
2973
|
-
"HanjaMode",
|
|
2974
|
-
"JunjaMode",
|
|
2975
|
-
"Eisu",
|
|
2976
|
-
"Hankaku",
|
|
2977
|
-
"Hiragana",
|
|
2978
|
-
"HiraganaKatakana",
|
|
2979
|
-
"KanaMode",
|
|
2980
|
-
"KanjiMode",
|
|
2981
|
-
"Katakana",
|
|
2982
|
-
"Romaji",
|
|
2983
|
-
"Zenkaku",
|
|
2984
|
-
"ZenkakuHanaku",
|
|
2985
|
-
"F1",
|
|
2986
|
-
"F2",
|
|
2987
|
-
"F3",
|
|
2988
|
-
"F4",
|
|
2989
|
-
"F5",
|
|
2990
|
-
"F6",
|
|
2991
|
-
"F7",
|
|
2992
|
-
"F8",
|
|
2993
|
-
"F9",
|
|
2994
|
-
"F10",
|
|
2995
|
-
"F11",
|
|
2996
|
-
"F12",
|
|
2997
|
-
"Soft1",
|
|
2998
|
-
"Soft2",
|
|
2999
|
-
"Soft3",
|
|
3000
|
-
"Soft4",
|
|
3001
|
-
"ChannelDown",
|
|
3002
|
-
"ChannelUp",
|
|
3003
|
-
"Close",
|
|
3004
|
-
"MailForward",
|
|
3005
|
-
"MailReply",
|
|
3006
|
-
"MailSend",
|
|
3007
|
-
"MediaFastForward",
|
|
3008
|
-
"MediaPause",
|
|
3009
|
-
"MediaPlay",
|
|
3010
|
-
"MediaPlayPause",
|
|
3011
|
-
"MediaRecord",
|
|
3012
|
-
"MediaRewind",
|
|
3013
|
-
"MediaStop",
|
|
3014
|
-
"MediaTrackNext",
|
|
3015
|
-
"MediaTrackPrevious",
|
|
3016
|
-
"AudioBalanceLeft",
|
|
3017
|
-
"AudioBalanceRight",
|
|
3018
|
-
"AudioBassBoostDown",
|
|
3019
|
-
"AudioBassBoostToggle",
|
|
3020
|
-
"AudioBassBoostUp",
|
|
3021
|
-
"AudioFaderFront",
|
|
3022
|
-
"AudioFaderRear",
|
|
3023
|
-
"AudioSurroundModeNext",
|
|
3024
|
-
"AudioTrebleDown",
|
|
3025
|
-
"AudioTrebleUp",
|
|
3026
|
-
"AudioVolumeDown",
|
|
3027
|
-
"AudioVolumeMute",
|
|
3028
|
-
"AudioVolumeUp",
|
|
3029
|
-
"MicrophoneToggle",
|
|
3030
|
-
"MicrophoneVolumeDown",
|
|
3031
|
-
"MicrophoneVolumeMute",
|
|
3032
|
-
"MicrophoneVolumeUp",
|
|
3033
|
-
"TV",
|
|
3034
|
-
"TV3DMode",
|
|
3035
|
-
"TVAntennaCable",
|
|
3036
|
-
"TVAudioDescription",
|
|
3037
|
-
];
|
|
3038
|
-
function unEscapeString(str) {
|
|
3039
|
-
const placeholder = "__NEWLINE__";
|
|
3040
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
3041
|
-
return str;
|
|
3042
|
-
}
|
|
3043
3196
|
export { StableBrowser };
|
|
3044
3197
|
//# sourceMappingURL=stable_browser.js.map
|