automation_model 1.0.524-dev → 1.0.524-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 +8 -2
- 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 +15 -4
- 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 +49 -15
- package/lib/stable_browser.js +573 -470
- package/lib/stable_browser.js.map +1 -1
- package/lib/utils.d.ts +13 -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, } 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
270
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
else if (this._isObject(currentObj[key])) {
|
|
289
|
-
// Recursively scan nested objects
|
|
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
|
-
}
|
|
413
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
414
|
-
elements = elements.concat(shadowElements);
|
|
415
|
-
}
|
|
416
|
-
let randomToken = null;
|
|
417
|
-
const foundElements = [];
|
|
418
|
-
if (regex) {
|
|
419
|
-
if (!regexpSearch) {
|
|
420
|
-
regexpSearch = new RegExp(text, "im");
|
|
421
365
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
425
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
426
|
-
foundElements.push(element);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
text = text.trim();
|
|
432
|
-
for (let i = 0; i < elements.length; i++) {
|
|
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++;
|
|
366
|
+
if (!el.setAttribute) {
|
|
367
|
+
el = el.parentElement;
|
|
474
368
|
}
|
|
369
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
370
|
+
return true;
|
|
371
|
+
}, [tag1, randomToken]))) {
|
|
372
|
+
continue;
|
|
475
373
|
}
|
|
476
|
-
|
|
477
|
-
}
|
|
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
|
}
|
|
@@ -1687,11 +1716,9 @@ class StableBrowser {
|
|
|
1687
1716
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1688
1717
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1689
1718
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
1694
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1719
|
+
// to make sure the path doesn't start with -
|
|
1720
|
+
const uuidStr = "id_" + randomUUID();
|
|
1721
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1695
1722
|
try {
|
|
1696
1723
|
await this.takeScreenshot(screenshotPath);
|
|
1697
1724
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1701,15 +1728,15 @@ class StableBrowser {
|
|
|
1701
1728
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1702
1729
|
// }
|
|
1703
1730
|
// });
|
|
1731
|
+
result.screenshotId = uuidStr;
|
|
1732
|
+
result.screenshotPath = screenshotPath;
|
|
1733
|
+
if (info && info.box) {
|
|
1734
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1735
|
+
}
|
|
1704
1736
|
}
|
|
1705
1737
|
catch (e) {
|
|
1706
1738
|
this.logger.info("unable to take screenshot, ignored");
|
|
1707
1739
|
}
|
|
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
1740
|
}
|
|
1714
1741
|
else if (options && options.screenshot) {
|
|
1715
1742
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1744,6 +1771,15 @@ class StableBrowser {
|
|
|
1744
1771
|
document.documentElement.clientWidth,
|
|
1745
1772
|
])));
|
|
1746
1773
|
let screenshotBuffer = null;
|
|
1774
|
+
// if (focusedElement) {
|
|
1775
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1776
|
+
// await this._unhighlightElements(focusedElement);
|
|
1777
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1778
|
+
// console.log(`Unhighlighted previous element`);
|
|
1779
|
+
// }
|
|
1780
|
+
// if (focusedElement) {
|
|
1781
|
+
// await this._highlightElements(focusedElement);
|
|
1782
|
+
// }
|
|
1747
1783
|
if (this.context.browserName === "chromium") {
|
|
1748
1784
|
const client = await playContext.newCDPSession(this.page);
|
|
1749
1785
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1765,6 +1801,10 @@ class StableBrowser {
|
|
|
1765
1801
|
else {
|
|
1766
1802
|
screenshotBuffer = await this.page.screenshot();
|
|
1767
1803
|
}
|
|
1804
|
+
// if (focusedElement) {
|
|
1805
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1806
|
+
// await this._unhighlightElements(focusedElement);
|
|
1807
|
+
// }
|
|
1768
1808
|
let image = await Jimp.read(screenshotBuffer);
|
|
1769
1809
|
// Get the image dimensions
|
|
1770
1810
|
const { width, height } = image.bitmap;
|
|
@@ -1814,6 +1854,7 @@ class StableBrowser {
|
|
|
1814
1854
|
text: `Extract attribute from element`,
|
|
1815
1855
|
operation: "extractAttribute",
|
|
1816
1856
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1857
|
+
allowDisabled: true,
|
|
1817
1858
|
};
|
|
1818
1859
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1819
1860
|
try {
|
|
@@ -1835,6 +1876,70 @@ class StableBrowser {
|
|
|
1835
1876
|
state.info.value = state.value;
|
|
1836
1877
|
this.setTestData({ [variable]: state.value }, world);
|
|
1837
1878
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1879
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1880
|
+
return state.info;
|
|
1881
|
+
}
|
|
1882
|
+
catch (e) {
|
|
1883
|
+
await _commandError(state, e, this);
|
|
1884
|
+
}
|
|
1885
|
+
finally {
|
|
1886
|
+
_commandFinally(state, this);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1890
|
+
const state = {
|
|
1891
|
+
selectors,
|
|
1892
|
+
_params,
|
|
1893
|
+
attribute,
|
|
1894
|
+
value,
|
|
1895
|
+
options,
|
|
1896
|
+
world,
|
|
1897
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1898
|
+
highlight: true,
|
|
1899
|
+
screenshot: true,
|
|
1900
|
+
text: `Verify element attribute`,
|
|
1901
|
+
operation: "verifyAttribute",
|
|
1902
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1903
|
+
allowDisabled: true,
|
|
1904
|
+
};
|
|
1905
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1906
|
+
let val;
|
|
1907
|
+
try {
|
|
1908
|
+
await _preCommand(state, this);
|
|
1909
|
+
switch (attribute) {
|
|
1910
|
+
case "innerText":
|
|
1911
|
+
val = String(await state.element.innerText());
|
|
1912
|
+
break;
|
|
1913
|
+
case "value":
|
|
1914
|
+
val = String(await state.element.inputValue());
|
|
1915
|
+
break;
|
|
1916
|
+
case "checked":
|
|
1917
|
+
val = String(await state.element.isChecked());
|
|
1918
|
+
break;
|
|
1919
|
+
case "disabled":
|
|
1920
|
+
val = String(await state.element.isDisabled());
|
|
1921
|
+
break;
|
|
1922
|
+
case "readOnly":
|
|
1923
|
+
const isEditable = await state.element.isEditable();
|
|
1924
|
+
val = String(!isEditable);
|
|
1925
|
+
break;
|
|
1926
|
+
default:
|
|
1927
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
state.info.expectedValue = val;
|
|
1931
|
+
let regex;
|
|
1932
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1933
|
+
const patternBody = value.slice(1, -1);
|
|
1934
|
+
regex = new RegExp(patternBody, "g");
|
|
1935
|
+
}
|
|
1936
|
+
else {
|
|
1937
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1938
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1939
|
+
}
|
|
1940
|
+
if (!val.match(regex)) {
|
|
1941
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1942
|
+
}
|
|
1838
1943
|
return state.info;
|
|
1839
1944
|
}
|
|
1840
1945
|
catch (e) {
|
|
@@ -1862,7 +1967,7 @@ class StableBrowser {
|
|
|
1862
1967
|
if (options && options.timeout) {
|
|
1863
1968
|
timeout = options.timeout;
|
|
1864
1969
|
}
|
|
1865
|
-
const serviceUrl =
|
|
1970
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1866
1971
|
const request = {
|
|
1867
1972
|
method: "POST",
|
|
1868
1973
|
url: serviceUrl,
|
|
@@ -1933,27 +2038,32 @@ class StableBrowser {
|
|
|
1933
2038
|
async _highlightElements(scope, css) {
|
|
1934
2039
|
try {
|
|
1935
2040
|
if (!scope) {
|
|
2041
|
+
// console.log(`Scope is not defined`);
|
|
1936
2042
|
return;
|
|
1937
2043
|
}
|
|
1938
2044
|
if (!css) {
|
|
1939
2045
|
scope
|
|
1940
2046
|
.evaluate((node) => {
|
|
1941
2047
|
if (node && node.style) {
|
|
1942
|
-
let
|
|
1943
|
-
|
|
2048
|
+
let originalOutline = node.style.outline;
|
|
2049
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2050
|
+
// node.__previousOutline = originalOutline;
|
|
2051
|
+
node.style.outline = "2px solid red";
|
|
2052
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1944
2053
|
if (window) {
|
|
1945
2054
|
window.addEventListener("beforeunload", function (e) {
|
|
1946
|
-
node.style.
|
|
2055
|
+
node.style.outline = originalOutline;
|
|
1947
2056
|
});
|
|
1948
2057
|
}
|
|
1949
2058
|
setTimeout(function () {
|
|
1950
|
-
node.style.
|
|
2059
|
+
node.style.outline = originalOutline;
|
|
1951
2060
|
}, 2000);
|
|
1952
2061
|
}
|
|
1953
2062
|
})
|
|
1954
2063
|
.then(() => { })
|
|
1955
2064
|
.catch((e) => {
|
|
1956
2065
|
// ignore
|
|
2066
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1957
2067
|
});
|
|
1958
2068
|
}
|
|
1959
2069
|
else {
|
|
@@ -1969,17 +2079,18 @@ class StableBrowser {
|
|
|
1969
2079
|
if (!element.style) {
|
|
1970
2080
|
return;
|
|
1971
2081
|
}
|
|
1972
|
-
|
|
2082
|
+
let originalOutline = element.style.outline;
|
|
2083
|
+
element.__previousOutline = originalOutline;
|
|
1973
2084
|
// Set the new border to be red and 2px solid
|
|
1974
|
-
element.style.
|
|
2085
|
+
element.style.outline = "2px solid red";
|
|
1975
2086
|
if (window) {
|
|
1976
2087
|
window.addEventListener("beforeunload", function (e) {
|
|
1977
|
-
element.style.
|
|
2088
|
+
element.style.outline = originalOutline;
|
|
1978
2089
|
});
|
|
1979
2090
|
}
|
|
1980
2091
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1981
2092
|
setTimeout(function () {
|
|
1982
|
-
element.style.
|
|
2093
|
+
element.style.outline = originalOutline;
|
|
1983
2094
|
}, 2000);
|
|
1984
2095
|
}
|
|
1985
2096
|
return;
|
|
@@ -1987,6 +2098,7 @@ class StableBrowser {
|
|
|
1987
2098
|
.then(() => { })
|
|
1988
2099
|
.catch((e) => {
|
|
1989
2100
|
// ignore
|
|
2101
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
1990
2102
|
});
|
|
1991
2103
|
}
|
|
1992
2104
|
}
|
|
@@ -1994,6 +2106,54 @@ class StableBrowser {
|
|
|
1994
2106
|
console.debug(error);
|
|
1995
2107
|
}
|
|
1996
2108
|
}
|
|
2109
|
+
// async _unhighlightElements(scope, css) {
|
|
2110
|
+
// try {
|
|
2111
|
+
// if (!scope) {
|
|
2112
|
+
// return;
|
|
2113
|
+
// }
|
|
2114
|
+
// if (!css) {
|
|
2115
|
+
// scope
|
|
2116
|
+
// .evaluate((node) => {
|
|
2117
|
+
// if (node && node.style) {
|
|
2118
|
+
// if (!node.__previousOutline) {
|
|
2119
|
+
// node.style.outline = "";
|
|
2120
|
+
// } else {
|
|
2121
|
+
// node.style.outline = node.__previousOutline;
|
|
2122
|
+
// }
|
|
2123
|
+
// }
|
|
2124
|
+
// })
|
|
2125
|
+
// .then(() => {})
|
|
2126
|
+
// .catch((e) => {
|
|
2127
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2128
|
+
// });
|
|
2129
|
+
// } else {
|
|
2130
|
+
// scope
|
|
2131
|
+
// .evaluate(([css]) => {
|
|
2132
|
+
// if (!css) {
|
|
2133
|
+
// return;
|
|
2134
|
+
// }
|
|
2135
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2136
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2137
|
+
// let element = elements[i];
|
|
2138
|
+
// if (!element.style) {
|
|
2139
|
+
// return;
|
|
2140
|
+
// }
|
|
2141
|
+
// if (!element.__previousOutline) {
|
|
2142
|
+
// element.style.outline = "";
|
|
2143
|
+
// } else {
|
|
2144
|
+
// element.style.outline = element.__previousOutline;
|
|
2145
|
+
// }
|
|
2146
|
+
// }
|
|
2147
|
+
// })
|
|
2148
|
+
// .then(() => {})
|
|
2149
|
+
// .catch((e) => {
|
|
2150
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2151
|
+
// });
|
|
2152
|
+
// }
|
|
2153
|
+
// } catch (error) {
|
|
2154
|
+
// // console.debug(error);
|
|
2155
|
+
// }
|
|
2156
|
+
// }
|
|
1997
2157
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
1998
2158
|
const startTime = Date.now();
|
|
1999
2159
|
let error = null;
|
|
@@ -2055,6 +2215,35 @@ class StableBrowser {
|
|
|
2055
2215
|
});
|
|
2056
2216
|
}
|
|
2057
2217
|
}
|
|
2218
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2219
|
+
const frames = this.page.frames();
|
|
2220
|
+
let results = [];
|
|
2221
|
+
let ignoreCase = false;
|
|
2222
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2223
|
+
if (dateAlternatives.date) {
|
|
2224
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2225
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2226
|
+
result.frame = frames[i];
|
|
2227
|
+
results.push(result);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
else if (numberAlternatives.number) {
|
|
2231
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2232
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2233
|
+
result.frame = frames[i];
|
|
2234
|
+
results.push(result);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
else {
|
|
2238
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2239
|
+
result.frame = frames[i];
|
|
2240
|
+
results.push(result);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
state.info.results = results;
|
|
2244
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2245
|
+
return resultWithElementsFound;
|
|
2246
|
+
}
|
|
2058
2247
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2059
2248
|
text = unEscapeString(text);
|
|
2060
2249
|
const state = {
|
|
@@ -2064,7 +2253,7 @@ class StableBrowser {
|
|
|
2064
2253
|
locate: false,
|
|
2065
2254
|
scroll: false,
|
|
2066
2255
|
highlight: false,
|
|
2067
|
-
type: Types.
|
|
2256
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2068
2257
|
text: `Verify text exists in page`,
|
|
2069
2258
|
operation: "verifyTextExistInPage",
|
|
2070
2259
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2082,31 +2271,7 @@ class StableBrowser {
|
|
|
2082
2271
|
await _preCommand(state, this);
|
|
2083
2272
|
state.info.text = text;
|
|
2084
2273
|
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);
|
|
2274
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2110
2275
|
if (resultWithElementsFound.length === 0) {
|
|
2111
2276
|
if (Date.now() - state.startTime > timeout) {
|
|
2112
2277
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2116,12 +2281,29 @@ class StableBrowser {
|
|
|
2116
2281
|
}
|
|
2117
2282
|
if (resultWithElementsFound[0].randomToken) {
|
|
2118
2283
|
const frame = resultWithElementsFound[0].frame;
|
|
2119
|
-
const dataAttribute = `[data-blinq-id
|
|
2284
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2120
2285
|
await this._highlightElements(frame, dataAttribute);
|
|
2121
|
-
|
|
2286
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2287
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2288
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2289
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2290
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2291
|
+
// .then(async () => {
|
|
2292
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2293
|
+
// })
|
|
2294
|
+
// .catch(
|
|
2295
|
+
// (e) => {}
|
|
2296
|
+
// console.error(e)
|
|
2297
|
+
// );
|
|
2298
|
+
// });
|
|
2299
|
+
// }
|
|
2300
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2301
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2302
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2122
2303
|
if (element) {
|
|
2123
2304
|
await this.scrollIfNeeded(element, state.info);
|
|
2124
2305
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2306
|
+
// await _screenshot(state, this, element);
|
|
2125
2307
|
}
|
|
2126
2308
|
}
|
|
2127
2309
|
await _screenshot(state, this);
|
|
@@ -2163,31 +2345,7 @@ class StableBrowser {
|
|
|
2163
2345
|
await _preCommand(state, this);
|
|
2164
2346
|
state.info.text = text;
|
|
2165
2347
|
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);
|
|
2348
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2191
2349
|
if (resultWithElementsFound.length === 0) {
|
|
2192
2350
|
await _screenshot(state, this);
|
|
2193
2351
|
return state.info;
|
|
@@ -2205,15 +2363,102 @@ class StableBrowser {
|
|
|
2205
2363
|
_commandFinally(state, this);
|
|
2206
2364
|
}
|
|
2207
2365
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2366
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2367
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2368
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2369
|
+
const state = {
|
|
2370
|
+
text_search: textToVerify,
|
|
2371
|
+
options,
|
|
2372
|
+
world,
|
|
2373
|
+
locate: false,
|
|
2374
|
+
scroll: false,
|
|
2375
|
+
highlight: false,
|
|
2376
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2377
|
+
text: `Verify text with relation to another text`,
|
|
2378
|
+
operation: "verify_text_with_relation",
|
|
2379
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2380
|
+
};
|
|
2381
|
+
const timeout = this._getLoadTimeout(options);
|
|
2382
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2383
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2384
|
+
if (newValue !== textAnchor) {
|
|
2385
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2386
|
+
textAnchor = newValue;
|
|
2387
|
+
}
|
|
2388
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2389
|
+
if (newValue !== textToVerify) {
|
|
2390
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2391
|
+
textToVerify = newValue;
|
|
2392
|
+
}
|
|
2393
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2394
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2395
|
+
let foundAncore = false;
|
|
2396
|
+
try {
|
|
2397
|
+
await _preCommand(state, this);
|
|
2398
|
+
state.info.text = textToVerify;
|
|
2399
|
+
while (true) {
|
|
2400
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2401
|
+
if (resultWithElementsFound.length === 0) {
|
|
2402
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2403
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2404
|
+
}
|
|
2405
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2409
|
+
foundAncore = true;
|
|
2410
|
+
const result = resultWithElementsFound[i];
|
|
2411
|
+
const token = result.randomToken;
|
|
2412
|
+
const frame = result.frame;
|
|
2413
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2414
|
+
const climbArray1 = [];
|
|
2415
|
+
for (let i = 0; i < climb; i++) {
|
|
2416
|
+
climbArray1.push("..");
|
|
2417
|
+
}
|
|
2418
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2419
|
+
css = css + " >> " + climbXpath;
|
|
2420
|
+
const count = await frame.locator(css).count();
|
|
2421
|
+
for (let j = 0; j < count; j++) {
|
|
2422
|
+
const continer = await frame.locator(css).nth(j);
|
|
2423
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2424
|
+
if (result.elementCount > 0) {
|
|
2425
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2426
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2427
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2428
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2429
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2430
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2431
|
+
// .then(async () => {
|
|
2432
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2433
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2434
|
+
// () => {}
|
|
2435
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2436
|
+
// );
|
|
2437
|
+
// })
|
|
2438
|
+
// .catch(e);
|
|
2439
|
+
// }
|
|
2440
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2441
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2442
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2443
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2444
|
+
if (element) {
|
|
2445
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2446
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2447
|
+
}
|
|
2448
|
+
await _screenshot(state, this);
|
|
2449
|
+
return state.info;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2455
|
+
}
|
|
2456
|
+
catch (e) {
|
|
2457
|
+
await _commandError(state, e, this);
|
|
2212
2458
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2459
|
+
finally {
|
|
2460
|
+
_commandFinally(state, this);
|
|
2215
2461
|
}
|
|
2216
|
-
return serviceUrl;
|
|
2217
2462
|
}
|
|
2218
2463
|
async visualVerification(text, options = {}, world = null) {
|
|
2219
2464
|
const startTime = Date.now();
|
|
@@ -2229,7 +2474,7 @@ class StableBrowser {
|
|
|
2229
2474
|
throw new Error("TOKEN is not set");
|
|
2230
2475
|
}
|
|
2231
2476
|
try {
|
|
2232
|
-
let serviceUrl =
|
|
2477
|
+
let serviceUrl = _getServerUrl();
|
|
2233
2478
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2234
2479
|
info.screenshotPath = screenshotPath;
|
|
2235
2480
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2318,6 +2563,7 @@ class StableBrowser {
|
|
|
2318
2563
|
let screenshotPath = null;
|
|
2319
2564
|
const info = {};
|
|
2320
2565
|
info.log = "";
|
|
2566
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2321
2567
|
info.operation = "getTableData";
|
|
2322
2568
|
info.selectors = selectors;
|
|
2323
2569
|
try {
|
|
@@ -2393,7 +2639,7 @@ class StableBrowser {
|
|
|
2393
2639
|
info.operation = "analyzeTable";
|
|
2394
2640
|
info.selectors = selectors;
|
|
2395
2641
|
info.query = query;
|
|
2396
|
-
query =
|
|
2642
|
+
query = _fixUsingParams(query, _params);
|
|
2397
2643
|
info.query_fixed = query;
|
|
2398
2644
|
info.operator = operator;
|
|
2399
2645
|
info.value = value;
|
|
@@ -2723,14 +2969,22 @@ class StableBrowser {
|
|
|
2723
2969
|
}
|
|
2724
2970
|
}
|
|
2725
2971
|
async beforeStep(world, step) {
|
|
2726
|
-
this.stepName = step.pickleStep.text;
|
|
2727
|
-
this.logger.info("step: " + this.stepName);
|
|
2728
2972
|
if (this.stepIndex === undefined) {
|
|
2729
2973
|
this.stepIndex = 0;
|
|
2730
2974
|
}
|
|
2731
2975
|
else {
|
|
2732
2976
|
this.stepIndex++;
|
|
2733
2977
|
}
|
|
2978
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2979
|
+
this.stepName = step.pickleStep.text;
|
|
2980
|
+
this.logger.info("step: " + this.stepName);
|
|
2981
|
+
}
|
|
2982
|
+
else if (step && step.text) {
|
|
2983
|
+
this.stepName = step.text;
|
|
2984
|
+
}
|
|
2985
|
+
else {
|
|
2986
|
+
this.stepName = "step " + this.stepIndex;
|
|
2987
|
+
}
|
|
2734
2988
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2735
2989
|
if (this.context.browserObject.context) {
|
|
2736
2990
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2760,156 +3014,5 @@ function createTimedPromise(promise, label) {
|
|
|
2760
3014
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2761
3015
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2762
3016
|
}
|
|
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
3017
|
export { StableBrowser };
|
|
2915
3018
|
//# sourceMappingURL=stable_browser.js.map
|