automation_model 1.0.531-dev → 1.0.531-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 +4 -2
- package/lib/auto_page.js +90 -4
- 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 +625 -509
- 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
270
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
else if (this._isObject(currentObj[key])) {
|
|
309
|
-
// Recursively scan nested objects
|
|
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
|
-
}
|
|
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;
|
|
465
364
|
}
|
|
466
365
|
}
|
|
467
|
-
|
|
468
|
-
|
|
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;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (!hasChild) {
|
|
482
|
-
noChildElements.push(element);
|
|
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,15 +652,16 @@ 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;
|
|
736
666
|
let lazy_scroll = false;
|
|
737
667
|
//let arrayMode = Array.isArray(selectors);
|
|
@@ -771,17 +701,17 @@ class StableBrowser {
|
|
|
771
701
|
}
|
|
772
702
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
773
703
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
774
|
-
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);
|
|
775
705
|
if (result.foundElements.length === 0) {
|
|
776
706
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
777
|
-
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);
|
|
778
708
|
}
|
|
779
709
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
780
|
-
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);
|
|
781
711
|
}
|
|
782
712
|
else {
|
|
783
713
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
784
|
-
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);
|
|
785
715
|
}
|
|
786
716
|
}
|
|
787
717
|
let foundElements = result.foundElements;
|
|
@@ -822,30 +752,38 @@ class StableBrowser {
|
|
|
822
752
|
return maxCountElement.locator;
|
|
823
753
|
}
|
|
824
754
|
}
|
|
825
|
-
if (
|
|
755
|
+
if (Date.now() - startTime > timeout) {
|
|
826
756
|
break;
|
|
827
757
|
}
|
|
828
|
-
if (
|
|
829
|
-
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";
|
|
830
760
|
highPriorityOnly = false;
|
|
831
761
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
832
762
|
lazy_scroll = true;
|
|
833
|
-
await this.
|
|
763
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
834
764
|
}
|
|
835
765
|
}
|
|
836
|
-
if (
|
|
837
|
-
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";
|
|
838
768
|
visibleOnly = false;
|
|
839
769
|
}
|
|
840
770
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
841
771
|
}
|
|
842
772
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
843
|
-
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";
|
|
844
780
|
info.failCause.locatorNotFound = true;
|
|
845
|
-
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
|
+
}
|
|
846
784
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
847
785
|
}
|
|
848
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
786
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
849
787
|
let foundElements = [];
|
|
850
788
|
const result = {
|
|
851
789
|
foundElements: foundElements,
|
|
@@ -853,14 +791,15 @@ class StableBrowser {
|
|
|
853
791
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
854
792
|
let foundLocators = [];
|
|
855
793
|
try {
|
|
856
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
794
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
857
795
|
}
|
|
858
796
|
catch (e) {
|
|
859
|
-
this
|
|
860
|
-
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);
|
|
861
800
|
foundLocators = [];
|
|
862
801
|
try {
|
|
863
|
-
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);
|
|
864
803
|
}
|
|
865
804
|
catch (e) {
|
|
866
805
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -876,11 +815,27 @@ class StableBrowser {
|
|
|
876
815
|
}
|
|
877
816
|
if (foundLocators.length > 1) {
|
|
878
817
|
info.failCause.foundMultiple = true;
|
|
818
|
+
if (info.locatorLog) {
|
|
819
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
820
|
+
}
|
|
879
821
|
}
|
|
880
822
|
}
|
|
881
823
|
return result;
|
|
882
824
|
}
|
|
883
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);
|
|
884
839
|
const startTime = Date.now();
|
|
885
840
|
let timeout = 30000;
|
|
886
841
|
if (options && options.timeout) {
|
|
@@ -905,13 +860,31 @@ class StableBrowser {
|
|
|
905
860
|
catch (e) {
|
|
906
861
|
if (performance.now() - startTime > timeout) {
|
|
907
862
|
// throw e;
|
|
908
|
-
|
|
863
|
+
try {
|
|
864
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
_commandFinally(state, this);
|
|
868
|
+
}
|
|
909
869
|
}
|
|
910
870
|
}
|
|
911
871
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
912
872
|
}
|
|
913
873
|
}
|
|
914
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);
|
|
915
888
|
const startTime = Date.now();
|
|
916
889
|
let timeout = 30000;
|
|
917
890
|
if (options && options.timeout) {
|
|
@@ -936,7 +909,12 @@ class StableBrowser {
|
|
|
936
909
|
catch (e) {
|
|
937
910
|
if (performance.now() - startTime > timeout) {
|
|
938
911
|
// throw e;
|
|
939
|
-
|
|
912
|
+
try {
|
|
913
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
_commandFinally(state, this);
|
|
917
|
+
}
|
|
940
918
|
}
|
|
941
919
|
}
|
|
942
920
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -955,9 +933,9 @@ class StableBrowser {
|
|
|
955
933
|
};
|
|
956
934
|
try {
|
|
957
935
|
await _preCommand(state, this);
|
|
958
|
-
if (state.options && state.options.context) {
|
|
959
|
-
|
|
960
|
-
}
|
|
936
|
+
// if (state.options && state.options.context) {
|
|
937
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
938
|
+
// }
|
|
961
939
|
try {
|
|
962
940
|
await state.element.click();
|
|
963
941
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -995,9 +973,15 @@ class StableBrowser {
|
|
|
995
973
|
// let element = await this._locate(selectors, info, _params);
|
|
996
974
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
997
975
|
try {
|
|
998
|
-
//
|
|
976
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
977
|
+
// console.log(`Highlighting while running from recorder`);
|
|
978
|
+
await this._highlightElements(element);
|
|
999
979
|
await state.element.setChecked(checked);
|
|
1000
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);
|
|
1001
985
|
}
|
|
1002
986
|
catch (e) {
|
|
1003
987
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1036,6 +1020,7 @@ class StableBrowser {
|
|
|
1036
1020
|
await _preCommand(state, this);
|
|
1037
1021
|
try {
|
|
1038
1022
|
await state.element.hover();
|
|
1023
|
+
// await _screenshot(state, this);
|
|
1039
1024
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1040
1025
|
}
|
|
1041
1026
|
catch (e) {
|
|
@@ -1043,6 +1028,7 @@ class StableBrowser {
|
|
|
1043
1028
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1044
1029
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1045
1030
|
await state.element.hover({ timeout: 10000 });
|
|
1031
|
+
// await _screenshot(state, this);
|
|
1046
1032
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1047
1033
|
}
|
|
1048
1034
|
await _screenshot(state, this);
|
|
@@ -1317,7 +1303,12 @@ class StableBrowser {
|
|
|
1317
1303
|
await this.waitForPageLoad();
|
|
1318
1304
|
}
|
|
1319
1305
|
else if (enter === false) {
|
|
1320
|
-
|
|
1306
|
+
try {
|
|
1307
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1308
|
+
}
|
|
1309
|
+
catch (e) {
|
|
1310
|
+
// ignore
|
|
1311
|
+
}
|
|
1321
1312
|
//await this.page.keyboard.press("Tab");
|
|
1322
1313
|
}
|
|
1323
1314
|
else {
|
|
@@ -1374,6 +1365,7 @@ class StableBrowser {
|
|
|
1374
1365
|
let screenshotPath = null;
|
|
1375
1366
|
if (!info.log) {
|
|
1376
1367
|
info.log = "";
|
|
1368
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1377
1369
|
}
|
|
1378
1370
|
info.operation = "getText";
|
|
1379
1371
|
info.selectors = selectors;
|
|
@@ -1396,6 +1388,18 @@ class StableBrowser {
|
|
|
1396
1388
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1397
1389
|
try {
|
|
1398
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
|
+
// }
|
|
1399
1403
|
const elementText = await element.innerText();
|
|
1400
1404
|
return {
|
|
1401
1405
|
text: elementText,
|
|
@@ -1407,7 +1411,7 @@ class StableBrowser {
|
|
|
1407
1411
|
}
|
|
1408
1412
|
catch (e) {
|
|
1409
1413
|
//await this.closeUnexpectedPopups();
|
|
1410
|
-
this.logger.info("no innerText will use textContent");
|
|
1414
|
+
this.logger.info("no innerText, will use textContent");
|
|
1411
1415
|
const elementText = await element.textContent();
|
|
1412
1416
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1413
1417
|
}
|
|
@@ -1533,22 +1537,6 @@ class StableBrowser {
|
|
|
1533
1537
|
_commandFinally(state, this);
|
|
1534
1538
|
}
|
|
1535
1539
|
}
|
|
1536
|
-
_getDataFile(world = null) {
|
|
1537
|
-
let dataFile = null;
|
|
1538
|
-
if (world && world.reportFolder) {
|
|
1539
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1540
|
-
}
|
|
1541
|
-
else if (this.reportFolder) {
|
|
1542
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1543
|
-
}
|
|
1544
|
-
else if (this.context && this.context.reportFolder) {
|
|
1545
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1546
|
-
}
|
|
1547
|
-
else {
|
|
1548
|
-
dataFile = "data.json";
|
|
1549
|
-
}
|
|
1550
|
-
return dataFile;
|
|
1551
|
-
}
|
|
1552
1540
|
async waitForUserInput(message, world = null) {
|
|
1553
1541
|
if (!message) {
|
|
1554
1542
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1577,7 +1565,7 @@ class StableBrowser {
|
|
|
1577
1565
|
return;
|
|
1578
1566
|
}
|
|
1579
1567
|
// if data file exists, load it
|
|
1580
|
-
const dataFile =
|
|
1568
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1581
1569
|
let data = this.getTestData(world);
|
|
1582
1570
|
// merge the testData with the existing data
|
|
1583
1571
|
Object.assign(data, testData);
|
|
@@ -1680,7 +1668,7 @@ class StableBrowser {
|
|
|
1680
1668
|
}
|
|
1681
1669
|
}
|
|
1682
1670
|
getTestData(world = null) {
|
|
1683
|
-
const dataFile =
|
|
1671
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1684
1672
|
let data = {};
|
|
1685
1673
|
if (fs.existsSync(dataFile)) {
|
|
1686
1674
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1724,15 +1712,15 @@ class StableBrowser {
|
|
|
1724
1712
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1725
1713
|
// }
|
|
1726
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
|
+
}
|
|
1727
1720
|
}
|
|
1728
1721
|
catch (e) {
|
|
1729
1722
|
this.logger.info("unable to take screenshot, ignored");
|
|
1730
1723
|
}
|
|
1731
|
-
result.screenshotId = uuidStr;
|
|
1732
|
-
result.screenshotPath = screenshotPath;
|
|
1733
|
-
if (info && info.box) {
|
|
1734
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1735
|
-
}
|
|
1736
1724
|
}
|
|
1737
1725
|
else if (options && options.screenshot) {
|
|
1738
1726
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1767,6 +1755,15 @@ class StableBrowser {
|
|
|
1767
1755
|
document.documentElement.clientWidth,
|
|
1768
1756
|
])));
|
|
1769
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
|
+
// }
|
|
1770
1767
|
if (this.context.browserName === "chromium") {
|
|
1771
1768
|
const client = await playContext.newCDPSession(this.page);
|
|
1772
1769
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1788,6 +1785,10 @@ class StableBrowser {
|
|
|
1788
1785
|
else {
|
|
1789
1786
|
screenshotBuffer = await this.page.screenshot();
|
|
1790
1787
|
}
|
|
1788
|
+
// if (focusedElement) {
|
|
1789
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1790
|
+
// await this._unhighlightElements(focusedElement);
|
|
1791
|
+
// }
|
|
1791
1792
|
let image = await Jimp.read(screenshotBuffer);
|
|
1792
1793
|
// Get the image dimensions
|
|
1793
1794
|
const { width, height } = image.bitmap;
|
|
@@ -1800,6 +1801,7 @@ class StableBrowser {
|
|
|
1800
1801
|
else {
|
|
1801
1802
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1802
1803
|
}
|
|
1804
|
+
return screenshotBuffer;
|
|
1803
1805
|
}
|
|
1804
1806
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1805
1807
|
const state = {
|
|
@@ -1837,6 +1839,7 @@ class StableBrowser {
|
|
|
1837
1839
|
text: `Extract attribute from element`,
|
|
1838
1840
|
operation: "extractAttribute",
|
|
1839
1841
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1842
|
+
allowDisabled: true,
|
|
1840
1843
|
};
|
|
1841
1844
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1842
1845
|
try {
|
|
@@ -1858,6 +1861,76 @@ class StableBrowser {
|
|
|
1858
1861
|
state.info.value = state.value;
|
|
1859
1862
|
this.setTestData({ [variable]: state.value }, world);
|
|
1860
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
|
+
}
|
|
1861
1934
|
return state.info;
|
|
1862
1935
|
}
|
|
1863
1936
|
catch (e) {
|
|
@@ -1885,7 +1958,7 @@ class StableBrowser {
|
|
|
1885
1958
|
if (options && options.timeout) {
|
|
1886
1959
|
timeout = options.timeout;
|
|
1887
1960
|
}
|
|
1888
|
-
const serviceUrl =
|
|
1961
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1889
1962
|
const request = {
|
|
1890
1963
|
method: "POST",
|
|
1891
1964
|
url: serviceUrl,
|
|
@@ -1956,27 +2029,32 @@ class StableBrowser {
|
|
|
1956
2029
|
async _highlightElements(scope, css) {
|
|
1957
2030
|
try {
|
|
1958
2031
|
if (!scope) {
|
|
2032
|
+
// console.log(`Scope is not defined`);
|
|
1959
2033
|
return;
|
|
1960
2034
|
}
|
|
1961
2035
|
if (!css) {
|
|
1962
2036
|
scope
|
|
1963
2037
|
.evaluate((node) => {
|
|
1964
2038
|
if (node && node.style) {
|
|
1965
|
-
let
|
|
1966
|
-
|
|
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}`);
|
|
1967
2044
|
if (window) {
|
|
1968
2045
|
window.addEventListener("beforeunload", function (e) {
|
|
1969
|
-
node.style.
|
|
2046
|
+
node.style.outline = originalOutline;
|
|
1970
2047
|
});
|
|
1971
2048
|
}
|
|
1972
2049
|
setTimeout(function () {
|
|
1973
|
-
node.style.
|
|
2050
|
+
node.style.outline = originalOutline;
|
|
1974
2051
|
}, 2000);
|
|
1975
2052
|
}
|
|
1976
2053
|
})
|
|
1977
2054
|
.then(() => { })
|
|
1978
2055
|
.catch((e) => {
|
|
1979
2056
|
// ignore
|
|
2057
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1980
2058
|
});
|
|
1981
2059
|
}
|
|
1982
2060
|
else {
|
|
@@ -1992,17 +2070,18 @@ class StableBrowser {
|
|
|
1992
2070
|
if (!element.style) {
|
|
1993
2071
|
return;
|
|
1994
2072
|
}
|
|
1995
|
-
|
|
2073
|
+
let originalOutline = element.style.outline;
|
|
2074
|
+
element.__previousOutline = originalOutline;
|
|
1996
2075
|
// Set the new border to be red and 2px solid
|
|
1997
|
-
element.style.
|
|
2076
|
+
element.style.outline = "2px solid red";
|
|
1998
2077
|
if (window) {
|
|
1999
2078
|
window.addEventListener("beforeunload", function (e) {
|
|
2000
|
-
element.style.
|
|
2079
|
+
element.style.outline = originalOutline;
|
|
2001
2080
|
});
|
|
2002
2081
|
}
|
|
2003
2082
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2004
2083
|
setTimeout(function () {
|
|
2005
|
-
element.style.
|
|
2084
|
+
element.style.outline = originalOutline;
|
|
2006
2085
|
}, 2000);
|
|
2007
2086
|
}
|
|
2008
2087
|
return;
|
|
@@ -2010,6 +2089,7 @@ class StableBrowser {
|
|
|
2010
2089
|
.then(() => { })
|
|
2011
2090
|
.catch((e) => {
|
|
2012
2091
|
// ignore
|
|
2092
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2013
2093
|
});
|
|
2014
2094
|
}
|
|
2015
2095
|
}
|
|
@@ -2017,6 +2097,54 @@ class StableBrowser {
|
|
|
2017
2097
|
console.debug(error);
|
|
2018
2098
|
}
|
|
2019
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
|
+
// }
|
|
2020
2148
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2021
2149
|
const startTime = Date.now();
|
|
2022
2150
|
let error = null;
|
|
@@ -2078,6 +2206,35 @@ class StableBrowser {
|
|
|
2078
2206
|
});
|
|
2079
2207
|
}
|
|
2080
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
|
+
}
|
|
2081
2238
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2082
2239
|
text = unEscapeString(text);
|
|
2083
2240
|
const state = {
|
|
@@ -2087,7 +2244,7 @@ class StableBrowser {
|
|
|
2087
2244
|
locate: false,
|
|
2088
2245
|
scroll: false,
|
|
2089
2246
|
highlight: false,
|
|
2090
|
-
type: Types.
|
|
2247
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2091
2248
|
text: `Verify text exists in page`,
|
|
2092
2249
|
operation: "verifyTextExistInPage",
|
|
2093
2250
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2105,31 +2262,15 @@ class StableBrowser {
|
|
|
2105
2262
|
await _preCommand(state, this);
|
|
2106
2263
|
state.info.text = text;
|
|
2107
2264
|
while (true) {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
else if (numberAlternatives.number) {
|
|
2119
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2120
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2121
|
-
result.frame = frames[i];
|
|
2122
|
-
results.push(result);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
else {
|
|
2126
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2127
|
-
result.frame = frames[i];
|
|
2128
|
-
results.push(result);
|
|
2129
|
-
}
|
|
2265
|
+
let resultWithElementsFound = {
|
|
2266
|
+
length: 0,
|
|
2267
|
+
};
|
|
2268
|
+
try {
|
|
2269
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2270
|
+
}
|
|
2271
|
+
catch (error) {
|
|
2272
|
+
// ignore
|
|
2130
2273
|
}
|
|
2131
|
-
state.info.results = results;
|
|
2132
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2133
2274
|
if (resultWithElementsFound.length === 0) {
|
|
2134
2275
|
if (Date.now() - state.startTime > timeout) {
|
|
2135
2276
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2137,18 +2278,40 @@ class StableBrowser {
|
|
|
2137
2278
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2138
2279
|
continue;
|
|
2139
2280
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2281
|
+
try {
|
|
2282
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2283
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2284
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2285
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2286
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2287
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2288
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2289
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2290
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2291
|
+
// .then(async () => {
|
|
2292
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2293
|
+
// })
|
|
2294
|
+
// .catch(
|
|
2295
|
+
// (e) => {}
|
|
2296
|
+
// console.error(e)
|
|
2297
|
+
// );
|
|
2298
|
+
// });
|
|
2299
|
+
// }
|
|
2300
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2301
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2302
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2303
|
+
if (element) {
|
|
2304
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2305
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2306
|
+
// await _screenshot(state, this, element);
|
|
2307
|
+
}
|
|
2148
2308
|
}
|
|
2309
|
+
await _screenshot(state, this);
|
|
2310
|
+
return state.info;
|
|
2311
|
+
}
|
|
2312
|
+
catch (error) {
|
|
2313
|
+
console.error(error);
|
|
2149
2314
|
}
|
|
2150
|
-
await _screenshot(state, this);
|
|
2151
|
-
return state.info;
|
|
2152
2315
|
}
|
|
2153
2316
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2154
2317
|
}
|
|
@@ -2185,32 +2348,16 @@ class StableBrowser {
|
|
|
2185
2348
|
try {
|
|
2186
2349
|
await _preCommand(state, this);
|
|
2187
2350
|
state.info.text = text;
|
|
2351
|
+
let resultWithElementsFound = {
|
|
2352
|
+
length: null, // initial cannot be 0
|
|
2353
|
+
};
|
|
2188
2354
|
while (true) {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2195
|
-
result.frame = frames[i];
|
|
2196
|
-
results.push(result);
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
else if (numberAlternatives.number) {
|
|
2200
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2201
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2202
|
-
result.frame = frames[i];
|
|
2203
|
-
results.push(result);
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
else {
|
|
2207
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2208
|
-
result.frame = frames[i];
|
|
2209
|
-
results.push(result);
|
|
2210
|
-
}
|
|
2355
|
+
try {
|
|
2356
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2357
|
+
}
|
|
2358
|
+
catch (error) {
|
|
2359
|
+
// ignore
|
|
2211
2360
|
}
|
|
2212
|
-
state.info.results = results;
|
|
2213
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2214
2361
|
if (resultWithElementsFound.length === 0) {
|
|
2215
2362
|
await _screenshot(state, this);
|
|
2216
2363
|
return state.info;
|
|
@@ -2228,15 +2375,115 @@ class StableBrowser {
|
|
|
2228
2375
|
_commandFinally(state, this);
|
|
2229
2376
|
}
|
|
2230
2377
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2378
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2379
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2380
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2381
|
+
const state = {
|
|
2382
|
+
text_search: textToVerify,
|
|
2383
|
+
options,
|
|
2384
|
+
world,
|
|
2385
|
+
locate: false,
|
|
2386
|
+
scroll: false,
|
|
2387
|
+
highlight: false,
|
|
2388
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2389
|
+
text: `Verify text with relation to another text`,
|
|
2390
|
+
operation: "verify_text_with_relation",
|
|
2391
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2392
|
+
};
|
|
2393
|
+
const timeout = this._getLoadTimeout(options);
|
|
2394
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2395
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2396
|
+
if (newValue !== textAnchor) {
|
|
2397
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2398
|
+
textAnchor = newValue;
|
|
2399
|
+
}
|
|
2400
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2401
|
+
if (newValue !== textToVerify) {
|
|
2402
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2403
|
+
textToVerify = newValue;
|
|
2404
|
+
}
|
|
2405
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2406
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2407
|
+
let foundAncore = false;
|
|
2408
|
+
try {
|
|
2409
|
+
await _preCommand(state, this);
|
|
2410
|
+
state.info.text = textToVerify;
|
|
2411
|
+
let resultWithElementsFound = {
|
|
2412
|
+
length: 0,
|
|
2413
|
+
};
|
|
2414
|
+
while (true) {
|
|
2415
|
+
try {
|
|
2416
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2417
|
+
}
|
|
2418
|
+
catch (error) {
|
|
2419
|
+
// ignore
|
|
2420
|
+
}
|
|
2421
|
+
if (resultWithElementsFound.length === 0) {
|
|
2422
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2423
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2424
|
+
}
|
|
2425
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
try {
|
|
2429
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2430
|
+
foundAncore = true;
|
|
2431
|
+
const result = resultWithElementsFound[i];
|
|
2432
|
+
const token = result.randomToken;
|
|
2433
|
+
const frame = result.frame;
|
|
2434
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2435
|
+
const climbArray1 = [];
|
|
2436
|
+
for (let i = 0; i < climb; i++) {
|
|
2437
|
+
climbArray1.push("..");
|
|
2438
|
+
}
|
|
2439
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2440
|
+
css = css + " >> " + climbXpath;
|
|
2441
|
+
const count = await frame.locator(css).count();
|
|
2442
|
+
for (let j = 0; j < count; j++) {
|
|
2443
|
+
const continer = await frame.locator(css).nth(j);
|
|
2444
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2445
|
+
if (result.elementCount > 0) {
|
|
2446
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2447
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2448
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2449
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2450
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2451
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2452
|
+
// .then(async () => {
|
|
2453
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2454
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2455
|
+
// () => {}
|
|
2456
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2457
|
+
// );
|
|
2458
|
+
// })
|
|
2459
|
+
// .catch(e);
|
|
2460
|
+
// }
|
|
2461
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2462
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2463
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2464
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2465
|
+
if (element) {
|
|
2466
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2467
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2468
|
+
}
|
|
2469
|
+
await _screenshot(state, this);
|
|
2470
|
+
return state.info;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
console.error(error);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2480
|
+
}
|
|
2481
|
+
catch (e) {
|
|
2482
|
+
await _commandError(state, e, this);
|
|
2235
2483
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2484
|
+
finally {
|
|
2485
|
+
_commandFinally(state, this);
|
|
2238
2486
|
}
|
|
2239
|
-
return serviceUrl;
|
|
2240
2487
|
}
|
|
2241
2488
|
async visualVerification(text, options = {}, world = null) {
|
|
2242
2489
|
const startTime = Date.now();
|
|
@@ -2252,7 +2499,7 @@ class StableBrowser {
|
|
|
2252
2499
|
throw new Error("TOKEN is not set");
|
|
2253
2500
|
}
|
|
2254
2501
|
try {
|
|
2255
|
-
let serviceUrl =
|
|
2502
|
+
let serviceUrl = _getServerUrl();
|
|
2256
2503
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2257
2504
|
info.screenshotPath = screenshotPath;
|
|
2258
2505
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2341,6 +2588,7 @@ class StableBrowser {
|
|
|
2341
2588
|
let screenshotPath = null;
|
|
2342
2589
|
const info = {};
|
|
2343
2590
|
info.log = "";
|
|
2591
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2344
2592
|
info.operation = "getTableData";
|
|
2345
2593
|
info.selectors = selectors;
|
|
2346
2594
|
try {
|
|
@@ -2416,7 +2664,7 @@ class StableBrowser {
|
|
|
2416
2664
|
info.operation = "analyzeTable";
|
|
2417
2665
|
info.selectors = selectors;
|
|
2418
2666
|
info.query = query;
|
|
2419
|
-
query =
|
|
2667
|
+
query = _fixUsingParams(query, _params);
|
|
2420
2668
|
info.query_fixed = query;
|
|
2421
2669
|
info.operator = operator;
|
|
2422
2670
|
info.value = value;
|
|
@@ -2561,6 +2809,17 @@ class StableBrowser {
|
|
|
2561
2809
|
}
|
|
2562
2810
|
return timeout;
|
|
2563
2811
|
}
|
|
2812
|
+
async saveStoreState(path = null, world = null) {
|
|
2813
|
+
const storageState = await this.page.context().storageState();
|
|
2814
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2815
|
+
if (path) {
|
|
2816
|
+
// save { storageState: storageState } into the path
|
|
2817
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2818
|
+
}
|
|
2819
|
+
else {
|
|
2820
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2564
2823
|
async waitForPageLoad(options = {}, world = null) {
|
|
2565
2824
|
let timeout = this._getLoadTimeout(options);
|
|
2566
2825
|
const promiseArray = [];
|
|
@@ -2746,14 +3005,22 @@ class StableBrowser {
|
|
|
2746
3005
|
}
|
|
2747
3006
|
}
|
|
2748
3007
|
async beforeStep(world, step) {
|
|
2749
|
-
this.stepName = step.pickleStep.text;
|
|
2750
|
-
this.logger.info("step: " + this.stepName);
|
|
2751
3008
|
if (this.stepIndex === undefined) {
|
|
2752
3009
|
this.stepIndex = 0;
|
|
2753
3010
|
}
|
|
2754
3011
|
else {
|
|
2755
3012
|
this.stepIndex++;
|
|
2756
3013
|
}
|
|
3014
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3015
|
+
this.stepName = step.pickleStep.text;
|
|
3016
|
+
this.logger.info("step: " + this.stepName);
|
|
3017
|
+
}
|
|
3018
|
+
else if (step && step.text) {
|
|
3019
|
+
this.stepName = step.text;
|
|
3020
|
+
}
|
|
3021
|
+
else {
|
|
3022
|
+
this.stepName = "step " + this.stepIndex;
|
|
3023
|
+
}
|
|
2757
3024
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2758
3025
|
if (this.context.browserObject.context) {
|
|
2759
3026
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2783,156 +3050,5 @@ function createTimedPromise(promise, label) {
|
|
|
2783
3050
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2784
3051
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2785
3052
|
}
|
|
2786
|
-
const KEYBOARD_EVENTS = [
|
|
2787
|
-
"ALT",
|
|
2788
|
-
"AltGraph",
|
|
2789
|
-
"CapsLock",
|
|
2790
|
-
"Control",
|
|
2791
|
-
"Fn",
|
|
2792
|
-
"FnLock",
|
|
2793
|
-
"Hyper",
|
|
2794
|
-
"Meta",
|
|
2795
|
-
"NumLock",
|
|
2796
|
-
"ScrollLock",
|
|
2797
|
-
"Shift",
|
|
2798
|
-
"Super",
|
|
2799
|
-
"Symbol",
|
|
2800
|
-
"SymbolLock",
|
|
2801
|
-
"Enter",
|
|
2802
|
-
"Tab",
|
|
2803
|
-
"ArrowDown",
|
|
2804
|
-
"ArrowLeft",
|
|
2805
|
-
"ArrowRight",
|
|
2806
|
-
"ArrowUp",
|
|
2807
|
-
"End",
|
|
2808
|
-
"Home",
|
|
2809
|
-
"PageDown",
|
|
2810
|
-
"PageUp",
|
|
2811
|
-
"Backspace",
|
|
2812
|
-
"Clear",
|
|
2813
|
-
"Copy",
|
|
2814
|
-
"CrSel",
|
|
2815
|
-
"Cut",
|
|
2816
|
-
"Delete",
|
|
2817
|
-
"EraseEof",
|
|
2818
|
-
"ExSel",
|
|
2819
|
-
"Insert",
|
|
2820
|
-
"Paste",
|
|
2821
|
-
"Redo",
|
|
2822
|
-
"Undo",
|
|
2823
|
-
"Accept",
|
|
2824
|
-
"Again",
|
|
2825
|
-
"Attn",
|
|
2826
|
-
"Cancel",
|
|
2827
|
-
"ContextMenu",
|
|
2828
|
-
"Escape",
|
|
2829
|
-
"Execute",
|
|
2830
|
-
"Find",
|
|
2831
|
-
"Finish",
|
|
2832
|
-
"Help",
|
|
2833
|
-
"Pause",
|
|
2834
|
-
"Play",
|
|
2835
|
-
"Props",
|
|
2836
|
-
"Select",
|
|
2837
|
-
"ZoomIn",
|
|
2838
|
-
"ZoomOut",
|
|
2839
|
-
"BrightnessDown",
|
|
2840
|
-
"BrightnessUp",
|
|
2841
|
-
"Eject",
|
|
2842
|
-
"LogOff",
|
|
2843
|
-
"Power",
|
|
2844
|
-
"PowerOff",
|
|
2845
|
-
"PrintScreen",
|
|
2846
|
-
"Hibernate",
|
|
2847
|
-
"Standby",
|
|
2848
|
-
"WakeUp",
|
|
2849
|
-
"AllCandidates",
|
|
2850
|
-
"Alphanumeric",
|
|
2851
|
-
"CodeInput",
|
|
2852
|
-
"Compose",
|
|
2853
|
-
"Convert",
|
|
2854
|
-
"Dead",
|
|
2855
|
-
"FinalMode",
|
|
2856
|
-
"GroupFirst",
|
|
2857
|
-
"GroupLast",
|
|
2858
|
-
"GroupNext",
|
|
2859
|
-
"GroupPrevious",
|
|
2860
|
-
"ModeChange",
|
|
2861
|
-
"NextCandidate",
|
|
2862
|
-
"NonConvert",
|
|
2863
|
-
"PreviousCandidate",
|
|
2864
|
-
"Process",
|
|
2865
|
-
"SingleCandidate",
|
|
2866
|
-
"HangulMode",
|
|
2867
|
-
"HanjaMode",
|
|
2868
|
-
"JunjaMode",
|
|
2869
|
-
"Eisu",
|
|
2870
|
-
"Hankaku",
|
|
2871
|
-
"Hiragana",
|
|
2872
|
-
"HiraganaKatakana",
|
|
2873
|
-
"KanaMode",
|
|
2874
|
-
"KanjiMode",
|
|
2875
|
-
"Katakana",
|
|
2876
|
-
"Romaji",
|
|
2877
|
-
"Zenkaku",
|
|
2878
|
-
"ZenkakuHanaku",
|
|
2879
|
-
"F1",
|
|
2880
|
-
"F2",
|
|
2881
|
-
"F3",
|
|
2882
|
-
"F4",
|
|
2883
|
-
"F5",
|
|
2884
|
-
"F6",
|
|
2885
|
-
"F7",
|
|
2886
|
-
"F8",
|
|
2887
|
-
"F9",
|
|
2888
|
-
"F10",
|
|
2889
|
-
"F11",
|
|
2890
|
-
"F12",
|
|
2891
|
-
"Soft1",
|
|
2892
|
-
"Soft2",
|
|
2893
|
-
"Soft3",
|
|
2894
|
-
"Soft4",
|
|
2895
|
-
"ChannelDown",
|
|
2896
|
-
"ChannelUp",
|
|
2897
|
-
"Close",
|
|
2898
|
-
"MailForward",
|
|
2899
|
-
"MailReply",
|
|
2900
|
-
"MailSend",
|
|
2901
|
-
"MediaFastForward",
|
|
2902
|
-
"MediaPause",
|
|
2903
|
-
"MediaPlay",
|
|
2904
|
-
"MediaPlayPause",
|
|
2905
|
-
"MediaRecord",
|
|
2906
|
-
"MediaRewind",
|
|
2907
|
-
"MediaStop",
|
|
2908
|
-
"MediaTrackNext",
|
|
2909
|
-
"MediaTrackPrevious",
|
|
2910
|
-
"AudioBalanceLeft",
|
|
2911
|
-
"AudioBalanceRight",
|
|
2912
|
-
"AudioBassBoostDown",
|
|
2913
|
-
"AudioBassBoostToggle",
|
|
2914
|
-
"AudioBassBoostUp",
|
|
2915
|
-
"AudioFaderFront",
|
|
2916
|
-
"AudioFaderRear",
|
|
2917
|
-
"AudioSurroundModeNext",
|
|
2918
|
-
"AudioTrebleDown",
|
|
2919
|
-
"AudioTrebleUp",
|
|
2920
|
-
"AudioVolumeDown",
|
|
2921
|
-
"AudioVolumeMute",
|
|
2922
|
-
"AudioVolumeUp",
|
|
2923
|
-
"MicrophoneToggle",
|
|
2924
|
-
"MicrophoneVolumeDown",
|
|
2925
|
-
"MicrophoneVolumeMute",
|
|
2926
|
-
"MicrophoneVolumeUp",
|
|
2927
|
-
"TV",
|
|
2928
|
-
"TV3DMode",
|
|
2929
|
-
"TVAntennaCable",
|
|
2930
|
-
"TVAudioDescription",
|
|
2931
|
-
];
|
|
2932
|
-
function unEscapeString(str) {
|
|
2933
|
-
const placeholder = "__NEWLINE__";
|
|
2934
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2935
|
-
return str;
|
|
2936
|
-
}
|
|
2937
3053
|
export { StableBrowser };
|
|
2938
3054
|
//# sourceMappingURL=stable_browser.js.map
|