automation_model 1.0.551-dev → 1.0.551-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 +28 -17
- package/lib/stable_browser.js +743 -558
- 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
280
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
else if (this._isObject(currentObj[key])) {
|
|
311
|
-
// Recursively scan nested objects
|
|
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,29 +775,34 @@ 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));
|
|
885
790
|
}
|
|
886
791
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
887
|
-
if (info.locatorLog) {
|
|
888
|
-
|
|
889
|
-
|
|
792
|
+
// if (info.locatorLog) {
|
|
793
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
794
|
+
// for (let line of lines) {
|
|
795
|
+
// this.logger.debug(line);
|
|
796
|
+
// }
|
|
797
|
+
// }
|
|
890
798
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
891
799
|
info.failCause.locatorNotFound = true;
|
|
892
|
-
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
|
+
}
|
|
893
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
894
804
|
}
|
|
895
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
896
806
|
let foundElements = [];
|
|
897
807
|
const result = {
|
|
898
808
|
foundElements: foundElements,
|
|
@@ -900,14 +810,15 @@ class StableBrowser {
|
|
|
900
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
901
811
|
let foundLocators = [];
|
|
902
812
|
try {
|
|
903
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
904
814
|
}
|
|
905
815
|
catch (e) {
|
|
906
|
-
this
|
|
907
|
-
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);
|
|
908
819
|
foundLocators = [];
|
|
909
820
|
try {
|
|
910
|
-
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);
|
|
911
822
|
}
|
|
912
823
|
catch (e) {
|
|
913
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -924,7 +835,7 @@ class StableBrowser {
|
|
|
924
835
|
if (foundLocators.length > 1) {
|
|
925
836
|
info.failCause.foundMultiple = true;
|
|
926
837
|
if (info.locatorLog) {
|
|
927
|
-
info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
|
|
838
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
928
839
|
}
|
|
929
840
|
}
|
|
930
841
|
}
|
|
@@ -1035,25 +946,14 @@ class StableBrowser {
|
|
|
1035
946
|
options,
|
|
1036
947
|
world,
|
|
1037
948
|
text: "Click element",
|
|
949
|
+
_text: "Click on " + selectors.element_name,
|
|
1038
950
|
type: Types.CLICK,
|
|
1039
951
|
operation: "click",
|
|
1040
952
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1041
953
|
};
|
|
1042
954
|
try {
|
|
1043
955
|
await _preCommand(state, this);
|
|
1044
|
-
|
|
1045
|
-
state.selectors.locators[0].text = state.options.context;
|
|
1046
|
-
}
|
|
1047
|
-
try {
|
|
1048
|
-
await state.element.click();
|
|
1049
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1050
|
-
}
|
|
1051
|
-
catch (e) {
|
|
1052
|
-
// await this.closeUnexpectedPopups();
|
|
1053
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1054
|
-
await state.element.dispatchEvent("click");
|
|
1055
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1056
|
-
}
|
|
956
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1057
957
|
await this.waitForPageLoad();
|
|
1058
958
|
return state.info;
|
|
1059
959
|
}
|
|
@@ -1064,6 +964,38 @@ class StableBrowser {
|
|
|
1064
964
|
_commandFinally(state, this);
|
|
1065
965
|
}
|
|
1066
966
|
}
|
|
967
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
968
|
+
const timeout = this._getFindElementTimeout(options);
|
|
969
|
+
const state = {
|
|
970
|
+
selectors,
|
|
971
|
+
_params,
|
|
972
|
+
options,
|
|
973
|
+
world,
|
|
974
|
+
text: "Wait for element",
|
|
975
|
+
_text: "Wait for " + selectors.element_name,
|
|
976
|
+
type: Types.WAIT_ELEMENT,
|
|
977
|
+
operation: "waitForElement",
|
|
978
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
979
|
+
};
|
|
980
|
+
let found = false;
|
|
981
|
+
try {
|
|
982
|
+
await _preCommand(state, this);
|
|
983
|
+
// if (state.options && state.options.context) {
|
|
984
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
985
|
+
// }
|
|
986
|
+
await state.element.waitFor({ timeout: timeout });
|
|
987
|
+
found = true;
|
|
988
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
989
|
+
}
|
|
990
|
+
catch (e) {
|
|
991
|
+
console.error("Error on waitForElement", e);
|
|
992
|
+
// await _commandError(state, e, this);
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
_commandFinally(state, this);
|
|
996
|
+
}
|
|
997
|
+
return found;
|
|
998
|
+
}
|
|
1067
999
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
1068
1000
|
const state = {
|
|
1069
1001
|
selectors,
|
|
@@ -1072,6 +1004,7 @@ class StableBrowser {
|
|
|
1072
1004
|
world,
|
|
1073
1005
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1074
1006
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1007
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1075
1008
|
operation: "setCheck",
|
|
1076
1009
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1077
1010
|
};
|
|
@@ -1081,9 +1014,15 @@ class StableBrowser {
|
|
|
1081
1014
|
// let element = await this._locate(selectors, info, _params);
|
|
1082
1015
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1083
1016
|
try {
|
|
1084
|
-
//
|
|
1017
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1018
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1019
|
+
await this._highlightElements(element);
|
|
1085
1020
|
await state.element.setChecked(checked);
|
|
1086
1021
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1022
|
+
// await this._unHighlightElements(element);
|
|
1023
|
+
// }
|
|
1024
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1025
|
+
// await this._unHighlightElements(element);
|
|
1087
1026
|
}
|
|
1088
1027
|
catch (e) {
|
|
1089
1028
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1115,22 +1054,13 @@ class StableBrowser {
|
|
|
1115
1054
|
world,
|
|
1116
1055
|
type: Types.HOVER,
|
|
1117
1056
|
text: `Hover element`,
|
|
1057
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1118
1058
|
operation: "hover",
|
|
1119
1059
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1120
1060
|
};
|
|
1121
1061
|
try {
|
|
1122
1062
|
await _preCommand(state, this);
|
|
1123
|
-
|
|
1124
|
-
await state.element.hover();
|
|
1125
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1126
|
-
}
|
|
1127
|
-
catch (e) {
|
|
1128
|
-
//await this.closeUnexpectedPopups();
|
|
1129
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1130
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1131
|
-
await state.element.hover({ timeout: 10000 });
|
|
1132
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1133
|
-
}
|
|
1063
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1134
1064
|
await _screenshot(state, this);
|
|
1135
1065
|
await this.waitForPageLoad();
|
|
1136
1066
|
return state.info;
|
|
@@ -1154,6 +1084,7 @@ class StableBrowser {
|
|
|
1154
1084
|
value: values.toString(),
|
|
1155
1085
|
type: Types.SELECT,
|
|
1156
1086
|
text: `Select option: ${values}`,
|
|
1087
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1157
1088
|
operation: "selectOption",
|
|
1158
1089
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1159
1090
|
};
|
|
@@ -1188,6 +1119,7 @@ class StableBrowser {
|
|
|
1188
1119
|
highlight: false,
|
|
1189
1120
|
type: Types.TYPE_PRESS,
|
|
1190
1121
|
text: `Type value: ${_value}`,
|
|
1122
|
+
_text: `Type value: ${_value}`,
|
|
1191
1123
|
operation: "type",
|
|
1192
1124
|
log: "",
|
|
1193
1125
|
};
|
|
@@ -1267,6 +1199,7 @@ class StableBrowser {
|
|
|
1267
1199
|
world,
|
|
1268
1200
|
type: Types.SET_DATE_TIME,
|
|
1269
1201
|
text: `Set date time value: ${value}`,
|
|
1202
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1270
1203
|
operation: "setDateTime",
|
|
1271
1204
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1272
1205
|
throwError: false,
|
|
@@ -1274,7 +1207,7 @@ class StableBrowser {
|
|
|
1274
1207
|
try {
|
|
1275
1208
|
await _preCommand(state, this);
|
|
1276
1209
|
try {
|
|
1277
|
-
await state.element
|
|
1210
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1278
1211
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1279
1212
|
if (format) {
|
|
1280
1213
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1338,6 +1271,7 @@ class StableBrowser {
|
|
|
1338
1271
|
world,
|
|
1339
1272
|
type: Types.FILL,
|
|
1340
1273
|
text: `Click type input with value: ${_value}`,
|
|
1274
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1341
1275
|
operation: "clickType",
|
|
1342
1276
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1343
1277
|
};
|
|
@@ -1360,12 +1294,11 @@ class StableBrowser {
|
|
|
1360
1294
|
}
|
|
1361
1295
|
}
|
|
1362
1296
|
if (options === null || options === undefined || options.press) {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
catch (e) {
|
|
1367
|
-
await state.element.dispatchEvent("click");
|
|
1297
|
+
if (!options) {
|
|
1298
|
+
options = {};
|
|
1368
1299
|
}
|
|
1300
|
+
options.timeout = 5000;
|
|
1301
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1369
1302
|
}
|
|
1370
1303
|
else {
|
|
1371
1304
|
try {
|
|
@@ -1403,7 +1336,12 @@ class StableBrowser {
|
|
|
1403
1336
|
await this.waitForPageLoad();
|
|
1404
1337
|
}
|
|
1405
1338
|
else if (enter === false) {
|
|
1406
|
-
|
|
1339
|
+
try {
|
|
1340
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1341
|
+
}
|
|
1342
|
+
catch (e) {
|
|
1343
|
+
// ignore
|
|
1344
|
+
}
|
|
1407
1345
|
//await this.page.keyboard.press("Tab");
|
|
1408
1346
|
}
|
|
1409
1347
|
else {
|
|
@@ -1455,6 +1393,7 @@ class StableBrowser {
|
|
|
1455
1393
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1456
1394
|
}
|
|
1457
1395
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1396
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1458
1397
|
_validateSelectors(selectors);
|
|
1459
1398
|
let screenshotId = null;
|
|
1460
1399
|
let screenshotPath = null;
|
|
@@ -1464,7 +1403,7 @@ class StableBrowser {
|
|
|
1464
1403
|
}
|
|
1465
1404
|
info.operation = "getText";
|
|
1466
1405
|
info.selectors = selectors;
|
|
1467
|
-
let element = await this._locate(selectors, info, _params);
|
|
1406
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1468
1407
|
if (climb > 0) {
|
|
1469
1408
|
const climbArray = [];
|
|
1470
1409
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1483,6 +1422,18 @@ class StableBrowser {
|
|
|
1483
1422
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1484
1423
|
try {
|
|
1485
1424
|
await this._highlightElements(element);
|
|
1425
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1426
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1427
|
+
// this._highlightElements(element)
|
|
1428
|
+
// .then(async () => {
|
|
1429
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1430
|
+
// this._unhighlightElements(element).then(
|
|
1431
|
+
// () => {}
|
|
1432
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1433
|
+
// );
|
|
1434
|
+
// })
|
|
1435
|
+
// .catch(e);
|
|
1436
|
+
// }
|
|
1486
1437
|
const elementText = await element.innerText();
|
|
1487
1438
|
return {
|
|
1488
1439
|
text: elementText,
|
|
@@ -1494,7 +1445,7 @@ class StableBrowser {
|
|
|
1494
1445
|
}
|
|
1495
1446
|
catch (e) {
|
|
1496
1447
|
//await this.closeUnexpectedPopups();
|
|
1497
|
-
this.logger.info("no innerText will use textContent");
|
|
1448
|
+
this.logger.info("no innerText, will use textContent");
|
|
1498
1449
|
const elementText = await element.textContent();
|
|
1499
1450
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1500
1451
|
}
|
|
@@ -1519,6 +1470,7 @@ class StableBrowser {
|
|
|
1519
1470
|
highlight: false,
|
|
1520
1471
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1521
1472
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1473
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1522
1474
|
operation: "containsPattern",
|
|
1523
1475
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1524
1476
|
};
|
|
@@ -1554,6 +1506,8 @@ class StableBrowser {
|
|
|
1554
1506
|
}
|
|
1555
1507
|
}
|
|
1556
1508
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1509
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1510
|
+
const startTime = Date.now();
|
|
1557
1511
|
const state = {
|
|
1558
1512
|
selectors,
|
|
1559
1513
|
_params,
|
|
@@ -1580,62 +1534,54 @@ class StableBrowser {
|
|
|
1580
1534
|
}
|
|
1581
1535
|
let foundObj = null;
|
|
1582
1536
|
try {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1590
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1591
|
-
if (dateAlternatives.date) {
|
|
1592
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1593
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1594
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1595
|
-
return state.info;
|
|
1537
|
+
while (Date.now() - startTime < timeout) {
|
|
1538
|
+
try {
|
|
1539
|
+
await _preCommand(state, this);
|
|
1540
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1541
|
+
if (foundObj && foundObj.element) {
|
|
1542
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1596
1543
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1544
|
+
await _screenshot(state, this);
|
|
1545
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1546
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1547
|
+
if (dateAlternatives.date) {
|
|
1548
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1549
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1550
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1551
|
+
return state.info;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
else if (numberAlternatives.number) {
|
|
1556
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1557
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1558
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1559
|
+
return state.info;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1604
1564
|
return state.info;
|
|
1605
1565
|
}
|
|
1606
1566
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
throw new Error("element doesn't contain text " + text);
|
|
1567
|
+
catch (e) {
|
|
1568
|
+
// Log error but continue retrying until timeout is reached
|
|
1569
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1570
|
+
}
|
|
1571
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1613
1572
|
}
|
|
1614
|
-
|
|
1573
|
+
state.info.foundText = foundObj?.text;
|
|
1574
|
+
state.info.value = foundObj?.value;
|
|
1575
|
+
throw new Error("element doesn't contain text " + text);
|
|
1615
1576
|
}
|
|
1616
1577
|
catch (e) {
|
|
1617
1578
|
await _commandError(state, e, this);
|
|
1579
|
+
throw e;
|
|
1618
1580
|
}
|
|
1619
1581
|
finally {
|
|
1620
1582
|
_commandFinally(state, this);
|
|
1621
1583
|
}
|
|
1622
1584
|
}
|
|
1623
|
-
_getDataFile(world = null) {
|
|
1624
|
-
let dataFile = null;
|
|
1625
|
-
if (world && world.reportFolder) {
|
|
1626
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1627
|
-
}
|
|
1628
|
-
else if (this.reportFolder) {
|
|
1629
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1630
|
-
}
|
|
1631
|
-
else if (this.context && this.context.reportFolder) {
|
|
1632
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1633
|
-
}
|
|
1634
|
-
else {
|
|
1635
|
-
dataFile = "data.json";
|
|
1636
|
-
}
|
|
1637
|
-
return dataFile;
|
|
1638
|
-
}
|
|
1639
1585
|
async waitForUserInput(message, world = null) {
|
|
1640
1586
|
if (!message) {
|
|
1641
1587
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1664,7 +1610,7 @@ class StableBrowser {
|
|
|
1664
1610
|
return;
|
|
1665
1611
|
}
|
|
1666
1612
|
// if data file exists, load it
|
|
1667
|
-
const dataFile =
|
|
1613
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1668
1614
|
let data = this.getTestData(world);
|
|
1669
1615
|
// merge the testData with the existing data
|
|
1670
1616
|
Object.assign(data, testData);
|
|
@@ -1767,7 +1713,7 @@ class StableBrowser {
|
|
|
1767
1713
|
}
|
|
1768
1714
|
}
|
|
1769
1715
|
getTestData(world = null) {
|
|
1770
|
-
const dataFile =
|
|
1716
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1771
1717
|
let data = {};
|
|
1772
1718
|
if (fs.existsSync(dataFile)) {
|
|
1773
1719
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1854,6 +1800,15 @@ class StableBrowser {
|
|
|
1854
1800
|
document.documentElement.clientWidth,
|
|
1855
1801
|
])));
|
|
1856
1802
|
let screenshotBuffer = null;
|
|
1803
|
+
// if (focusedElement) {
|
|
1804
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1805
|
+
// await this._unhighlightElements(focusedElement);
|
|
1806
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1807
|
+
// console.log(`Unhighlighted previous element`);
|
|
1808
|
+
// }
|
|
1809
|
+
// if (focusedElement) {
|
|
1810
|
+
// await this._highlightElements(focusedElement);
|
|
1811
|
+
// }
|
|
1857
1812
|
if (this.context.browserName === "chromium") {
|
|
1858
1813
|
const client = await playContext.newCDPSession(this.page);
|
|
1859
1814
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1875,6 +1830,10 @@ class StableBrowser {
|
|
|
1875
1830
|
else {
|
|
1876
1831
|
screenshotBuffer = await this.page.screenshot();
|
|
1877
1832
|
}
|
|
1833
|
+
// if (focusedElement) {
|
|
1834
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1835
|
+
// await this._unhighlightElements(focusedElement);
|
|
1836
|
+
// }
|
|
1878
1837
|
let image = await Jimp.read(screenshotBuffer);
|
|
1879
1838
|
// Get the image dimensions
|
|
1880
1839
|
const { width, height } = image.bitmap;
|
|
@@ -1887,6 +1846,7 @@ class StableBrowser {
|
|
|
1887
1846
|
else {
|
|
1888
1847
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1889
1848
|
}
|
|
1849
|
+
return screenshotBuffer;
|
|
1890
1850
|
}
|
|
1891
1851
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1892
1852
|
const state = {
|
|
@@ -1922,8 +1882,10 @@ class StableBrowser {
|
|
|
1922
1882
|
world,
|
|
1923
1883
|
type: Types.EXTRACT,
|
|
1924
1884
|
text: `Extract attribute from element`,
|
|
1885
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1925
1886
|
operation: "extractAttribute",
|
|
1926
1887
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1888
|
+
allowDisabled: true,
|
|
1927
1889
|
};
|
|
1928
1890
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1929
1891
|
try {
|
|
@@ -1945,6 +1907,7 @@ class StableBrowser {
|
|
|
1945
1907
|
state.info.value = state.value;
|
|
1946
1908
|
this.setTestData({ [variable]: state.value }, world);
|
|
1947
1909
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1910
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1948
1911
|
return state.info;
|
|
1949
1912
|
}
|
|
1950
1913
|
catch (e) {
|
|
@@ -1963,14 +1926,21 @@ class StableBrowser {
|
|
|
1963
1926
|
options,
|
|
1964
1927
|
world,
|
|
1965
1928
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1929
|
+
highlight: true,
|
|
1930
|
+
screenshot: true,
|
|
1966
1931
|
text: `Verify element attribute`,
|
|
1932
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1967
1933
|
operation: "verifyAttribute",
|
|
1968
1934
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1935
|
+
allowDisabled: true,
|
|
1969
1936
|
};
|
|
1970
1937
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1971
1938
|
let val;
|
|
1939
|
+
let expectedValue;
|
|
1972
1940
|
try {
|
|
1973
1941
|
await _preCommand(state, this);
|
|
1942
|
+
expectedValue = state.value;
|
|
1943
|
+
state.info.expectedValue = expectedValue;
|
|
1974
1944
|
switch (attribute) {
|
|
1975
1945
|
case "innerText":
|
|
1976
1946
|
val = String(await state.element.innerText());
|
|
@@ -1984,23 +1954,30 @@ class StableBrowser {
|
|
|
1984
1954
|
case "disabled":
|
|
1985
1955
|
val = String(await state.element.isDisabled());
|
|
1986
1956
|
break;
|
|
1957
|
+
case "readOnly":
|
|
1958
|
+
const isEditable = await state.element.isEditable();
|
|
1959
|
+
val = String(!isEditable);
|
|
1960
|
+
break;
|
|
1987
1961
|
default:
|
|
1988
1962
|
val = String(await state.element.getAttribute(attribute));
|
|
1989
1963
|
break;
|
|
1990
1964
|
}
|
|
1965
|
+
state.info.value = val;
|
|
1991
1966
|
let regex;
|
|
1992
|
-
if (
|
|
1993
|
-
const patternBody =
|
|
1967
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1968
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1994
1969
|
regex = new RegExp(patternBody, "g");
|
|
1995
1970
|
}
|
|
1996
1971
|
else {
|
|
1997
|
-
const escapedPattern =
|
|
1972
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1998
1973
|
regex = new RegExp(escapedPattern, "g");
|
|
1999
1974
|
}
|
|
2000
1975
|
if (!val.match(regex)) {
|
|
2001
|
-
|
|
1976
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1977
|
+
state.info.failCause.assertionFailed = true;
|
|
1978
|
+
state.info.failCause.lastError = errorMessage;
|
|
1979
|
+
throw new Error(errorMessage);
|
|
2002
1980
|
}
|
|
2003
|
-
state.info.value = val;
|
|
2004
1981
|
return state.info;
|
|
2005
1982
|
}
|
|
2006
1983
|
catch (e) {
|
|
@@ -2028,7 +2005,7 @@ class StableBrowser {
|
|
|
2028
2005
|
if (options && options.timeout) {
|
|
2029
2006
|
timeout = options.timeout;
|
|
2030
2007
|
}
|
|
2031
|
-
const serviceUrl =
|
|
2008
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
2032
2009
|
const request = {
|
|
2033
2010
|
method: "POST",
|
|
2034
2011
|
url: serviceUrl,
|
|
@@ -2099,27 +2076,32 @@ class StableBrowser {
|
|
|
2099
2076
|
async _highlightElements(scope, css) {
|
|
2100
2077
|
try {
|
|
2101
2078
|
if (!scope) {
|
|
2079
|
+
// console.log(`Scope is not defined`);
|
|
2102
2080
|
return;
|
|
2103
2081
|
}
|
|
2104
2082
|
if (!css) {
|
|
2105
2083
|
scope
|
|
2106
2084
|
.evaluate((node) => {
|
|
2107
2085
|
if (node && node.style) {
|
|
2108
|
-
let
|
|
2109
|
-
|
|
2086
|
+
let originalOutline = node.style.outline;
|
|
2087
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2088
|
+
// node.__previousOutline = originalOutline;
|
|
2089
|
+
node.style.outline = "2px solid red";
|
|
2090
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2110
2091
|
if (window) {
|
|
2111
2092
|
window.addEventListener("beforeunload", function (e) {
|
|
2112
|
-
node.style.
|
|
2093
|
+
node.style.outline = originalOutline;
|
|
2113
2094
|
});
|
|
2114
2095
|
}
|
|
2115
2096
|
setTimeout(function () {
|
|
2116
|
-
node.style.
|
|
2097
|
+
node.style.outline = originalOutline;
|
|
2117
2098
|
}, 2000);
|
|
2118
2099
|
}
|
|
2119
2100
|
})
|
|
2120
2101
|
.then(() => { })
|
|
2121
2102
|
.catch((e) => {
|
|
2122
2103
|
// ignore
|
|
2104
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2123
2105
|
});
|
|
2124
2106
|
}
|
|
2125
2107
|
else {
|
|
@@ -2135,17 +2117,18 @@ class StableBrowser {
|
|
|
2135
2117
|
if (!element.style) {
|
|
2136
2118
|
return;
|
|
2137
2119
|
}
|
|
2138
|
-
|
|
2120
|
+
let originalOutline = element.style.outline;
|
|
2121
|
+
element.__previousOutline = originalOutline;
|
|
2139
2122
|
// Set the new border to be red and 2px solid
|
|
2140
|
-
element.style.
|
|
2123
|
+
element.style.outline = "2px solid red";
|
|
2141
2124
|
if (window) {
|
|
2142
2125
|
window.addEventListener("beforeunload", function (e) {
|
|
2143
|
-
element.style.
|
|
2126
|
+
element.style.outline = originalOutline;
|
|
2144
2127
|
});
|
|
2145
2128
|
}
|
|
2146
2129
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2147
2130
|
setTimeout(function () {
|
|
2148
|
-
element.style.
|
|
2131
|
+
element.style.outline = originalOutline;
|
|
2149
2132
|
}, 2000);
|
|
2150
2133
|
}
|
|
2151
2134
|
return;
|
|
@@ -2153,6 +2136,7 @@ class StableBrowser {
|
|
|
2153
2136
|
.then(() => { })
|
|
2154
2137
|
.catch((e) => {
|
|
2155
2138
|
// ignore
|
|
2139
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2156
2140
|
});
|
|
2157
2141
|
}
|
|
2158
2142
|
}
|
|
@@ -2160,6 +2144,54 @@ class StableBrowser {
|
|
|
2160
2144
|
console.debug(error);
|
|
2161
2145
|
}
|
|
2162
2146
|
}
|
|
2147
|
+
// async _unhighlightElements(scope, css) {
|
|
2148
|
+
// try {
|
|
2149
|
+
// if (!scope) {
|
|
2150
|
+
// return;
|
|
2151
|
+
// }
|
|
2152
|
+
// if (!css) {
|
|
2153
|
+
// scope
|
|
2154
|
+
// .evaluate((node) => {
|
|
2155
|
+
// if (node && node.style) {
|
|
2156
|
+
// if (!node.__previousOutline) {
|
|
2157
|
+
// node.style.outline = "";
|
|
2158
|
+
// } else {
|
|
2159
|
+
// node.style.outline = node.__previousOutline;
|
|
2160
|
+
// }
|
|
2161
|
+
// }
|
|
2162
|
+
// })
|
|
2163
|
+
// .then(() => {})
|
|
2164
|
+
// .catch((e) => {
|
|
2165
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2166
|
+
// });
|
|
2167
|
+
// } else {
|
|
2168
|
+
// scope
|
|
2169
|
+
// .evaluate(([css]) => {
|
|
2170
|
+
// if (!css) {
|
|
2171
|
+
// return;
|
|
2172
|
+
// }
|
|
2173
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2174
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2175
|
+
// let element = elements[i];
|
|
2176
|
+
// if (!element.style) {
|
|
2177
|
+
// return;
|
|
2178
|
+
// }
|
|
2179
|
+
// if (!element.__previousOutline) {
|
|
2180
|
+
// element.style.outline = "";
|
|
2181
|
+
// } else {
|
|
2182
|
+
// element.style.outline = element.__previousOutline;
|
|
2183
|
+
// }
|
|
2184
|
+
// }
|
|
2185
|
+
// })
|
|
2186
|
+
// .then(() => {})
|
|
2187
|
+
// .catch((e) => {
|
|
2188
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2189
|
+
// });
|
|
2190
|
+
// }
|
|
2191
|
+
// } catch (error) {
|
|
2192
|
+
// // console.debug(error);
|
|
2193
|
+
// }
|
|
2194
|
+
// }
|
|
2163
2195
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2164
2196
|
const startTime = Date.now();
|
|
2165
2197
|
let error = null;
|
|
@@ -2204,6 +2236,7 @@ class StableBrowser {
|
|
|
2204
2236
|
_reportToWorld(world, {
|
|
2205
2237
|
type: Types.VERIFY_PAGE_PATH,
|
|
2206
2238
|
text: "Verify page path",
|
|
2239
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2207
2240
|
screenshotId,
|
|
2208
2241
|
result: error
|
|
2209
2242
|
? {
|
|
@@ -2221,6 +2254,35 @@ class StableBrowser {
|
|
|
2221
2254
|
});
|
|
2222
2255
|
}
|
|
2223
2256
|
}
|
|
2257
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2258
|
+
const frames = this.page.frames();
|
|
2259
|
+
let results = [];
|
|
2260
|
+
let ignoreCase = false;
|
|
2261
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2262
|
+
if (dateAlternatives.date) {
|
|
2263
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2264
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2265
|
+
result.frame = frames[i];
|
|
2266
|
+
results.push(result);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
else if (numberAlternatives.number) {
|
|
2270
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2271
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2272
|
+
result.frame = frames[i];
|
|
2273
|
+
results.push(result);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2278
|
+
result.frame = frames[i];
|
|
2279
|
+
results.push(result);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
state.info.results = results;
|
|
2283
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2284
|
+
return resultWithElementsFound;
|
|
2285
|
+
}
|
|
2224
2286
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2225
2287
|
text = unEscapeString(text);
|
|
2226
2288
|
const state = {
|
|
@@ -2230,12 +2292,16 @@ class StableBrowser {
|
|
|
2230
2292
|
locate: false,
|
|
2231
2293
|
scroll: false,
|
|
2232
2294
|
highlight: false,
|
|
2233
|
-
type: Types.
|
|
2295
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2234
2296
|
text: `Verify text exists in page`,
|
|
2297
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2235
2298
|
operation: "verifyTextExistInPage",
|
|
2236
2299
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2237
2300
|
};
|
|
2238
|
-
|
|
2301
|
+
if (testForRegex(text)) {
|
|
2302
|
+
text = text.replace(/\\"/g, '"');
|
|
2303
|
+
}
|
|
2304
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2239
2305
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2240
2306
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2241
2307
|
if (newValue !== text) {
|
|
@@ -2248,31 +2314,15 @@ class StableBrowser {
|
|
|
2248
2314
|
await _preCommand(state, this);
|
|
2249
2315
|
state.info.text = text;
|
|
2250
2316
|
while (true) {
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
else if (numberAlternatives.number) {
|
|
2262
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2263
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2264
|
-
result.frame = frames[i];
|
|
2265
|
-
results.push(result);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
else {
|
|
2269
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2270
|
-
result.frame = frames[i];
|
|
2271
|
-
results.push(result);
|
|
2272
|
-
}
|
|
2317
|
+
let resultWithElementsFound = {
|
|
2318
|
+
length: 0,
|
|
2319
|
+
};
|
|
2320
|
+
try {
|
|
2321
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2322
|
+
}
|
|
2323
|
+
catch (error) {
|
|
2324
|
+
// ignore
|
|
2273
2325
|
}
|
|
2274
|
-
state.info.results = results;
|
|
2275
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2276
2326
|
if (resultWithElementsFound.length === 0) {
|
|
2277
2327
|
if (Date.now() - state.startTime > timeout) {
|
|
2278
2328
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2280,18 +2330,40 @@ class StableBrowser {
|
|
|
2280
2330
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2281
2331
|
continue;
|
|
2282
2332
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2333
|
+
try {
|
|
2334
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2335
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2336
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2337
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2338
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2339
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2340
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2341
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2342
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2343
|
+
// .then(async () => {
|
|
2344
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2345
|
+
// })
|
|
2346
|
+
// .catch(
|
|
2347
|
+
// (e) => {}
|
|
2348
|
+
// console.error(e)
|
|
2349
|
+
// );
|
|
2350
|
+
// });
|
|
2351
|
+
// }
|
|
2352
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2353
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2354
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2355
|
+
if (element) {
|
|
2356
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2357
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2358
|
+
// await _screenshot(state, this, element);
|
|
2359
|
+
}
|
|
2291
2360
|
}
|
|
2361
|
+
await _screenshot(state, this);
|
|
2362
|
+
return state.info;
|
|
2363
|
+
}
|
|
2364
|
+
catch (error) {
|
|
2365
|
+
console.error(error);
|
|
2292
2366
|
}
|
|
2293
|
-
await _screenshot(state, this);
|
|
2294
|
-
return state.info;
|
|
2295
2367
|
}
|
|
2296
2368
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2297
2369
|
}
|
|
@@ -2313,10 +2385,14 @@ class StableBrowser {
|
|
|
2313
2385
|
highlight: false,
|
|
2314
2386
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2315
2387
|
text: `Verify text does not exist in page`,
|
|
2388
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2316
2389
|
operation: "verifyTextNotExistInPage",
|
|
2317
2390
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2318
2391
|
};
|
|
2319
|
-
|
|
2392
|
+
if (testForRegex(text)) {
|
|
2393
|
+
text = text.replace(/\\"/g, '"');
|
|
2394
|
+
}
|
|
2395
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2320
2396
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2321
2397
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2322
2398
|
if (newValue !== text) {
|
|
@@ -2328,32 +2404,16 @@ class StableBrowser {
|
|
|
2328
2404
|
try {
|
|
2329
2405
|
await _preCommand(state, this);
|
|
2330
2406
|
state.info.text = text;
|
|
2407
|
+
let resultWithElementsFound = {
|
|
2408
|
+
length: null, // initial cannot be 0
|
|
2409
|
+
};
|
|
2331
2410
|
while (true) {
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2338
|
-
result.frame = frames[i];
|
|
2339
|
-
results.push(result);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
else if (numberAlternatives.number) {
|
|
2343
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2344
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2345
|
-
result.frame = frames[i];
|
|
2346
|
-
results.push(result);
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
else {
|
|
2350
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2351
|
-
result.frame = frames[i];
|
|
2352
|
-
results.push(result);
|
|
2353
|
-
}
|
|
2411
|
+
try {
|
|
2412
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2413
|
+
}
|
|
2414
|
+
catch (error) {
|
|
2415
|
+
// ignore
|
|
2354
2416
|
}
|
|
2355
|
-
state.info.results = results;
|
|
2356
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2357
2417
|
if (resultWithElementsFound.length === 0) {
|
|
2358
2418
|
await _screenshot(state, this);
|
|
2359
2419
|
return state.info;
|
|
@@ -2371,15 +2431,116 @@ class StableBrowser {
|
|
|
2371
2431
|
_commandFinally(state, this);
|
|
2372
2432
|
}
|
|
2373
2433
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2434
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2435
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2436
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2437
|
+
const state = {
|
|
2438
|
+
text_search: textToVerify,
|
|
2439
|
+
options,
|
|
2440
|
+
world,
|
|
2441
|
+
locate: false,
|
|
2442
|
+
scroll: false,
|
|
2443
|
+
highlight: false,
|
|
2444
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2445
|
+
text: `Verify text with relation to another text`,
|
|
2446
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2447
|
+
operation: "verify_text_with_relation",
|
|
2448
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2449
|
+
};
|
|
2450
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2451
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2452
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2453
|
+
if (newValue !== textAnchor) {
|
|
2454
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2455
|
+
textAnchor = newValue;
|
|
2456
|
+
}
|
|
2457
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2458
|
+
if (newValue !== textToVerify) {
|
|
2459
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2460
|
+
textToVerify = newValue;
|
|
2461
|
+
}
|
|
2462
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2463
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2464
|
+
let foundAncore = false;
|
|
2465
|
+
try {
|
|
2466
|
+
await _preCommand(state, this);
|
|
2467
|
+
state.info.text = textToVerify;
|
|
2468
|
+
let resultWithElementsFound = {
|
|
2469
|
+
length: 0,
|
|
2470
|
+
};
|
|
2471
|
+
while (true) {
|
|
2472
|
+
try {
|
|
2473
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
// ignore
|
|
2477
|
+
}
|
|
2478
|
+
if (resultWithElementsFound.length === 0) {
|
|
2479
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2480
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2481
|
+
}
|
|
2482
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2485
|
+
try {
|
|
2486
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2487
|
+
foundAncore = true;
|
|
2488
|
+
const result = resultWithElementsFound[i];
|
|
2489
|
+
const token = result.randomToken;
|
|
2490
|
+
const frame = result.frame;
|
|
2491
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2492
|
+
const climbArray1 = [];
|
|
2493
|
+
for (let i = 0; i < climb; i++) {
|
|
2494
|
+
climbArray1.push("..");
|
|
2495
|
+
}
|
|
2496
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2497
|
+
css = css + " >> " + climbXpath;
|
|
2498
|
+
const count = await frame.locator(css).count();
|
|
2499
|
+
for (let j = 0; j < count; j++) {
|
|
2500
|
+
const continer = await frame.locator(css).nth(j);
|
|
2501
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2502
|
+
if (result.elementCount > 0) {
|
|
2503
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2504
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2505
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2506
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2507
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2508
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2509
|
+
// .then(async () => {
|
|
2510
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2511
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2512
|
+
// () => {}
|
|
2513
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2514
|
+
// );
|
|
2515
|
+
// })
|
|
2516
|
+
// .catch(e);
|
|
2517
|
+
// }
|
|
2518
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2519
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2520
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2521
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2522
|
+
if (element) {
|
|
2523
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2524
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2525
|
+
}
|
|
2526
|
+
await _screenshot(state, this);
|
|
2527
|
+
return state.info;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
catch (error) {
|
|
2533
|
+
console.error(error);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2378
2537
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2538
|
+
catch (e) {
|
|
2539
|
+
await _commandError(state, e, this);
|
|
2540
|
+
}
|
|
2541
|
+
finally {
|
|
2542
|
+
_commandFinally(state, this);
|
|
2381
2543
|
}
|
|
2382
|
-
return serviceUrl;
|
|
2383
2544
|
}
|
|
2384
2545
|
async visualVerification(text, options = {}, world = null) {
|
|
2385
2546
|
const startTime = Date.now();
|
|
@@ -2395,14 +2556,17 @@ class StableBrowser {
|
|
|
2395
2556
|
throw new Error("TOKEN is not set");
|
|
2396
2557
|
}
|
|
2397
2558
|
try {
|
|
2398
|
-
let serviceUrl =
|
|
2559
|
+
let serviceUrl = _getServerUrl();
|
|
2399
2560
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2400
2561
|
info.screenshotPath = screenshotPath;
|
|
2401
2562
|
const screenshot = await this.takeScreenshot();
|
|
2402
|
-
|
|
2403
|
-
method: "
|
|
2563
|
+
let request = {
|
|
2564
|
+
method: "post",
|
|
2565
|
+
maxBodyLength: Infinity,
|
|
2404
2566
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2405
2567
|
headers: {
|
|
2568
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2569
|
+
"x-source": "aaa",
|
|
2406
2570
|
"Content-Type": "application/json",
|
|
2407
2571
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2408
2572
|
},
|
|
@@ -2411,7 +2575,7 @@ class StableBrowser {
|
|
|
2411
2575
|
screenshot: screenshot,
|
|
2412
2576
|
}),
|
|
2413
2577
|
};
|
|
2414
|
-
|
|
2578
|
+
const result = await axios.request(request);
|
|
2415
2579
|
if (result.data.status !== true) {
|
|
2416
2580
|
throw new Error("Visual validation failed");
|
|
2417
2581
|
}
|
|
@@ -2439,6 +2603,7 @@ class StableBrowser {
|
|
|
2439
2603
|
_reportToWorld(world, {
|
|
2440
2604
|
type: Types.VERIFY_VISUAL,
|
|
2441
2605
|
text: "Visual verification",
|
|
2606
|
+
_text: "Visual verification of " + text,
|
|
2442
2607
|
screenshotId,
|
|
2443
2608
|
result: error
|
|
2444
2609
|
? {
|
|
@@ -2560,7 +2725,7 @@ class StableBrowser {
|
|
|
2560
2725
|
info.operation = "analyzeTable";
|
|
2561
2726
|
info.selectors = selectors;
|
|
2562
2727
|
info.query = query;
|
|
2563
|
-
query =
|
|
2728
|
+
query = _fixUsingParams(query, _params);
|
|
2564
2729
|
info.query_fixed = query;
|
|
2565
2730
|
info.operator = operator;
|
|
2566
2731
|
info.value = value;
|
|
@@ -2705,6 +2870,32 @@ class StableBrowser {
|
|
|
2705
2870
|
}
|
|
2706
2871
|
return timeout;
|
|
2707
2872
|
}
|
|
2873
|
+
_getFindElementTimeout(options) {
|
|
2874
|
+
if (options && options.timeout) {
|
|
2875
|
+
return options.timeout;
|
|
2876
|
+
}
|
|
2877
|
+
if (this.configuration.find_element_timeout) {
|
|
2878
|
+
return this.configuration.find_element_timeout;
|
|
2879
|
+
}
|
|
2880
|
+
return 30000;
|
|
2881
|
+
}
|
|
2882
|
+
async saveStoreState(path = null, world = null) {
|
|
2883
|
+
const storageState = await this.page.context().storageState();
|
|
2884
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2885
|
+
if (path) {
|
|
2886
|
+
// save { storageState: storageState } into the path
|
|
2887
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2888
|
+
}
|
|
2889
|
+
else {
|
|
2890
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
async restoreSaveState(path = null, world = null) {
|
|
2894
|
+
await refreshBrowser(this, path, world);
|
|
2895
|
+
this.registerEventListeners(this.context);
|
|
2896
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2897
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2898
|
+
}
|
|
2708
2899
|
async waitForPageLoad(options = {}, world = null) {
|
|
2709
2900
|
let timeout = this._getLoadTimeout(options);
|
|
2710
2901
|
const promiseArray = [];
|
|
@@ -2772,6 +2963,7 @@ class StableBrowser {
|
|
|
2772
2963
|
highlight: false,
|
|
2773
2964
|
type: Types.CLOSE_PAGE,
|
|
2774
2965
|
text: `Close page`,
|
|
2966
|
+
_text: `Close the page`,
|
|
2775
2967
|
operation: "closePage",
|
|
2776
2968
|
log: "***** close page *****\n",
|
|
2777
2969
|
throwError: false,
|
|
@@ -2788,8 +2980,95 @@ class StableBrowser {
|
|
|
2788
2980
|
_commandFinally(state, this);
|
|
2789
2981
|
}
|
|
2790
2982
|
}
|
|
2983
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
2984
|
+
let operation = null;
|
|
2985
|
+
if (!options || !options.operation) {
|
|
2986
|
+
throw new Error("operation is not defined");
|
|
2987
|
+
}
|
|
2988
|
+
operation = options.operation;
|
|
2989
|
+
// validate operation is one of the supported operations
|
|
2990
|
+
if (operation != "click" && operation != "hover+click") {
|
|
2991
|
+
throw new Error("operation is not supported");
|
|
2992
|
+
}
|
|
2993
|
+
const state = {
|
|
2994
|
+
options,
|
|
2995
|
+
world,
|
|
2996
|
+
locate: false,
|
|
2997
|
+
scroll: false,
|
|
2998
|
+
highlight: false,
|
|
2999
|
+
type: Types.TABLE_OPERATION,
|
|
3000
|
+
text: `Table operation`,
|
|
3001
|
+
_text: `Table ${operation} operation`,
|
|
3002
|
+
operation: operation,
|
|
3003
|
+
log: "***** Table operation *****\n",
|
|
3004
|
+
};
|
|
3005
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3006
|
+
try {
|
|
3007
|
+
await _preCommand(state, this);
|
|
3008
|
+
const start = Date.now();
|
|
3009
|
+
let cellArea = null;
|
|
3010
|
+
while (true) {
|
|
3011
|
+
try {
|
|
3012
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3013
|
+
if (cellArea) {
|
|
3014
|
+
break;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
catch (e) {
|
|
3018
|
+
// ignore
|
|
3019
|
+
}
|
|
3020
|
+
if (Date.now() - start > timeout) {
|
|
3021
|
+
throw new Error(`Cell not found in table`);
|
|
3022
|
+
}
|
|
3023
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3024
|
+
}
|
|
3025
|
+
switch (operation) {
|
|
3026
|
+
case "click":
|
|
3027
|
+
if (!options.css) {
|
|
3028
|
+
// will click in the center of the cell
|
|
3029
|
+
let xOffset = 0;
|
|
3030
|
+
let yOffset = 0;
|
|
3031
|
+
if (options.xOffset) {
|
|
3032
|
+
xOffset = options.xOffset;
|
|
3033
|
+
}
|
|
3034
|
+
if (options.yOffset) {
|
|
3035
|
+
yOffset = options.yOffset;
|
|
3036
|
+
}
|
|
3037
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3038
|
+
}
|
|
3039
|
+
else {
|
|
3040
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3041
|
+
if (results.length === 0) {
|
|
3042
|
+
throw new Error(`Element not found in cell area`);
|
|
3043
|
+
}
|
|
3044
|
+
state.element = results[0];
|
|
3045
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3046
|
+
}
|
|
3047
|
+
break;
|
|
3048
|
+
case "hover+click":
|
|
3049
|
+
if (!options.css) {
|
|
3050
|
+
throw new Error("css is not defined");
|
|
3051
|
+
}
|
|
3052
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3053
|
+
if (results.length === 0) {
|
|
3054
|
+
throw new Error(`Element not found in cell area`);
|
|
3055
|
+
}
|
|
3056
|
+
state.element = results[0];
|
|
3057
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3058
|
+
break;
|
|
3059
|
+
default:
|
|
3060
|
+
throw new Error("operation is not supported");
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
catch (e) {
|
|
3064
|
+
await _commandError(state, e, this);
|
|
3065
|
+
}
|
|
3066
|
+
finally {
|
|
3067
|
+
_commandFinally(state, this);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
2791
3070
|
saveTestDataAsGlobal(options, world) {
|
|
2792
|
-
const dataFile =
|
|
3071
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2793
3072
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2794
3073
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2795
3074
|
}
|
|
@@ -2819,6 +3098,7 @@ class StableBrowser {
|
|
|
2819
3098
|
_reportToWorld(world, {
|
|
2820
3099
|
type: Types.SET_VIEWPORT,
|
|
2821
3100
|
text: "set viewport size to " + width + "x" + hight,
|
|
3101
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2822
3102
|
screenshotId,
|
|
2823
3103
|
result: error
|
|
2824
3104
|
? {
|
|
@@ -2890,14 +3170,25 @@ class StableBrowser {
|
|
|
2890
3170
|
}
|
|
2891
3171
|
}
|
|
2892
3172
|
async beforeStep(world, step) {
|
|
2893
|
-
this.stepName = step.pickleStep.text;
|
|
2894
|
-
this.logger.info("step: " + this.stepName);
|
|
2895
3173
|
if (this.stepIndex === undefined) {
|
|
2896
3174
|
this.stepIndex = 0;
|
|
2897
3175
|
}
|
|
2898
3176
|
else {
|
|
2899
3177
|
this.stepIndex++;
|
|
2900
3178
|
}
|
|
3179
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3180
|
+
this.stepName = step.pickleStep.text;
|
|
3181
|
+
this.logger.info("step: " + this.stepName);
|
|
3182
|
+
}
|
|
3183
|
+
else if (step && step.text) {
|
|
3184
|
+
this.stepName = step.text;
|
|
3185
|
+
}
|
|
3186
|
+
else {
|
|
3187
|
+
this.stepName = "step " + this.stepIndex;
|
|
3188
|
+
}
|
|
3189
|
+
if (this.context) {
|
|
3190
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3191
|
+
}
|
|
2901
3192
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2902
3193
|
if (this.context.browserObject.context) {
|
|
2903
3194
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2910,6 +3201,41 @@ class StableBrowser {
|
|
|
2910
3201
|
this.saveTestDataAsGlobal({}, world);
|
|
2911
3202
|
}
|
|
2912
3203
|
}
|
|
3204
|
+
if (this.initSnapshotTaken === false) {
|
|
3205
|
+
this.initSnapshotTaken = true;
|
|
3206
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3207
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3208
|
+
if (snapshot) {
|
|
3209
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
async getAriaSnapshot() {
|
|
3215
|
+
try {
|
|
3216
|
+
// find the page url
|
|
3217
|
+
const url = await this.page.url();
|
|
3218
|
+
// extract the path from the url
|
|
3219
|
+
const path = new URL(url).pathname;
|
|
3220
|
+
// get the page title
|
|
3221
|
+
const title = await this.page.title();
|
|
3222
|
+
// go over other frams
|
|
3223
|
+
const frames = this.page.frames();
|
|
3224
|
+
const snapshots = [];
|
|
3225
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3226
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3227
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3228
|
+
content.push(`- frame: ${i}`);
|
|
3229
|
+
const frame = frames[i];
|
|
3230
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3231
|
+
content.push(snapshot);
|
|
3232
|
+
}
|
|
3233
|
+
return content.join("\n");
|
|
3234
|
+
}
|
|
3235
|
+
catch (e) {
|
|
3236
|
+
console.error(e);
|
|
3237
|
+
}
|
|
3238
|
+
return null;
|
|
2913
3239
|
}
|
|
2914
3240
|
async afterStep(world, step) {
|
|
2915
3241
|
this.stepName = null;
|
|
@@ -2920,6 +3246,16 @@ class StableBrowser {
|
|
|
2920
3246
|
});
|
|
2921
3247
|
}
|
|
2922
3248
|
}
|
|
3249
|
+
if (this.context) {
|
|
3250
|
+
this.context.examplesRow = null;
|
|
3251
|
+
}
|
|
3252
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3253
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3254
|
+
if (snapshot) {
|
|
3255
|
+
const obj = {};
|
|
3256
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
2923
3259
|
}
|
|
2924
3260
|
}
|
|
2925
3261
|
function createTimedPromise(promise, label) {
|
|
@@ -2927,156 +3263,5 @@ function createTimedPromise(promise, label) {
|
|
|
2927
3263
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2928
3264
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2929
3265
|
}
|
|
2930
|
-
const KEYBOARD_EVENTS = [
|
|
2931
|
-
"ALT",
|
|
2932
|
-
"AltGraph",
|
|
2933
|
-
"CapsLock",
|
|
2934
|
-
"Control",
|
|
2935
|
-
"Fn",
|
|
2936
|
-
"FnLock",
|
|
2937
|
-
"Hyper",
|
|
2938
|
-
"Meta",
|
|
2939
|
-
"NumLock",
|
|
2940
|
-
"ScrollLock",
|
|
2941
|
-
"Shift",
|
|
2942
|
-
"Super",
|
|
2943
|
-
"Symbol",
|
|
2944
|
-
"SymbolLock",
|
|
2945
|
-
"Enter",
|
|
2946
|
-
"Tab",
|
|
2947
|
-
"ArrowDown",
|
|
2948
|
-
"ArrowLeft",
|
|
2949
|
-
"ArrowRight",
|
|
2950
|
-
"ArrowUp",
|
|
2951
|
-
"End",
|
|
2952
|
-
"Home",
|
|
2953
|
-
"PageDown",
|
|
2954
|
-
"PageUp",
|
|
2955
|
-
"Backspace",
|
|
2956
|
-
"Clear",
|
|
2957
|
-
"Copy",
|
|
2958
|
-
"CrSel",
|
|
2959
|
-
"Cut",
|
|
2960
|
-
"Delete",
|
|
2961
|
-
"EraseEof",
|
|
2962
|
-
"ExSel",
|
|
2963
|
-
"Insert",
|
|
2964
|
-
"Paste",
|
|
2965
|
-
"Redo",
|
|
2966
|
-
"Undo",
|
|
2967
|
-
"Accept",
|
|
2968
|
-
"Again",
|
|
2969
|
-
"Attn",
|
|
2970
|
-
"Cancel",
|
|
2971
|
-
"ContextMenu",
|
|
2972
|
-
"Escape",
|
|
2973
|
-
"Execute",
|
|
2974
|
-
"Find",
|
|
2975
|
-
"Finish",
|
|
2976
|
-
"Help",
|
|
2977
|
-
"Pause",
|
|
2978
|
-
"Play",
|
|
2979
|
-
"Props",
|
|
2980
|
-
"Select",
|
|
2981
|
-
"ZoomIn",
|
|
2982
|
-
"ZoomOut",
|
|
2983
|
-
"BrightnessDown",
|
|
2984
|
-
"BrightnessUp",
|
|
2985
|
-
"Eject",
|
|
2986
|
-
"LogOff",
|
|
2987
|
-
"Power",
|
|
2988
|
-
"PowerOff",
|
|
2989
|
-
"PrintScreen",
|
|
2990
|
-
"Hibernate",
|
|
2991
|
-
"Standby",
|
|
2992
|
-
"WakeUp",
|
|
2993
|
-
"AllCandidates",
|
|
2994
|
-
"Alphanumeric",
|
|
2995
|
-
"CodeInput",
|
|
2996
|
-
"Compose",
|
|
2997
|
-
"Convert",
|
|
2998
|
-
"Dead",
|
|
2999
|
-
"FinalMode",
|
|
3000
|
-
"GroupFirst",
|
|
3001
|
-
"GroupLast",
|
|
3002
|
-
"GroupNext",
|
|
3003
|
-
"GroupPrevious",
|
|
3004
|
-
"ModeChange",
|
|
3005
|
-
"NextCandidate",
|
|
3006
|
-
"NonConvert",
|
|
3007
|
-
"PreviousCandidate",
|
|
3008
|
-
"Process",
|
|
3009
|
-
"SingleCandidate",
|
|
3010
|
-
"HangulMode",
|
|
3011
|
-
"HanjaMode",
|
|
3012
|
-
"JunjaMode",
|
|
3013
|
-
"Eisu",
|
|
3014
|
-
"Hankaku",
|
|
3015
|
-
"Hiragana",
|
|
3016
|
-
"HiraganaKatakana",
|
|
3017
|
-
"KanaMode",
|
|
3018
|
-
"KanjiMode",
|
|
3019
|
-
"Katakana",
|
|
3020
|
-
"Romaji",
|
|
3021
|
-
"Zenkaku",
|
|
3022
|
-
"ZenkakuHanaku",
|
|
3023
|
-
"F1",
|
|
3024
|
-
"F2",
|
|
3025
|
-
"F3",
|
|
3026
|
-
"F4",
|
|
3027
|
-
"F5",
|
|
3028
|
-
"F6",
|
|
3029
|
-
"F7",
|
|
3030
|
-
"F8",
|
|
3031
|
-
"F9",
|
|
3032
|
-
"F10",
|
|
3033
|
-
"F11",
|
|
3034
|
-
"F12",
|
|
3035
|
-
"Soft1",
|
|
3036
|
-
"Soft2",
|
|
3037
|
-
"Soft3",
|
|
3038
|
-
"Soft4",
|
|
3039
|
-
"ChannelDown",
|
|
3040
|
-
"ChannelUp",
|
|
3041
|
-
"Close",
|
|
3042
|
-
"MailForward",
|
|
3043
|
-
"MailReply",
|
|
3044
|
-
"MailSend",
|
|
3045
|
-
"MediaFastForward",
|
|
3046
|
-
"MediaPause",
|
|
3047
|
-
"MediaPlay",
|
|
3048
|
-
"MediaPlayPause",
|
|
3049
|
-
"MediaRecord",
|
|
3050
|
-
"MediaRewind",
|
|
3051
|
-
"MediaStop",
|
|
3052
|
-
"MediaTrackNext",
|
|
3053
|
-
"MediaTrackPrevious",
|
|
3054
|
-
"AudioBalanceLeft",
|
|
3055
|
-
"AudioBalanceRight",
|
|
3056
|
-
"AudioBassBoostDown",
|
|
3057
|
-
"AudioBassBoostToggle",
|
|
3058
|
-
"AudioBassBoostUp",
|
|
3059
|
-
"AudioFaderFront",
|
|
3060
|
-
"AudioFaderRear",
|
|
3061
|
-
"AudioSurroundModeNext",
|
|
3062
|
-
"AudioTrebleDown",
|
|
3063
|
-
"AudioTrebleUp",
|
|
3064
|
-
"AudioVolumeDown",
|
|
3065
|
-
"AudioVolumeMute",
|
|
3066
|
-
"AudioVolumeUp",
|
|
3067
|
-
"MicrophoneToggle",
|
|
3068
|
-
"MicrophoneVolumeDown",
|
|
3069
|
-
"MicrophoneVolumeMute",
|
|
3070
|
-
"MicrophoneVolumeUp",
|
|
3071
|
-
"TV",
|
|
3072
|
-
"TV3DMode",
|
|
3073
|
-
"TVAntennaCable",
|
|
3074
|
-
"TVAudioDescription",
|
|
3075
|
-
];
|
|
3076
|
-
function unEscapeString(str) {
|
|
3077
|
-
const placeholder = "__NEWLINE__";
|
|
3078
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
3079
|
-
return str;
|
|
3080
|
-
}
|
|
3081
3266
|
export { StableBrowser };
|
|
3082
3267
|
//# sourceMappingURL=stable_browser.js.map
|