automation_model 1.0.525-dev → 1.0.525-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/lib/api.d.ts +2 -1
- package/lib/api.js +12 -11
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +7 -3
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +70 -27
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +51 -9
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +12 -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 +2 -1
- package/lib/init_browser.js +29 -5
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +51 -12
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +50 -16
- package/lib/stable_browser.js +586 -488
- package/lib/stable_browser.js.map +1 -1
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +344 -6
- package/lib/utils.js.map +1 -1
- package/package.json +6 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,15 +10,17 @@ 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, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
17
|
import { getContext } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
19
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -29,6 +31,8 @@ const Types = {
|
|
|
29
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
36
|
ANALYZE_TABLE: "analyze_table",
|
|
33
37
|
SELECT: "select_combobox",
|
|
34
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -45,8 +49,13 @@ const Types = {
|
|
|
45
49
|
LOAD_DATA: "load_data",
|
|
46
50
|
SET_INPUT: "set_input",
|
|
47
51
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
48
54
|
};
|
|
49
55
|
export const apps = {};
|
|
56
|
+
const formatElementName = (elementName) => {
|
|
57
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
58
|
+
};
|
|
50
59
|
class StableBrowser {
|
|
51
60
|
browser;
|
|
52
61
|
page;
|
|
@@ -89,17 +98,17 @@ class StableBrowser {
|
|
|
89
98
|
catch (e) {
|
|
90
99
|
this.logger.error("unable to read ai_config.json");
|
|
91
100
|
}
|
|
92
|
-
context.pageLoading = { status: false };
|
|
93
|
-
context.pages = [this.page];
|
|
94
101
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
95
102
|
this.world = world;
|
|
103
|
+
context.pages = [this.page];
|
|
104
|
+
context.pageLoading = { status: false };
|
|
96
105
|
this.registerEventListeners(this.context);
|
|
97
106
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
98
107
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
99
108
|
}
|
|
100
109
|
registerEventListeners(context) {
|
|
101
110
|
this.registerConsoleLogListener(this.page, context);
|
|
102
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
111
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
103
112
|
if (!context.pageLoading) {
|
|
104
113
|
context.pageLoading = { status: false };
|
|
105
114
|
}
|
|
@@ -144,7 +153,7 @@ class StableBrowser {
|
|
|
144
153
|
if (this.appName === appName) {
|
|
145
154
|
return;
|
|
146
155
|
}
|
|
147
|
-
let
|
|
156
|
+
let navigate = false;
|
|
148
157
|
if (!apps[appName]) {
|
|
149
158
|
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
150
159
|
newContextCreated = true;
|
|
@@ -155,32 +164,15 @@ class StableBrowser {
|
|
|
155
164
|
};
|
|
156
165
|
}
|
|
157
166
|
const tempContext = {};
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
_copyContext(this, tempContext);
|
|
168
|
+
_copyContext(apps[appName], this);
|
|
160
169
|
apps[this.appName] = tempContext;
|
|
161
170
|
this.appName = appName;
|
|
162
|
-
if (
|
|
163
|
-
this.registerEventListeners(this.context);
|
|
171
|
+
if (navigate) {
|
|
164
172
|
await this.goto(this.context.environment.baseUrl);
|
|
165
173
|
await this.waitForPageLoad();
|
|
166
174
|
}
|
|
167
175
|
}
|
|
168
|
-
_copyContext(from, to) {
|
|
169
|
-
to.browser = from.browser;
|
|
170
|
-
to.page = from.page;
|
|
171
|
-
to.context = from.context;
|
|
172
|
-
}
|
|
173
|
-
getWebLogFile(logFolder) {
|
|
174
|
-
if (!fs.existsSync(logFolder)) {
|
|
175
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
176
|
-
}
|
|
177
|
-
let nextIndex = 1;
|
|
178
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
179
|
-
nextIndex++;
|
|
180
|
-
}
|
|
181
|
-
const fileName = nextIndex + ".json";
|
|
182
|
-
return path.join(logFolder, fileName);
|
|
183
|
-
}
|
|
184
176
|
registerConsoleLogListener(page, context) {
|
|
185
177
|
if (!this.context.webLogger) {
|
|
186
178
|
this.context.webLogger = [];
|
|
@@ -244,55 +236,48 @@ class StableBrowser {
|
|
|
244
236
|
// async closeUnexpectedPopups() {
|
|
245
237
|
// await closeUnexpectedPopups(this.page);
|
|
246
238
|
// }
|
|
247
|
-
async goto(url) {
|
|
239
|
+
async goto(url, world = null) {
|
|
248
240
|
if (!url.startsWith("http")) {
|
|
249
241
|
url = "https://" + url;
|
|
250
242
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
243
|
+
const state = {
|
|
244
|
+
value: url,
|
|
245
|
+
world: world,
|
|
246
|
+
type: Types.NAVIGATE,
|
|
247
|
+
text: `Navigate Page to: ${url}`,
|
|
248
|
+
operation: "goto",
|
|
249
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
250
|
+
info: {},
|
|
251
|
+
locate: false,
|
|
252
|
+
scroll: false,
|
|
253
|
+
screenshot: false,
|
|
254
|
+
highlight: false,
|
|
255
|
+
};
|
|
256
|
+
try {
|
|
257
|
+
await _preCommand(state, this);
|
|
258
|
+
await this.page.goto(url, {
|
|
259
|
+
timeout: 60000,
|
|
260
|
+
});
|
|
261
|
+
await _screenshot(state, this);
|
|
258
262
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// remove the _ prefix
|
|
263
|
-
regValue = key.substring(1);
|
|
264
|
-
}
|
|
265
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error("Error on goto", error);
|
|
265
|
+
_commandError(state, error, this);
|
|
266
266
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
270
|
-
// check if not null
|
|
271
|
-
if (!locator) {
|
|
272
|
-
return locator;
|
|
267
|
+
finally {
|
|
268
|
+
_commandFinally(state, this);
|
|
273
269
|
}
|
|
274
|
-
// clone the locator
|
|
275
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
276
|
-
this.scanAndManipulate(locator, _params);
|
|
277
|
-
return locator;
|
|
278
|
-
}
|
|
279
|
-
_isObject(value) {
|
|
280
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
281
270
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
271
|
+
async _getLocator(locator, scope, _params) {
|
|
272
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
273
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
274
|
+
for (let key in locator) {
|
|
275
|
+
if (typeof locator[key] !== "string")
|
|
276
|
+
continue;
|
|
277
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
278
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
291
279
|
}
|
|
292
280
|
}
|
|
293
|
-
}
|
|
294
|
-
_getLocator(locator, scope, _params) {
|
|
295
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
296
281
|
let locatorReturn;
|
|
297
282
|
if (locator.role) {
|
|
298
283
|
if (locator.role[1].nameReg) {
|
|
@@ -300,7 +285,7 @@ class StableBrowser {
|
|
|
300
285
|
delete locator.role[1].nameReg;
|
|
301
286
|
}
|
|
302
287
|
// if (locator.role[1].name) {
|
|
303
|
-
// locator.role[1].name =
|
|
288
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
304
289
|
// }
|
|
305
290
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
306
291
|
}
|
|
@@ -343,140 +328,54 @@ class StableBrowser {
|
|
|
343
328
|
if (css && css.locator) {
|
|
344
329
|
css = css.locator;
|
|
345
330
|
}
|
|
346
|
-
let result = await this._locateElementByText(scope,
|
|
331
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
347
332
|
if (result.elementCount === 0) {
|
|
348
333
|
return;
|
|
349
334
|
}
|
|
350
|
-
let textElementCss = "[data-blinq-id
|
|
335
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
351
336
|
// css climb to parent element
|
|
352
337
|
const climbArray = [];
|
|
353
338
|
for (let i = 0; i < climb; i++) {
|
|
354
339
|
climbArray.push("..");
|
|
355
340
|
}
|
|
356
341
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
342
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
343
|
+
if (css) {
|
|
344
|
+
resultCss = resultCss + " >> " + css;
|
|
345
|
+
}
|
|
346
|
+
return resultCss;
|
|
347
|
+
}
|
|
348
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
349
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
350
|
+
const locator = scope.locator(query);
|
|
351
|
+
const count = await locator.count();
|
|
352
|
+
if (!tag1) {
|
|
353
|
+
tag1 = "*";
|
|
354
|
+
}
|
|
355
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
356
|
+
let tagCount = 0;
|
|
357
|
+
for (let i = 0; i < count; i++) {
|
|
358
|
+
const element = locator.nth(i);
|
|
359
|
+
// check if the tag matches
|
|
360
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
361
|
+
if (!tag.startsWith("*")) {
|
|
362
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
363
|
+
return false;
|
|
367
364
|
}
|
|
368
|
-
currentNode = currentNode.parentNode;
|
|
369
|
-
}
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
document.isParent = isParent;
|
|
373
|
-
function getRegex(str) {
|
|
374
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
375
|
-
if (!match) {
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
let [_, pattern, flags] = match;
|
|
379
|
-
return new RegExp(pattern, flags);
|
|
380
|
-
}
|
|
381
|
-
document.getRegex = getRegex;
|
|
382
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
383
|
-
// Check and add the element if it has a shadow root
|
|
384
|
-
if (element.shadowRoot) {
|
|
385
|
-
result.push(element);
|
|
386
|
-
// Also search within the shadow root
|
|
387
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
388
|
-
}
|
|
389
|
-
// Iterate over child nodes
|
|
390
|
-
element.childNodes.forEach((child) => {
|
|
391
|
-
// Recursively call the function for each child node
|
|
392
|
-
document.collectAllShadowDomElements(child, result);
|
|
393
|
-
});
|
|
394
|
-
return result;
|
|
395
|
-
}
|
|
396
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
397
|
-
if (!tag) {
|
|
398
|
-
tag = "*:not(script, style, head)";
|
|
399
|
-
}
|
|
400
|
-
let regexpSearch = document.getRegex(text);
|
|
401
|
-
if (regexpSearch) {
|
|
402
|
-
regex = true;
|
|
403
|
-
}
|
|
404
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
405
|
-
let shadowHosts = [];
|
|
406
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
407
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
408
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
409
|
-
if (!shadowElement) {
|
|
410
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
411
|
-
continue;
|
|
412
365
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
let randomToken = null;
|
|
417
|
-
const foundElements = [];
|
|
418
|
-
if (regex) {
|
|
419
|
-
if (!regexpSearch) {
|
|
420
|
-
regexpSearch = new RegExp(text, "im");
|
|
421
|
-
}
|
|
422
|
-
for (let i = 0; i < elements.length; i++) {
|
|
423
|
-
const element = elements[i];
|
|
424
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
425
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
426
|
-
foundElements.push(element);
|
|
427
|
-
}
|
|
366
|
+
if (!el.setAttribute) {
|
|
367
|
+
el = el.parentElement;
|
|
428
368
|
}
|
|
369
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
370
|
+
return true;
|
|
371
|
+
}, [tag1, randomToken]))) {
|
|
372
|
+
continue;
|
|
429
373
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const element = elements[i];
|
|
434
|
-
if (partial) {
|
|
435
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
436
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
437
|
-
foundElements.push(element);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
442
|
-
(element.value && element.value === text)) {
|
|
443
|
-
foundElements.push(element);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
let noChildElements = [];
|
|
449
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
450
|
-
let element = foundElements[i];
|
|
451
|
-
let hasChild = false;
|
|
452
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
453
|
-
if (i === j) {
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (isParent(element, foundElements[j])) {
|
|
457
|
-
hasChild = true;
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (!hasChild) {
|
|
462
|
-
noChildElements.push(element);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
let elementCount = 0;
|
|
466
|
-
if (noChildElements.length > 0) {
|
|
467
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
468
|
-
if (randomToken === null) {
|
|
469
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
470
|
-
}
|
|
471
|
-
let element = noChildElements[i];
|
|
472
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
473
|
-
elementCount++;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
477
|
-
}, [text1, tag1, regex1, partial1]);
|
|
374
|
+
tagCount++;
|
|
375
|
+
}
|
|
376
|
+
return { elementCount: tagCount, randomToken };
|
|
478
377
|
}
|
|
479
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
378
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
480
379
|
if (!info) {
|
|
481
380
|
info = {};
|
|
482
381
|
}
|
|
@@ -485,10 +384,13 @@ class StableBrowser {
|
|
|
485
384
|
}
|
|
486
385
|
if (!info.log) {
|
|
487
386
|
info.log = "";
|
|
387
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
488
388
|
}
|
|
489
389
|
let locatorSearch = selectorHierarchy[index];
|
|
390
|
+
let originalLocatorSearch = "";
|
|
490
391
|
try {
|
|
491
|
-
|
|
392
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
393
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
492
394
|
}
|
|
493
395
|
catch (e) {
|
|
494
396
|
console.error(e);
|
|
@@ -496,30 +398,31 @@ class StableBrowser {
|
|
|
496
398
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
497
399
|
let locator = null;
|
|
498
400
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
499
|
-
|
|
401
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
402
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
500
403
|
if (!locatorString) {
|
|
501
404
|
info.failCause.textNotFound = true;
|
|
502
|
-
info.failCause.lastError =
|
|
405
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
503
406
|
return;
|
|
504
407
|
}
|
|
505
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
408
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
506
409
|
}
|
|
507
410
|
else if (locatorSearch.text) {
|
|
508
|
-
let text =
|
|
509
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
411
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
412
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
510
413
|
if (result.elementCount === 0) {
|
|
511
414
|
info.failCause.textNotFound = true;
|
|
512
|
-
info.failCause.lastError =
|
|
415
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
513
416
|
return;
|
|
514
417
|
}
|
|
515
|
-
locatorSearch.css = "[data-blinq-id
|
|
418
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
516
419
|
if (locatorSearch.childCss) {
|
|
517
420
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
518
421
|
}
|
|
519
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
422
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
520
423
|
}
|
|
521
424
|
else {
|
|
522
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
425
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
523
426
|
}
|
|
524
427
|
// let cssHref = false;
|
|
525
428
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -532,18 +435,27 @@ class StableBrowser {
|
|
|
532
435
|
//info.log += "total elements found " + count + "\n";
|
|
533
436
|
//let visibleCount = 0;
|
|
534
437
|
let visibleLocator = null;
|
|
535
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
438
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
536
439
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
440
|
+
if (info.locatorLog) {
|
|
441
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
442
|
+
}
|
|
537
443
|
return;
|
|
538
444
|
}
|
|
445
|
+
if (info.locatorLog && count === 0) {
|
|
446
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
447
|
+
}
|
|
539
448
|
for (let j = 0; j < count; j++) {
|
|
540
449
|
let visible = await locator.nth(j).isVisible();
|
|
541
450
|
const enabled = await locator.nth(j).isEnabled();
|
|
542
451
|
if (!visibleOnly) {
|
|
543
452
|
visible = true;
|
|
544
453
|
}
|
|
545
|
-
if (visible && enabled) {
|
|
454
|
+
if (visible && (allowDisabled || enabled)) {
|
|
546
455
|
foundLocators.push(locator.nth(j));
|
|
456
|
+
if (info.locatorLog) {
|
|
457
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
458
|
+
}
|
|
547
459
|
}
|
|
548
460
|
else {
|
|
549
461
|
info.failCause.visible = visible;
|
|
@@ -551,8 +463,16 @@ class StableBrowser {
|
|
|
551
463
|
if (!info.printMessages) {
|
|
552
464
|
info.printMessages = {};
|
|
553
465
|
}
|
|
466
|
+
if (info.locatorLog && !visible) {
|
|
467
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
468
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
469
|
+
}
|
|
470
|
+
if (info.locatorLog && !enabled) {
|
|
471
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
472
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
473
|
+
}
|
|
554
474
|
if (!info.printMessages[j.toString()]) {
|
|
555
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
475
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
556
476
|
info.printMessages[j.toString()] = true;
|
|
557
477
|
}
|
|
558
478
|
}
|
|
@@ -568,7 +488,7 @@ class StableBrowser {
|
|
|
568
488
|
if (!info) {
|
|
569
489
|
info = {};
|
|
570
490
|
}
|
|
571
|
-
info.log += "scan for popup handlers" + "\n";
|
|
491
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
572
492
|
const handlerGroup = [];
|
|
573
493
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
574
494
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -595,16 +515,28 @@ class StableBrowser {
|
|
|
595
515
|
}
|
|
596
516
|
if (result.foundElements.length > 0) {
|
|
597
517
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
518
|
+
try {
|
|
519
|
+
await scope?.evaluate(() => {
|
|
520
|
+
window.__isClosingPopups = true;
|
|
521
|
+
});
|
|
522
|
+
await dialogCloseLocator.click();
|
|
523
|
+
// wait for the dialog to close
|
|
524
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
await scope?.evaluate(() => {
|
|
530
|
+
window.__isClosingPopups = false;
|
|
531
|
+
});
|
|
532
|
+
}
|
|
601
533
|
return { rerun: true };
|
|
602
534
|
}
|
|
603
535
|
}
|
|
604
536
|
}
|
|
605
537
|
return { rerun: false };
|
|
606
538
|
}
|
|
607
|
-
async _locate(selectors, info, _params, timeout) {
|
|
539
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
608
540
|
if (!timeout) {
|
|
609
541
|
timeout = 30000;
|
|
610
542
|
}
|
|
@@ -614,9 +546,15 @@ class StableBrowser {
|
|
|
614
546
|
let selector = selectors.locators[j];
|
|
615
547
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
616
548
|
}
|
|
617
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
549
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
618
550
|
if (!element.rerun) {
|
|
619
|
-
|
|
551
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
552
|
+
element.evaluate((el, randomToken) => {
|
|
553
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
554
|
+
}, randomToken);
|
|
555
|
+
const scope = element._frame ?? element.page();
|
|
556
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
557
|
+
return newSelector;
|
|
620
558
|
}
|
|
621
559
|
}
|
|
622
560
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -627,6 +565,7 @@ class StableBrowser {
|
|
|
627
565
|
info.failCause = {};
|
|
628
566
|
info.log = "";
|
|
629
567
|
}
|
|
568
|
+
let startTime = Date.now();
|
|
630
569
|
let scope = this.page;
|
|
631
570
|
if (selectors.frame) {
|
|
632
571
|
return selectors.frame;
|
|
@@ -657,9 +596,11 @@ class StableBrowser {
|
|
|
657
596
|
}
|
|
658
597
|
return framescope;
|
|
659
598
|
};
|
|
599
|
+
let fLocator = null;
|
|
660
600
|
while (true) {
|
|
661
601
|
let frameFound = false;
|
|
662
602
|
if (selectors.nestFrmLoc) {
|
|
603
|
+
fLocator = selectors.nestFrmLoc;
|
|
663
604
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
664
605
|
frameFound = true;
|
|
665
606
|
break;
|
|
@@ -668,6 +609,7 @@ class StableBrowser {
|
|
|
668
609
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
669
610
|
let frameLocator = selectors.frameLocators[i];
|
|
670
611
|
if (frameLocator.css) {
|
|
612
|
+
fLocator = frameLocator.css;
|
|
671
613
|
scope = scope.frameLocator(frameLocator.css);
|
|
672
614
|
frameFound = true;
|
|
673
615
|
break;
|
|
@@ -675,18 +617,25 @@ class StableBrowser {
|
|
|
675
617
|
}
|
|
676
618
|
}
|
|
677
619
|
if (!frameFound && selectors.iframe_src) {
|
|
620
|
+
fLocator = selectors.iframe_src;
|
|
678
621
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
679
622
|
}
|
|
680
623
|
if (!scope) {
|
|
681
|
-
info
|
|
682
|
-
|
|
624
|
+
if (info && info.locatorLog) {
|
|
625
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
626
|
+
}
|
|
627
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
628
|
+
if (Date.now() - startTime > timeout) {
|
|
683
629
|
info.failCause.iframeNotFound = true;
|
|
684
|
-
info.failCause.lastError =
|
|
630
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
685
631
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
686
632
|
}
|
|
687
633
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
688
634
|
}
|
|
689
635
|
else {
|
|
636
|
+
if (info && info.locatorLog) {
|
|
637
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
638
|
+
}
|
|
690
639
|
break;
|
|
691
640
|
}
|
|
692
641
|
}
|
|
@@ -703,16 +652,18 @@ class StableBrowser {
|
|
|
703
652
|
return bodyContent;
|
|
704
653
|
});
|
|
705
654
|
}
|
|
706
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
655
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
707
656
|
if (!info) {
|
|
708
657
|
info = {};
|
|
709
658
|
info.failCause = {};
|
|
710
659
|
info.log = "";
|
|
660
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
711
661
|
}
|
|
712
662
|
let highPriorityTimeout = 5000;
|
|
713
663
|
let visibleOnlyTimeout = 6000;
|
|
714
|
-
let startTime =
|
|
664
|
+
let startTime = Date.now();
|
|
715
665
|
let locatorsCount = 0;
|
|
666
|
+
let lazy_scroll = false;
|
|
716
667
|
//let arrayMode = Array.isArray(selectors);
|
|
717
668
|
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
718
669
|
let selectorsLocators = null;
|
|
@@ -750,17 +701,17 @@ class StableBrowser {
|
|
|
750
701
|
}
|
|
751
702
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
752
703
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
753
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
704
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
754
705
|
if (result.foundElements.length === 0) {
|
|
755
706
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
756
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
707
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
757
708
|
}
|
|
758
709
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
759
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
710
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
760
711
|
}
|
|
761
712
|
else {
|
|
762
713
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
763
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
714
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
764
715
|
}
|
|
765
716
|
}
|
|
766
717
|
let foundElements = result.foundElements;
|
|
@@ -801,26 +752,38 @@ class StableBrowser {
|
|
|
801
752
|
return maxCountElement.locator;
|
|
802
753
|
}
|
|
803
754
|
}
|
|
804
|
-
if (
|
|
755
|
+
if (Date.now() - startTime > timeout) {
|
|
805
756
|
break;
|
|
806
757
|
}
|
|
807
|
-
if (
|
|
808
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
758
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
759
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
809
760
|
highPriorityOnly = false;
|
|
761
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
762
|
+
lazy_scroll = true;
|
|
763
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
764
|
+
}
|
|
810
765
|
}
|
|
811
|
-
if (
|
|
812
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
766
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
767
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
813
768
|
visibleOnly = false;
|
|
814
769
|
}
|
|
815
770
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
816
771
|
}
|
|
817
772
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
818
|
-
info.
|
|
773
|
+
// if (info.locatorLog) {
|
|
774
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
775
|
+
// for (let line of lines) {
|
|
776
|
+
// this.logger.debug(line);
|
|
777
|
+
// }
|
|
778
|
+
// }
|
|
779
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
819
780
|
info.failCause.locatorNotFound = true;
|
|
820
|
-
info
|
|
781
|
+
if (!info?.failCause?.lastError) {
|
|
782
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
783
|
+
}
|
|
821
784
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
822
785
|
}
|
|
823
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
786
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
824
787
|
let foundElements = [];
|
|
825
788
|
const result = {
|
|
826
789
|
foundElements: foundElements,
|
|
@@ -828,14 +791,15 @@ class StableBrowser {
|
|
|
828
791
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
829
792
|
let foundLocators = [];
|
|
830
793
|
try {
|
|
831
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
794
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
832
795
|
}
|
|
833
796
|
catch (e) {
|
|
834
|
-
this
|
|
835
|
-
this.logger.debug(
|
|
797
|
+
// this call can fail it the browser is navigating
|
|
798
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
799
|
+
// this.logger.debug(e);
|
|
836
800
|
foundLocators = [];
|
|
837
801
|
try {
|
|
838
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
802
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
839
803
|
}
|
|
840
804
|
catch (e) {
|
|
841
805
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -851,11 +815,27 @@ class StableBrowser {
|
|
|
851
815
|
}
|
|
852
816
|
if (foundLocators.length > 1) {
|
|
853
817
|
info.failCause.foundMultiple = true;
|
|
818
|
+
if (info.locatorLog) {
|
|
819
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
820
|
+
}
|
|
854
821
|
}
|
|
855
822
|
}
|
|
856
823
|
return result;
|
|
857
824
|
}
|
|
858
825
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
826
|
+
const state = {
|
|
827
|
+
locate: false,
|
|
828
|
+
scroll: false,
|
|
829
|
+
highlight: false,
|
|
830
|
+
_params,
|
|
831
|
+
options,
|
|
832
|
+
world,
|
|
833
|
+
type: Types.CLICK,
|
|
834
|
+
text: "Click element",
|
|
835
|
+
operation: "simpleClick",
|
|
836
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
837
|
+
};
|
|
838
|
+
_preCommand(state, this);
|
|
859
839
|
const startTime = Date.now();
|
|
860
840
|
let timeout = 30000;
|
|
861
841
|
if (options && options.timeout) {
|
|
@@ -880,13 +860,31 @@ class StableBrowser {
|
|
|
880
860
|
catch (e) {
|
|
881
861
|
if (performance.now() - startTime > timeout) {
|
|
882
862
|
// throw e;
|
|
883
|
-
|
|
863
|
+
try {
|
|
864
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
_commandFinally(state, this);
|
|
868
|
+
}
|
|
884
869
|
}
|
|
885
870
|
}
|
|
886
871
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
887
872
|
}
|
|
888
873
|
}
|
|
889
874
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
875
|
+
const state = {
|
|
876
|
+
locate: false,
|
|
877
|
+
scroll: false,
|
|
878
|
+
highlight: false,
|
|
879
|
+
_params,
|
|
880
|
+
options,
|
|
881
|
+
world,
|
|
882
|
+
type: Types.FILL,
|
|
883
|
+
text: "Fill element",
|
|
884
|
+
operation: "simpleClickType",
|
|
885
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
886
|
+
};
|
|
887
|
+
_preCommand(state, this);
|
|
890
888
|
const startTime = Date.now();
|
|
891
889
|
let timeout = 30000;
|
|
892
890
|
if (options && options.timeout) {
|
|
@@ -911,7 +909,12 @@ class StableBrowser {
|
|
|
911
909
|
catch (e) {
|
|
912
910
|
if (performance.now() - startTime > timeout) {
|
|
913
911
|
// throw e;
|
|
914
|
-
|
|
912
|
+
try {
|
|
913
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
_commandFinally(state, this);
|
|
917
|
+
}
|
|
915
918
|
}
|
|
916
919
|
}
|
|
917
920
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -930,9 +933,9 @@ class StableBrowser {
|
|
|
930
933
|
};
|
|
931
934
|
try {
|
|
932
935
|
await _preCommand(state, this);
|
|
933
|
-
if (state.options && state.options.context) {
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
+
// if (state.options && state.options.context) {
|
|
937
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
938
|
+
// }
|
|
936
939
|
try {
|
|
937
940
|
await state.element.click();
|
|
938
941
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -970,9 +973,15 @@ class StableBrowser {
|
|
|
970
973
|
// let element = await this._locate(selectors, info, _params);
|
|
971
974
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
972
975
|
try {
|
|
973
|
-
//
|
|
976
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
977
|
+
// console.log(`Highlighting while running from recorder`);
|
|
978
|
+
await this._highlightElements(element);
|
|
974
979
|
await state.element.setChecked(checked);
|
|
975
980
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
981
|
+
// await this._unHighlightElements(element);
|
|
982
|
+
// }
|
|
983
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
984
|
+
// await this._unHighlightElements(element);
|
|
976
985
|
}
|
|
977
986
|
catch (e) {
|
|
978
987
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1011,6 +1020,7 @@ class StableBrowser {
|
|
|
1011
1020
|
await _preCommand(state, this);
|
|
1012
1021
|
try {
|
|
1013
1022
|
await state.element.hover();
|
|
1023
|
+
// await _screenshot(state, this);
|
|
1014
1024
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1015
1025
|
}
|
|
1016
1026
|
catch (e) {
|
|
@@ -1018,6 +1028,7 @@ class StableBrowser {
|
|
|
1018
1028
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1019
1029
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1020
1030
|
await state.element.hover({ timeout: 10000 });
|
|
1031
|
+
// await _screenshot(state, this);
|
|
1021
1032
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1022
1033
|
}
|
|
1023
1034
|
await _screenshot(state, this);
|
|
@@ -1292,7 +1303,12 @@ class StableBrowser {
|
|
|
1292
1303
|
await this.waitForPageLoad();
|
|
1293
1304
|
}
|
|
1294
1305
|
else if (enter === false) {
|
|
1295
|
-
|
|
1306
|
+
try {
|
|
1307
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1308
|
+
}
|
|
1309
|
+
catch (e) {
|
|
1310
|
+
// ignore
|
|
1311
|
+
}
|
|
1296
1312
|
//await this.page.keyboard.press("Tab");
|
|
1297
1313
|
}
|
|
1298
1314
|
else {
|
|
@@ -1349,6 +1365,7 @@ class StableBrowser {
|
|
|
1349
1365
|
let screenshotPath = null;
|
|
1350
1366
|
if (!info.log) {
|
|
1351
1367
|
info.log = "";
|
|
1368
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1352
1369
|
}
|
|
1353
1370
|
info.operation = "getText";
|
|
1354
1371
|
info.selectors = selectors;
|
|
@@ -1371,6 +1388,18 @@ class StableBrowser {
|
|
|
1371
1388
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1372
1389
|
try {
|
|
1373
1390
|
await this._highlightElements(element);
|
|
1391
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1392
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1393
|
+
// this._highlightElements(element)
|
|
1394
|
+
// .then(async () => {
|
|
1395
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1396
|
+
// this._unhighlightElements(element).then(
|
|
1397
|
+
// () => {}
|
|
1398
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1399
|
+
// );
|
|
1400
|
+
// })
|
|
1401
|
+
// .catch(e);
|
|
1402
|
+
// }
|
|
1374
1403
|
const elementText = await element.innerText();
|
|
1375
1404
|
return {
|
|
1376
1405
|
text: elementText,
|
|
@@ -1382,7 +1411,7 @@ class StableBrowser {
|
|
|
1382
1411
|
}
|
|
1383
1412
|
catch (e) {
|
|
1384
1413
|
//await this.closeUnexpectedPopups();
|
|
1385
|
-
this.logger.info("no innerText will use textContent");
|
|
1414
|
+
this.logger.info("no innerText, will use textContent");
|
|
1386
1415
|
const elementText = await element.textContent();
|
|
1387
1416
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1388
1417
|
}
|
|
@@ -1508,22 +1537,6 @@ class StableBrowser {
|
|
|
1508
1537
|
_commandFinally(state, this);
|
|
1509
1538
|
}
|
|
1510
1539
|
}
|
|
1511
|
-
_getDataFile(world = null) {
|
|
1512
|
-
let dataFile = null;
|
|
1513
|
-
if (world && world.reportFolder) {
|
|
1514
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1515
|
-
}
|
|
1516
|
-
else if (this.reportFolder) {
|
|
1517
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1518
|
-
}
|
|
1519
|
-
else if (this.context && this.context.reportFolder) {
|
|
1520
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1521
|
-
}
|
|
1522
|
-
else {
|
|
1523
|
-
dataFile = "data.json";
|
|
1524
|
-
}
|
|
1525
|
-
return dataFile;
|
|
1526
|
-
}
|
|
1527
1540
|
async waitForUserInput(message, world = null) {
|
|
1528
1541
|
if (!message) {
|
|
1529
1542
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1552,7 +1565,7 @@ class StableBrowser {
|
|
|
1552
1565
|
return;
|
|
1553
1566
|
}
|
|
1554
1567
|
// if data file exists, load it
|
|
1555
|
-
const dataFile =
|
|
1568
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1556
1569
|
let data = this.getTestData(world);
|
|
1557
1570
|
// merge the testData with the existing data
|
|
1558
1571
|
Object.assign(data, testData);
|
|
@@ -1655,7 +1668,7 @@ class StableBrowser {
|
|
|
1655
1668
|
}
|
|
1656
1669
|
}
|
|
1657
1670
|
getTestData(world = null) {
|
|
1658
|
-
const dataFile =
|
|
1671
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1659
1672
|
let data = {};
|
|
1660
1673
|
if (fs.existsSync(dataFile)) {
|
|
1661
1674
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1687,11 +1700,9 @@ class StableBrowser {
|
|
|
1687
1700
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1688
1701
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1689
1702
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
1694
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1703
|
+
// to make sure the path doesn't start with -
|
|
1704
|
+
const uuidStr = "id_" + randomUUID();
|
|
1705
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1695
1706
|
try {
|
|
1696
1707
|
await this.takeScreenshot(screenshotPath);
|
|
1697
1708
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1701,15 +1712,15 @@ class StableBrowser {
|
|
|
1701
1712
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1702
1713
|
// }
|
|
1703
1714
|
// });
|
|
1715
|
+
result.screenshotId = uuidStr;
|
|
1716
|
+
result.screenshotPath = screenshotPath;
|
|
1717
|
+
if (info && info.box) {
|
|
1718
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1719
|
+
}
|
|
1704
1720
|
}
|
|
1705
1721
|
catch (e) {
|
|
1706
1722
|
this.logger.info("unable to take screenshot, ignored");
|
|
1707
1723
|
}
|
|
1708
|
-
result.screenshotId = nextIndex;
|
|
1709
|
-
result.screenshotPath = screenshotPath;
|
|
1710
|
-
if (info && info.box) {
|
|
1711
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1712
|
-
}
|
|
1713
1724
|
}
|
|
1714
1725
|
else if (options && options.screenshot) {
|
|
1715
1726
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1744,6 +1755,15 @@ class StableBrowser {
|
|
|
1744
1755
|
document.documentElement.clientWidth,
|
|
1745
1756
|
])));
|
|
1746
1757
|
let screenshotBuffer = null;
|
|
1758
|
+
// if (focusedElement) {
|
|
1759
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1760
|
+
// await this._unhighlightElements(focusedElement);
|
|
1761
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1762
|
+
// console.log(`Unhighlighted previous element`);
|
|
1763
|
+
// }
|
|
1764
|
+
// if (focusedElement) {
|
|
1765
|
+
// await this._highlightElements(focusedElement);
|
|
1766
|
+
// }
|
|
1747
1767
|
if (this.context.browserName === "chromium") {
|
|
1748
1768
|
const client = await playContext.newCDPSession(this.page);
|
|
1749
1769
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1765,6 +1785,10 @@ class StableBrowser {
|
|
|
1765
1785
|
else {
|
|
1766
1786
|
screenshotBuffer = await this.page.screenshot();
|
|
1767
1787
|
}
|
|
1788
|
+
// if (focusedElement) {
|
|
1789
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1790
|
+
// await this._unhighlightElements(focusedElement);
|
|
1791
|
+
// }
|
|
1768
1792
|
let image = await Jimp.read(screenshotBuffer);
|
|
1769
1793
|
// Get the image dimensions
|
|
1770
1794
|
const { width, height } = image.bitmap;
|
|
@@ -1814,6 +1838,7 @@ class StableBrowser {
|
|
|
1814
1838
|
text: `Extract attribute from element`,
|
|
1815
1839
|
operation: "extractAttribute",
|
|
1816
1840
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1841
|
+
allowDisabled: true,
|
|
1817
1842
|
};
|
|
1818
1843
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1819
1844
|
try {
|
|
@@ -1835,6 +1860,70 @@ class StableBrowser {
|
|
|
1835
1860
|
state.info.value = state.value;
|
|
1836
1861
|
this.setTestData({ [variable]: state.value }, world);
|
|
1837
1862
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1863
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1864
|
+
return state.info;
|
|
1865
|
+
}
|
|
1866
|
+
catch (e) {
|
|
1867
|
+
await _commandError(state, e, this);
|
|
1868
|
+
}
|
|
1869
|
+
finally {
|
|
1870
|
+
_commandFinally(state, this);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1874
|
+
const state = {
|
|
1875
|
+
selectors,
|
|
1876
|
+
_params,
|
|
1877
|
+
attribute,
|
|
1878
|
+
value,
|
|
1879
|
+
options,
|
|
1880
|
+
world,
|
|
1881
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1882
|
+
highlight: true,
|
|
1883
|
+
screenshot: true,
|
|
1884
|
+
text: `Verify element attribute`,
|
|
1885
|
+
operation: "verifyAttribute",
|
|
1886
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1887
|
+
allowDisabled: true,
|
|
1888
|
+
};
|
|
1889
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1890
|
+
let val;
|
|
1891
|
+
try {
|
|
1892
|
+
await _preCommand(state, this);
|
|
1893
|
+
switch (attribute) {
|
|
1894
|
+
case "innerText":
|
|
1895
|
+
val = String(await state.element.innerText());
|
|
1896
|
+
break;
|
|
1897
|
+
case "value":
|
|
1898
|
+
val = String(await state.element.inputValue());
|
|
1899
|
+
break;
|
|
1900
|
+
case "checked":
|
|
1901
|
+
val = String(await state.element.isChecked());
|
|
1902
|
+
break;
|
|
1903
|
+
case "disabled":
|
|
1904
|
+
val = String(await state.element.isDisabled());
|
|
1905
|
+
break;
|
|
1906
|
+
case "readOnly":
|
|
1907
|
+
const isEditable = await state.element.isEditable();
|
|
1908
|
+
val = String(!isEditable);
|
|
1909
|
+
break;
|
|
1910
|
+
default:
|
|
1911
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
state.info.expectedValue = val;
|
|
1915
|
+
let regex;
|
|
1916
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1917
|
+
const patternBody = value.slice(1, -1);
|
|
1918
|
+
regex = new RegExp(patternBody, "g");
|
|
1919
|
+
}
|
|
1920
|
+
else {
|
|
1921
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1922
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1923
|
+
}
|
|
1924
|
+
if (!val.match(regex)) {
|
|
1925
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1926
|
+
}
|
|
1838
1927
|
return state.info;
|
|
1839
1928
|
}
|
|
1840
1929
|
catch (e) {
|
|
@@ -1862,7 +1951,7 @@ class StableBrowser {
|
|
|
1862
1951
|
if (options && options.timeout) {
|
|
1863
1952
|
timeout = options.timeout;
|
|
1864
1953
|
}
|
|
1865
|
-
const serviceUrl =
|
|
1954
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1866
1955
|
const request = {
|
|
1867
1956
|
method: "POST",
|
|
1868
1957
|
url: serviceUrl,
|
|
@@ -1933,27 +2022,32 @@ class StableBrowser {
|
|
|
1933
2022
|
async _highlightElements(scope, css) {
|
|
1934
2023
|
try {
|
|
1935
2024
|
if (!scope) {
|
|
2025
|
+
// console.log(`Scope is not defined`);
|
|
1936
2026
|
return;
|
|
1937
2027
|
}
|
|
1938
2028
|
if (!css) {
|
|
1939
2029
|
scope
|
|
1940
2030
|
.evaluate((node) => {
|
|
1941
2031
|
if (node && node.style) {
|
|
1942
|
-
let
|
|
1943
|
-
|
|
2032
|
+
let originalOutline = node.style.outline;
|
|
2033
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2034
|
+
// node.__previousOutline = originalOutline;
|
|
2035
|
+
node.style.outline = "2px solid red";
|
|
2036
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1944
2037
|
if (window) {
|
|
1945
2038
|
window.addEventListener("beforeunload", function (e) {
|
|
1946
|
-
node.style.
|
|
2039
|
+
node.style.outline = originalOutline;
|
|
1947
2040
|
});
|
|
1948
2041
|
}
|
|
1949
2042
|
setTimeout(function () {
|
|
1950
|
-
node.style.
|
|
2043
|
+
node.style.outline = originalOutline;
|
|
1951
2044
|
}, 2000);
|
|
1952
2045
|
}
|
|
1953
2046
|
})
|
|
1954
2047
|
.then(() => { })
|
|
1955
2048
|
.catch((e) => {
|
|
1956
2049
|
// ignore
|
|
2050
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1957
2051
|
});
|
|
1958
2052
|
}
|
|
1959
2053
|
else {
|
|
@@ -1969,17 +2063,18 @@ class StableBrowser {
|
|
|
1969
2063
|
if (!element.style) {
|
|
1970
2064
|
return;
|
|
1971
2065
|
}
|
|
1972
|
-
|
|
2066
|
+
let originalOutline = element.style.outline;
|
|
2067
|
+
element.__previousOutline = originalOutline;
|
|
1973
2068
|
// Set the new border to be red and 2px solid
|
|
1974
|
-
element.style.
|
|
2069
|
+
element.style.outline = "2px solid red";
|
|
1975
2070
|
if (window) {
|
|
1976
2071
|
window.addEventListener("beforeunload", function (e) {
|
|
1977
|
-
element.style.
|
|
2072
|
+
element.style.outline = originalOutline;
|
|
1978
2073
|
});
|
|
1979
2074
|
}
|
|
1980
2075
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1981
2076
|
setTimeout(function () {
|
|
1982
|
-
element.style.
|
|
2077
|
+
element.style.outline = originalOutline;
|
|
1983
2078
|
}, 2000);
|
|
1984
2079
|
}
|
|
1985
2080
|
return;
|
|
@@ -1987,6 +2082,7 @@ class StableBrowser {
|
|
|
1987
2082
|
.then(() => { })
|
|
1988
2083
|
.catch((e) => {
|
|
1989
2084
|
// ignore
|
|
2085
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
1990
2086
|
});
|
|
1991
2087
|
}
|
|
1992
2088
|
}
|
|
@@ -1994,6 +2090,54 @@ class StableBrowser {
|
|
|
1994
2090
|
console.debug(error);
|
|
1995
2091
|
}
|
|
1996
2092
|
}
|
|
2093
|
+
// async _unhighlightElements(scope, css) {
|
|
2094
|
+
// try {
|
|
2095
|
+
// if (!scope) {
|
|
2096
|
+
// return;
|
|
2097
|
+
// }
|
|
2098
|
+
// if (!css) {
|
|
2099
|
+
// scope
|
|
2100
|
+
// .evaluate((node) => {
|
|
2101
|
+
// if (node && node.style) {
|
|
2102
|
+
// if (!node.__previousOutline) {
|
|
2103
|
+
// node.style.outline = "";
|
|
2104
|
+
// } else {
|
|
2105
|
+
// node.style.outline = node.__previousOutline;
|
|
2106
|
+
// }
|
|
2107
|
+
// }
|
|
2108
|
+
// })
|
|
2109
|
+
// .then(() => {})
|
|
2110
|
+
// .catch((e) => {
|
|
2111
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2112
|
+
// });
|
|
2113
|
+
// } else {
|
|
2114
|
+
// scope
|
|
2115
|
+
// .evaluate(([css]) => {
|
|
2116
|
+
// if (!css) {
|
|
2117
|
+
// return;
|
|
2118
|
+
// }
|
|
2119
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2120
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2121
|
+
// let element = elements[i];
|
|
2122
|
+
// if (!element.style) {
|
|
2123
|
+
// return;
|
|
2124
|
+
// }
|
|
2125
|
+
// if (!element.__previousOutline) {
|
|
2126
|
+
// element.style.outline = "";
|
|
2127
|
+
// } else {
|
|
2128
|
+
// element.style.outline = element.__previousOutline;
|
|
2129
|
+
// }
|
|
2130
|
+
// }
|
|
2131
|
+
// })
|
|
2132
|
+
// .then(() => {})
|
|
2133
|
+
// .catch((e) => {
|
|
2134
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2135
|
+
// });
|
|
2136
|
+
// }
|
|
2137
|
+
// } catch (error) {
|
|
2138
|
+
// // console.debug(error);
|
|
2139
|
+
// }
|
|
2140
|
+
// }
|
|
1997
2141
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
1998
2142
|
const startTime = Date.now();
|
|
1999
2143
|
let error = null;
|
|
@@ -2055,6 +2199,35 @@ class StableBrowser {
|
|
|
2055
2199
|
});
|
|
2056
2200
|
}
|
|
2057
2201
|
}
|
|
2202
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2203
|
+
const frames = this.page.frames();
|
|
2204
|
+
let results = [];
|
|
2205
|
+
let ignoreCase = false;
|
|
2206
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2207
|
+
if (dateAlternatives.date) {
|
|
2208
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2209
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2210
|
+
result.frame = frames[i];
|
|
2211
|
+
results.push(result);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
else if (numberAlternatives.number) {
|
|
2215
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2216
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2217
|
+
result.frame = frames[i];
|
|
2218
|
+
results.push(result);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
else {
|
|
2222
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2223
|
+
result.frame = frames[i];
|
|
2224
|
+
results.push(result);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
state.info.results = results;
|
|
2228
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2229
|
+
return resultWithElementsFound;
|
|
2230
|
+
}
|
|
2058
2231
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2059
2232
|
text = unEscapeString(text);
|
|
2060
2233
|
const state = {
|
|
@@ -2064,7 +2237,7 @@ class StableBrowser {
|
|
|
2064
2237
|
locate: false,
|
|
2065
2238
|
scroll: false,
|
|
2066
2239
|
highlight: false,
|
|
2067
|
-
type: Types.
|
|
2240
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2068
2241
|
text: `Verify text exists in page`,
|
|
2069
2242
|
operation: "verifyTextExistInPage",
|
|
2070
2243
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2082,31 +2255,7 @@ class StableBrowser {
|
|
|
2082
2255
|
await _preCommand(state, this);
|
|
2083
2256
|
state.info.text = text;
|
|
2084
2257
|
while (true) {
|
|
2085
|
-
const
|
|
2086
|
-
let results = [];
|
|
2087
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2088
|
-
if (dateAlternatives.date) {
|
|
2089
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2090
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2091
|
-
result.frame = frames[i];
|
|
2092
|
-
results.push(result);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
else if (numberAlternatives.number) {
|
|
2096
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2097
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2098
|
-
result.frame = frames[i];
|
|
2099
|
-
results.push(result);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
else {
|
|
2103
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2104
|
-
result.frame = frames[i];
|
|
2105
|
-
results.push(result);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
state.info.results = results;
|
|
2109
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2258
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2110
2259
|
if (resultWithElementsFound.length === 0) {
|
|
2111
2260
|
if (Date.now() - state.startTime > timeout) {
|
|
2112
2261
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2116,12 +2265,29 @@ class StableBrowser {
|
|
|
2116
2265
|
}
|
|
2117
2266
|
if (resultWithElementsFound[0].randomToken) {
|
|
2118
2267
|
const frame = resultWithElementsFound[0].frame;
|
|
2119
|
-
const dataAttribute = `[data-blinq-id
|
|
2268
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2120
2269
|
await this._highlightElements(frame, dataAttribute);
|
|
2121
|
-
|
|
2270
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2271
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2272
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2273
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2274
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2275
|
+
// .then(async () => {
|
|
2276
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2277
|
+
// })
|
|
2278
|
+
// .catch(
|
|
2279
|
+
// (e) => {}
|
|
2280
|
+
// console.error(e)
|
|
2281
|
+
// );
|
|
2282
|
+
// });
|
|
2283
|
+
// }
|
|
2284
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2285
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2286
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2122
2287
|
if (element) {
|
|
2123
2288
|
await this.scrollIfNeeded(element, state.info);
|
|
2124
2289
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2290
|
+
// await _screenshot(state, this, element);
|
|
2125
2291
|
}
|
|
2126
2292
|
}
|
|
2127
2293
|
await _screenshot(state, this);
|
|
@@ -2163,31 +2329,7 @@ class StableBrowser {
|
|
|
2163
2329
|
await _preCommand(state, this);
|
|
2164
2330
|
state.info.text = text;
|
|
2165
2331
|
while (true) {
|
|
2166
|
-
const
|
|
2167
|
-
let results = [];
|
|
2168
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2169
|
-
if (dateAlternatives.date) {
|
|
2170
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2171
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2172
|
-
result.frame = frames[i];
|
|
2173
|
-
results.push(result);
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
else if (numberAlternatives.number) {
|
|
2177
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2178
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2179
|
-
result.frame = frames[i];
|
|
2180
|
-
results.push(result);
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
else {
|
|
2184
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2185
|
-
result.frame = frames[i];
|
|
2186
|
-
results.push(result);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
state.info.results = results;
|
|
2190
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2332
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2191
2333
|
if (resultWithElementsFound.length === 0) {
|
|
2192
2334
|
await _screenshot(state, this);
|
|
2193
2335
|
return state.info;
|
|
@@ -2205,15 +2347,102 @@ class StableBrowser {
|
|
|
2205
2347
|
_commandFinally(state, this);
|
|
2206
2348
|
}
|
|
2207
2349
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2350
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2351
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2352
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2353
|
+
const state = {
|
|
2354
|
+
text_search: textToVerify,
|
|
2355
|
+
options,
|
|
2356
|
+
world,
|
|
2357
|
+
locate: false,
|
|
2358
|
+
scroll: false,
|
|
2359
|
+
highlight: false,
|
|
2360
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2361
|
+
text: `Verify text with relation to another text`,
|
|
2362
|
+
operation: "verify_text_with_relation",
|
|
2363
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2364
|
+
};
|
|
2365
|
+
const timeout = this._getLoadTimeout(options);
|
|
2366
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2367
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2368
|
+
if (newValue !== textAnchor) {
|
|
2369
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2370
|
+
textAnchor = newValue;
|
|
2371
|
+
}
|
|
2372
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2373
|
+
if (newValue !== textToVerify) {
|
|
2374
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2375
|
+
textToVerify = newValue;
|
|
2376
|
+
}
|
|
2377
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2378
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2379
|
+
let foundAncore = false;
|
|
2380
|
+
try {
|
|
2381
|
+
await _preCommand(state, this);
|
|
2382
|
+
state.info.text = textToVerify;
|
|
2383
|
+
while (true) {
|
|
2384
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2385
|
+
if (resultWithElementsFound.length === 0) {
|
|
2386
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2387
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2388
|
+
}
|
|
2389
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2393
|
+
foundAncore = true;
|
|
2394
|
+
const result = resultWithElementsFound[i];
|
|
2395
|
+
const token = result.randomToken;
|
|
2396
|
+
const frame = result.frame;
|
|
2397
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2398
|
+
const climbArray1 = [];
|
|
2399
|
+
for (let i = 0; i < climb; i++) {
|
|
2400
|
+
climbArray1.push("..");
|
|
2401
|
+
}
|
|
2402
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2403
|
+
css = css + " >> " + climbXpath;
|
|
2404
|
+
const count = await frame.locator(css).count();
|
|
2405
|
+
for (let j = 0; j < count; j++) {
|
|
2406
|
+
const continer = await frame.locator(css).nth(j);
|
|
2407
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2408
|
+
if (result.elementCount > 0) {
|
|
2409
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2410
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2411
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2412
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2413
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2414
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2415
|
+
// .then(async () => {
|
|
2416
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2417
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2418
|
+
// () => {}
|
|
2419
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2420
|
+
// );
|
|
2421
|
+
// })
|
|
2422
|
+
// .catch(e);
|
|
2423
|
+
// }
|
|
2424
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2425
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2426
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2427
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2428
|
+
if (element) {
|
|
2429
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2430
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2431
|
+
}
|
|
2432
|
+
await _screenshot(state, this);
|
|
2433
|
+
return state.info;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2439
|
+
}
|
|
2440
|
+
catch (e) {
|
|
2441
|
+
await _commandError(state, e, this);
|
|
2212
2442
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2443
|
+
finally {
|
|
2444
|
+
_commandFinally(state, this);
|
|
2215
2445
|
}
|
|
2216
|
-
return serviceUrl;
|
|
2217
2446
|
}
|
|
2218
2447
|
async visualVerification(text, options = {}, world = null) {
|
|
2219
2448
|
const startTime = Date.now();
|
|
@@ -2229,7 +2458,7 @@ class StableBrowser {
|
|
|
2229
2458
|
throw new Error("TOKEN is not set");
|
|
2230
2459
|
}
|
|
2231
2460
|
try {
|
|
2232
|
-
let serviceUrl =
|
|
2461
|
+
let serviceUrl = _getServerUrl();
|
|
2233
2462
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2234
2463
|
info.screenshotPath = screenshotPath;
|
|
2235
2464
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2318,6 +2547,7 @@ class StableBrowser {
|
|
|
2318
2547
|
let screenshotPath = null;
|
|
2319
2548
|
const info = {};
|
|
2320
2549
|
info.log = "";
|
|
2550
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2321
2551
|
info.operation = "getTableData";
|
|
2322
2552
|
info.selectors = selectors;
|
|
2323
2553
|
try {
|
|
@@ -2393,7 +2623,7 @@ class StableBrowser {
|
|
|
2393
2623
|
info.operation = "analyzeTable";
|
|
2394
2624
|
info.selectors = selectors;
|
|
2395
2625
|
info.query = query;
|
|
2396
|
-
query =
|
|
2626
|
+
query = _fixUsingParams(query, _params);
|
|
2397
2627
|
info.query_fixed = query;
|
|
2398
2628
|
info.operator = operator;
|
|
2399
2629
|
info.value = value;
|
|
@@ -2538,6 +2768,17 @@ class StableBrowser {
|
|
|
2538
2768
|
}
|
|
2539
2769
|
return timeout;
|
|
2540
2770
|
}
|
|
2771
|
+
async saveStoreState(path = null, world = null) {
|
|
2772
|
+
const storageState = await this.page.context().storageState();
|
|
2773
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2774
|
+
if (path) {
|
|
2775
|
+
// save { storageState: storageState } into the path
|
|
2776
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2541
2782
|
async waitForPageLoad(options = {}, world = null) {
|
|
2542
2783
|
let timeout = this._getLoadTimeout(options);
|
|
2543
2784
|
const promiseArray = [];
|
|
@@ -2723,14 +2964,22 @@ class StableBrowser {
|
|
|
2723
2964
|
}
|
|
2724
2965
|
}
|
|
2725
2966
|
async beforeStep(world, step) {
|
|
2726
|
-
this.stepName = step.pickleStep.text;
|
|
2727
|
-
this.logger.info("step: " + this.stepName);
|
|
2728
2967
|
if (this.stepIndex === undefined) {
|
|
2729
2968
|
this.stepIndex = 0;
|
|
2730
2969
|
}
|
|
2731
2970
|
else {
|
|
2732
2971
|
this.stepIndex++;
|
|
2733
2972
|
}
|
|
2973
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2974
|
+
this.stepName = step.pickleStep.text;
|
|
2975
|
+
this.logger.info("step: " + this.stepName);
|
|
2976
|
+
}
|
|
2977
|
+
else if (step && step.text) {
|
|
2978
|
+
this.stepName = step.text;
|
|
2979
|
+
}
|
|
2980
|
+
else {
|
|
2981
|
+
this.stepName = "step " + this.stepIndex;
|
|
2982
|
+
}
|
|
2734
2983
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2735
2984
|
if (this.context.browserObject.context) {
|
|
2736
2985
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2760,156 +3009,5 @@ function createTimedPromise(promise, label) {
|
|
|
2760
3009
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2761
3010
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2762
3011
|
}
|
|
2763
|
-
const KEYBOARD_EVENTS = [
|
|
2764
|
-
"ALT",
|
|
2765
|
-
"AltGraph",
|
|
2766
|
-
"CapsLock",
|
|
2767
|
-
"Control",
|
|
2768
|
-
"Fn",
|
|
2769
|
-
"FnLock",
|
|
2770
|
-
"Hyper",
|
|
2771
|
-
"Meta",
|
|
2772
|
-
"NumLock",
|
|
2773
|
-
"ScrollLock",
|
|
2774
|
-
"Shift",
|
|
2775
|
-
"Super",
|
|
2776
|
-
"Symbol",
|
|
2777
|
-
"SymbolLock",
|
|
2778
|
-
"Enter",
|
|
2779
|
-
"Tab",
|
|
2780
|
-
"ArrowDown",
|
|
2781
|
-
"ArrowLeft",
|
|
2782
|
-
"ArrowRight",
|
|
2783
|
-
"ArrowUp",
|
|
2784
|
-
"End",
|
|
2785
|
-
"Home",
|
|
2786
|
-
"PageDown",
|
|
2787
|
-
"PageUp",
|
|
2788
|
-
"Backspace",
|
|
2789
|
-
"Clear",
|
|
2790
|
-
"Copy",
|
|
2791
|
-
"CrSel",
|
|
2792
|
-
"Cut",
|
|
2793
|
-
"Delete",
|
|
2794
|
-
"EraseEof",
|
|
2795
|
-
"ExSel",
|
|
2796
|
-
"Insert",
|
|
2797
|
-
"Paste",
|
|
2798
|
-
"Redo",
|
|
2799
|
-
"Undo",
|
|
2800
|
-
"Accept",
|
|
2801
|
-
"Again",
|
|
2802
|
-
"Attn",
|
|
2803
|
-
"Cancel",
|
|
2804
|
-
"ContextMenu",
|
|
2805
|
-
"Escape",
|
|
2806
|
-
"Execute",
|
|
2807
|
-
"Find",
|
|
2808
|
-
"Finish",
|
|
2809
|
-
"Help",
|
|
2810
|
-
"Pause",
|
|
2811
|
-
"Play",
|
|
2812
|
-
"Props",
|
|
2813
|
-
"Select",
|
|
2814
|
-
"ZoomIn",
|
|
2815
|
-
"ZoomOut",
|
|
2816
|
-
"BrightnessDown",
|
|
2817
|
-
"BrightnessUp",
|
|
2818
|
-
"Eject",
|
|
2819
|
-
"LogOff",
|
|
2820
|
-
"Power",
|
|
2821
|
-
"PowerOff",
|
|
2822
|
-
"PrintScreen",
|
|
2823
|
-
"Hibernate",
|
|
2824
|
-
"Standby",
|
|
2825
|
-
"WakeUp",
|
|
2826
|
-
"AllCandidates",
|
|
2827
|
-
"Alphanumeric",
|
|
2828
|
-
"CodeInput",
|
|
2829
|
-
"Compose",
|
|
2830
|
-
"Convert",
|
|
2831
|
-
"Dead",
|
|
2832
|
-
"FinalMode",
|
|
2833
|
-
"GroupFirst",
|
|
2834
|
-
"GroupLast",
|
|
2835
|
-
"GroupNext",
|
|
2836
|
-
"GroupPrevious",
|
|
2837
|
-
"ModeChange",
|
|
2838
|
-
"NextCandidate",
|
|
2839
|
-
"NonConvert",
|
|
2840
|
-
"PreviousCandidate",
|
|
2841
|
-
"Process",
|
|
2842
|
-
"SingleCandidate",
|
|
2843
|
-
"HangulMode",
|
|
2844
|
-
"HanjaMode",
|
|
2845
|
-
"JunjaMode",
|
|
2846
|
-
"Eisu",
|
|
2847
|
-
"Hankaku",
|
|
2848
|
-
"Hiragana",
|
|
2849
|
-
"HiraganaKatakana",
|
|
2850
|
-
"KanaMode",
|
|
2851
|
-
"KanjiMode",
|
|
2852
|
-
"Katakana",
|
|
2853
|
-
"Romaji",
|
|
2854
|
-
"Zenkaku",
|
|
2855
|
-
"ZenkakuHanaku",
|
|
2856
|
-
"F1",
|
|
2857
|
-
"F2",
|
|
2858
|
-
"F3",
|
|
2859
|
-
"F4",
|
|
2860
|
-
"F5",
|
|
2861
|
-
"F6",
|
|
2862
|
-
"F7",
|
|
2863
|
-
"F8",
|
|
2864
|
-
"F9",
|
|
2865
|
-
"F10",
|
|
2866
|
-
"F11",
|
|
2867
|
-
"F12",
|
|
2868
|
-
"Soft1",
|
|
2869
|
-
"Soft2",
|
|
2870
|
-
"Soft3",
|
|
2871
|
-
"Soft4",
|
|
2872
|
-
"ChannelDown",
|
|
2873
|
-
"ChannelUp",
|
|
2874
|
-
"Close",
|
|
2875
|
-
"MailForward",
|
|
2876
|
-
"MailReply",
|
|
2877
|
-
"MailSend",
|
|
2878
|
-
"MediaFastForward",
|
|
2879
|
-
"MediaPause",
|
|
2880
|
-
"MediaPlay",
|
|
2881
|
-
"MediaPlayPause",
|
|
2882
|
-
"MediaRecord",
|
|
2883
|
-
"MediaRewind",
|
|
2884
|
-
"MediaStop",
|
|
2885
|
-
"MediaTrackNext",
|
|
2886
|
-
"MediaTrackPrevious",
|
|
2887
|
-
"AudioBalanceLeft",
|
|
2888
|
-
"AudioBalanceRight",
|
|
2889
|
-
"AudioBassBoostDown",
|
|
2890
|
-
"AudioBassBoostToggle",
|
|
2891
|
-
"AudioBassBoostUp",
|
|
2892
|
-
"AudioFaderFront",
|
|
2893
|
-
"AudioFaderRear",
|
|
2894
|
-
"AudioSurroundModeNext",
|
|
2895
|
-
"AudioTrebleDown",
|
|
2896
|
-
"AudioTrebleUp",
|
|
2897
|
-
"AudioVolumeDown",
|
|
2898
|
-
"AudioVolumeMute",
|
|
2899
|
-
"AudioVolumeUp",
|
|
2900
|
-
"MicrophoneToggle",
|
|
2901
|
-
"MicrophoneVolumeDown",
|
|
2902
|
-
"MicrophoneVolumeMute",
|
|
2903
|
-
"MicrophoneVolumeUp",
|
|
2904
|
-
"TV",
|
|
2905
|
-
"TV3DMode",
|
|
2906
|
-
"TVAntennaCable",
|
|
2907
|
-
"TVAudioDescription",
|
|
2908
|
-
];
|
|
2909
|
-
function unEscapeString(str) {
|
|
2910
|
-
const placeholder = "__NEWLINE__";
|
|
2911
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2912
|
-
return str;
|
|
2913
|
-
}
|
|
2914
3012
|
export { StableBrowser };
|
|
2915
3013
|
//# sourceMappingURL=stable_browser.js.map
|