automation_model 1.0.554-dev → 1.0.554-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 +104 -7
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +94 -39
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +34 -12
- 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/network.js +45 -11
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +32 -17
- package/lib/stable_browser.js +794 -557
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +101 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +17 -1
- package/lib/utils.js +489 -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,18 +10,21 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
22
|
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
25
|
export const Types = {
|
|
24
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
28
|
NAVIGATE: "navigate",
|
|
26
29
|
FILL: "fill_element",
|
|
27
30
|
EXECUTE: "execute_page_method",
|
|
@@ -31,6 +34,8 @@ export const Types = {
|
|
|
31
34
|
GET_PAGE_STATUS: "get_page_status",
|
|
32
35
|
CLICK_ROW_ACTION: "click_row_action",
|
|
33
36
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
37
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
38
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
34
39
|
ANALYZE_TABLE: "analyze_table",
|
|
35
40
|
SELECT: "select_combobox",
|
|
36
41
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -41,6 +46,7 @@ export const Types = {
|
|
|
41
46
|
UNCHECK: "uncheck_element",
|
|
42
47
|
EXTRACT: "extract_attribute",
|
|
43
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
44
50
|
SET_DATE_TIME: "set_date_time",
|
|
45
51
|
SET_VIEWPORT: "set_viewport",
|
|
46
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -48,8 +54,12 @@ export const Types = {
|
|
|
48
54
|
SET_INPUT: "set_input",
|
|
49
55
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
50
56
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
57
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
51
58
|
};
|
|
52
59
|
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
62
|
+
};
|
|
53
63
|
class StableBrowser {
|
|
54
64
|
browser;
|
|
55
65
|
page;
|
|
@@ -63,6 +73,7 @@ class StableBrowser {
|
|
|
63
73
|
appName = "main";
|
|
64
74
|
tags = null;
|
|
65
75
|
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
66
77
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
67
78
|
this.browser = browser;
|
|
68
79
|
this.page = page;
|
|
@@ -101,27 +112,9 @@ class StableBrowser {
|
|
|
101
112
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
102
113
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
103
114
|
}
|
|
104
|
-
async scrollPageToLoadLazyElements() {
|
|
105
|
-
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
106
|
-
let retry = 0;
|
|
107
|
-
while (true) {
|
|
108
|
-
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
109
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
110
|
-
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
111
|
-
if (newHeight === lastHeight) {
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
lastHeight = newHeight;
|
|
115
|
-
retry++;
|
|
116
|
-
if (retry > 10) {
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
121
|
-
}
|
|
122
115
|
registerEventListeners(context) {
|
|
123
116
|
this.registerConsoleLogListener(this.page, context);
|
|
124
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
117
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
125
118
|
if (!context.pageLoading) {
|
|
126
119
|
context.pageLoading = { status: false };
|
|
127
120
|
}
|
|
@@ -177,8 +170,8 @@ class StableBrowser {
|
|
|
177
170
|
};
|
|
178
171
|
}
|
|
179
172
|
const tempContext = {};
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
_copyContext(this, tempContext);
|
|
174
|
+
_copyContext(apps[appName], this);
|
|
182
175
|
apps[this.appName] = tempContext;
|
|
183
176
|
this.appName = appName;
|
|
184
177
|
if (newContextCreated) {
|
|
@@ -187,22 +180,6 @@ class StableBrowser {
|
|
|
187
180
|
await this.waitForPageLoad();
|
|
188
181
|
}
|
|
189
182
|
}
|
|
190
|
-
_copyContext(from, to) {
|
|
191
|
-
to.browser = from.browser;
|
|
192
|
-
to.page = from.page;
|
|
193
|
-
to.context = from.context;
|
|
194
|
-
}
|
|
195
|
-
getWebLogFile(logFolder) {
|
|
196
|
-
if (!fs.existsSync(logFolder)) {
|
|
197
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
198
|
-
}
|
|
199
|
-
let nextIndex = 1;
|
|
200
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
201
|
-
nextIndex++;
|
|
202
|
-
}
|
|
203
|
-
const fileName = nextIndex + ".json";
|
|
204
|
-
return path.join(logFolder, fileName);
|
|
205
|
-
}
|
|
206
183
|
registerConsoleLogListener(page, context) {
|
|
207
184
|
if (!this.context.webLogger) {
|
|
208
185
|
this.context.webLogger = [];
|
|
@@ -266,55 +243,51 @@ class StableBrowser {
|
|
|
266
243
|
// async closeUnexpectedPopups() {
|
|
267
244
|
// await closeUnexpectedPopups(this.page);
|
|
268
245
|
// }
|
|
269
|
-
async goto(url) {
|
|
246
|
+
async goto(url, world = null) {
|
|
247
|
+
if (!url) {
|
|
248
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
249
|
+
}
|
|
270
250
|
if (!url.startsWith("http")) {
|
|
271
251
|
url = "https://" + url;
|
|
272
252
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
253
|
+
const state = {
|
|
254
|
+
value: url,
|
|
255
|
+
world: world,
|
|
256
|
+
type: Types.NAVIGATE,
|
|
257
|
+
text: `Navigate Page to: ${url}`,
|
|
258
|
+
operation: "goto",
|
|
259
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
260
|
+
info: {},
|
|
261
|
+
locate: false,
|
|
262
|
+
scroll: false,
|
|
263
|
+
screenshot: false,
|
|
264
|
+
highlight: false,
|
|
265
|
+
};
|
|
266
|
+
try {
|
|
267
|
+
await _preCommand(state, this);
|
|
268
|
+
await this.page.goto(url, {
|
|
269
|
+
timeout: 60000,
|
|
270
|
+
});
|
|
271
|
+
await _screenshot(state, this);
|
|
280
272
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// remove the _ prefix
|
|
285
|
-
regValue = key.substring(1);
|
|
286
|
-
}
|
|
287
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error("Error on goto", error);
|
|
275
|
+
_commandError(state, error, this);
|
|
288
276
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
292
|
-
// check if not null
|
|
293
|
-
if (!locator) {
|
|
294
|
-
return locator;
|
|
277
|
+
finally {
|
|
278
|
+
_commandFinally(state, this);
|
|
295
279
|
}
|
|
296
|
-
// clone the locator
|
|
297
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
298
|
-
this.scanAndManipulate(locator, _params);
|
|
299
|
-
return locator;
|
|
300
|
-
}
|
|
301
|
-
_isObject(value) {
|
|
302
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
303
280
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
281
|
+
async _getLocator(locator, scope, _params) {
|
|
282
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
283
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
284
|
+
for (let key in locator) {
|
|
285
|
+
if (typeof locator[key] !== "string")
|
|
286
|
+
continue;
|
|
287
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
288
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
313
289
|
}
|
|
314
290
|
}
|
|
315
|
-
}
|
|
316
|
-
_getLocator(locator, scope, _params) {
|
|
317
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
318
291
|
let locatorReturn;
|
|
319
292
|
if (locator.role) {
|
|
320
293
|
if (locator.role[1].nameReg) {
|
|
@@ -322,7 +295,7 @@ class StableBrowser {
|
|
|
322
295
|
delete locator.role[1].nameReg;
|
|
323
296
|
}
|
|
324
297
|
// if (locator.role[1].name) {
|
|
325
|
-
// locator.role[1].name =
|
|
298
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
326
299
|
// }
|
|
327
300
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
328
301
|
}
|
|
@@ -365,140 +338,60 @@ class StableBrowser {
|
|
|
365
338
|
if (css && css.locator) {
|
|
366
339
|
css = css.locator;
|
|
367
340
|
}
|
|
368
|
-
let result = await this._locateElementByText(scope,
|
|
341
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
369
342
|
if (result.elementCount === 0) {
|
|
370
343
|
return;
|
|
371
344
|
}
|
|
372
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
373
346
|
// css climb to parent element
|
|
374
347
|
const climbArray = [];
|
|
375
348
|
for (let i = 0; i < climb; i++) {
|
|
376
349
|
climbArray.push("..");
|
|
377
350
|
}
|
|
378
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return result;
|
|
417
|
-
}
|
|
418
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
419
|
-
if (!tag) {
|
|
420
|
-
tag = "*:not(script, style, head)";
|
|
421
|
-
}
|
|
422
|
-
let regexpSearch = document.getRegex(text);
|
|
423
|
-
if (regexpSearch) {
|
|
424
|
-
regex = true;
|
|
425
|
-
}
|
|
426
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
427
|
-
let shadowHosts = [];
|
|
428
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
429
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
430
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
431
|
-
if (!shadowElement) {
|
|
432
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
436
|
-
elements = elements.concat(shadowElements);
|
|
437
|
-
}
|
|
438
|
-
let randomToken = null;
|
|
439
|
-
const foundElements = [];
|
|
440
|
-
if (regex) {
|
|
441
|
-
if (!regexpSearch) {
|
|
442
|
-
regexpSearch = new RegExp(text, "im");
|
|
443
|
-
}
|
|
444
|
-
for (let i = 0; i < elements.length; i++) {
|
|
445
|
-
const element = elements[i];
|
|
446
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
447
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
448
|
-
foundElements.push(element);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
text = text.trim();
|
|
454
|
-
for (let i = 0; i < elements.length; i++) {
|
|
455
|
-
const element = elements[i];
|
|
456
|
-
if (partial) {
|
|
457
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
458
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
459
|
-
foundElements.push(element);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
464
|
-
(element.value && element.value === text)) {
|
|
465
|
-
foundElements.push(element);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
let noChildElements = [];
|
|
471
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
472
|
-
let element = foundElements[i];
|
|
473
|
-
let hasChild = false;
|
|
474
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
475
|
-
if (i === j) {
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
if (isParent(element, foundElements[j])) {
|
|
479
|
-
hasChild = true;
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (!hasChild) {
|
|
484
|
-
noChildElements.push(element);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
let elementCount = 0;
|
|
488
|
-
if (noChildElements.length > 0) {
|
|
489
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
490
|
-
if (randomToken === null) {
|
|
491
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
492
|
-
}
|
|
493
|
-
let element = noChildElements[i];
|
|
494
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
495
|
-
elementCount++;
|
|
496
|
-
}
|
|
352
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
353
|
+
if (css) {
|
|
354
|
+
resultCss = resultCss + " >> " + css;
|
|
355
|
+
}
|
|
356
|
+
return resultCss;
|
|
357
|
+
}
|
|
358
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
359
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
360
|
+
const locator = scope.locator(query);
|
|
361
|
+
const count = await locator.count();
|
|
362
|
+
if (!tag1) {
|
|
363
|
+
tag1 = "*";
|
|
364
|
+
}
|
|
365
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
366
|
+
let tagCount = 0;
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const element = locator.nth(i);
|
|
369
|
+
// check if the tag matches
|
|
370
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
371
|
+
if (!tag.startsWith("*")) {
|
|
372
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (!el.setAttribute) {
|
|
377
|
+
el = el.parentElement;
|
|
378
|
+
}
|
|
379
|
+
// remove any attributes start with data-blinq-id
|
|
380
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
381
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
382
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
383
|
+
// }
|
|
384
|
+
// }
|
|
385
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
386
|
+
return true;
|
|
387
|
+
}, [tag1, randomToken]))) {
|
|
388
|
+
continue;
|
|
497
389
|
}
|
|
498
|
-
|
|
499
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
500
393
|
}
|
|
501
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
394
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
502
395
|
if (!info) {
|
|
503
396
|
info = {};
|
|
504
397
|
}
|
|
@@ -512,7 +405,7 @@ class StableBrowser {
|
|
|
512
405
|
let locatorSearch = selectorHierarchy[index];
|
|
513
406
|
let originalLocatorSearch = "";
|
|
514
407
|
try {
|
|
515
|
-
originalLocatorSearch =
|
|
408
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
516
409
|
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
517
410
|
}
|
|
518
411
|
catch (e) {
|
|
@@ -521,30 +414,31 @@ class StableBrowser {
|
|
|
521
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
522
415
|
let locator = null;
|
|
523
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
524
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
525
419
|
if (!locatorString) {
|
|
526
420
|
info.failCause.textNotFound = true;
|
|
527
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
528
422
|
return;
|
|
529
423
|
}
|
|
530
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
531
425
|
}
|
|
532
426
|
else if (locatorSearch.text) {
|
|
533
|
-
let text =
|
|
534
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
427
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
428
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
535
429
|
if (result.elementCount === 0) {
|
|
536
430
|
info.failCause.textNotFound = true;
|
|
537
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
538
432
|
return;
|
|
539
433
|
}
|
|
540
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
541
435
|
if (locatorSearch.childCss) {
|
|
542
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
543
437
|
}
|
|
544
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
545
439
|
}
|
|
546
440
|
else {
|
|
547
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
548
442
|
}
|
|
549
443
|
// let cssHref = false;
|
|
550
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -573,7 +467,7 @@ class StableBrowser {
|
|
|
573
467
|
if (!visibleOnly) {
|
|
574
468
|
visible = true;
|
|
575
469
|
}
|
|
576
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
577
471
|
foundLocators.push(locator.nth(j));
|
|
578
472
|
if (info.locatorLog) {
|
|
579
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -586,9 +480,11 @@ class StableBrowser {
|
|
|
586
480
|
info.printMessages = {};
|
|
587
481
|
}
|
|
588
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
589
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
590
485
|
}
|
|
591
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
592
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
593
489
|
}
|
|
594
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -656,7 +552,7 @@ class StableBrowser {
|
|
|
656
552
|
}
|
|
657
553
|
return { rerun: false };
|
|
658
554
|
}
|
|
659
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
660
556
|
if (!timeout) {
|
|
661
557
|
timeout = 30000;
|
|
662
558
|
}
|
|
@@ -666,9 +562,18 @@ class StableBrowser {
|
|
|
666
562
|
let selector = selectors.locators[j];
|
|
667
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
668
564
|
}
|
|
669
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
670
566
|
if (!element.rerun) {
|
|
671
|
-
|
|
567
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
+
element.evaluate((el, randomToken) => {
|
|
569
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
|
+
}, randomToken);
|
|
571
|
+
if (element._frame) {
|
|
572
|
+
return element;
|
|
573
|
+
}
|
|
574
|
+
const scope = element.page();
|
|
575
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
576
|
+
return newSelector;
|
|
672
577
|
}
|
|
673
578
|
}
|
|
674
579
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -736,19 +641,19 @@ class StableBrowser {
|
|
|
736
641
|
}
|
|
737
642
|
if (!scope) {
|
|
738
643
|
if (info && info.locatorLog) {
|
|
739
|
-
info.locatorLog.
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
740
645
|
}
|
|
741
646
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
742
647
|
if (Date.now() - startTime > timeout) {
|
|
743
648
|
info.failCause.iframeNotFound = true;
|
|
744
|
-
info.failCause.lastError =
|
|
649
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
745
650
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
746
651
|
}
|
|
747
652
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
748
653
|
}
|
|
749
654
|
else {
|
|
750
655
|
if (info && info.locatorLog) {
|
|
751
|
-
info.locatorLog.
|
|
656
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
752
657
|
}
|
|
753
658
|
break;
|
|
754
659
|
}
|
|
@@ -766,7 +671,7 @@ class StableBrowser {
|
|
|
766
671
|
return bodyContent;
|
|
767
672
|
});
|
|
768
673
|
}
|
|
769
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
674
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
770
675
|
if (!info) {
|
|
771
676
|
info = {};
|
|
772
677
|
info.failCause = {};
|
|
@@ -815,17 +720,17 @@ class StableBrowser {
|
|
|
815
720
|
}
|
|
816
721
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
817
722
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
818
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
723
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
819
724
|
if (result.foundElements.length === 0) {
|
|
820
725
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
821
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
726
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
822
727
|
}
|
|
823
728
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
824
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
729
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
825
730
|
}
|
|
826
731
|
else {
|
|
827
732
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
828
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
733
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
829
734
|
}
|
|
830
735
|
}
|
|
831
736
|
let foundElements = result.foundElements;
|
|
@@ -870,15 +775,15 @@ class StableBrowser {
|
|
|
870
775
|
break;
|
|
871
776
|
}
|
|
872
777
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
873
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
778
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
874
779
|
highPriorityOnly = false;
|
|
875
780
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
876
781
|
lazy_scroll = true;
|
|
877
|
-
await this.
|
|
782
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
878
783
|
}
|
|
879
784
|
}
|
|
880
785
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
881
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
786
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
882
787
|
visibleOnly = false;
|
|
883
788
|
}
|
|
884
789
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -892,10 +797,12 @@ class StableBrowser {
|
|
|
892
797
|
// }
|
|
893
798
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
894
799
|
info.failCause.locatorNotFound = true;
|
|
895
|
-
info
|
|
800
|
+
if (!info?.failCause?.lastError) {
|
|
801
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
802
|
+
}
|
|
896
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
897
804
|
}
|
|
898
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
899
806
|
let foundElements = [];
|
|
900
807
|
const result = {
|
|
901
808
|
foundElements: foundElements,
|
|
@@ -903,14 +810,15 @@ class StableBrowser {
|
|
|
903
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
904
811
|
let foundLocators = [];
|
|
905
812
|
try {
|
|
906
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
907
814
|
}
|
|
908
815
|
catch (e) {
|
|
909
|
-
this
|
|
910
|
-
this.logger.debug(
|
|
816
|
+
// this call can fail it the browser is navigating
|
|
817
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
818
|
+
// this.logger.debug(e);
|
|
911
819
|
foundLocators = [];
|
|
912
820
|
try {
|
|
913
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
821
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
914
822
|
}
|
|
915
823
|
catch (e) {
|
|
916
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -925,9 +833,40 @@ class StableBrowser {
|
|
|
925
833
|
result.locatorIndex = i;
|
|
926
834
|
}
|
|
927
835
|
if (foundLocators.length > 1) {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
836
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
837
|
+
const boxes = [];
|
|
838
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
839
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
840
|
+
}
|
|
841
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
842
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
843
|
+
if (j === k) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
847
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
848
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
849
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
850
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
851
|
+
// as the element is not unique, will remove it
|
|
852
|
+
boxes.splice(k, 1);
|
|
853
|
+
k--;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (boxes.length === 1) {
|
|
858
|
+
result.foundElements.push({
|
|
859
|
+
locator: boxes[0].locator.first(),
|
|
860
|
+
box: boxes[0].box,
|
|
861
|
+
unique: true,
|
|
862
|
+
});
|
|
863
|
+
result.locatorIndex = i;
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
info.failCause.foundMultiple = true;
|
|
867
|
+
if (info.locatorLog) {
|
|
868
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
869
|
+
}
|
|
931
870
|
}
|
|
932
871
|
}
|
|
933
872
|
}
|
|
@@ -1038,25 +977,14 @@ class StableBrowser {
|
|
|
1038
977
|
options,
|
|
1039
978
|
world,
|
|
1040
979
|
text: "Click element",
|
|
980
|
+
_text: "Click on " + selectors.element_name,
|
|
1041
981
|
type: Types.CLICK,
|
|
1042
982
|
operation: "click",
|
|
1043
983
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1044
984
|
};
|
|
1045
985
|
try {
|
|
1046
986
|
await _preCommand(state, this);
|
|
1047
|
-
|
|
1048
|
-
state.selectors.locators[0].text = state.options.context;
|
|
1049
|
-
}
|
|
1050
|
-
try {
|
|
1051
|
-
await state.element.click();
|
|
1052
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
|
-
}
|
|
1054
|
-
catch (e) {
|
|
1055
|
-
// await this.closeUnexpectedPopups();
|
|
1056
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1057
|
-
await state.element.dispatchEvent("click");
|
|
1058
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
|
-
}
|
|
987
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1060
988
|
await this.waitForPageLoad();
|
|
1061
989
|
return state.info;
|
|
1062
990
|
}
|
|
@@ -1067,6 +995,38 @@ class StableBrowser {
|
|
|
1067
995
|
_commandFinally(state, this);
|
|
1068
996
|
}
|
|
1069
997
|
}
|
|
998
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
999
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1000
|
+
const state = {
|
|
1001
|
+
selectors,
|
|
1002
|
+
_params,
|
|
1003
|
+
options,
|
|
1004
|
+
world,
|
|
1005
|
+
text: "Wait for element",
|
|
1006
|
+
_text: "Wait for " + selectors.element_name,
|
|
1007
|
+
type: Types.WAIT_ELEMENT,
|
|
1008
|
+
operation: "waitForElement",
|
|
1009
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1010
|
+
};
|
|
1011
|
+
let found = false;
|
|
1012
|
+
try {
|
|
1013
|
+
await _preCommand(state, this);
|
|
1014
|
+
// if (state.options && state.options.context) {
|
|
1015
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1016
|
+
// }
|
|
1017
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1018
|
+
found = true;
|
|
1019
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
console.error("Error on waitForElement", e);
|
|
1023
|
+
// await _commandError(state, e, this);
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
_commandFinally(state, this);
|
|
1027
|
+
}
|
|
1028
|
+
return found;
|
|
1029
|
+
}
|
|
1070
1030
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
1071
1031
|
const state = {
|
|
1072
1032
|
selectors,
|
|
@@ -1075,6 +1035,7 @@ class StableBrowser {
|
|
|
1075
1035
|
world,
|
|
1076
1036
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1077
1037
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1038
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1078
1039
|
operation: "setCheck",
|
|
1079
1040
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1080
1041
|
};
|
|
@@ -1084,9 +1045,15 @@ class StableBrowser {
|
|
|
1084
1045
|
// let element = await this._locate(selectors, info, _params);
|
|
1085
1046
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1086
1047
|
try {
|
|
1087
|
-
//
|
|
1048
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1050
|
+
await this._highlightElements(element);
|
|
1088
1051
|
await state.element.setChecked(checked);
|
|
1089
1052
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
|
+
// await this._unHighlightElements(element);
|
|
1054
|
+
// }
|
|
1055
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1056
|
+
// await this._unHighlightElements(element);
|
|
1090
1057
|
}
|
|
1091
1058
|
catch (e) {
|
|
1092
1059
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1118,22 +1085,13 @@ class StableBrowser {
|
|
|
1118
1085
|
world,
|
|
1119
1086
|
type: Types.HOVER,
|
|
1120
1087
|
text: `Hover element`,
|
|
1088
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1121
1089
|
operation: "hover",
|
|
1122
1090
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1123
1091
|
};
|
|
1124
1092
|
try {
|
|
1125
1093
|
await _preCommand(state, this);
|
|
1126
|
-
|
|
1127
|
-
await state.element.hover();
|
|
1128
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1129
|
-
}
|
|
1130
|
-
catch (e) {
|
|
1131
|
-
//await this.closeUnexpectedPopups();
|
|
1132
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1133
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1134
|
-
await state.element.hover({ timeout: 10000 });
|
|
1135
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1136
|
-
}
|
|
1094
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1137
1095
|
await _screenshot(state, this);
|
|
1138
1096
|
await this.waitForPageLoad();
|
|
1139
1097
|
return state.info;
|
|
@@ -1157,6 +1115,7 @@ class StableBrowser {
|
|
|
1157
1115
|
value: values.toString(),
|
|
1158
1116
|
type: Types.SELECT,
|
|
1159
1117
|
text: `Select option: ${values}`,
|
|
1118
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1160
1119
|
operation: "selectOption",
|
|
1161
1120
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1162
1121
|
};
|
|
@@ -1191,6 +1150,7 @@ class StableBrowser {
|
|
|
1191
1150
|
highlight: false,
|
|
1192
1151
|
type: Types.TYPE_PRESS,
|
|
1193
1152
|
text: `Type value: ${_value}`,
|
|
1153
|
+
_text: `Type value: ${_value}`,
|
|
1194
1154
|
operation: "type",
|
|
1195
1155
|
log: "",
|
|
1196
1156
|
};
|
|
@@ -1270,6 +1230,7 @@ class StableBrowser {
|
|
|
1270
1230
|
world,
|
|
1271
1231
|
type: Types.SET_DATE_TIME,
|
|
1272
1232
|
text: `Set date time value: ${value}`,
|
|
1233
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1273
1234
|
operation: "setDateTime",
|
|
1274
1235
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1275
1236
|
throwError: false,
|
|
@@ -1277,7 +1238,7 @@ class StableBrowser {
|
|
|
1277
1238
|
try {
|
|
1278
1239
|
await _preCommand(state, this);
|
|
1279
1240
|
try {
|
|
1280
|
-
await state.element
|
|
1241
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1281
1242
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1282
1243
|
if (format) {
|
|
1283
1244
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1341,6 +1302,7 @@ class StableBrowser {
|
|
|
1341
1302
|
world,
|
|
1342
1303
|
type: Types.FILL,
|
|
1343
1304
|
text: `Click type input with value: ${_value}`,
|
|
1305
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1344
1306
|
operation: "clickType",
|
|
1345
1307
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1346
1308
|
};
|
|
@@ -1363,12 +1325,11 @@ class StableBrowser {
|
|
|
1363
1325
|
}
|
|
1364
1326
|
}
|
|
1365
1327
|
if (options === null || options === undefined || options.press) {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
catch (e) {
|
|
1370
|
-
await state.element.dispatchEvent("click");
|
|
1328
|
+
if (!options) {
|
|
1329
|
+
options = {};
|
|
1371
1330
|
}
|
|
1331
|
+
options.timeout = 5000;
|
|
1332
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1372
1333
|
}
|
|
1373
1334
|
else {
|
|
1374
1335
|
try {
|
|
@@ -1406,7 +1367,12 @@ class StableBrowser {
|
|
|
1406
1367
|
await this.waitForPageLoad();
|
|
1407
1368
|
}
|
|
1408
1369
|
else if (enter === false) {
|
|
1409
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
// ignore
|
|
1375
|
+
}
|
|
1410
1376
|
//await this.page.keyboard.press("Tab");
|
|
1411
1377
|
}
|
|
1412
1378
|
else {
|
|
@@ -1458,6 +1424,7 @@ class StableBrowser {
|
|
|
1458
1424
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1459
1425
|
}
|
|
1460
1426
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1427
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1461
1428
|
_validateSelectors(selectors);
|
|
1462
1429
|
let screenshotId = null;
|
|
1463
1430
|
let screenshotPath = null;
|
|
@@ -1467,7 +1434,7 @@ class StableBrowser {
|
|
|
1467
1434
|
}
|
|
1468
1435
|
info.operation = "getText";
|
|
1469
1436
|
info.selectors = selectors;
|
|
1470
|
-
let element = await this._locate(selectors, info, _params);
|
|
1437
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1471
1438
|
if (climb > 0) {
|
|
1472
1439
|
const climbArray = [];
|
|
1473
1440
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1486,6 +1453,18 @@ class StableBrowser {
|
|
|
1486
1453
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1487
1454
|
try {
|
|
1488
1455
|
await this._highlightElements(element);
|
|
1456
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1457
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1458
|
+
// this._highlightElements(element)
|
|
1459
|
+
// .then(async () => {
|
|
1460
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1461
|
+
// this._unhighlightElements(element).then(
|
|
1462
|
+
// () => {}
|
|
1463
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1464
|
+
// );
|
|
1465
|
+
// })
|
|
1466
|
+
// .catch(e);
|
|
1467
|
+
// }
|
|
1489
1468
|
const elementText = await element.innerText();
|
|
1490
1469
|
return {
|
|
1491
1470
|
text: elementText,
|
|
@@ -1497,7 +1476,7 @@ class StableBrowser {
|
|
|
1497
1476
|
}
|
|
1498
1477
|
catch (e) {
|
|
1499
1478
|
//await this.closeUnexpectedPopups();
|
|
1500
|
-
this.logger.info("no innerText will use textContent");
|
|
1479
|
+
this.logger.info("no innerText, will use textContent");
|
|
1501
1480
|
const elementText = await element.textContent();
|
|
1502
1481
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1503
1482
|
}
|
|
@@ -1522,6 +1501,7 @@ class StableBrowser {
|
|
|
1522
1501
|
highlight: false,
|
|
1523
1502
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1524
1503
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1504
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1525
1505
|
operation: "containsPattern",
|
|
1526
1506
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1527
1507
|
};
|
|
@@ -1557,6 +1537,8 @@ class StableBrowser {
|
|
|
1557
1537
|
}
|
|
1558
1538
|
}
|
|
1559
1539
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1540
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1541
|
+
const startTime = Date.now();
|
|
1560
1542
|
const state = {
|
|
1561
1543
|
selectors,
|
|
1562
1544
|
_params,
|
|
@@ -1583,62 +1565,54 @@ class StableBrowser {
|
|
|
1583
1565
|
}
|
|
1584
1566
|
let foundObj = null;
|
|
1585
1567
|
try {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1593
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1594
|
-
if (dateAlternatives.date) {
|
|
1595
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1596
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1597
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1598
|
-
return state.info;
|
|
1568
|
+
while (Date.now() - startTime < timeout) {
|
|
1569
|
+
try {
|
|
1570
|
+
await _preCommand(state, this);
|
|
1571
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1572
|
+
if (foundObj && foundObj.element) {
|
|
1573
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1599
1574
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1575
|
+
await _screenshot(state, this);
|
|
1576
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1577
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1578
|
+
if (dateAlternatives.date) {
|
|
1579
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1580
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1581
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1582
|
+
return state.info;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
else if (numberAlternatives.number) {
|
|
1587
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1588
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1589
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1590
|
+
return state.info;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1607
1595
|
return state.info;
|
|
1608
1596
|
}
|
|
1609
1597
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
throw new Error("element doesn't contain text " + text);
|
|
1598
|
+
catch (e) {
|
|
1599
|
+
// Log error but continue retrying until timeout is reached
|
|
1600
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1601
|
+
}
|
|
1602
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1616
1603
|
}
|
|
1617
|
-
|
|
1604
|
+
state.info.foundText = foundObj?.text;
|
|
1605
|
+
state.info.value = foundObj?.value;
|
|
1606
|
+
throw new Error("element doesn't contain text " + text);
|
|
1618
1607
|
}
|
|
1619
1608
|
catch (e) {
|
|
1620
1609
|
await _commandError(state, e, this);
|
|
1610
|
+
throw e;
|
|
1621
1611
|
}
|
|
1622
1612
|
finally {
|
|
1623
1613
|
_commandFinally(state, this);
|
|
1624
1614
|
}
|
|
1625
1615
|
}
|
|
1626
|
-
_getDataFile(world = null) {
|
|
1627
|
-
let dataFile = null;
|
|
1628
|
-
if (world && world.reportFolder) {
|
|
1629
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1630
|
-
}
|
|
1631
|
-
else if (this.reportFolder) {
|
|
1632
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1633
|
-
}
|
|
1634
|
-
else if (this.context && this.context.reportFolder) {
|
|
1635
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1636
|
-
}
|
|
1637
|
-
else {
|
|
1638
|
-
dataFile = "data.json";
|
|
1639
|
-
}
|
|
1640
|
-
return dataFile;
|
|
1641
|
-
}
|
|
1642
1616
|
async waitForUserInput(message, world = null) {
|
|
1643
1617
|
if (!message) {
|
|
1644
1618
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1667,7 +1641,7 @@ class StableBrowser {
|
|
|
1667
1641
|
return;
|
|
1668
1642
|
}
|
|
1669
1643
|
// if data file exists, load it
|
|
1670
|
-
const dataFile =
|
|
1644
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1671
1645
|
let data = this.getTestData(world);
|
|
1672
1646
|
// merge the testData with the existing data
|
|
1673
1647
|
Object.assign(data, testData);
|
|
@@ -1770,7 +1744,7 @@ class StableBrowser {
|
|
|
1770
1744
|
}
|
|
1771
1745
|
}
|
|
1772
1746
|
getTestData(world = null) {
|
|
1773
|
-
const dataFile =
|
|
1747
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1774
1748
|
let data = {};
|
|
1775
1749
|
if (fs.existsSync(dataFile)) {
|
|
1776
1750
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1857,6 +1831,15 @@ class StableBrowser {
|
|
|
1857
1831
|
document.documentElement.clientWidth,
|
|
1858
1832
|
])));
|
|
1859
1833
|
let screenshotBuffer = null;
|
|
1834
|
+
// if (focusedElement) {
|
|
1835
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1836
|
+
// await this._unhighlightElements(focusedElement);
|
|
1837
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1838
|
+
// console.log(`Unhighlighted previous element`);
|
|
1839
|
+
// }
|
|
1840
|
+
// if (focusedElement) {
|
|
1841
|
+
// await this._highlightElements(focusedElement);
|
|
1842
|
+
// }
|
|
1860
1843
|
if (this.context.browserName === "chromium") {
|
|
1861
1844
|
const client = await playContext.newCDPSession(this.page);
|
|
1862
1845
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1878,6 +1861,10 @@ class StableBrowser {
|
|
|
1878
1861
|
else {
|
|
1879
1862
|
screenshotBuffer = await this.page.screenshot();
|
|
1880
1863
|
}
|
|
1864
|
+
// if (focusedElement) {
|
|
1865
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1866
|
+
// await this._unhighlightElements(focusedElement);
|
|
1867
|
+
// }
|
|
1881
1868
|
let image = await Jimp.read(screenshotBuffer);
|
|
1882
1869
|
// Get the image dimensions
|
|
1883
1870
|
const { width, height } = image.bitmap;
|
|
@@ -1890,6 +1877,7 @@ class StableBrowser {
|
|
|
1890
1877
|
else {
|
|
1891
1878
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1892
1879
|
}
|
|
1880
|
+
return screenshotBuffer;
|
|
1893
1881
|
}
|
|
1894
1882
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1895
1883
|
const state = {
|
|
@@ -1925,8 +1913,10 @@ class StableBrowser {
|
|
|
1925
1913
|
world,
|
|
1926
1914
|
type: Types.EXTRACT,
|
|
1927
1915
|
text: `Extract attribute from element`,
|
|
1916
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1928
1917
|
operation: "extractAttribute",
|
|
1929
1918
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1919
|
+
allowDisabled: true,
|
|
1930
1920
|
};
|
|
1931
1921
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1932
1922
|
try {
|
|
@@ -1948,6 +1938,7 @@ class StableBrowser {
|
|
|
1948
1938
|
state.info.value = state.value;
|
|
1949
1939
|
this.setTestData({ [variable]: state.value }, world);
|
|
1950
1940
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1941
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1951
1942
|
return state.info;
|
|
1952
1943
|
}
|
|
1953
1944
|
catch (e) {
|
|
@@ -1966,14 +1957,21 @@ class StableBrowser {
|
|
|
1966
1957
|
options,
|
|
1967
1958
|
world,
|
|
1968
1959
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1960
|
+
highlight: true,
|
|
1961
|
+
screenshot: true,
|
|
1969
1962
|
text: `Verify element attribute`,
|
|
1963
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1970
1964
|
operation: "verifyAttribute",
|
|
1971
1965
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1972
1967
|
};
|
|
1973
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1974
1969
|
let val;
|
|
1970
|
+
let expectedValue;
|
|
1975
1971
|
try {
|
|
1976
1972
|
await _preCommand(state, this);
|
|
1973
|
+
expectedValue = state.value;
|
|
1974
|
+
state.info.expectedValue = expectedValue;
|
|
1977
1975
|
switch (attribute) {
|
|
1978
1976
|
case "innerText":
|
|
1979
1977
|
val = String(await state.element.innerText());
|
|
@@ -1987,23 +1985,30 @@ class StableBrowser {
|
|
|
1987
1985
|
case "disabled":
|
|
1988
1986
|
val = String(await state.element.isDisabled());
|
|
1989
1987
|
break;
|
|
1988
|
+
case "readOnly":
|
|
1989
|
+
const isEditable = await state.element.isEditable();
|
|
1990
|
+
val = String(!isEditable);
|
|
1991
|
+
break;
|
|
1990
1992
|
default:
|
|
1991
1993
|
val = String(await state.element.getAttribute(attribute));
|
|
1992
1994
|
break;
|
|
1993
1995
|
}
|
|
1996
|
+
state.info.value = val;
|
|
1994
1997
|
let regex;
|
|
1995
|
-
if (
|
|
1996
|
-
const patternBody =
|
|
1998
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1999
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1997
2000
|
regex = new RegExp(patternBody, "g");
|
|
1998
2001
|
}
|
|
1999
2002
|
else {
|
|
2000
|
-
const escapedPattern =
|
|
2003
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2001
2004
|
regex = new RegExp(escapedPattern, "g");
|
|
2002
2005
|
}
|
|
2003
2006
|
if (!val.match(regex)) {
|
|
2004
|
-
|
|
2007
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2008
|
+
state.info.failCause.assertionFailed = true;
|
|
2009
|
+
state.info.failCause.lastError = errorMessage;
|
|
2010
|
+
throw new Error(errorMessage);
|
|
2005
2011
|
}
|
|
2006
|
-
state.info.value = val;
|
|
2007
2012
|
return state.info;
|
|
2008
2013
|
}
|
|
2009
2014
|
catch (e) {
|
|
@@ -2031,7 +2036,7 @@ class StableBrowser {
|
|
|
2031
2036
|
if (options && options.timeout) {
|
|
2032
2037
|
timeout = options.timeout;
|
|
2033
2038
|
}
|
|
2034
|
-
const serviceUrl =
|
|
2039
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
2035
2040
|
const request = {
|
|
2036
2041
|
method: "POST",
|
|
2037
2042
|
url: serviceUrl,
|
|
@@ -2102,27 +2107,32 @@ class StableBrowser {
|
|
|
2102
2107
|
async _highlightElements(scope, css) {
|
|
2103
2108
|
try {
|
|
2104
2109
|
if (!scope) {
|
|
2110
|
+
// console.log(`Scope is not defined`);
|
|
2105
2111
|
return;
|
|
2106
2112
|
}
|
|
2107
2113
|
if (!css) {
|
|
2108
2114
|
scope
|
|
2109
2115
|
.evaluate((node) => {
|
|
2110
2116
|
if (node && node.style) {
|
|
2111
|
-
let
|
|
2112
|
-
|
|
2117
|
+
let originalOutline = node.style.outline;
|
|
2118
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2119
|
+
// node.__previousOutline = originalOutline;
|
|
2120
|
+
node.style.outline = "2px solid red";
|
|
2121
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2113
2122
|
if (window) {
|
|
2114
2123
|
window.addEventListener("beforeunload", function (e) {
|
|
2115
|
-
node.style.
|
|
2124
|
+
node.style.outline = originalOutline;
|
|
2116
2125
|
});
|
|
2117
2126
|
}
|
|
2118
2127
|
setTimeout(function () {
|
|
2119
|
-
node.style.
|
|
2128
|
+
node.style.outline = originalOutline;
|
|
2120
2129
|
}, 2000);
|
|
2121
2130
|
}
|
|
2122
2131
|
})
|
|
2123
2132
|
.then(() => { })
|
|
2124
2133
|
.catch((e) => {
|
|
2125
2134
|
// ignore
|
|
2135
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2126
2136
|
});
|
|
2127
2137
|
}
|
|
2128
2138
|
else {
|
|
@@ -2138,17 +2148,18 @@ class StableBrowser {
|
|
|
2138
2148
|
if (!element.style) {
|
|
2139
2149
|
return;
|
|
2140
2150
|
}
|
|
2141
|
-
|
|
2151
|
+
let originalOutline = element.style.outline;
|
|
2152
|
+
element.__previousOutline = originalOutline;
|
|
2142
2153
|
// Set the new border to be red and 2px solid
|
|
2143
|
-
element.style.
|
|
2154
|
+
element.style.outline = "2px solid red";
|
|
2144
2155
|
if (window) {
|
|
2145
2156
|
window.addEventListener("beforeunload", function (e) {
|
|
2146
|
-
element.style.
|
|
2157
|
+
element.style.outline = originalOutline;
|
|
2147
2158
|
});
|
|
2148
2159
|
}
|
|
2149
2160
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2150
2161
|
setTimeout(function () {
|
|
2151
|
-
element.style.
|
|
2162
|
+
element.style.outline = originalOutline;
|
|
2152
2163
|
}, 2000);
|
|
2153
2164
|
}
|
|
2154
2165
|
return;
|
|
@@ -2156,6 +2167,7 @@ class StableBrowser {
|
|
|
2156
2167
|
.then(() => { })
|
|
2157
2168
|
.catch((e) => {
|
|
2158
2169
|
// ignore
|
|
2170
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2159
2171
|
});
|
|
2160
2172
|
}
|
|
2161
2173
|
}
|
|
@@ -2163,6 +2175,54 @@ class StableBrowser {
|
|
|
2163
2175
|
console.debug(error);
|
|
2164
2176
|
}
|
|
2165
2177
|
}
|
|
2178
|
+
// async _unhighlightElements(scope, css) {
|
|
2179
|
+
// try {
|
|
2180
|
+
// if (!scope) {
|
|
2181
|
+
// return;
|
|
2182
|
+
// }
|
|
2183
|
+
// if (!css) {
|
|
2184
|
+
// scope
|
|
2185
|
+
// .evaluate((node) => {
|
|
2186
|
+
// if (node && node.style) {
|
|
2187
|
+
// if (!node.__previousOutline) {
|
|
2188
|
+
// node.style.outline = "";
|
|
2189
|
+
// } else {
|
|
2190
|
+
// node.style.outline = node.__previousOutline;
|
|
2191
|
+
// }
|
|
2192
|
+
// }
|
|
2193
|
+
// })
|
|
2194
|
+
// .then(() => {})
|
|
2195
|
+
// .catch((e) => {
|
|
2196
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2197
|
+
// });
|
|
2198
|
+
// } else {
|
|
2199
|
+
// scope
|
|
2200
|
+
// .evaluate(([css]) => {
|
|
2201
|
+
// if (!css) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2205
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2206
|
+
// let element = elements[i];
|
|
2207
|
+
// if (!element.style) {
|
|
2208
|
+
// return;
|
|
2209
|
+
// }
|
|
2210
|
+
// if (!element.__previousOutline) {
|
|
2211
|
+
// element.style.outline = "";
|
|
2212
|
+
// } else {
|
|
2213
|
+
// element.style.outline = element.__previousOutline;
|
|
2214
|
+
// }
|
|
2215
|
+
// }
|
|
2216
|
+
// })
|
|
2217
|
+
// .then(() => {})
|
|
2218
|
+
// .catch((e) => {
|
|
2219
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2220
|
+
// });
|
|
2221
|
+
// }
|
|
2222
|
+
// } catch (error) {
|
|
2223
|
+
// // console.debug(error);
|
|
2224
|
+
// }
|
|
2225
|
+
// }
|
|
2166
2226
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2167
2227
|
const startTime = Date.now();
|
|
2168
2228
|
let error = null;
|
|
@@ -2207,6 +2267,7 @@ class StableBrowser {
|
|
|
2207
2267
|
_reportToWorld(world, {
|
|
2208
2268
|
type: Types.VERIFY_PAGE_PATH,
|
|
2209
2269
|
text: "Verify page path",
|
|
2270
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2210
2271
|
screenshotId,
|
|
2211
2272
|
result: error
|
|
2212
2273
|
? {
|
|
@@ -2224,6 +2285,35 @@ class StableBrowser {
|
|
|
2224
2285
|
});
|
|
2225
2286
|
}
|
|
2226
2287
|
}
|
|
2288
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2289
|
+
const frames = this.page.frames();
|
|
2290
|
+
let results = [];
|
|
2291
|
+
// let ignoreCase = false;
|
|
2292
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2293
|
+
if (dateAlternatives.date) {
|
|
2294
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2295
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2296
|
+
result.frame = frames[i];
|
|
2297
|
+
results.push(result);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
else if (numberAlternatives.number) {
|
|
2301
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2302
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2303
|
+
result.frame = frames[i];
|
|
2304
|
+
results.push(result);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
else {
|
|
2308
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2309
|
+
result.frame = frames[i];
|
|
2310
|
+
results.push(result);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
state.info.results = results;
|
|
2314
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2315
|
+
return resultWithElementsFound;
|
|
2316
|
+
}
|
|
2227
2317
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2228
2318
|
text = unEscapeString(text);
|
|
2229
2319
|
const state = {
|
|
@@ -2233,12 +2323,16 @@ class StableBrowser {
|
|
|
2233
2323
|
locate: false,
|
|
2234
2324
|
scroll: false,
|
|
2235
2325
|
highlight: false,
|
|
2236
|
-
type: Types.
|
|
2326
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2237
2327
|
text: `Verify text exists in page`,
|
|
2328
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2238
2329
|
operation: "verifyTextExistInPage",
|
|
2239
2330
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2240
2331
|
};
|
|
2241
|
-
|
|
2332
|
+
if (testForRegex(text)) {
|
|
2333
|
+
text = text.replace(/\\"/g, '"');
|
|
2334
|
+
}
|
|
2335
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2242
2336
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2243
2337
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2244
2338
|
if (newValue !== text) {
|
|
@@ -2251,31 +2345,15 @@ class StableBrowser {
|
|
|
2251
2345
|
await _preCommand(state, this);
|
|
2252
2346
|
state.info.text = text;
|
|
2253
2347
|
while (true) {
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
else if (numberAlternatives.number) {
|
|
2265
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2266
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2267
|
-
result.frame = frames[i];
|
|
2268
|
-
results.push(result);
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
else {
|
|
2272
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2273
|
-
result.frame = frames[i];
|
|
2274
|
-
results.push(result);
|
|
2275
|
-
}
|
|
2348
|
+
let resultWithElementsFound = {
|
|
2349
|
+
length: 0,
|
|
2350
|
+
};
|
|
2351
|
+
try {
|
|
2352
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2353
|
+
}
|
|
2354
|
+
catch (error) {
|
|
2355
|
+
// ignore
|
|
2276
2356
|
}
|
|
2277
|
-
state.info.results = results;
|
|
2278
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2279
2357
|
if (resultWithElementsFound.length === 0) {
|
|
2280
2358
|
if (Date.now() - state.startTime > timeout) {
|
|
2281
2359
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2283,18 +2361,40 @@ class StableBrowser {
|
|
|
2283
2361
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2284
2362
|
continue;
|
|
2285
2363
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2364
|
+
try {
|
|
2365
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2366
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2367
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2368
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2369
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2370
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2371
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2372
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2373
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2374
|
+
// .then(async () => {
|
|
2375
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2376
|
+
// })
|
|
2377
|
+
// .catch(
|
|
2378
|
+
// (e) => {}
|
|
2379
|
+
// console.error(e)
|
|
2380
|
+
// );
|
|
2381
|
+
// });
|
|
2382
|
+
// }
|
|
2383
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2384
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2385
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2386
|
+
if (element) {
|
|
2387
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2388
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2389
|
+
// await _screenshot(state, this, element);
|
|
2390
|
+
}
|
|
2294
2391
|
}
|
|
2392
|
+
await _screenshot(state, this);
|
|
2393
|
+
return state.info;
|
|
2394
|
+
}
|
|
2395
|
+
catch (error) {
|
|
2396
|
+
console.error(error);
|
|
2295
2397
|
}
|
|
2296
|
-
await _screenshot(state, this);
|
|
2297
|
-
return state.info;
|
|
2298
2398
|
}
|
|
2299
2399
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2300
2400
|
}
|
|
@@ -2316,10 +2416,14 @@ class StableBrowser {
|
|
|
2316
2416
|
highlight: false,
|
|
2317
2417
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2318
2418
|
text: `Verify text does not exist in page`,
|
|
2419
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2319
2420
|
operation: "verifyTextNotExistInPage",
|
|
2320
2421
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2321
2422
|
};
|
|
2322
|
-
|
|
2423
|
+
if (testForRegex(text)) {
|
|
2424
|
+
text = text.replace(/\\"/g, '"');
|
|
2425
|
+
}
|
|
2426
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2323
2427
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2324
2428
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2325
2429
|
if (newValue !== text) {
|
|
@@ -2331,32 +2435,16 @@ class StableBrowser {
|
|
|
2331
2435
|
try {
|
|
2332
2436
|
await _preCommand(state, this);
|
|
2333
2437
|
state.info.text = text;
|
|
2438
|
+
let resultWithElementsFound = {
|
|
2439
|
+
length: null, // initial cannot be 0
|
|
2440
|
+
};
|
|
2334
2441
|
while (true) {
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2341
|
-
result.frame = frames[i];
|
|
2342
|
-
results.push(result);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
else if (numberAlternatives.number) {
|
|
2346
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2347
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2348
|
-
result.frame = frames[i];
|
|
2349
|
-
results.push(result);
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
else {
|
|
2353
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2354
|
-
result.frame = frames[i];
|
|
2355
|
-
results.push(result);
|
|
2356
|
-
}
|
|
2442
|
+
try {
|
|
2443
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2444
|
+
}
|
|
2445
|
+
catch (error) {
|
|
2446
|
+
// ignore
|
|
2357
2447
|
}
|
|
2358
|
-
state.info.results = results;
|
|
2359
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2360
2448
|
if (resultWithElementsFound.length === 0) {
|
|
2361
2449
|
await _screenshot(state, this);
|
|
2362
2450
|
return state.info;
|
|
@@ -2374,15 +2462,140 @@ class StableBrowser {
|
|
|
2374
2462
|
_commandFinally(state, this);
|
|
2375
2463
|
}
|
|
2376
2464
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2465
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2466
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2467
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2468
|
+
const state = {
|
|
2469
|
+
text_search: textToVerify,
|
|
2470
|
+
options,
|
|
2471
|
+
world,
|
|
2472
|
+
locate: false,
|
|
2473
|
+
scroll: false,
|
|
2474
|
+
highlight: false,
|
|
2475
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2476
|
+
text: `Verify text with relation to another text`,
|
|
2477
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2478
|
+
operation: "verify_text_with_relation",
|
|
2479
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2480
|
+
};
|
|
2481
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2482
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2483
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2484
|
+
if (newValue !== textAnchor) {
|
|
2485
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2486
|
+
textAnchor = newValue;
|
|
2487
|
+
}
|
|
2488
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2489
|
+
if (newValue !== textToVerify) {
|
|
2490
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2491
|
+
textToVerify = newValue;
|
|
2492
|
+
}
|
|
2493
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2494
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2495
|
+
let foundAncore = false;
|
|
2496
|
+
try {
|
|
2497
|
+
await _preCommand(state, this);
|
|
2498
|
+
state.info.text = textToVerify;
|
|
2499
|
+
let resultWithElementsFound = {
|
|
2500
|
+
length: 0,
|
|
2501
|
+
};
|
|
2502
|
+
while (true) {
|
|
2503
|
+
try {
|
|
2504
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2505
|
+
}
|
|
2506
|
+
catch (error) {
|
|
2507
|
+
// ignore
|
|
2508
|
+
}
|
|
2509
|
+
if (resultWithElementsFound.length === 0) {
|
|
2510
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2511
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2512
|
+
}
|
|
2513
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2518
|
+
foundAncore = true;
|
|
2519
|
+
const result = resultWithElementsFound[i];
|
|
2520
|
+
const token = result.randomToken;
|
|
2521
|
+
const frame = result.frame;
|
|
2522
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2523
|
+
const climbArray1 = [];
|
|
2524
|
+
for (let i = 0; i < climb; i++) {
|
|
2525
|
+
climbArray1.push("..");
|
|
2526
|
+
}
|
|
2527
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2528
|
+
css = css + " >> " + climbXpath;
|
|
2529
|
+
const count = await frame.locator(css).count();
|
|
2530
|
+
for (let j = 0; j < count; j++) {
|
|
2531
|
+
const continer = await frame.locator(css).nth(j);
|
|
2532
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
|
|
2533
|
+
if (result.elementCount > 0) {
|
|
2534
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2535
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2536
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2537
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2538
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2539
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2540
|
+
// .then(async () => {
|
|
2541
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2542
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2543
|
+
// () => {}
|
|
2544
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2545
|
+
// );
|
|
2546
|
+
// })
|
|
2547
|
+
// .catch(e);
|
|
2548
|
+
// }
|
|
2549
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2550
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2551
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2552
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2553
|
+
if (element) {
|
|
2554
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2555
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2556
|
+
}
|
|
2557
|
+
await _screenshot(state, this);
|
|
2558
|
+
return state.info;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
catch (error) {
|
|
2564
|
+
console.error(error);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2381
2568
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
2569
|
+
catch (e) {
|
|
2570
|
+
await _commandError(state, e, this);
|
|
2571
|
+
}
|
|
2572
|
+
finally {
|
|
2573
|
+
_commandFinally(state, this);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2577
|
+
const frames = this.page.frames();
|
|
2578
|
+
let results = [];
|
|
2579
|
+
let ignoreCase = false;
|
|
2580
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2581
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2582
|
+
result.frame = frames[i];
|
|
2583
|
+
const climbArray = [];
|
|
2584
|
+
for (let i = 0; i < climb; i++) {
|
|
2585
|
+
climbArray.push("..");
|
|
2586
|
+
}
|
|
2587
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2588
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2589
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2590
|
+
if (count > 0) {
|
|
2591
|
+
result.elementCount = count;
|
|
2592
|
+
result.locator = newLocator;
|
|
2593
|
+
results.push(result);
|
|
2594
|
+
}
|
|
2384
2595
|
}
|
|
2385
|
-
|
|
2596
|
+
// state.info.results = results;
|
|
2597
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2598
|
+
return resultWithElementsFound;
|
|
2386
2599
|
}
|
|
2387
2600
|
async visualVerification(text, options = {}, world = null) {
|
|
2388
2601
|
const startTime = Date.now();
|
|
@@ -2398,14 +2611,17 @@ class StableBrowser {
|
|
|
2398
2611
|
throw new Error("TOKEN is not set");
|
|
2399
2612
|
}
|
|
2400
2613
|
try {
|
|
2401
|
-
let serviceUrl =
|
|
2614
|
+
let serviceUrl = _getServerUrl();
|
|
2402
2615
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2403
2616
|
info.screenshotPath = screenshotPath;
|
|
2404
2617
|
const screenshot = await this.takeScreenshot();
|
|
2405
|
-
|
|
2406
|
-
method: "
|
|
2618
|
+
let request = {
|
|
2619
|
+
method: "post",
|
|
2620
|
+
maxBodyLength: Infinity,
|
|
2407
2621
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2408
2622
|
headers: {
|
|
2623
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2624
|
+
"x-source": "aaa",
|
|
2409
2625
|
"Content-Type": "application/json",
|
|
2410
2626
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2411
2627
|
},
|
|
@@ -2414,7 +2630,7 @@ class StableBrowser {
|
|
|
2414
2630
|
screenshot: screenshot,
|
|
2415
2631
|
}),
|
|
2416
2632
|
};
|
|
2417
|
-
|
|
2633
|
+
const result = await axios.request(request);
|
|
2418
2634
|
if (result.data.status !== true) {
|
|
2419
2635
|
throw new Error("Visual validation failed");
|
|
2420
2636
|
}
|
|
@@ -2442,6 +2658,7 @@ class StableBrowser {
|
|
|
2442
2658
|
_reportToWorld(world, {
|
|
2443
2659
|
type: Types.VERIFY_VISUAL,
|
|
2444
2660
|
text: "Visual verification",
|
|
2661
|
+
_text: "Visual verification of " + text,
|
|
2445
2662
|
screenshotId,
|
|
2446
2663
|
result: error
|
|
2447
2664
|
? {
|
|
@@ -2563,7 +2780,7 @@ class StableBrowser {
|
|
|
2563
2780
|
info.operation = "analyzeTable";
|
|
2564
2781
|
info.selectors = selectors;
|
|
2565
2782
|
info.query = query;
|
|
2566
|
-
query =
|
|
2783
|
+
query = _fixUsingParams(query, _params);
|
|
2567
2784
|
info.query_fixed = query;
|
|
2568
2785
|
info.operator = operator;
|
|
2569
2786
|
info.value = value;
|
|
@@ -2708,6 +2925,32 @@ class StableBrowser {
|
|
|
2708
2925
|
}
|
|
2709
2926
|
return timeout;
|
|
2710
2927
|
}
|
|
2928
|
+
_getFindElementTimeout(options) {
|
|
2929
|
+
if (options && options.timeout) {
|
|
2930
|
+
return options.timeout;
|
|
2931
|
+
}
|
|
2932
|
+
if (this.configuration.find_element_timeout) {
|
|
2933
|
+
return this.configuration.find_element_timeout;
|
|
2934
|
+
}
|
|
2935
|
+
return 30000;
|
|
2936
|
+
}
|
|
2937
|
+
async saveStoreState(path = null, world = null) {
|
|
2938
|
+
const storageState = await this.page.context().storageState();
|
|
2939
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2940
|
+
if (path) {
|
|
2941
|
+
// save { storageState: storageState } into the path
|
|
2942
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2943
|
+
}
|
|
2944
|
+
else {
|
|
2945
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
async restoreSaveState(path = null, world = null) {
|
|
2949
|
+
await refreshBrowser(this, path, world);
|
|
2950
|
+
this.registerEventListeners(this.context);
|
|
2951
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2952
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2953
|
+
}
|
|
2711
2954
|
async waitForPageLoad(options = {}, world = null) {
|
|
2712
2955
|
let timeout = this._getLoadTimeout(options);
|
|
2713
2956
|
const promiseArray = [];
|
|
@@ -2775,6 +3018,7 @@ class StableBrowser {
|
|
|
2775
3018
|
highlight: false,
|
|
2776
3019
|
type: Types.CLOSE_PAGE,
|
|
2777
3020
|
text: `Close page`,
|
|
3021
|
+
_text: `Close the page`,
|
|
2778
3022
|
operation: "closePage",
|
|
2779
3023
|
log: "***** close page *****\n",
|
|
2780
3024
|
throwError: false,
|
|
@@ -2791,8 +3035,95 @@ class StableBrowser {
|
|
|
2791
3035
|
_commandFinally(state, this);
|
|
2792
3036
|
}
|
|
2793
3037
|
}
|
|
3038
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3039
|
+
let operation = null;
|
|
3040
|
+
if (!options || !options.operation) {
|
|
3041
|
+
throw new Error("operation is not defined");
|
|
3042
|
+
}
|
|
3043
|
+
operation = options.operation;
|
|
3044
|
+
// validate operation is one of the supported operations
|
|
3045
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3046
|
+
throw new Error("operation is not supported");
|
|
3047
|
+
}
|
|
3048
|
+
const state = {
|
|
3049
|
+
options,
|
|
3050
|
+
world,
|
|
3051
|
+
locate: false,
|
|
3052
|
+
scroll: false,
|
|
3053
|
+
highlight: false,
|
|
3054
|
+
type: Types.TABLE_OPERATION,
|
|
3055
|
+
text: `Table operation`,
|
|
3056
|
+
_text: `Table ${operation} operation`,
|
|
3057
|
+
operation: operation,
|
|
3058
|
+
log: "***** Table operation *****\n",
|
|
3059
|
+
};
|
|
3060
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3061
|
+
try {
|
|
3062
|
+
await _preCommand(state, this);
|
|
3063
|
+
const start = Date.now();
|
|
3064
|
+
let cellArea = null;
|
|
3065
|
+
while (true) {
|
|
3066
|
+
try {
|
|
3067
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3068
|
+
if (cellArea) {
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
catch (e) {
|
|
3073
|
+
// ignore
|
|
3074
|
+
}
|
|
3075
|
+
if (Date.now() - start > timeout) {
|
|
3076
|
+
throw new Error(`Cell not found in table`);
|
|
3077
|
+
}
|
|
3078
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3079
|
+
}
|
|
3080
|
+
switch (operation) {
|
|
3081
|
+
case "click":
|
|
3082
|
+
if (!options.css) {
|
|
3083
|
+
// will click in the center of the cell
|
|
3084
|
+
let xOffset = 0;
|
|
3085
|
+
let yOffset = 0;
|
|
3086
|
+
if (options.xOffset) {
|
|
3087
|
+
xOffset = options.xOffset;
|
|
3088
|
+
}
|
|
3089
|
+
if (options.yOffset) {
|
|
3090
|
+
yOffset = options.yOffset;
|
|
3091
|
+
}
|
|
3092
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3093
|
+
}
|
|
3094
|
+
else {
|
|
3095
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3096
|
+
if (results.length === 0) {
|
|
3097
|
+
throw new Error(`Element not found in cell area`);
|
|
3098
|
+
}
|
|
3099
|
+
state.element = results[0];
|
|
3100
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3101
|
+
}
|
|
3102
|
+
break;
|
|
3103
|
+
case "hover+click":
|
|
3104
|
+
if (!options.css) {
|
|
3105
|
+
throw new Error("css is not defined");
|
|
3106
|
+
}
|
|
3107
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3108
|
+
if (results.length === 0) {
|
|
3109
|
+
throw new Error(`Element not found in cell area`);
|
|
3110
|
+
}
|
|
3111
|
+
state.element = results[0];
|
|
3112
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3113
|
+
break;
|
|
3114
|
+
default:
|
|
3115
|
+
throw new Error("operation is not supported");
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
catch (e) {
|
|
3119
|
+
await _commandError(state, e, this);
|
|
3120
|
+
}
|
|
3121
|
+
finally {
|
|
3122
|
+
_commandFinally(state, this);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
2794
3125
|
saveTestDataAsGlobal(options, world) {
|
|
2795
|
-
const dataFile =
|
|
3126
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2796
3127
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2797
3128
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2798
3129
|
}
|
|
@@ -2822,6 +3153,7 @@ class StableBrowser {
|
|
|
2822
3153
|
_reportToWorld(world, {
|
|
2823
3154
|
type: Types.SET_VIEWPORT,
|
|
2824
3155
|
text: "set viewport size to " + width + "x" + hight,
|
|
3156
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2825
3157
|
screenshotId,
|
|
2826
3158
|
result: error
|
|
2827
3159
|
? {
|
|
@@ -2893,14 +3225,25 @@ class StableBrowser {
|
|
|
2893
3225
|
}
|
|
2894
3226
|
}
|
|
2895
3227
|
async beforeStep(world, step) {
|
|
2896
|
-
this.stepName = step.pickleStep.text;
|
|
2897
|
-
this.logger.info("step: " + this.stepName);
|
|
2898
3228
|
if (this.stepIndex === undefined) {
|
|
2899
3229
|
this.stepIndex = 0;
|
|
2900
3230
|
}
|
|
2901
3231
|
else {
|
|
2902
3232
|
this.stepIndex++;
|
|
2903
3233
|
}
|
|
3234
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3235
|
+
this.stepName = step.pickleStep.text;
|
|
3236
|
+
this.logger.info("step: " + this.stepName);
|
|
3237
|
+
}
|
|
3238
|
+
else if (step && step.text) {
|
|
3239
|
+
this.stepName = step.text;
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
this.stepName = "step " + this.stepIndex;
|
|
3243
|
+
}
|
|
3244
|
+
if (this.context) {
|
|
3245
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3246
|
+
}
|
|
2904
3247
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2905
3248
|
if (this.context.browserObject.context) {
|
|
2906
3249
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2913,6 +3256,41 @@ class StableBrowser {
|
|
|
2913
3256
|
this.saveTestDataAsGlobal({}, world);
|
|
2914
3257
|
}
|
|
2915
3258
|
}
|
|
3259
|
+
if (this.initSnapshotTaken === false) {
|
|
3260
|
+
this.initSnapshotTaken = true;
|
|
3261
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3262
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3263
|
+
if (snapshot) {
|
|
3264
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async getAriaSnapshot() {
|
|
3270
|
+
try {
|
|
3271
|
+
// find the page url
|
|
3272
|
+
const url = await this.page.url();
|
|
3273
|
+
// extract the path from the url
|
|
3274
|
+
const path = new URL(url).pathname;
|
|
3275
|
+
// get the page title
|
|
3276
|
+
const title = await this.page.title();
|
|
3277
|
+
// go over other frams
|
|
3278
|
+
const frames = this.page.frames();
|
|
3279
|
+
const snapshots = [];
|
|
3280
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3281
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3282
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3283
|
+
content.push(`- frame: ${i}`);
|
|
3284
|
+
const frame = frames[i];
|
|
3285
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3286
|
+
content.push(snapshot);
|
|
3287
|
+
}
|
|
3288
|
+
return content.join("\n");
|
|
3289
|
+
}
|
|
3290
|
+
catch (e) {
|
|
3291
|
+
console.error(e);
|
|
3292
|
+
}
|
|
3293
|
+
return null;
|
|
2916
3294
|
}
|
|
2917
3295
|
async afterStep(world, step) {
|
|
2918
3296
|
this.stepName = null;
|
|
@@ -2923,6 +3301,16 @@ class StableBrowser {
|
|
|
2923
3301
|
});
|
|
2924
3302
|
}
|
|
2925
3303
|
}
|
|
3304
|
+
if (this.context) {
|
|
3305
|
+
this.context.examplesRow = null;
|
|
3306
|
+
}
|
|
3307
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3308
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3309
|
+
if (snapshot) {
|
|
3310
|
+
const obj = {};
|
|
3311
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
2926
3314
|
}
|
|
2927
3315
|
}
|
|
2928
3316
|
function createTimedPromise(promise, label) {
|
|
@@ -2930,156 +3318,5 @@ function createTimedPromise(promise, label) {
|
|
|
2930
3318
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2931
3319
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2932
3320
|
}
|
|
2933
|
-
const KEYBOARD_EVENTS = [
|
|
2934
|
-
"ALT",
|
|
2935
|
-
"AltGraph",
|
|
2936
|
-
"CapsLock",
|
|
2937
|
-
"Control",
|
|
2938
|
-
"Fn",
|
|
2939
|
-
"FnLock",
|
|
2940
|
-
"Hyper",
|
|
2941
|
-
"Meta",
|
|
2942
|
-
"NumLock",
|
|
2943
|
-
"ScrollLock",
|
|
2944
|
-
"Shift",
|
|
2945
|
-
"Super",
|
|
2946
|
-
"Symbol",
|
|
2947
|
-
"SymbolLock",
|
|
2948
|
-
"Enter",
|
|
2949
|
-
"Tab",
|
|
2950
|
-
"ArrowDown",
|
|
2951
|
-
"ArrowLeft",
|
|
2952
|
-
"ArrowRight",
|
|
2953
|
-
"ArrowUp",
|
|
2954
|
-
"End",
|
|
2955
|
-
"Home",
|
|
2956
|
-
"PageDown",
|
|
2957
|
-
"PageUp",
|
|
2958
|
-
"Backspace",
|
|
2959
|
-
"Clear",
|
|
2960
|
-
"Copy",
|
|
2961
|
-
"CrSel",
|
|
2962
|
-
"Cut",
|
|
2963
|
-
"Delete",
|
|
2964
|
-
"EraseEof",
|
|
2965
|
-
"ExSel",
|
|
2966
|
-
"Insert",
|
|
2967
|
-
"Paste",
|
|
2968
|
-
"Redo",
|
|
2969
|
-
"Undo",
|
|
2970
|
-
"Accept",
|
|
2971
|
-
"Again",
|
|
2972
|
-
"Attn",
|
|
2973
|
-
"Cancel",
|
|
2974
|
-
"ContextMenu",
|
|
2975
|
-
"Escape",
|
|
2976
|
-
"Execute",
|
|
2977
|
-
"Find",
|
|
2978
|
-
"Finish",
|
|
2979
|
-
"Help",
|
|
2980
|
-
"Pause",
|
|
2981
|
-
"Play",
|
|
2982
|
-
"Props",
|
|
2983
|
-
"Select",
|
|
2984
|
-
"ZoomIn",
|
|
2985
|
-
"ZoomOut",
|
|
2986
|
-
"BrightnessDown",
|
|
2987
|
-
"BrightnessUp",
|
|
2988
|
-
"Eject",
|
|
2989
|
-
"LogOff",
|
|
2990
|
-
"Power",
|
|
2991
|
-
"PowerOff",
|
|
2992
|
-
"PrintScreen",
|
|
2993
|
-
"Hibernate",
|
|
2994
|
-
"Standby",
|
|
2995
|
-
"WakeUp",
|
|
2996
|
-
"AllCandidates",
|
|
2997
|
-
"Alphanumeric",
|
|
2998
|
-
"CodeInput",
|
|
2999
|
-
"Compose",
|
|
3000
|
-
"Convert",
|
|
3001
|
-
"Dead",
|
|
3002
|
-
"FinalMode",
|
|
3003
|
-
"GroupFirst",
|
|
3004
|
-
"GroupLast",
|
|
3005
|
-
"GroupNext",
|
|
3006
|
-
"GroupPrevious",
|
|
3007
|
-
"ModeChange",
|
|
3008
|
-
"NextCandidate",
|
|
3009
|
-
"NonConvert",
|
|
3010
|
-
"PreviousCandidate",
|
|
3011
|
-
"Process",
|
|
3012
|
-
"SingleCandidate",
|
|
3013
|
-
"HangulMode",
|
|
3014
|
-
"HanjaMode",
|
|
3015
|
-
"JunjaMode",
|
|
3016
|
-
"Eisu",
|
|
3017
|
-
"Hankaku",
|
|
3018
|
-
"Hiragana",
|
|
3019
|
-
"HiraganaKatakana",
|
|
3020
|
-
"KanaMode",
|
|
3021
|
-
"KanjiMode",
|
|
3022
|
-
"Katakana",
|
|
3023
|
-
"Romaji",
|
|
3024
|
-
"Zenkaku",
|
|
3025
|
-
"ZenkakuHanaku",
|
|
3026
|
-
"F1",
|
|
3027
|
-
"F2",
|
|
3028
|
-
"F3",
|
|
3029
|
-
"F4",
|
|
3030
|
-
"F5",
|
|
3031
|
-
"F6",
|
|
3032
|
-
"F7",
|
|
3033
|
-
"F8",
|
|
3034
|
-
"F9",
|
|
3035
|
-
"F10",
|
|
3036
|
-
"F11",
|
|
3037
|
-
"F12",
|
|
3038
|
-
"Soft1",
|
|
3039
|
-
"Soft2",
|
|
3040
|
-
"Soft3",
|
|
3041
|
-
"Soft4",
|
|
3042
|
-
"ChannelDown",
|
|
3043
|
-
"ChannelUp",
|
|
3044
|
-
"Close",
|
|
3045
|
-
"MailForward",
|
|
3046
|
-
"MailReply",
|
|
3047
|
-
"MailSend",
|
|
3048
|
-
"MediaFastForward",
|
|
3049
|
-
"MediaPause",
|
|
3050
|
-
"MediaPlay",
|
|
3051
|
-
"MediaPlayPause",
|
|
3052
|
-
"MediaRecord",
|
|
3053
|
-
"MediaRewind",
|
|
3054
|
-
"MediaStop",
|
|
3055
|
-
"MediaTrackNext",
|
|
3056
|
-
"MediaTrackPrevious",
|
|
3057
|
-
"AudioBalanceLeft",
|
|
3058
|
-
"AudioBalanceRight",
|
|
3059
|
-
"AudioBassBoostDown",
|
|
3060
|
-
"AudioBassBoostToggle",
|
|
3061
|
-
"AudioBassBoostUp",
|
|
3062
|
-
"AudioFaderFront",
|
|
3063
|
-
"AudioFaderRear",
|
|
3064
|
-
"AudioSurroundModeNext",
|
|
3065
|
-
"AudioTrebleDown",
|
|
3066
|
-
"AudioTrebleUp",
|
|
3067
|
-
"AudioVolumeDown",
|
|
3068
|
-
"AudioVolumeMute",
|
|
3069
|
-
"AudioVolumeUp",
|
|
3070
|
-
"MicrophoneToggle",
|
|
3071
|
-
"MicrophoneVolumeDown",
|
|
3072
|
-
"MicrophoneVolumeMute",
|
|
3073
|
-
"MicrophoneVolumeUp",
|
|
3074
|
-
"TV",
|
|
3075
|
-
"TV3DMode",
|
|
3076
|
-
"TVAntennaCable",
|
|
3077
|
-
"TVAudioDescription",
|
|
3078
|
-
];
|
|
3079
|
-
function unEscapeString(str) {
|
|
3080
|
-
const placeholder = "__NEWLINE__";
|
|
3081
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
3082
|
-
return str;
|
|
3083
|
-
}
|
|
3084
3321
|
export { StableBrowser };
|
|
3085
3322
|
//# sourceMappingURL=stable_browser.js.map
|