automation_model 1.0.550-dev → 1.0.550-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.d.ts +1 -0
- package/lib/api.js +8 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +101 -5
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +89 -39
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +38 -13
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/init_browser.d.ts +4 -2
- package/lib/init_browser.js +52 -5
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +10 -10
- package/lib/locate_element.js.map +1 -1
- package/lib/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 -531
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +95 -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 +16 -1
- package/lib/utils.js +416 -6
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,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, } 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,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,15 +946,16 @@ 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
|
-
if (state.options && state.options.context) {
|
|
1045
|
-
|
|
1046
|
-
}
|
|
956
|
+
// if (state.options && state.options.context) {
|
|
957
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
958
|
+
// }
|
|
1047
959
|
try {
|
|
1048
960
|
await state.element.click();
|
|
1049
961
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1064,6 +976,38 @@ class StableBrowser {
|
|
|
1064
976
|
_commandFinally(state, this);
|
|
1065
977
|
}
|
|
1066
978
|
}
|
|
979
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
980
|
+
const timeout = this._getFindElementTimeout(options);
|
|
981
|
+
const state = {
|
|
982
|
+
selectors,
|
|
983
|
+
_params,
|
|
984
|
+
options,
|
|
985
|
+
world,
|
|
986
|
+
text: "Wait for element",
|
|
987
|
+
_text: "Wait for " + selectors.element_name,
|
|
988
|
+
type: Types.WAIT_ELEMENT,
|
|
989
|
+
operation: "waitForElement",
|
|
990
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
991
|
+
};
|
|
992
|
+
let found = false;
|
|
993
|
+
try {
|
|
994
|
+
await _preCommand(state, this);
|
|
995
|
+
// if (state.options && state.options.context) {
|
|
996
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
997
|
+
// }
|
|
998
|
+
await state.element.waitFor({ timeout: timeout });
|
|
999
|
+
found = true;
|
|
1000
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1001
|
+
}
|
|
1002
|
+
catch (e) {
|
|
1003
|
+
console.error("Error on waitForElement", e);
|
|
1004
|
+
// await _commandError(state, e, this);
|
|
1005
|
+
}
|
|
1006
|
+
finally {
|
|
1007
|
+
_commandFinally(state, this);
|
|
1008
|
+
}
|
|
1009
|
+
return found;
|
|
1010
|
+
}
|
|
1067
1011
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
1068
1012
|
const state = {
|
|
1069
1013
|
selectors,
|
|
@@ -1072,6 +1016,7 @@ class StableBrowser {
|
|
|
1072
1016
|
world,
|
|
1073
1017
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1074
1018
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1019
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1075
1020
|
operation: "setCheck",
|
|
1076
1021
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
1077
1022
|
};
|
|
@@ -1081,9 +1026,15 @@ class StableBrowser {
|
|
|
1081
1026
|
// let element = await this._locate(selectors, info, _params);
|
|
1082
1027
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1083
1028
|
try {
|
|
1084
|
-
//
|
|
1029
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1030
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1031
|
+
await this._highlightElements(element);
|
|
1085
1032
|
await state.element.setChecked(checked);
|
|
1086
1033
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1034
|
+
// await this._unHighlightElements(element);
|
|
1035
|
+
// }
|
|
1036
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1037
|
+
// await this._unHighlightElements(element);
|
|
1087
1038
|
}
|
|
1088
1039
|
catch (e) {
|
|
1089
1040
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1115,6 +1066,7 @@ class StableBrowser {
|
|
|
1115
1066
|
world,
|
|
1116
1067
|
type: Types.HOVER,
|
|
1117
1068
|
text: `Hover element`,
|
|
1069
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1118
1070
|
operation: "hover",
|
|
1119
1071
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1120
1072
|
};
|
|
@@ -1122,6 +1074,7 @@ class StableBrowser {
|
|
|
1122
1074
|
await _preCommand(state, this);
|
|
1123
1075
|
try {
|
|
1124
1076
|
await state.element.hover();
|
|
1077
|
+
// await _screenshot(state, this);
|
|
1125
1078
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1126
1079
|
}
|
|
1127
1080
|
catch (e) {
|
|
@@ -1129,6 +1082,7 @@ class StableBrowser {
|
|
|
1129
1082
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1130
1083
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1131
1084
|
await state.element.hover({ timeout: 10000 });
|
|
1085
|
+
// await _screenshot(state, this);
|
|
1132
1086
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1133
1087
|
}
|
|
1134
1088
|
await _screenshot(state, this);
|
|
@@ -1154,6 +1108,7 @@ class StableBrowser {
|
|
|
1154
1108
|
value: values.toString(),
|
|
1155
1109
|
type: Types.SELECT,
|
|
1156
1110
|
text: `Select option: ${values}`,
|
|
1111
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1157
1112
|
operation: "selectOption",
|
|
1158
1113
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1159
1114
|
};
|
|
@@ -1188,6 +1143,7 @@ class StableBrowser {
|
|
|
1188
1143
|
highlight: false,
|
|
1189
1144
|
type: Types.TYPE_PRESS,
|
|
1190
1145
|
text: `Type value: ${_value}`,
|
|
1146
|
+
_text: `Type value: ${_value}`,
|
|
1191
1147
|
operation: "type",
|
|
1192
1148
|
log: "",
|
|
1193
1149
|
};
|
|
@@ -1267,6 +1223,7 @@ class StableBrowser {
|
|
|
1267
1223
|
world,
|
|
1268
1224
|
type: Types.SET_DATE_TIME,
|
|
1269
1225
|
text: `Set date time value: ${value}`,
|
|
1226
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1270
1227
|
operation: "setDateTime",
|
|
1271
1228
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1272
1229
|
throwError: false,
|
|
@@ -1338,6 +1295,7 @@ class StableBrowser {
|
|
|
1338
1295
|
world,
|
|
1339
1296
|
type: Types.FILL,
|
|
1340
1297
|
text: `Click type input with value: ${_value}`,
|
|
1298
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1341
1299
|
operation: "clickType",
|
|
1342
1300
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1343
1301
|
};
|
|
@@ -1403,7 +1361,12 @@ class StableBrowser {
|
|
|
1403
1361
|
await this.waitForPageLoad();
|
|
1404
1362
|
}
|
|
1405
1363
|
else if (enter === false) {
|
|
1406
|
-
|
|
1364
|
+
try {
|
|
1365
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1366
|
+
}
|
|
1367
|
+
catch (e) {
|
|
1368
|
+
// ignore
|
|
1369
|
+
}
|
|
1407
1370
|
//await this.page.keyboard.press("Tab");
|
|
1408
1371
|
}
|
|
1409
1372
|
else {
|
|
@@ -1455,6 +1418,7 @@ class StableBrowser {
|
|
|
1455
1418
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1456
1419
|
}
|
|
1457
1420
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1421
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1458
1422
|
_validateSelectors(selectors);
|
|
1459
1423
|
let screenshotId = null;
|
|
1460
1424
|
let screenshotPath = null;
|
|
@@ -1464,7 +1428,7 @@ class StableBrowser {
|
|
|
1464
1428
|
}
|
|
1465
1429
|
info.operation = "getText";
|
|
1466
1430
|
info.selectors = selectors;
|
|
1467
|
-
let element = await this._locate(selectors, info, _params);
|
|
1431
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1468
1432
|
if (climb > 0) {
|
|
1469
1433
|
const climbArray = [];
|
|
1470
1434
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1483,6 +1447,18 @@ class StableBrowser {
|
|
|
1483
1447
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1484
1448
|
try {
|
|
1485
1449
|
await this._highlightElements(element);
|
|
1450
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1451
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1452
|
+
// this._highlightElements(element)
|
|
1453
|
+
// .then(async () => {
|
|
1454
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1455
|
+
// this._unhighlightElements(element).then(
|
|
1456
|
+
// () => {}
|
|
1457
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1458
|
+
// );
|
|
1459
|
+
// })
|
|
1460
|
+
// .catch(e);
|
|
1461
|
+
// }
|
|
1486
1462
|
const elementText = await element.innerText();
|
|
1487
1463
|
return {
|
|
1488
1464
|
text: elementText,
|
|
@@ -1494,7 +1470,7 @@ class StableBrowser {
|
|
|
1494
1470
|
}
|
|
1495
1471
|
catch (e) {
|
|
1496
1472
|
//await this.closeUnexpectedPopups();
|
|
1497
|
-
this.logger.info("no innerText will use textContent");
|
|
1473
|
+
this.logger.info("no innerText, will use textContent");
|
|
1498
1474
|
const elementText = await element.textContent();
|
|
1499
1475
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1500
1476
|
}
|
|
@@ -1519,6 +1495,7 @@ class StableBrowser {
|
|
|
1519
1495
|
highlight: false,
|
|
1520
1496
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1521
1497
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1498
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1522
1499
|
operation: "containsPattern",
|
|
1523
1500
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1524
1501
|
};
|
|
@@ -1554,6 +1531,8 @@ class StableBrowser {
|
|
|
1554
1531
|
}
|
|
1555
1532
|
}
|
|
1556
1533
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1534
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1535
|
+
const startTime = Date.now();
|
|
1557
1536
|
const state = {
|
|
1558
1537
|
selectors,
|
|
1559
1538
|
_params,
|
|
@@ -1580,62 +1559,54 @@ class StableBrowser {
|
|
|
1580
1559
|
}
|
|
1581
1560
|
let foundObj = null;
|
|
1582
1561
|
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;
|
|
1562
|
+
while (Date.now() - startTime < timeout) {
|
|
1563
|
+
try {
|
|
1564
|
+
await _preCommand(state, this);
|
|
1565
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1566
|
+
if (foundObj && foundObj.element) {
|
|
1567
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1596
1568
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1569
|
+
await _screenshot(state, this);
|
|
1570
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1571
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1572
|
+
if (dateAlternatives.date) {
|
|
1573
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1574
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1575
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1576
|
+
return state.info;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
else if (numberAlternatives.number) {
|
|
1581
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1582
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1583
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1584
|
+
return state.info;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1604
1589
|
return state.info;
|
|
1605
1590
|
}
|
|
1606
1591
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
throw new Error("element doesn't contain text " + text);
|
|
1592
|
+
catch (e) {
|
|
1593
|
+
// Log error but continue retrying until timeout is reached
|
|
1594
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1595
|
+
}
|
|
1596
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1613
1597
|
}
|
|
1614
|
-
|
|
1598
|
+
state.info.foundText = foundObj?.text;
|
|
1599
|
+
state.info.value = foundObj?.value;
|
|
1600
|
+
throw new Error("element doesn't contain text " + text);
|
|
1615
1601
|
}
|
|
1616
1602
|
catch (e) {
|
|
1617
1603
|
await _commandError(state, e, this);
|
|
1604
|
+
throw e;
|
|
1618
1605
|
}
|
|
1619
1606
|
finally {
|
|
1620
1607
|
_commandFinally(state, this);
|
|
1621
1608
|
}
|
|
1622
1609
|
}
|
|
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
1610
|
async waitForUserInput(message, world = null) {
|
|
1640
1611
|
if (!message) {
|
|
1641
1612
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1664,7 +1635,7 @@ class StableBrowser {
|
|
|
1664
1635
|
return;
|
|
1665
1636
|
}
|
|
1666
1637
|
// if data file exists, load it
|
|
1667
|
-
const dataFile =
|
|
1638
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1668
1639
|
let data = this.getTestData(world);
|
|
1669
1640
|
// merge the testData with the existing data
|
|
1670
1641
|
Object.assign(data, testData);
|
|
@@ -1767,7 +1738,7 @@ class StableBrowser {
|
|
|
1767
1738
|
}
|
|
1768
1739
|
}
|
|
1769
1740
|
getTestData(world = null) {
|
|
1770
|
-
const dataFile =
|
|
1741
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1771
1742
|
let data = {};
|
|
1772
1743
|
if (fs.existsSync(dataFile)) {
|
|
1773
1744
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1854,6 +1825,15 @@ class StableBrowser {
|
|
|
1854
1825
|
document.documentElement.clientWidth,
|
|
1855
1826
|
])));
|
|
1856
1827
|
let screenshotBuffer = null;
|
|
1828
|
+
// if (focusedElement) {
|
|
1829
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1830
|
+
// await this._unhighlightElements(focusedElement);
|
|
1831
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1832
|
+
// console.log(`Unhighlighted previous element`);
|
|
1833
|
+
// }
|
|
1834
|
+
// if (focusedElement) {
|
|
1835
|
+
// await this._highlightElements(focusedElement);
|
|
1836
|
+
// }
|
|
1857
1837
|
if (this.context.browserName === "chromium") {
|
|
1858
1838
|
const client = await playContext.newCDPSession(this.page);
|
|
1859
1839
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1875,6 +1855,10 @@ class StableBrowser {
|
|
|
1875
1855
|
else {
|
|
1876
1856
|
screenshotBuffer = await this.page.screenshot();
|
|
1877
1857
|
}
|
|
1858
|
+
// if (focusedElement) {
|
|
1859
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1860
|
+
// await this._unhighlightElements(focusedElement);
|
|
1861
|
+
// }
|
|
1878
1862
|
let image = await Jimp.read(screenshotBuffer);
|
|
1879
1863
|
// Get the image dimensions
|
|
1880
1864
|
const { width, height } = image.bitmap;
|
|
@@ -1887,6 +1871,7 @@ class StableBrowser {
|
|
|
1887
1871
|
else {
|
|
1888
1872
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1889
1873
|
}
|
|
1874
|
+
return screenshotBuffer;
|
|
1890
1875
|
}
|
|
1891
1876
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1892
1877
|
const state = {
|
|
@@ -1922,8 +1907,10 @@ class StableBrowser {
|
|
|
1922
1907
|
world,
|
|
1923
1908
|
type: Types.EXTRACT,
|
|
1924
1909
|
text: `Extract attribute from element`,
|
|
1910
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1925
1911
|
operation: "extractAttribute",
|
|
1926
1912
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1913
|
+
allowDisabled: true,
|
|
1927
1914
|
};
|
|
1928
1915
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1929
1916
|
try {
|
|
@@ -1945,6 +1932,7 @@ class StableBrowser {
|
|
|
1945
1932
|
state.info.value = state.value;
|
|
1946
1933
|
this.setTestData({ [variable]: state.value }, world);
|
|
1947
1934
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1935
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1948
1936
|
return state.info;
|
|
1949
1937
|
}
|
|
1950
1938
|
catch (e) {
|
|
@@ -1963,14 +1951,21 @@ class StableBrowser {
|
|
|
1963
1951
|
options,
|
|
1964
1952
|
world,
|
|
1965
1953
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1954
|
+
highlight: true,
|
|
1955
|
+
screenshot: true,
|
|
1966
1956
|
text: `Verify element attribute`,
|
|
1957
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1967
1958
|
operation: "verifyAttribute",
|
|
1968
1959
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1960
|
+
allowDisabled: true,
|
|
1969
1961
|
};
|
|
1970
1962
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1971
1963
|
let val;
|
|
1964
|
+
let expectedValue;
|
|
1972
1965
|
try {
|
|
1973
1966
|
await _preCommand(state, this);
|
|
1967
|
+
expectedValue = state.value;
|
|
1968
|
+
state.info.expectedValue = expectedValue;
|
|
1974
1969
|
switch (attribute) {
|
|
1975
1970
|
case "innerText":
|
|
1976
1971
|
val = String(await state.element.innerText());
|
|
@@ -1984,23 +1979,30 @@ class StableBrowser {
|
|
|
1984
1979
|
case "disabled":
|
|
1985
1980
|
val = String(await state.element.isDisabled());
|
|
1986
1981
|
break;
|
|
1982
|
+
case "readOnly":
|
|
1983
|
+
const isEditable = await state.element.isEditable();
|
|
1984
|
+
val = String(!isEditable);
|
|
1985
|
+
break;
|
|
1987
1986
|
default:
|
|
1988
1987
|
val = String(await state.element.getAttribute(attribute));
|
|
1989
1988
|
break;
|
|
1990
1989
|
}
|
|
1990
|
+
state.info.value = val;
|
|
1991
1991
|
let regex;
|
|
1992
|
-
if (
|
|
1993
|
-
const patternBody =
|
|
1992
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1993
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1994
1994
|
regex = new RegExp(patternBody, "g");
|
|
1995
1995
|
}
|
|
1996
1996
|
else {
|
|
1997
|
-
const escapedPattern =
|
|
1997
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1998
1998
|
regex = new RegExp(escapedPattern, "g");
|
|
1999
1999
|
}
|
|
2000
2000
|
if (!val.match(regex)) {
|
|
2001
|
-
|
|
2001
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2002
|
+
state.info.failCause.assertionFailed = true;
|
|
2003
|
+
state.info.failCause.lastError = errorMessage;
|
|
2004
|
+
throw new Error(errorMessage);
|
|
2002
2005
|
}
|
|
2003
|
-
state.info.value = val;
|
|
2004
2006
|
return state.info;
|
|
2005
2007
|
}
|
|
2006
2008
|
catch (e) {
|
|
@@ -2028,7 +2030,7 @@ class StableBrowser {
|
|
|
2028
2030
|
if (options && options.timeout) {
|
|
2029
2031
|
timeout = options.timeout;
|
|
2030
2032
|
}
|
|
2031
|
-
const serviceUrl =
|
|
2033
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
2032
2034
|
const request = {
|
|
2033
2035
|
method: "POST",
|
|
2034
2036
|
url: serviceUrl,
|
|
@@ -2099,27 +2101,32 @@ class StableBrowser {
|
|
|
2099
2101
|
async _highlightElements(scope, css) {
|
|
2100
2102
|
try {
|
|
2101
2103
|
if (!scope) {
|
|
2104
|
+
// console.log(`Scope is not defined`);
|
|
2102
2105
|
return;
|
|
2103
2106
|
}
|
|
2104
2107
|
if (!css) {
|
|
2105
2108
|
scope
|
|
2106
2109
|
.evaluate((node) => {
|
|
2107
2110
|
if (node && node.style) {
|
|
2108
|
-
let
|
|
2109
|
-
|
|
2111
|
+
let originalOutline = node.style.outline;
|
|
2112
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2113
|
+
// node.__previousOutline = originalOutline;
|
|
2114
|
+
node.style.outline = "2px solid red";
|
|
2115
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2110
2116
|
if (window) {
|
|
2111
2117
|
window.addEventListener("beforeunload", function (e) {
|
|
2112
|
-
node.style.
|
|
2118
|
+
node.style.outline = originalOutline;
|
|
2113
2119
|
});
|
|
2114
2120
|
}
|
|
2115
2121
|
setTimeout(function () {
|
|
2116
|
-
node.style.
|
|
2122
|
+
node.style.outline = originalOutline;
|
|
2117
2123
|
}, 2000);
|
|
2118
2124
|
}
|
|
2119
2125
|
})
|
|
2120
2126
|
.then(() => { })
|
|
2121
2127
|
.catch((e) => {
|
|
2122
2128
|
// ignore
|
|
2129
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2123
2130
|
});
|
|
2124
2131
|
}
|
|
2125
2132
|
else {
|
|
@@ -2135,17 +2142,18 @@ class StableBrowser {
|
|
|
2135
2142
|
if (!element.style) {
|
|
2136
2143
|
return;
|
|
2137
2144
|
}
|
|
2138
|
-
|
|
2145
|
+
let originalOutline = element.style.outline;
|
|
2146
|
+
element.__previousOutline = originalOutline;
|
|
2139
2147
|
// Set the new border to be red and 2px solid
|
|
2140
|
-
element.style.
|
|
2148
|
+
element.style.outline = "2px solid red";
|
|
2141
2149
|
if (window) {
|
|
2142
2150
|
window.addEventListener("beforeunload", function (e) {
|
|
2143
|
-
element.style.
|
|
2151
|
+
element.style.outline = originalOutline;
|
|
2144
2152
|
});
|
|
2145
2153
|
}
|
|
2146
2154
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2147
2155
|
setTimeout(function () {
|
|
2148
|
-
element.style.
|
|
2156
|
+
element.style.outline = originalOutline;
|
|
2149
2157
|
}, 2000);
|
|
2150
2158
|
}
|
|
2151
2159
|
return;
|
|
@@ -2153,6 +2161,7 @@ class StableBrowser {
|
|
|
2153
2161
|
.then(() => { })
|
|
2154
2162
|
.catch((e) => {
|
|
2155
2163
|
// ignore
|
|
2164
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2156
2165
|
});
|
|
2157
2166
|
}
|
|
2158
2167
|
}
|
|
@@ -2160,6 +2169,54 @@ class StableBrowser {
|
|
|
2160
2169
|
console.debug(error);
|
|
2161
2170
|
}
|
|
2162
2171
|
}
|
|
2172
|
+
// async _unhighlightElements(scope, css) {
|
|
2173
|
+
// try {
|
|
2174
|
+
// if (!scope) {
|
|
2175
|
+
// return;
|
|
2176
|
+
// }
|
|
2177
|
+
// if (!css) {
|
|
2178
|
+
// scope
|
|
2179
|
+
// .evaluate((node) => {
|
|
2180
|
+
// if (node && node.style) {
|
|
2181
|
+
// if (!node.__previousOutline) {
|
|
2182
|
+
// node.style.outline = "";
|
|
2183
|
+
// } else {
|
|
2184
|
+
// node.style.outline = node.__previousOutline;
|
|
2185
|
+
// }
|
|
2186
|
+
// }
|
|
2187
|
+
// })
|
|
2188
|
+
// .then(() => {})
|
|
2189
|
+
// .catch((e) => {
|
|
2190
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2191
|
+
// });
|
|
2192
|
+
// } else {
|
|
2193
|
+
// scope
|
|
2194
|
+
// .evaluate(([css]) => {
|
|
2195
|
+
// if (!css) {
|
|
2196
|
+
// return;
|
|
2197
|
+
// }
|
|
2198
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2199
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2200
|
+
// let element = elements[i];
|
|
2201
|
+
// if (!element.style) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// if (!element.__previousOutline) {
|
|
2205
|
+
// element.style.outline = "";
|
|
2206
|
+
// } else {
|
|
2207
|
+
// element.style.outline = element.__previousOutline;
|
|
2208
|
+
// }
|
|
2209
|
+
// }
|
|
2210
|
+
// })
|
|
2211
|
+
// .then(() => {})
|
|
2212
|
+
// .catch((e) => {
|
|
2213
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2214
|
+
// });
|
|
2215
|
+
// }
|
|
2216
|
+
// } catch (error) {
|
|
2217
|
+
// // console.debug(error);
|
|
2218
|
+
// }
|
|
2219
|
+
// }
|
|
2163
2220
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2164
2221
|
const startTime = Date.now();
|
|
2165
2222
|
let error = null;
|
|
@@ -2204,6 +2261,7 @@ class StableBrowser {
|
|
|
2204
2261
|
_reportToWorld(world, {
|
|
2205
2262
|
type: Types.VERIFY_PAGE_PATH,
|
|
2206
2263
|
text: "Verify page path",
|
|
2264
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2207
2265
|
screenshotId,
|
|
2208
2266
|
result: error
|
|
2209
2267
|
? {
|
|
@@ -2221,6 +2279,35 @@ class StableBrowser {
|
|
|
2221
2279
|
});
|
|
2222
2280
|
}
|
|
2223
2281
|
}
|
|
2282
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2283
|
+
const frames = this.page.frames();
|
|
2284
|
+
let results = [];
|
|
2285
|
+
let ignoreCase = false;
|
|
2286
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2287
|
+
if (dateAlternatives.date) {
|
|
2288
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2289
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2290
|
+
result.frame = frames[i];
|
|
2291
|
+
results.push(result);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
else if (numberAlternatives.number) {
|
|
2295
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2296
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2297
|
+
result.frame = frames[i];
|
|
2298
|
+
results.push(result);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
else {
|
|
2302
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2303
|
+
result.frame = frames[i];
|
|
2304
|
+
results.push(result);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
state.info.results = results;
|
|
2308
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2309
|
+
return resultWithElementsFound;
|
|
2310
|
+
}
|
|
2224
2311
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2225
2312
|
text = unEscapeString(text);
|
|
2226
2313
|
const state = {
|
|
@@ -2230,12 +2317,16 @@ class StableBrowser {
|
|
|
2230
2317
|
locate: false,
|
|
2231
2318
|
scroll: false,
|
|
2232
2319
|
highlight: false,
|
|
2233
|
-
type: Types.
|
|
2320
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2234
2321
|
text: `Verify text exists in page`,
|
|
2322
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2235
2323
|
operation: "verifyTextExistInPage",
|
|
2236
2324
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2237
2325
|
};
|
|
2238
|
-
|
|
2326
|
+
if (testForRegex(text)) {
|
|
2327
|
+
text = text.replace(/\\"/g, '"');
|
|
2328
|
+
}
|
|
2329
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2239
2330
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2240
2331
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2241
2332
|
if (newValue !== text) {
|
|
@@ -2248,31 +2339,15 @@ class StableBrowser {
|
|
|
2248
2339
|
await _preCommand(state, this);
|
|
2249
2340
|
state.info.text = text;
|
|
2250
2341
|
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
|
-
}
|
|
2342
|
+
let resultWithElementsFound = {
|
|
2343
|
+
length: 0,
|
|
2344
|
+
};
|
|
2345
|
+
try {
|
|
2346
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
// ignore
|
|
2273
2350
|
}
|
|
2274
|
-
state.info.results = results;
|
|
2275
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2276
2351
|
if (resultWithElementsFound.length === 0) {
|
|
2277
2352
|
if (Date.now() - state.startTime > timeout) {
|
|
2278
2353
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2280,18 +2355,40 @@ class StableBrowser {
|
|
|
2280
2355
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2281
2356
|
continue;
|
|
2282
2357
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2358
|
+
try {
|
|
2359
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2360
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2361
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2362
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2363
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2364
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2365
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2366
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2367
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2368
|
+
// .then(async () => {
|
|
2369
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2370
|
+
// })
|
|
2371
|
+
// .catch(
|
|
2372
|
+
// (e) => {}
|
|
2373
|
+
// console.error(e)
|
|
2374
|
+
// );
|
|
2375
|
+
// });
|
|
2376
|
+
// }
|
|
2377
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2378
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2379
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2380
|
+
if (element) {
|
|
2381
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2382
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2383
|
+
// await _screenshot(state, this, element);
|
|
2384
|
+
}
|
|
2291
2385
|
}
|
|
2386
|
+
await _screenshot(state, this);
|
|
2387
|
+
return state.info;
|
|
2388
|
+
}
|
|
2389
|
+
catch (error) {
|
|
2390
|
+
console.error(error);
|
|
2292
2391
|
}
|
|
2293
|
-
await _screenshot(state, this);
|
|
2294
|
-
return state.info;
|
|
2295
2392
|
}
|
|
2296
2393
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2297
2394
|
}
|
|
@@ -2313,10 +2410,14 @@ class StableBrowser {
|
|
|
2313
2410
|
highlight: false,
|
|
2314
2411
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2315
2412
|
text: `Verify text does not exist in page`,
|
|
2413
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2316
2414
|
operation: "verifyTextNotExistInPage",
|
|
2317
2415
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2318
2416
|
};
|
|
2319
|
-
|
|
2417
|
+
if (testForRegex(text)) {
|
|
2418
|
+
text = text.replace(/\\"/g, '"');
|
|
2419
|
+
}
|
|
2420
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2320
2421
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2321
2422
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2322
2423
|
if (newValue !== text) {
|
|
@@ -2328,32 +2429,16 @@ class StableBrowser {
|
|
|
2328
2429
|
try {
|
|
2329
2430
|
await _preCommand(state, this);
|
|
2330
2431
|
state.info.text = text;
|
|
2432
|
+
let resultWithElementsFound = {
|
|
2433
|
+
length: null, // initial cannot be 0
|
|
2434
|
+
};
|
|
2331
2435
|
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
|
-
}
|
|
2436
|
+
try {
|
|
2437
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2438
|
+
}
|
|
2439
|
+
catch (error) {
|
|
2440
|
+
// ignore
|
|
2354
2441
|
}
|
|
2355
|
-
state.info.results = results;
|
|
2356
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2357
2442
|
if (resultWithElementsFound.length === 0) {
|
|
2358
2443
|
await _screenshot(state, this);
|
|
2359
2444
|
return state.info;
|
|
@@ -2371,15 +2456,116 @@ class StableBrowser {
|
|
|
2371
2456
|
_commandFinally(state, this);
|
|
2372
2457
|
}
|
|
2373
2458
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2459
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2460
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2461
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2462
|
+
const state = {
|
|
2463
|
+
text_search: textToVerify,
|
|
2464
|
+
options,
|
|
2465
|
+
world,
|
|
2466
|
+
locate: false,
|
|
2467
|
+
scroll: false,
|
|
2468
|
+
highlight: false,
|
|
2469
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2470
|
+
text: `Verify text with relation to another text`,
|
|
2471
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2472
|
+
operation: "verify_text_with_relation",
|
|
2473
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2474
|
+
};
|
|
2475
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2476
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2477
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2478
|
+
if (newValue !== textAnchor) {
|
|
2479
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2480
|
+
textAnchor = newValue;
|
|
2481
|
+
}
|
|
2482
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2483
|
+
if (newValue !== textToVerify) {
|
|
2484
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2485
|
+
textToVerify = newValue;
|
|
2486
|
+
}
|
|
2487
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2488
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2489
|
+
let foundAncore = false;
|
|
2490
|
+
try {
|
|
2491
|
+
await _preCommand(state, this);
|
|
2492
|
+
state.info.text = textToVerify;
|
|
2493
|
+
let resultWithElementsFound = {
|
|
2494
|
+
length: 0,
|
|
2495
|
+
};
|
|
2496
|
+
while (true) {
|
|
2497
|
+
try {
|
|
2498
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2499
|
+
}
|
|
2500
|
+
catch (error) {
|
|
2501
|
+
// ignore
|
|
2502
|
+
}
|
|
2503
|
+
if (resultWithElementsFound.length === 0) {
|
|
2504
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2505
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2506
|
+
}
|
|
2507
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
try {
|
|
2511
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2512
|
+
foundAncore = true;
|
|
2513
|
+
const result = resultWithElementsFound[i];
|
|
2514
|
+
const token = result.randomToken;
|
|
2515
|
+
const frame = result.frame;
|
|
2516
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2517
|
+
const climbArray1 = [];
|
|
2518
|
+
for (let i = 0; i < climb; i++) {
|
|
2519
|
+
climbArray1.push("..");
|
|
2520
|
+
}
|
|
2521
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2522
|
+
css = css + " >> " + climbXpath;
|
|
2523
|
+
const count = await frame.locator(css).count();
|
|
2524
|
+
for (let j = 0; j < count; j++) {
|
|
2525
|
+
const continer = await frame.locator(css).nth(j);
|
|
2526
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2527
|
+
if (result.elementCount > 0) {
|
|
2528
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2529
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2530
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2531
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2532
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2533
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2534
|
+
// .then(async () => {
|
|
2535
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2536
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2537
|
+
// () => {}
|
|
2538
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2539
|
+
// );
|
|
2540
|
+
// })
|
|
2541
|
+
// .catch(e);
|
|
2542
|
+
// }
|
|
2543
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2544
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2545
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2546
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2547
|
+
if (element) {
|
|
2548
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2549
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2550
|
+
}
|
|
2551
|
+
await _screenshot(state, this);
|
|
2552
|
+
return state.info;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (error) {
|
|
2558
|
+
console.error(error);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2378
2562
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2563
|
+
catch (e) {
|
|
2564
|
+
await _commandError(state, e, this);
|
|
2565
|
+
}
|
|
2566
|
+
finally {
|
|
2567
|
+
_commandFinally(state, this);
|
|
2381
2568
|
}
|
|
2382
|
-
return serviceUrl;
|
|
2383
2569
|
}
|
|
2384
2570
|
async visualVerification(text, options = {}, world = null) {
|
|
2385
2571
|
const startTime = Date.now();
|
|
@@ -2395,14 +2581,17 @@ class StableBrowser {
|
|
|
2395
2581
|
throw new Error("TOKEN is not set");
|
|
2396
2582
|
}
|
|
2397
2583
|
try {
|
|
2398
|
-
let serviceUrl =
|
|
2584
|
+
let serviceUrl = _getServerUrl();
|
|
2399
2585
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2400
2586
|
info.screenshotPath = screenshotPath;
|
|
2401
2587
|
const screenshot = await this.takeScreenshot();
|
|
2402
|
-
|
|
2403
|
-
method: "
|
|
2588
|
+
let request = {
|
|
2589
|
+
method: "post",
|
|
2590
|
+
maxBodyLength: Infinity,
|
|
2404
2591
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2405
2592
|
headers: {
|
|
2593
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2594
|
+
"x-source": "aaa",
|
|
2406
2595
|
"Content-Type": "application/json",
|
|
2407
2596
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2408
2597
|
},
|
|
@@ -2411,7 +2600,7 @@ class StableBrowser {
|
|
|
2411
2600
|
screenshot: screenshot,
|
|
2412
2601
|
}),
|
|
2413
2602
|
};
|
|
2414
|
-
|
|
2603
|
+
const result = await axios.request(request);
|
|
2415
2604
|
if (result.data.status !== true) {
|
|
2416
2605
|
throw new Error("Visual validation failed");
|
|
2417
2606
|
}
|
|
@@ -2439,6 +2628,7 @@ class StableBrowser {
|
|
|
2439
2628
|
_reportToWorld(world, {
|
|
2440
2629
|
type: Types.VERIFY_VISUAL,
|
|
2441
2630
|
text: "Visual verification",
|
|
2631
|
+
_text: "Visual verification of " + text,
|
|
2442
2632
|
screenshotId,
|
|
2443
2633
|
result: error
|
|
2444
2634
|
? {
|
|
@@ -2560,7 +2750,7 @@ class StableBrowser {
|
|
|
2560
2750
|
info.operation = "analyzeTable";
|
|
2561
2751
|
info.selectors = selectors;
|
|
2562
2752
|
info.query = query;
|
|
2563
|
-
query =
|
|
2753
|
+
query = _fixUsingParams(query, _params);
|
|
2564
2754
|
info.query_fixed = query;
|
|
2565
2755
|
info.operator = operator;
|
|
2566
2756
|
info.value = value;
|
|
@@ -2705,6 +2895,32 @@ class StableBrowser {
|
|
|
2705
2895
|
}
|
|
2706
2896
|
return timeout;
|
|
2707
2897
|
}
|
|
2898
|
+
_getFindElementTimeout(options) {
|
|
2899
|
+
if (options && options.timeout) {
|
|
2900
|
+
return options.timeout;
|
|
2901
|
+
}
|
|
2902
|
+
if (this.configuration.find_element_timeout) {
|
|
2903
|
+
return this.configuration.find_element_timeout;
|
|
2904
|
+
}
|
|
2905
|
+
return 30000;
|
|
2906
|
+
}
|
|
2907
|
+
async saveStoreState(path = null, world = null) {
|
|
2908
|
+
const storageState = await this.page.context().storageState();
|
|
2909
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2910
|
+
if (path) {
|
|
2911
|
+
// save { storageState: storageState } into the path
|
|
2912
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2913
|
+
}
|
|
2914
|
+
else {
|
|
2915
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
async restoreSaveState(path = null, world = null) {
|
|
2919
|
+
await refreshBrowser(this, path, world);
|
|
2920
|
+
this.registerEventListeners(this.context);
|
|
2921
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2922
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2923
|
+
}
|
|
2708
2924
|
async waitForPageLoad(options = {}, world = null) {
|
|
2709
2925
|
let timeout = this._getLoadTimeout(options);
|
|
2710
2926
|
const promiseArray = [];
|
|
@@ -2772,6 +2988,7 @@ class StableBrowser {
|
|
|
2772
2988
|
highlight: false,
|
|
2773
2989
|
type: Types.CLOSE_PAGE,
|
|
2774
2990
|
text: `Close page`,
|
|
2991
|
+
_text: `Close the page`,
|
|
2775
2992
|
operation: "closePage",
|
|
2776
2993
|
log: "***** close page *****\n",
|
|
2777
2994
|
throwError: false,
|
|
@@ -2788,8 +3005,97 @@ class StableBrowser {
|
|
|
2788
3005
|
_commandFinally(state, this);
|
|
2789
3006
|
}
|
|
2790
3007
|
}
|
|
3008
|
+
async tableCellOperation(headerText, rowText, options, world = null) {
|
|
3009
|
+
let operation = null;
|
|
3010
|
+
if (!options || !options.operation) {
|
|
3011
|
+
throw new Error("operation is not defined");
|
|
3012
|
+
}
|
|
3013
|
+
operation = options.operation;
|
|
3014
|
+
// validate operation is one of the supported operations
|
|
3015
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3016
|
+
throw new Error("operation is not supported");
|
|
3017
|
+
}
|
|
3018
|
+
const state = {
|
|
3019
|
+
options,
|
|
3020
|
+
world,
|
|
3021
|
+
locate: false,
|
|
3022
|
+
scroll: false,
|
|
3023
|
+
highlight: false,
|
|
3024
|
+
type: Types.TABLE_OPERATION,
|
|
3025
|
+
text: `Table operation`,
|
|
3026
|
+
_text: `Table ${operation} operation`,
|
|
3027
|
+
operation: operation,
|
|
3028
|
+
log: "***** Table operation *****\n",
|
|
3029
|
+
};
|
|
3030
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3031
|
+
try {
|
|
3032
|
+
await _preCommand(state, this);
|
|
3033
|
+
const start = Date.now();
|
|
3034
|
+
let cellArea = null;
|
|
3035
|
+
while (true) {
|
|
3036
|
+
try {
|
|
3037
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3038
|
+
if (cellArea) {
|
|
3039
|
+
break;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
catch (e) {
|
|
3043
|
+
// ignore
|
|
3044
|
+
}
|
|
3045
|
+
if (Date.now() - start > timeout) {
|
|
3046
|
+
throw new Error(`Cell not found in table`);
|
|
3047
|
+
}
|
|
3048
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3049
|
+
}
|
|
3050
|
+
switch (operation) {
|
|
3051
|
+
case "click":
|
|
3052
|
+
if (!options.css) {
|
|
3053
|
+
// will click in the center of the cell
|
|
3054
|
+
let xOffset = 0;
|
|
3055
|
+
let yOffset = 0;
|
|
3056
|
+
if (options.xOffset) {
|
|
3057
|
+
xOffset = options.xOffset;
|
|
3058
|
+
}
|
|
3059
|
+
if (options.yOffset) {
|
|
3060
|
+
yOffset = options.yOffset;
|
|
3061
|
+
}
|
|
3062
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3063
|
+
}
|
|
3064
|
+
else {
|
|
3065
|
+
const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
|
|
3066
|
+
if (results.length === 0) {
|
|
3067
|
+
throw new Error(`Element not found in cell area`);
|
|
3068
|
+
}
|
|
3069
|
+
state.element = results[0];
|
|
3070
|
+
await results[0].click();
|
|
3071
|
+
}
|
|
3072
|
+
break;
|
|
3073
|
+
case "hover+click":
|
|
3074
|
+
if (!options.css) {
|
|
3075
|
+
throw new Error("css is not defined");
|
|
3076
|
+
}
|
|
3077
|
+
const results = await findElementsInArea(options.css, cellArea, this.page, this, options);
|
|
3078
|
+
if (results.length === 0) {
|
|
3079
|
+
throw new Error(`Element not found in cell area`);
|
|
3080
|
+
}
|
|
3081
|
+
state.element = results[0];
|
|
3082
|
+
await results[0].hover();
|
|
3083
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3084
|
+
await results[0].click();
|
|
3085
|
+
break;
|
|
3086
|
+
default:
|
|
3087
|
+
throw new Error("operation is not supported");
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
catch (e) {
|
|
3091
|
+
await _commandError(state, e, this);
|
|
3092
|
+
}
|
|
3093
|
+
finally {
|
|
3094
|
+
_commandFinally(state, this);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
2791
3097
|
saveTestDataAsGlobal(options, world) {
|
|
2792
|
-
const dataFile =
|
|
3098
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2793
3099
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2794
3100
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2795
3101
|
}
|
|
@@ -2819,6 +3125,7 @@ class StableBrowser {
|
|
|
2819
3125
|
_reportToWorld(world, {
|
|
2820
3126
|
type: Types.SET_VIEWPORT,
|
|
2821
3127
|
text: "set viewport size to " + width + "x" + hight,
|
|
3128
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2822
3129
|
screenshotId,
|
|
2823
3130
|
result: error
|
|
2824
3131
|
? {
|
|
@@ -2890,14 +3197,25 @@ class StableBrowser {
|
|
|
2890
3197
|
}
|
|
2891
3198
|
}
|
|
2892
3199
|
async beforeStep(world, step) {
|
|
2893
|
-
this.stepName = step.pickleStep.text;
|
|
2894
|
-
this.logger.info("step: " + this.stepName);
|
|
2895
3200
|
if (this.stepIndex === undefined) {
|
|
2896
3201
|
this.stepIndex = 0;
|
|
2897
3202
|
}
|
|
2898
3203
|
else {
|
|
2899
3204
|
this.stepIndex++;
|
|
2900
3205
|
}
|
|
3206
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3207
|
+
this.stepName = step.pickleStep.text;
|
|
3208
|
+
this.logger.info("step: " + this.stepName);
|
|
3209
|
+
}
|
|
3210
|
+
else if (step && step.text) {
|
|
3211
|
+
this.stepName = step.text;
|
|
3212
|
+
}
|
|
3213
|
+
else {
|
|
3214
|
+
this.stepName = "step " + this.stepIndex;
|
|
3215
|
+
}
|
|
3216
|
+
if (this.context) {
|
|
3217
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3218
|
+
}
|
|
2901
3219
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2902
3220
|
if (this.context.browserObject.context) {
|
|
2903
3221
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2910,6 +3228,41 @@ class StableBrowser {
|
|
|
2910
3228
|
this.saveTestDataAsGlobal({}, world);
|
|
2911
3229
|
}
|
|
2912
3230
|
}
|
|
3231
|
+
if (this.initSnapshotTaken === false) {
|
|
3232
|
+
this.initSnapshotTaken = true;
|
|
3233
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3234
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3235
|
+
if (snapshot) {
|
|
3236
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
async getAriaSnapshot() {
|
|
3242
|
+
try {
|
|
3243
|
+
// find the page url
|
|
3244
|
+
const url = await this.page.url();
|
|
3245
|
+
// extract the path from the url
|
|
3246
|
+
const path = new URL(url).pathname;
|
|
3247
|
+
// get the page title
|
|
3248
|
+
const title = await this.page.title();
|
|
3249
|
+
// go over other frams
|
|
3250
|
+
const frames = this.page.frames();
|
|
3251
|
+
const snapshots = [];
|
|
3252
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3253
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3254
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3255
|
+
content.push(`- frame: ${i}`);
|
|
3256
|
+
const frame = frames[i];
|
|
3257
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3258
|
+
content.push(snapshot);
|
|
3259
|
+
}
|
|
3260
|
+
return content.join("\n");
|
|
3261
|
+
}
|
|
3262
|
+
catch (e) {
|
|
3263
|
+
console.error(e);
|
|
3264
|
+
}
|
|
3265
|
+
return null;
|
|
2913
3266
|
}
|
|
2914
3267
|
async afterStep(world, step) {
|
|
2915
3268
|
this.stepName = null;
|
|
@@ -2920,6 +3273,16 @@ class StableBrowser {
|
|
|
2920
3273
|
});
|
|
2921
3274
|
}
|
|
2922
3275
|
}
|
|
3276
|
+
if (this.context) {
|
|
3277
|
+
this.context.examplesRow = null;
|
|
3278
|
+
}
|
|
3279
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3280
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3281
|
+
if (snapshot) {
|
|
3282
|
+
const obj = {};
|
|
3283
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
2923
3286
|
}
|
|
2924
3287
|
}
|
|
2925
3288
|
function createTimedPromise(promise, label) {
|
|
@@ -2927,156 +3290,5 @@ function createTimedPromise(promise, label) {
|
|
|
2927
3290
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2928
3291
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2929
3292
|
}
|
|
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
3293
|
export { StableBrowser };
|
|
3082
3294
|
//# sourceMappingURL=stable_browser.js.map
|