automation_model 1.0.529-dev → 1.0.529-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 -18
- package/lib/stable_browser.js +589 -505
- 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;
|
|
@@ -60,7 +68,6 @@ class StableBrowser {
|
|
|
60
68
|
configuration = null;
|
|
61
69
|
appName = "main";
|
|
62
70
|
tags = null;
|
|
63
|
-
isRecording = false;
|
|
64
71
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
65
72
|
this.browser = browser;
|
|
66
73
|
this.page = page;
|
|
@@ -91,35 +98,17 @@ class StableBrowser {
|
|
|
91
98
|
catch (e) {
|
|
92
99
|
this.logger.error("unable to read ai_config.json");
|
|
93
100
|
}
|
|
94
|
-
context.pageLoading = { status: false };
|
|
95
|
-
context.pages = [this.page];
|
|
96
101
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
97
102
|
this.world = world;
|
|
103
|
+
context.pages = [this.page];
|
|
104
|
+
context.pageLoading = { status: false };
|
|
98
105
|
this.registerEventListeners(this.context);
|
|
99
106
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
100
107
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
101
108
|
}
|
|
102
|
-
async scrollPageToLoadLazyElements() {
|
|
103
|
-
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
104
|
-
let retry = 0;
|
|
105
|
-
while (true) {
|
|
106
|
-
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
107
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
108
|
-
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
109
|
-
if (newHeight === lastHeight) {
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
lastHeight = newHeight;
|
|
113
|
-
retry++;
|
|
114
|
-
if (retry > 10) {
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
119
|
-
}
|
|
120
109
|
registerEventListeners(context) {
|
|
121
110
|
this.registerConsoleLogListener(this.page, context);
|
|
122
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
111
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
123
112
|
if (!context.pageLoading) {
|
|
124
113
|
context.pageLoading = { status: false };
|
|
125
114
|
}
|
|
@@ -164,7 +153,7 @@ class StableBrowser {
|
|
|
164
153
|
if (this.appName === appName) {
|
|
165
154
|
return;
|
|
166
155
|
}
|
|
167
|
-
let
|
|
156
|
+
let navigate = false;
|
|
168
157
|
if (!apps[appName]) {
|
|
169
158
|
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
170
159
|
newContextCreated = true;
|
|
@@ -175,32 +164,15 @@ class StableBrowser {
|
|
|
175
164
|
};
|
|
176
165
|
}
|
|
177
166
|
const tempContext = {};
|
|
178
|
-
|
|
179
|
-
|
|
167
|
+
_copyContext(this, tempContext);
|
|
168
|
+
_copyContext(apps[appName], this);
|
|
180
169
|
apps[this.appName] = tempContext;
|
|
181
170
|
this.appName = appName;
|
|
182
|
-
if (
|
|
183
|
-
this.registerEventListeners(this.context);
|
|
171
|
+
if (navigate) {
|
|
184
172
|
await this.goto(this.context.environment.baseUrl);
|
|
185
173
|
await this.waitForPageLoad();
|
|
186
174
|
}
|
|
187
175
|
}
|
|
188
|
-
_copyContext(from, to) {
|
|
189
|
-
to.browser = from.browser;
|
|
190
|
-
to.page = from.page;
|
|
191
|
-
to.context = from.context;
|
|
192
|
-
}
|
|
193
|
-
getWebLogFile(logFolder) {
|
|
194
|
-
if (!fs.existsSync(logFolder)) {
|
|
195
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
196
|
-
}
|
|
197
|
-
let nextIndex = 1;
|
|
198
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
199
|
-
nextIndex++;
|
|
200
|
-
}
|
|
201
|
-
const fileName = nextIndex + ".json";
|
|
202
|
-
return path.join(logFolder, fileName);
|
|
203
|
-
}
|
|
204
176
|
registerConsoleLogListener(page, context) {
|
|
205
177
|
if (!this.context.webLogger) {
|
|
206
178
|
this.context.webLogger = [];
|
|
@@ -264,55 +236,48 @@ class StableBrowser {
|
|
|
264
236
|
// async closeUnexpectedPopups() {
|
|
265
237
|
// await closeUnexpectedPopups(this.page);
|
|
266
238
|
// }
|
|
267
|
-
async goto(url) {
|
|
239
|
+
async goto(url, world = null) {
|
|
268
240
|
if (!url.startsWith("http")) {
|
|
269
241
|
url = "https://" + url;
|
|
270
242
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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);
|
|
278
262
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// remove the _ prefix
|
|
283
|
-
regValue = key.substring(1);
|
|
284
|
-
}
|
|
285
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error("Error on goto", error);
|
|
265
|
+
_commandError(state, error, this);
|
|
286
266
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
290
|
-
// check if not null
|
|
291
|
-
if (!locator) {
|
|
292
|
-
return locator;
|
|
267
|
+
finally {
|
|
268
|
+
_commandFinally(state, this);
|
|
293
269
|
}
|
|
294
|
-
// clone the locator
|
|
295
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
296
|
-
this.scanAndManipulate(locator, _params);
|
|
297
|
-
return locator;
|
|
298
|
-
}
|
|
299
|
-
_isObject(value) {
|
|
300
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
301
270
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
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);
|
|
311
279
|
}
|
|
312
280
|
}
|
|
313
|
-
}
|
|
314
|
-
_getLocator(locator, scope, _params) {
|
|
315
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
316
281
|
let locatorReturn;
|
|
317
282
|
if (locator.role) {
|
|
318
283
|
if (locator.role[1].nameReg) {
|
|
@@ -320,7 +285,7 @@ class StableBrowser {
|
|
|
320
285
|
delete locator.role[1].nameReg;
|
|
321
286
|
}
|
|
322
287
|
// if (locator.role[1].name) {
|
|
323
|
-
// locator.role[1].name =
|
|
288
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
324
289
|
// }
|
|
325
290
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
326
291
|
}
|
|
@@ -363,140 +328,54 @@ class StableBrowser {
|
|
|
363
328
|
if (css && css.locator) {
|
|
364
329
|
css = css.locator;
|
|
365
330
|
}
|
|
366
|
-
let result = await this._locateElementByText(scope,
|
|
331
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
367
332
|
if (result.elementCount === 0) {
|
|
368
333
|
return;
|
|
369
334
|
}
|
|
370
|
-
let textElementCss = "[data-blinq-id
|
|
335
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
371
336
|
// css climb to parent element
|
|
372
337
|
const climbArray = [];
|
|
373
338
|
for (let i = 0; i < climb; i++) {
|
|
374
339
|
climbArray.push("..");
|
|
375
340
|
}
|
|
376
341
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return new RegExp(pattern, flags);
|
|
400
|
-
}
|
|
401
|
-
document.getRegex = getRegex;
|
|
402
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
403
|
-
// Check and add the element if it has a shadow root
|
|
404
|
-
if (element.shadowRoot) {
|
|
405
|
-
result.push(element);
|
|
406
|
-
// Also search within the shadow root
|
|
407
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
408
|
-
}
|
|
409
|
-
// Iterate over child nodes
|
|
410
|
-
element.childNodes.forEach((child) => {
|
|
411
|
-
// Recursively call the function for each child node
|
|
412
|
-
document.collectAllShadowDomElements(child, result);
|
|
413
|
-
});
|
|
414
|
-
return result;
|
|
415
|
-
}
|
|
416
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
417
|
-
if (!tag) {
|
|
418
|
-
tag = "*:not(script, style, head)";
|
|
419
|
-
}
|
|
420
|
-
let regexpSearch = document.getRegex(text);
|
|
421
|
-
if (regexpSearch) {
|
|
422
|
-
regex = true;
|
|
423
|
-
}
|
|
424
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
425
|
-
let shadowHosts = [];
|
|
426
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
427
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
428
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
429
|
-
if (!shadowElement) {
|
|
430
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
434
|
-
elements = elements.concat(shadowElements);
|
|
435
|
-
}
|
|
436
|
-
let randomToken = null;
|
|
437
|
-
const foundElements = [];
|
|
438
|
-
if (regex) {
|
|
439
|
-
if (!regexpSearch) {
|
|
440
|
-
regexpSearch = new RegExp(text, "im");
|
|
441
|
-
}
|
|
442
|
-
for (let i = 0; i < elements.length; i++) {
|
|
443
|
-
const element = elements[i];
|
|
444
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
445
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
446
|
-
foundElements.push(element);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
text = text.trim();
|
|
452
|
-
for (let i = 0; i < elements.length; i++) {
|
|
453
|
-
const element = elements[i];
|
|
454
|
-
if (partial) {
|
|
455
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
456
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
457
|
-
foundElements.push(element);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
462
|
-
(element.value && element.value === text)) {
|
|
463
|
-
foundElements.push(element);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
let noChildElements = [];
|
|
469
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
470
|
-
let element = foundElements[i];
|
|
471
|
-
let hasChild = false;
|
|
472
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
473
|
-
if (i === j) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
if (isParent(element, foundElements[j])) {
|
|
477
|
-
hasChild = true;
|
|
478
|
-
break;
|
|
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;
|
|
479
364
|
}
|
|
480
365
|
}
|
|
481
|
-
if (!
|
|
482
|
-
|
|
366
|
+
if (!el.setAttribute) {
|
|
367
|
+
el = el.parentElement;
|
|
483
368
|
}
|
|
369
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
370
|
+
return true;
|
|
371
|
+
}, [tag1, randomToken]))) {
|
|
372
|
+
continue;
|
|
484
373
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (randomToken === null) {
|
|
489
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
490
|
-
}
|
|
491
|
-
let element = noChildElements[i];
|
|
492
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
493
|
-
elementCount++;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
497
|
-
}, [text1, tag1, regex1, partial1]);
|
|
374
|
+
tagCount++;
|
|
375
|
+
}
|
|
376
|
+
return { elementCount: tagCount, randomToken };
|
|
498
377
|
}
|
|
499
|
-
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) {
|
|
500
379
|
if (!info) {
|
|
501
380
|
info = {};
|
|
502
381
|
}
|
|
@@ -505,10 +384,13 @@ class StableBrowser {
|
|
|
505
384
|
}
|
|
506
385
|
if (!info.log) {
|
|
507
386
|
info.log = "";
|
|
387
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
508
388
|
}
|
|
509
389
|
let locatorSearch = selectorHierarchy[index];
|
|
390
|
+
let originalLocatorSearch = "";
|
|
510
391
|
try {
|
|
511
|
-
|
|
392
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
393
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
512
394
|
}
|
|
513
395
|
catch (e) {
|
|
514
396
|
console.error(e);
|
|
@@ -516,30 +398,31 @@ class StableBrowser {
|
|
|
516
398
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
517
399
|
let locator = null;
|
|
518
400
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
519
|
-
|
|
401
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
402
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
520
403
|
if (!locatorString) {
|
|
521
404
|
info.failCause.textNotFound = true;
|
|
522
|
-
info.failCause.lastError =
|
|
405
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
523
406
|
return;
|
|
524
407
|
}
|
|
525
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
408
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
526
409
|
}
|
|
527
410
|
else if (locatorSearch.text) {
|
|
528
|
-
let text =
|
|
529
|
-
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);
|
|
530
413
|
if (result.elementCount === 0) {
|
|
531
414
|
info.failCause.textNotFound = true;
|
|
532
|
-
info.failCause.lastError =
|
|
415
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
533
416
|
return;
|
|
534
417
|
}
|
|
535
|
-
locatorSearch.css = "[data-blinq-id
|
|
418
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
536
419
|
if (locatorSearch.childCss) {
|
|
537
420
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
538
421
|
}
|
|
539
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
422
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
540
423
|
}
|
|
541
424
|
else {
|
|
542
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
425
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
543
426
|
}
|
|
544
427
|
// let cssHref = false;
|
|
545
428
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -552,18 +435,27 @@ class StableBrowser {
|
|
|
552
435
|
//info.log += "total elements found " + count + "\n";
|
|
553
436
|
//let visibleCount = 0;
|
|
554
437
|
let visibleLocator = null;
|
|
555
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
438
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
556
439
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
440
|
+
if (info.locatorLog) {
|
|
441
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
442
|
+
}
|
|
557
443
|
return;
|
|
558
444
|
}
|
|
445
|
+
if (info.locatorLog && count === 0) {
|
|
446
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
447
|
+
}
|
|
559
448
|
for (let j = 0; j < count; j++) {
|
|
560
449
|
let visible = await locator.nth(j).isVisible();
|
|
561
450
|
const enabled = await locator.nth(j).isEnabled();
|
|
562
451
|
if (!visibleOnly) {
|
|
563
452
|
visible = true;
|
|
564
453
|
}
|
|
565
|
-
if (visible && enabled) {
|
|
454
|
+
if (visible && (allowDisabled || enabled)) {
|
|
566
455
|
foundLocators.push(locator.nth(j));
|
|
456
|
+
if (info.locatorLog) {
|
|
457
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
458
|
+
}
|
|
567
459
|
}
|
|
568
460
|
else {
|
|
569
461
|
info.failCause.visible = visible;
|
|
@@ -571,8 +463,16 @@ class StableBrowser {
|
|
|
571
463
|
if (!info.printMessages) {
|
|
572
464
|
info.printMessages = {};
|
|
573
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
|
+
}
|
|
574
474
|
if (!info.printMessages[j.toString()]) {
|
|
575
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
475
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
576
476
|
info.printMessages[j.toString()] = true;
|
|
577
477
|
}
|
|
578
478
|
}
|
|
@@ -588,7 +488,7 @@ class StableBrowser {
|
|
|
588
488
|
if (!info) {
|
|
589
489
|
info = {};
|
|
590
490
|
}
|
|
591
|
-
info.log += "scan for popup handlers" + "\n";
|
|
491
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
592
492
|
const handlerGroup = [];
|
|
593
493
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
594
494
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -615,16 +515,28 @@ class StableBrowser {
|
|
|
615
515
|
}
|
|
616
516
|
if (result.foundElements.length > 0) {
|
|
617
517
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
+
}
|
|
621
533
|
return { rerun: true };
|
|
622
534
|
}
|
|
623
535
|
}
|
|
624
536
|
}
|
|
625
537
|
return { rerun: false };
|
|
626
538
|
}
|
|
627
|
-
async _locate(selectors, info, _params, timeout) {
|
|
539
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
628
540
|
if (!timeout) {
|
|
629
541
|
timeout = 30000;
|
|
630
542
|
}
|
|
@@ -634,9 +546,15 @@ class StableBrowser {
|
|
|
634
546
|
let selector = selectors.locators[j];
|
|
635
547
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
636
548
|
}
|
|
637
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
549
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
638
550
|
if (!element.rerun) {
|
|
639
|
-
|
|
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;
|
|
640
558
|
}
|
|
641
559
|
}
|
|
642
560
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -647,6 +565,7 @@ class StableBrowser {
|
|
|
647
565
|
info.failCause = {};
|
|
648
566
|
info.log = "";
|
|
649
567
|
}
|
|
568
|
+
let startTime = Date.now();
|
|
650
569
|
let scope = this.page;
|
|
651
570
|
if (selectors.frame) {
|
|
652
571
|
return selectors.frame;
|
|
@@ -677,9 +596,11 @@ class StableBrowser {
|
|
|
677
596
|
}
|
|
678
597
|
return framescope;
|
|
679
598
|
};
|
|
599
|
+
let fLocator = null;
|
|
680
600
|
while (true) {
|
|
681
601
|
let frameFound = false;
|
|
682
602
|
if (selectors.nestFrmLoc) {
|
|
603
|
+
fLocator = selectors.nestFrmLoc;
|
|
683
604
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
684
605
|
frameFound = true;
|
|
685
606
|
break;
|
|
@@ -688,6 +609,7 @@ class StableBrowser {
|
|
|
688
609
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
689
610
|
let frameLocator = selectors.frameLocators[i];
|
|
690
611
|
if (frameLocator.css) {
|
|
612
|
+
fLocator = frameLocator.css;
|
|
691
613
|
scope = scope.frameLocator(frameLocator.css);
|
|
692
614
|
frameFound = true;
|
|
693
615
|
break;
|
|
@@ -695,18 +617,25 @@ class StableBrowser {
|
|
|
695
617
|
}
|
|
696
618
|
}
|
|
697
619
|
if (!frameFound && selectors.iframe_src) {
|
|
620
|
+
fLocator = selectors.iframe_src;
|
|
698
621
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
699
622
|
}
|
|
700
623
|
if (!scope) {
|
|
701
|
-
info
|
|
702
|
-
|
|
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) {
|
|
703
629
|
info.failCause.iframeNotFound = true;
|
|
704
|
-
info.failCause.lastError =
|
|
630
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
705
631
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
706
632
|
}
|
|
707
633
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
708
634
|
}
|
|
709
635
|
else {
|
|
636
|
+
if (info && info.locatorLog) {
|
|
637
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
638
|
+
}
|
|
710
639
|
break;
|
|
711
640
|
}
|
|
712
641
|
}
|
|
@@ -723,16 +652,18 @@ class StableBrowser {
|
|
|
723
652
|
return bodyContent;
|
|
724
653
|
});
|
|
725
654
|
}
|
|
726
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
655
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
727
656
|
if (!info) {
|
|
728
657
|
info = {};
|
|
729
658
|
info.failCause = {};
|
|
730
659
|
info.log = "";
|
|
660
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
731
661
|
}
|
|
732
662
|
let highPriorityTimeout = 5000;
|
|
733
663
|
let visibleOnlyTimeout = 6000;
|
|
734
|
-
let startTime =
|
|
664
|
+
let startTime = Date.now();
|
|
735
665
|
let locatorsCount = 0;
|
|
666
|
+
let lazy_scroll = false;
|
|
736
667
|
//let arrayMode = Array.isArray(selectors);
|
|
737
668
|
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
738
669
|
let selectorsLocators = null;
|
|
@@ -770,17 +701,17 @@ class StableBrowser {
|
|
|
770
701
|
}
|
|
771
702
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
772
703
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
773
|
-
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);
|
|
774
705
|
if (result.foundElements.length === 0) {
|
|
775
706
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
776
|
-
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);
|
|
777
708
|
}
|
|
778
709
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
779
|
-
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);
|
|
780
711
|
}
|
|
781
712
|
else {
|
|
782
713
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
783
|
-
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);
|
|
784
715
|
}
|
|
785
716
|
}
|
|
786
717
|
let foundElements = result.foundElements;
|
|
@@ -821,26 +752,38 @@ class StableBrowser {
|
|
|
821
752
|
return maxCountElement.locator;
|
|
822
753
|
}
|
|
823
754
|
}
|
|
824
|
-
if (
|
|
755
|
+
if (Date.now() - startTime > timeout) {
|
|
825
756
|
break;
|
|
826
757
|
}
|
|
827
|
-
if (
|
|
828
|
-
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";
|
|
829
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
|
+
}
|
|
830
765
|
}
|
|
831
|
-
if (
|
|
832
|
-
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";
|
|
833
768
|
visibleOnly = false;
|
|
834
769
|
}
|
|
835
770
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
836
771
|
}
|
|
837
772
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
838
|
-
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";
|
|
839
780
|
info.failCause.locatorNotFound = true;
|
|
840
|
-
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
|
+
}
|
|
841
784
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
842
785
|
}
|
|
843
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
786
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
844
787
|
let foundElements = [];
|
|
845
788
|
const result = {
|
|
846
789
|
foundElements: foundElements,
|
|
@@ -848,14 +791,15 @@ class StableBrowser {
|
|
|
848
791
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
849
792
|
let foundLocators = [];
|
|
850
793
|
try {
|
|
851
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
794
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
852
795
|
}
|
|
853
796
|
catch (e) {
|
|
854
|
-
this
|
|
855
|
-
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);
|
|
856
800
|
foundLocators = [];
|
|
857
801
|
try {
|
|
858
|
-
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);
|
|
859
803
|
}
|
|
860
804
|
catch (e) {
|
|
861
805
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -871,11 +815,27 @@ class StableBrowser {
|
|
|
871
815
|
}
|
|
872
816
|
if (foundLocators.length > 1) {
|
|
873
817
|
info.failCause.foundMultiple = true;
|
|
818
|
+
if (info.locatorLog) {
|
|
819
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
820
|
+
}
|
|
874
821
|
}
|
|
875
822
|
}
|
|
876
823
|
return result;
|
|
877
824
|
}
|
|
878
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);
|
|
879
839
|
const startTime = Date.now();
|
|
880
840
|
let timeout = 30000;
|
|
881
841
|
if (options && options.timeout) {
|
|
@@ -900,13 +860,31 @@ class StableBrowser {
|
|
|
900
860
|
catch (e) {
|
|
901
861
|
if (performance.now() - startTime > timeout) {
|
|
902
862
|
// throw e;
|
|
903
|
-
|
|
863
|
+
try {
|
|
864
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
_commandFinally(state, this);
|
|
868
|
+
}
|
|
904
869
|
}
|
|
905
870
|
}
|
|
906
871
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
907
872
|
}
|
|
908
873
|
}
|
|
909
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);
|
|
910
888
|
const startTime = Date.now();
|
|
911
889
|
let timeout = 30000;
|
|
912
890
|
if (options && options.timeout) {
|
|
@@ -931,7 +909,12 @@ class StableBrowser {
|
|
|
931
909
|
catch (e) {
|
|
932
910
|
if (performance.now() - startTime > timeout) {
|
|
933
911
|
// throw e;
|
|
934
|
-
|
|
912
|
+
try {
|
|
913
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
_commandFinally(state, this);
|
|
917
|
+
}
|
|
935
918
|
}
|
|
936
919
|
}
|
|
937
920
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -950,9 +933,9 @@ class StableBrowser {
|
|
|
950
933
|
};
|
|
951
934
|
try {
|
|
952
935
|
await _preCommand(state, this);
|
|
953
|
-
if (state.options && state.options.context) {
|
|
954
|
-
|
|
955
|
-
}
|
|
936
|
+
// if (state.options && state.options.context) {
|
|
937
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
938
|
+
// }
|
|
956
939
|
try {
|
|
957
940
|
await state.element.click();
|
|
958
941
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -990,9 +973,15 @@ class StableBrowser {
|
|
|
990
973
|
// let element = await this._locate(selectors, info, _params);
|
|
991
974
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
992
975
|
try {
|
|
993
|
-
//
|
|
976
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
977
|
+
// console.log(`Highlighting while running from recorder`);
|
|
978
|
+
await this._highlightElements(element);
|
|
994
979
|
await state.element.setChecked(checked);
|
|
995
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);
|
|
996
985
|
}
|
|
997
986
|
catch (e) {
|
|
998
987
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1031,6 +1020,7 @@ class StableBrowser {
|
|
|
1031
1020
|
await _preCommand(state, this);
|
|
1032
1021
|
try {
|
|
1033
1022
|
await state.element.hover();
|
|
1023
|
+
// await _screenshot(state, this);
|
|
1034
1024
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1035
1025
|
}
|
|
1036
1026
|
catch (e) {
|
|
@@ -1038,6 +1028,7 @@ class StableBrowser {
|
|
|
1038
1028
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1039
1029
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1040
1030
|
await state.element.hover({ timeout: 10000 });
|
|
1031
|
+
// await _screenshot(state, this);
|
|
1041
1032
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1042
1033
|
}
|
|
1043
1034
|
await _screenshot(state, this);
|
|
@@ -1312,7 +1303,12 @@ class StableBrowser {
|
|
|
1312
1303
|
await this.waitForPageLoad();
|
|
1313
1304
|
}
|
|
1314
1305
|
else if (enter === false) {
|
|
1315
|
-
|
|
1306
|
+
try {
|
|
1307
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1308
|
+
}
|
|
1309
|
+
catch (e) {
|
|
1310
|
+
// ignore
|
|
1311
|
+
}
|
|
1316
1312
|
//await this.page.keyboard.press("Tab");
|
|
1317
1313
|
}
|
|
1318
1314
|
else {
|
|
@@ -1369,6 +1365,7 @@ class StableBrowser {
|
|
|
1369
1365
|
let screenshotPath = null;
|
|
1370
1366
|
if (!info.log) {
|
|
1371
1367
|
info.log = "";
|
|
1368
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1372
1369
|
}
|
|
1373
1370
|
info.operation = "getText";
|
|
1374
1371
|
info.selectors = selectors;
|
|
@@ -1391,6 +1388,18 @@ class StableBrowser {
|
|
|
1391
1388
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1392
1389
|
try {
|
|
1393
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
|
+
// }
|
|
1394
1403
|
const elementText = await element.innerText();
|
|
1395
1404
|
return {
|
|
1396
1405
|
text: elementText,
|
|
@@ -1402,7 +1411,7 @@ class StableBrowser {
|
|
|
1402
1411
|
}
|
|
1403
1412
|
catch (e) {
|
|
1404
1413
|
//await this.closeUnexpectedPopups();
|
|
1405
|
-
this.logger.info("no innerText will use textContent");
|
|
1414
|
+
this.logger.info("no innerText, will use textContent");
|
|
1406
1415
|
const elementText = await element.textContent();
|
|
1407
1416
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1408
1417
|
}
|
|
@@ -1528,22 +1537,6 @@ class StableBrowser {
|
|
|
1528
1537
|
_commandFinally(state, this);
|
|
1529
1538
|
}
|
|
1530
1539
|
}
|
|
1531
|
-
_getDataFile(world = null) {
|
|
1532
|
-
let dataFile = null;
|
|
1533
|
-
if (world && world.reportFolder) {
|
|
1534
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1535
|
-
}
|
|
1536
|
-
else if (this.reportFolder) {
|
|
1537
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1538
|
-
}
|
|
1539
|
-
else if (this.context && this.context.reportFolder) {
|
|
1540
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1541
|
-
}
|
|
1542
|
-
else {
|
|
1543
|
-
dataFile = "data.json";
|
|
1544
|
-
}
|
|
1545
|
-
return dataFile;
|
|
1546
|
-
}
|
|
1547
1540
|
async waitForUserInput(message, world = null) {
|
|
1548
1541
|
if (!message) {
|
|
1549
1542
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1572,7 +1565,7 @@ class StableBrowser {
|
|
|
1572
1565
|
return;
|
|
1573
1566
|
}
|
|
1574
1567
|
// if data file exists, load it
|
|
1575
|
-
const dataFile =
|
|
1568
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1576
1569
|
let data = this.getTestData(world);
|
|
1577
1570
|
// merge the testData with the existing data
|
|
1578
1571
|
Object.assign(data, testData);
|
|
@@ -1675,7 +1668,7 @@ class StableBrowser {
|
|
|
1675
1668
|
}
|
|
1676
1669
|
}
|
|
1677
1670
|
getTestData(world = null) {
|
|
1678
|
-
const dataFile =
|
|
1671
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1679
1672
|
let data = {};
|
|
1680
1673
|
if (fs.existsSync(dataFile)) {
|
|
1681
1674
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1719,15 +1712,15 @@ class StableBrowser {
|
|
|
1719
1712
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1720
1713
|
// }
|
|
1721
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
|
+
}
|
|
1722
1720
|
}
|
|
1723
1721
|
catch (e) {
|
|
1724
1722
|
this.logger.info("unable to take screenshot, ignored");
|
|
1725
1723
|
}
|
|
1726
|
-
result.screenshotId = uuidStr;
|
|
1727
|
-
result.screenshotPath = screenshotPath;
|
|
1728
|
-
if (info && info.box) {
|
|
1729
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1730
|
-
}
|
|
1731
1724
|
}
|
|
1732
1725
|
else if (options && options.screenshot) {
|
|
1733
1726
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1762,6 +1755,15 @@ class StableBrowser {
|
|
|
1762
1755
|
document.documentElement.clientWidth,
|
|
1763
1756
|
])));
|
|
1764
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
|
+
// }
|
|
1765
1767
|
if (this.context.browserName === "chromium") {
|
|
1766
1768
|
const client = await playContext.newCDPSession(this.page);
|
|
1767
1769
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1783,6 +1785,10 @@ class StableBrowser {
|
|
|
1783
1785
|
else {
|
|
1784
1786
|
screenshotBuffer = await this.page.screenshot();
|
|
1785
1787
|
}
|
|
1788
|
+
// if (focusedElement) {
|
|
1789
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1790
|
+
// await this._unhighlightElements(focusedElement);
|
|
1791
|
+
// }
|
|
1786
1792
|
let image = await Jimp.read(screenshotBuffer);
|
|
1787
1793
|
// Get the image dimensions
|
|
1788
1794
|
const { width, height } = image.bitmap;
|
|
@@ -1795,6 +1801,7 @@ class StableBrowser {
|
|
|
1795
1801
|
else {
|
|
1796
1802
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1797
1803
|
}
|
|
1804
|
+
return screenshotBuffer;
|
|
1798
1805
|
}
|
|
1799
1806
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1800
1807
|
const state = {
|
|
@@ -1832,6 +1839,7 @@ class StableBrowser {
|
|
|
1832
1839
|
text: `Extract attribute from element`,
|
|
1833
1840
|
operation: "extractAttribute",
|
|
1834
1841
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1842
|
+
allowDisabled: true,
|
|
1835
1843
|
};
|
|
1836
1844
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1837
1845
|
try {
|
|
@@ -1853,6 +1861,76 @@ class StableBrowser {
|
|
|
1853
1861
|
state.info.value = state.value;
|
|
1854
1862
|
this.setTestData({ [variable]: state.value }, world);
|
|
1855
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
|
+
}
|
|
1856
1934
|
return state.info;
|
|
1857
1935
|
}
|
|
1858
1936
|
catch (e) {
|
|
@@ -1880,7 +1958,7 @@ class StableBrowser {
|
|
|
1880
1958
|
if (options && options.timeout) {
|
|
1881
1959
|
timeout = options.timeout;
|
|
1882
1960
|
}
|
|
1883
|
-
const serviceUrl =
|
|
1961
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1884
1962
|
const request = {
|
|
1885
1963
|
method: "POST",
|
|
1886
1964
|
url: serviceUrl,
|
|
@@ -1951,27 +2029,32 @@ class StableBrowser {
|
|
|
1951
2029
|
async _highlightElements(scope, css) {
|
|
1952
2030
|
try {
|
|
1953
2031
|
if (!scope) {
|
|
2032
|
+
// console.log(`Scope is not defined`);
|
|
1954
2033
|
return;
|
|
1955
2034
|
}
|
|
1956
2035
|
if (!css) {
|
|
1957
2036
|
scope
|
|
1958
2037
|
.evaluate((node) => {
|
|
1959
2038
|
if (node && node.style) {
|
|
1960
|
-
let
|
|
1961
|
-
|
|
2039
|
+
let originalOutline = node.style.outline;
|
|
2040
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2041
|
+
// node.__previousOutline = originalOutline;
|
|
2042
|
+
node.style.outline = "2px solid red";
|
|
2043
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1962
2044
|
if (window) {
|
|
1963
2045
|
window.addEventListener("beforeunload", function (e) {
|
|
1964
|
-
node.style.
|
|
2046
|
+
node.style.outline = originalOutline;
|
|
1965
2047
|
});
|
|
1966
2048
|
}
|
|
1967
2049
|
setTimeout(function () {
|
|
1968
|
-
node.style.
|
|
2050
|
+
node.style.outline = originalOutline;
|
|
1969
2051
|
}, 2000);
|
|
1970
2052
|
}
|
|
1971
2053
|
})
|
|
1972
2054
|
.then(() => { })
|
|
1973
2055
|
.catch((e) => {
|
|
1974
2056
|
// ignore
|
|
2057
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1975
2058
|
});
|
|
1976
2059
|
}
|
|
1977
2060
|
else {
|
|
@@ -1987,17 +2070,18 @@ class StableBrowser {
|
|
|
1987
2070
|
if (!element.style) {
|
|
1988
2071
|
return;
|
|
1989
2072
|
}
|
|
1990
|
-
|
|
2073
|
+
let originalOutline = element.style.outline;
|
|
2074
|
+
element.__previousOutline = originalOutline;
|
|
1991
2075
|
// Set the new border to be red and 2px solid
|
|
1992
|
-
element.style.
|
|
2076
|
+
element.style.outline = "2px solid red";
|
|
1993
2077
|
if (window) {
|
|
1994
2078
|
window.addEventListener("beforeunload", function (e) {
|
|
1995
|
-
element.style.
|
|
2079
|
+
element.style.outline = originalOutline;
|
|
1996
2080
|
});
|
|
1997
2081
|
}
|
|
1998
2082
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1999
2083
|
setTimeout(function () {
|
|
2000
|
-
element.style.
|
|
2084
|
+
element.style.outline = originalOutline;
|
|
2001
2085
|
}, 2000);
|
|
2002
2086
|
}
|
|
2003
2087
|
return;
|
|
@@ -2005,6 +2089,7 @@ class StableBrowser {
|
|
|
2005
2089
|
.then(() => { })
|
|
2006
2090
|
.catch((e) => {
|
|
2007
2091
|
// ignore
|
|
2092
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2008
2093
|
});
|
|
2009
2094
|
}
|
|
2010
2095
|
}
|
|
@@ -2012,6 +2097,54 @@ class StableBrowser {
|
|
|
2012
2097
|
console.debug(error);
|
|
2013
2098
|
}
|
|
2014
2099
|
}
|
|
2100
|
+
// async _unhighlightElements(scope, css) {
|
|
2101
|
+
// try {
|
|
2102
|
+
// if (!scope) {
|
|
2103
|
+
// return;
|
|
2104
|
+
// }
|
|
2105
|
+
// if (!css) {
|
|
2106
|
+
// scope
|
|
2107
|
+
// .evaluate((node) => {
|
|
2108
|
+
// if (node && node.style) {
|
|
2109
|
+
// if (!node.__previousOutline) {
|
|
2110
|
+
// node.style.outline = "";
|
|
2111
|
+
// } else {
|
|
2112
|
+
// node.style.outline = node.__previousOutline;
|
|
2113
|
+
// }
|
|
2114
|
+
// }
|
|
2115
|
+
// })
|
|
2116
|
+
// .then(() => {})
|
|
2117
|
+
// .catch((e) => {
|
|
2118
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2119
|
+
// });
|
|
2120
|
+
// } else {
|
|
2121
|
+
// scope
|
|
2122
|
+
// .evaluate(([css]) => {
|
|
2123
|
+
// if (!css) {
|
|
2124
|
+
// return;
|
|
2125
|
+
// }
|
|
2126
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2127
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2128
|
+
// let element = elements[i];
|
|
2129
|
+
// if (!element.style) {
|
|
2130
|
+
// return;
|
|
2131
|
+
// }
|
|
2132
|
+
// if (!element.__previousOutline) {
|
|
2133
|
+
// element.style.outline = "";
|
|
2134
|
+
// } else {
|
|
2135
|
+
// element.style.outline = element.__previousOutline;
|
|
2136
|
+
// }
|
|
2137
|
+
// }
|
|
2138
|
+
// })
|
|
2139
|
+
// .then(() => {})
|
|
2140
|
+
// .catch((e) => {
|
|
2141
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2142
|
+
// });
|
|
2143
|
+
// }
|
|
2144
|
+
// } catch (error) {
|
|
2145
|
+
// // console.debug(error);
|
|
2146
|
+
// }
|
|
2147
|
+
// }
|
|
2015
2148
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2016
2149
|
const startTime = Date.now();
|
|
2017
2150
|
let error = null;
|
|
@@ -2073,6 +2206,35 @@ class StableBrowser {
|
|
|
2073
2206
|
});
|
|
2074
2207
|
}
|
|
2075
2208
|
}
|
|
2209
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2210
|
+
const frames = this.page.frames();
|
|
2211
|
+
let results = [];
|
|
2212
|
+
let ignoreCase = false;
|
|
2213
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2214
|
+
if (dateAlternatives.date) {
|
|
2215
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2216
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2217
|
+
result.frame = frames[i];
|
|
2218
|
+
results.push(result);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
else if (numberAlternatives.number) {
|
|
2222
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2223
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2224
|
+
result.frame = frames[i];
|
|
2225
|
+
results.push(result);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
else {
|
|
2229
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2230
|
+
result.frame = frames[i];
|
|
2231
|
+
results.push(result);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
state.info.results = results;
|
|
2235
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2236
|
+
return resultWithElementsFound;
|
|
2237
|
+
}
|
|
2076
2238
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2077
2239
|
text = unEscapeString(text);
|
|
2078
2240
|
const state = {
|
|
@@ -2082,7 +2244,7 @@ class StableBrowser {
|
|
|
2082
2244
|
locate: false,
|
|
2083
2245
|
scroll: false,
|
|
2084
2246
|
highlight: false,
|
|
2085
|
-
type: Types.
|
|
2247
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2086
2248
|
text: `Verify text exists in page`,
|
|
2087
2249
|
operation: "verifyTextExistInPage",
|
|
2088
2250
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2100,31 +2262,7 @@ class StableBrowser {
|
|
|
2100
2262
|
await _preCommand(state, this);
|
|
2101
2263
|
state.info.text = text;
|
|
2102
2264
|
while (true) {
|
|
2103
|
-
const
|
|
2104
|
-
let results = [];
|
|
2105
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2106
|
-
if (dateAlternatives.date) {
|
|
2107
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2108
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2109
|
-
result.frame = frames[i];
|
|
2110
|
-
results.push(result);
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
else if (numberAlternatives.number) {
|
|
2114
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2115
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2116
|
-
result.frame = frames[i];
|
|
2117
|
-
results.push(result);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
else {
|
|
2121
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2122
|
-
result.frame = frames[i];
|
|
2123
|
-
results.push(result);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
state.info.results = results;
|
|
2127
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2265
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2128
2266
|
if (resultWithElementsFound.length === 0) {
|
|
2129
2267
|
if (Date.now() - state.startTime > timeout) {
|
|
2130
2268
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2134,12 +2272,29 @@ class StableBrowser {
|
|
|
2134
2272
|
}
|
|
2135
2273
|
if (resultWithElementsFound[0].randomToken) {
|
|
2136
2274
|
const frame = resultWithElementsFound[0].frame;
|
|
2137
|
-
const dataAttribute = `[data-blinq-id
|
|
2275
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2138
2276
|
await this._highlightElements(frame, dataAttribute);
|
|
2139
|
-
|
|
2277
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2278
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2279
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2280
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2281
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2282
|
+
// .then(async () => {
|
|
2283
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2284
|
+
// })
|
|
2285
|
+
// .catch(
|
|
2286
|
+
// (e) => {}
|
|
2287
|
+
// console.error(e)
|
|
2288
|
+
// );
|
|
2289
|
+
// });
|
|
2290
|
+
// }
|
|
2291
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2292
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2293
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2140
2294
|
if (element) {
|
|
2141
2295
|
await this.scrollIfNeeded(element, state.info);
|
|
2142
2296
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2297
|
+
// await _screenshot(state, this, element);
|
|
2143
2298
|
}
|
|
2144
2299
|
}
|
|
2145
2300
|
await _screenshot(state, this);
|
|
@@ -2181,31 +2336,7 @@ class StableBrowser {
|
|
|
2181
2336
|
await _preCommand(state, this);
|
|
2182
2337
|
state.info.text = text;
|
|
2183
2338
|
while (true) {
|
|
2184
|
-
const
|
|
2185
|
-
let results = [];
|
|
2186
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2187
|
-
if (dateAlternatives.date) {
|
|
2188
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2189
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2190
|
-
result.frame = frames[i];
|
|
2191
|
-
results.push(result);
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
else if (numberAlternatives.number) {
|
|
2195
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2196
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2197
|
-
result.frame = frames[i];
|
|
2198
|
-
results.push(result);
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
else {
|
|
2202
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2203
|
-
result.frame = frames[i];
|
|
2204
|
-
results.push(result);
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
state.info.results = results;
|
|
2208
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2339
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2209
2340
|
if (resultWithElementsFound.length === 0) {
|
|
2210
2341
|
await _screenshot(state, this);
|
|
2211
2342
|
return state.info;
|
|
@@ -2223,15 +2354,102 @@ class StableBrowser {
|
|
|
2223
2354
|
_commandFinally(state, this);
|
|
2224
2355
|
}
|
|
2225
2356
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2357
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2358
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2359
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2360
|
+
const state = {
|
|
2361
|
+
text_search: textToVerify,
|
|
2362
|
+
options,
|
|
2363
|
+
world,
|
|
2364
|
+
locate: false,
|
|
2365
|
+
scroll: false,
|
|
2366
|
+
highlight: false,
|
|
2367
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2368
|
+
text: `Verify text with relation to another text`,
|
|
2369
|
+
operation: "verify_text_with_relation",
|
|
2370
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2371
|
+
};
|
|
2372
|
+
const timeout = this._getLoadTimeout(options);
|
|
2373
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2374
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2375
|
+
if (newValue !== textAnchor) {
|
|
2376
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2377
|
+
textAnchor = newValue;
|
|
2378
|
+
}
|
|
2379
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2380
|
+
if (newValue !== textToVerify) {
|
|
2381
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2382
|
+
textToVerify = newValue;
|
|
2383
|
+
}
|
|
2384
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2385
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2386
|
+
let foundAncore = false;
|
|
2387
|
+
try {
|
|
2388
|
+
await _preCommand(state, this);
|
|
2389
|
+
state.info.text = textToVerify;
|
|
2390
|
+
while (true) {
|
|
2391
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2392
|
+
if (resultWithElementsFound.length === 0) {
|
|
2393
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2394
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2395
|
+
}
|
|
2396
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2400
|
+
foundAncore = true;
|
|
2401
|
+
const result = resultWithElementsFound[i];
|
|
2402
|
+
const token = result.randomToken;
|
|
2403
|
+
const frame = result.frame;
|
|
2404
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2405
|
+
const climbArray1 = [];
|
|
2406
|
+
for (let i = 0; i < climb; i++) {
|
|
2407
|
+
climbArray1.push("..");
|
|
2408
|
+
}
|
|
2409
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2410
|
+
css = css + " >> " + climbXpath;
|
|
2411
|
+
const count = await frame.locator(css).count();
|
|
2412
|
+
for (let j = 0; j < count; j++) {
|
|
2413
|
+
const continer = await frame.locator(css).nth(j);
|
|
2414
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2415
|
+
if (result.elementCount > 0) {
|
|
2416
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2417
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2418
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2419
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2420
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2421
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2422
|
+
// .then(async () => {
|
|
2423
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2424
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2425
|
+
// () => {}
|
|
2426
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2427
|
+
// );
|
|
2428
|
+
// })
|
|
2429
|
+
// .catch(e);
|
|
2430
|
+
// }
|
|
2431
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2432
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2433
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2434
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2435
|
+
if (element) {
|
|
2436
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2437
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2438
|
+
}
|
|
2439
|
+
await _screenshot(state, this);
|
|
2440
|
+
return state.info;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2230
2446
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2447
|
+
catch (e) {
|
|
2448
|
+
await _commandError(state, e, this);
|
|
2449
|
+
}
|
|
2450
|
+
finally {
|
|
2451
|
+
_commandFinally(state, this);
|
|
2233
2452
|
}
|
|
2234
|
-
return serviceUrl;
|
|
2235
2453
|
}
|
|
2236
2454
|
async visualVerification(text, options = {}, world = null) {
|
|
2237
2455
|
const startTime = Date.now();
|
|
@@ -2247,7 +2465,7 @@ class StableBrowser {
|
|
|
2247
2465
|
throw new Error("TOKEN is not set");
|
|
2248
2466
|
}
|
|
2249
2467
|
try {
|
|
2250
|
-
let serviceUrl =
|
|
2468
|
+
let serviceUrl = _getServerUrl();
|
|
2251
2469
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2252
2470
|
info.screenshotPath = screenshotPath;
|
|
2253
2471
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2336,6 +2554,7 @@ class StableBrowser {
|
|
|
2336
2554
|
let screenshotPath = null;
|
|
2337
2555
|
const info = {};
|
|
2338
2556
|
info.log = "";
|
|
2557
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2339
2558
|
info.operation = "getTableData";
|
|
2340
2559
|
info.selectors = selectors;
|
|
2341
2560
|
try {
|
|
@@ -2411,7 +2630,7 @@ class StableBrowser {
|
|
|
2411
2630
|
info.operation = "analyzeTable";
|
|
2412
2631
|
info.selectors = selectors;
|
|
2413
2632
|
info.query = query;
|
|
2414
|
-
query =
|
|
2633
|
+
query = _fixUsingParams(query, _params);
|
|
2415
2634
|
info.query_fixed = query;
|
|
2416
2635
|
info.operator = operator;
|
|
2417
2636
|
info.value = value;
|
|
@@ -2556,6 +2775,17 @@ class StableBrowser {
|
|
|
2556
2775
|
}
|
|
2557
2776
|
return timeout;
|
|
2558
2777
|
}
|
|
2778
|
+
async saveStoreState(path = null, world = null) {
|
|
2779
|
+
const storageState = await this.page.context().storageState();
|
|
2780
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2781
|
+
if (path) {
|
|
2782
|
+
// save { storageState: storageState } into the path
|
|
2783
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2784
|
+
}
|
|
2785
|
+
else {
|
|
2786
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2559
2789
|
async waitForPageLoad(options = {}, world = null) {
|
|
2560
2790
|
let timeout = this._getLoadTimeout(options);
|
|
2561
2791
|
const promiseArray = [];
|
|
@@ -2578,9 +2808,6 @@ class StableBrowser {
|
|
|
2578
2808
|
let screenshotPath = null;
|
|
2579
2809
|
try {
|
|
2580
2810
|
await Promise.all(promiseArray);
|
|
2581
|
-
if (this.isRecording === false && this.configuration.load_all_lazy === true) {
|
|
2582
|
-
await this.scrollPageToLoadLazyElements();
|
|
2583
|
-
}
|
|
2584
2811
|
}
|
|
2585
2812
|
catch (e) {
|
|
2586
2813
|
if (e.label === "networkidle") {
|
|
@@ -2744,14 +2971,22 @@ class StableBrowser {
|
|
|
2744
2971
|
}
|
|
2745
2972
|
}
|
|
2746
2973
|
async beforeStep(world, step) {
|
|
2747
|
-
this.stepName = step.pickleStep.text;
|
|
2748
|
-
this.logger.info("step: " + this.stepName);
|
|
2749
2974
|
if (this.stepIndex === undefined) {
|
|
2750
2975
|
this.stepIndex = 0;
|
|
2751
2976
|
}
|
|
2752
2977
|
else {
|
|
2753
2978
|
this.stepIndex++;
|
|
2754
2979
|
}
|
|
2980
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2981
|
+
this.stepName = step.pickleStep.text;
|
|
2982
|
+
this.logger.info("step: " + this.stepName);
|
|
2983
|
+
}
|
|
2984
|
+
else if (step && step.text) {
|
|
2985
|
+
this.stepName = step.text;
|
|
2986
|
+
}
|
|
2987
|
+
else {
|
|
2988
|
+
this.stepName = "step " + this.stepIndex;
|
|
2989
|
+
}
|
|
2755
2990
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2756
2991
|
if (this.context.browserObject.context) {
|
|
2757
2992
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2781,156 +3016,5 @@ function createTimedPromise(promise, label) {
|
|
|
2781
3016
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2782
3017
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2783
3018
|
}
|
|
2784
|
-
const KEYBOARD_EVENTS = [
|
|
2785
|
-
"ALT",
|
|
2786
|
-
"AltGraph",
|
|
2787
|
-
"CapsLock",
|
|
2788
|
-
"Control",
|
|
2789
|
-
"Fn",
|
|
2790
|
-
"FnLock",
|
|
2791
|
-
"Hyper",
|
|
2792
|
-
"Meta",
|
|
2793
|
-
"NumLock",
|
|
2794
|
-
"ScrollLock",
|
|
2795
|
-
"Shift",
|
|
2796
|
-
"Super",
|
|
2797
|
-
"Symbol",
|
|
2798
|
-
"SymbolLock",
|
|
2799
|
-
"Enter",
|
|
2800
|
-
"Tab",
|
|
2801
|
-
"ArrowDown",
|
|
2802
|
-
"ArrowLeft",
|
|
2803
|
-
"ArrowRight",
|
|
2804
|
-
"ArrowUp",
|
|
2805
|
-
"End",
|
|
2806
|
-
"Home",
|
|
2807
|
-
"PageDown",
|
|
2808
|
-
"PageUp",
|
|
2809
|
-
"Backspace",
|
|
2810
|
-
"Clear",
|
|
2811
|
-
"Copy",
|
|
2812
|
-
"CrSel",
|
|
2813
|
-
"Cut",
|
|
2814
|
-
"Delete",
|
|
2815
|
-
"EraseEof",
|
|
2816
|
-
"ExSel",
|
|
2817
|
-
"Insert",
|
|
2818
|
-
"Paste",
|
|
2819
|
-
"Redo",
|
|
2820
|
-
"Undo",
|
|
2821
|
-
"Accept",
|
|
2822
|
-
"Again",
|
|
2823
|
-
"Attn",
|
|
2824
|
-
"Cancel",
|
|
2825
|
-
"ContextMenu",
|
|
2826
|
-
"Escape",
|
|
2827
|
-
"Execute",
|
|
2828
|
-
"Find",
|
|
2829
|
-
"Finish",
|
|
2830
|
-
"Help",
|
|
2831
|
-
"Pause",
|
|
2832
|
-
"Play",
|
|
2833
|
-
"Props",
|
|
2834
|
-
"Select",
|
|
2835
|
-
"ZoomIn",
|
|
2836
|
-
"ZoomOut",
|
|
2837
|
-
"BrightnessDown",
|
|
2838
|
-
"BrightnessUp",
|
|
2839
|
-
"Eject",
|
|
2840
|
-
"LogOff",
|
|
2841
|
-
"Power",
|
|
2842
|
-
"PowerOff",
|
|
2843
|
-
"PrintScreen",
|
|
2844
|
-
"Hibernate",
|
|
2845
|
-
"Standby",
|
|
2846
|
-
"WakeUp",
|
|
2847
|
-
"AllCandidates",
|
|
2848
|
-
"Alphanumeric",
|
|
2849
|
-
"CodeInput",
|
|
2850
|
-
"Compose",
|
|
2851
|
-
"Convert",
|
|
2852
|
-
"Dead",
|
|
2853
|
-
"FinalMode",
|
|
2854
|
-
"GroupFirst",
|
|
2855
|
-
"GroupLast",
|
|
2856
|
-
"GroupNext",
|
|
2857
|
-
"GroupPrevious",
|
|
2858
|
-
"ModeChange",
|
|
2859
|
-
"NextCandidate",
|
|
2860
|
-
"NonConvert",
|
|
2861
|
-
"PreviousCandidate",
|
|
2862
|
-
"Process",
|
|
2863
|
-
"SingleCandidate",
|
|
2864
|
-
"HangulMode",
|
|
2865
|
-
"HanjaMode",
|
|
2866
|
-
"JunjaMode",
|
|
2867
|
-
"Eisu",
|
|
2868
|
-
"Hankaku",
|
|
2869
|
-
"Hiragana",
|
|
2870
|
-
"HiraganaKatakana",
|
|
2871
|
-
"KanaMode",
|
|
2872
|
-
"KanjiMode",
|
|
2873
|
-
"Katakana",
|
|
2874
|
-
"Romaji",
|
|
2875
|
-
"Zenkaku",
|
|
2876
|
-
"ZenkakuHanaku",
|
|
2877
|
-
"F1",
|
|
2878
|
-
"F2",
|
|
2879
|
-
"F3",
|
|
2880
|
-
"F4",
|
|
2881
|
-
"F5",
|
|
2882
|
-
"F6",
|
|
2883
|
-
"F7",
|
|
2884
|
-
"F8",
|
|
2885
|
-
"F9",
|
|
2886
|
-
"F10",
|
|
2887
|
-
"F11",
|
|
2888
|
-
"F12",
|
|
2889
|
-
"Soft1",
|
|
2890
|
-
"Soft2",
|
|
2891
|
-
"Soft3",
|
|
2892
|
-
"Soft4",
|
|
2893
|
-
"ChannelDown",
|
|
2894
|
-
"ChannelUp",
|
|
2895
|
-
"Close",
|
|
2896
|
-
"MailForward",
|
|
2897
|
-
"MailReply",
|
|
2898
|
-
"MailSend",
|
|
2899
|
-
"MediaFastForward",
|
|
2900
|
-
"MediaPause",
|
|
2901
|
-
"MediaPlay",
|
|
2902
|
-
"MediaPlayPause",
|
|
2903
|
-
"MediaRecord",
|
|
2904
|
-
"MediaRewind",
|
|
2905
|
-
"MediaStop",
|
|
2906
|
-
"MediaTrackNext",
|
|
2907
|
-
"MediaTrackPrevious",
|
|
2908
|
-
"AudioBalanceLeft",
|
|
2909
|
-
"AudioBalanceRight",
|
|
2910
|
-
"AudioBassBoostDown",
|
|
2911
|
-
"AudioBassBoostToggle",
|
|
2912
|
-
"AudioBassBoostUp",
|
|
2913
|
-
"AudioFaderFront",
|
|
2914
|
-
"AudioFaderRear",
|
|
2915
|
-
"AudioSurroundModeNext",
|
|
2916
|
-
"AudioTrebleDown",
|
|
2917
|
-
"AudioTrebleUp",
|
|
2918
|
-
"AudioVolumeDown",
|
|
2919
|
-
"AudioVolumeMute",
|
|
2920
|
-
"AudioVolumeUp",
|
|
2921
|
-
"MicrophoneToggle",
|
|
2922
|
-
"MicrophoneVolumeDown",
|
|
2923
|
-
"MicrophoneVolumeMute",
|
|
2924
|
-
"MicrophoneVolumeUp",
|
|
2925
|
-
"TV",
|
|
2926
|
-
"TV3DMode",
|
|
2927
|
-
"TVAntennaCable",
|
|
2928
|
-
"TVAudioDescription",
|
|
2929
|
-
];
|
|
2930
|
-
function unEscapeString(str) {
|
|
2931
|
-
const placeholder = "__NEWLINE__";
|
|
2932
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2933
|
-
return str;
|
|
2934
|
-
}
|
|
2935
3019
|
export { StableBrowser };
|
|
2936
3020
|
//# sourceMappingURL=stable_browser.js.map
|