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