automation_model 1.0.538-dev → 1.0.538-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 +5 -2
- package/lib/auto_page.js +95 -5
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +56 -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 +22 -6
- 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 +619 -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 +8 -7
- /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,9 @@ 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
|
+
import axios from "axios";
|
|
24
|
+
export const Types = {
|
|
23
25
|
CLICK: "click_element",
|
|
24
26
|
NAVIGATE: "navigate",
|
|
25
27
|
FILL: "fill_element",
|
|
@@ -30,6 +32,8 @@ const Types = {
|
|
|
30
32
|
GET_PAGE_STATUS: "get_page_status",
|
|
31
33
|
CLICK_ROW_ACTION: "click_row_action",
|
|
32
34
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
36
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
33
37
|
ANALYZE_TABLE: "analyze_table",
|
|
34
38
|
SELECT: "select_combobox",
|
|
35
39
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -46,8 +50,13 @@ const Types = {
|
|
|
46
50
|
LOAD_DATA: "load_data",
|
|
47
51
|
SET_INPUT: "set_input",
|
|
48
52
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
53
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
54
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
49
55
|
};
|
|
50
56
|
export const apps = {};
|
|
57
|
+
const formatElementName = (elementName) => {
|
|
58
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
59
|
+
};
|
|
51
60
|
class StableBrowser {
|
|
52
61
|
browser;
|
|
53
62
|
page;
|
|
@@ -60,7 +69,6 @@ class StableBrowser {
|
|
|
60
69
|
configuration = null;
|
|
61
70
|
appName = "main";
|
|
62
71
|
tags = null;
|
|
63
|
-
isRecording = false;
|
|
64
72
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
65
73
|
this.browser = browser;
|
|
66
74
|
this.page = page;
|
|
@@ -91,35 +99,17 @@ class StableBrowser {
|
|
|
91
99
|
catch (e) {
|
|
92
100
|
this.logger.error("unable to read ai_config.json");
|
|
93
101
|
}
|
|
94
|
-
context.pageLoading = { status: false };
|
|
95
|
-
context.pages = [this.page];
|
|
96
102
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
97
103
|
this.world = world;
|
|
104
|
+
context.pages = [this.page];
|
|
105
|
+
context.pageLoading = { status: false };
|
|
98
106
|
this.registerEventListeners(this.context);
|
|
99
107
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
100
108
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
101
109
|
}
|
|
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
110
|
registerEventListeners(context) {
|
|
121
111
|
this.registerConsoleLogListener(this.page, context);
|
|
122
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
112
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
123
113
|
if (!context.pageLoading) {
|
|
124
114
|
context.pageLoading = { status: false };
|
|
125
115
|
}
|
|
@@ -164,7 +154,7 @@ class StableBrowser {
|
|
|
164
154
|
if (this.appName === appName) {
|
|
165
155
|
return;
|
|
166
156
|
}
|
|
167
|
-
let
|
|
157
|
+
let navigate = false;
|
|
168
158
|
if (!apps[appName]) {
|
|
169
159
|
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
170
160
|
newContextCreated = true;
|
|
@@ -175,32 +165,15 @@ class StableBrowser {
|
|
|
175
165
|
};
|
|
176
166
|
}
|
|
177
167
|
const tempContext = {};
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
_copyContext(this, tempContext);
|
|
169
|
+
_copyContext(apps[appName], this);
|
|
180
170
|
apps[this.appName] = tempContext;
|
|
181
171
|
this.appName = appName;
|
|
182
|
-
if (
|
|
183
|
-
this.registerEventListeners(this.context);
|
|
172
|
+
if (navigate) {
|
|
184
173
|
await this.goto(this.context.environment.baseUrl);
|
|
185
174
|
await this.waitForPageLoad();
|
|
186
175
|
}
|
|
187
176
|
}
|
|
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
177
|
registerConsoleLogListener(page, context) {
|
|
205
178
|
if (!this.context.webLogger) {
|
|
206
179
|
this.context.webLogger = [];
|
|
@@ -264,55 +237,51 @@ class StableBrowser {
|
|
|
264
237
|
// async closeUnexpectedPopups() {
|
|
265
238
|
// await closeUnexpectedPopups(this.page);
|
|
266
239
|
// }
|
|
267
|
-
async goto(url) {
|
|
240
|
+
async goto(url, world = null) {
|
|
241
|
+
if (!url) {
|
|
242
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
243
|
+
}
|
|
268
244
|
if (!url.startsWith("http")) {
|
|
269
245
|
url = "https://" + url;
|
|
270
246
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
247
|
+
const state = {
|
|
248
|
+
value: url,
|
|
249
|
+
world: world,
|
|
250
|
+
type: Types.NAVIGATE,
|
|
251
|
+
text: `Navigate Page to: ${url}`,
|
|
252
|
+
operation: "goto",
|
|
253
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
254
|
+
info: {},
|
|
255
|
+
locate: false,
|
|
256
|
+
scroll: false,
|
|
257
|
+
screenshot: false,
|
|
258
|
+
highlight: false,
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
await _preCommand(state, this);
|
|
262
|
+
await this.page.goto(url, {
|
|
263
|
+
timeout: 60000,
|
|
264
|
+
});
|
|
265
|
+
await _screenshot(state, this);
|
|
278
266
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// remove the _ prefix
|
|
283
|
-
regValue = key.substring(1);
|
|
284
|
-
}
|
|
285
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error("Error on goto", error);
|
|
269
|
+
_commandError(state, error, this);
|
|
286
270
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
290
|
-
// check if not null
|
|
291
|
-
if (!locator) {
|
|
292
|
-
return locator;
|
|
271
|
+
finally {
|
|
272
|
+
_commandFinally(state, this);
|
|
293
273
|
}
|
|
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
274
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
275
|
+
async _getLocator(locator, scope, _params) {
|
|
276
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
277
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
278
|
+
for (let key in locator) {
|
|
279
|
+
if (typeof locator[key] !== "string")
|
|
280
|
+
continue;
|
|
281
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
282
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
311
283
|
}
|
|
312
284
|
}
|
|
313
|
-
}
|
|
314
|
-
_getLocator(locator, scope, _params) {
|
|
315
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
316
285
|
let locatorReturn;
|
|
317
286
|
if (locator.role) {
|
|
318
287
|
if (locator.role[1].nameReg) {
|
|
@@ -320,7 +289,7 @@ class StableBrowser {
|
|
|
320
289
|
delete locator.role[1].nameReg;
|
|
321
290
|
}
|
|
322
291
|
// if (locator.role[1].name) {
|
|
323
|
-
// locator.role[1].name =
|
|
292
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
324
293
|
// }
|
|
325
294
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
326
295
|
}
|
|
@@ -363,140 +332,54 @@ class StableBrowser {
|
|
|
363
332
|
if (css && css.locator) {
|
|
364
333
|
css = css.locator;
|
|
365
334
|
}
|
|
366
|
-
let result = await this._locateElementByText(scope,
|
|
335
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
367
336
|
if (result.elementCount === 0) {
|
|
368
337
|
return;
|
|
369
338
|
}
|
|
370
|
-
let textElementCss = "[data-blinq-id
|
|
339
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
371
340
|
// css climb to parent element
|
|
372
341
|
const climbArray = [];
|
|
373
342
|
for (let i = 0; i < climb; i++) {
|
|
374
343
|
climbArray.push("..");
|
|
375
344
|
}
|
|
376
345
|
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
|
-
}
|
|
346
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
347
|
+
if (css) {
|
|
348
|
+
resultCss = resultCss + " >> " + css;
|
|
349
|
+
}
|
|
350
|
+
return resultCss;
|
|
351
|
+
}
|
|
352
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
353
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
354
|
+
const locator = scope.locator(query);
|
|
355
|
+
const count = await locator.count();
|
|
356
|
+
if (!tag1) {
|
|
357
|
+
tag1 = "*";
|
|
358
|
+
}
|
|
359
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
360
|
+
let tagCount = 0;
|
|
361
|
+
for (let i = 0; i < count; i++) {
|
|
362
|
+
const element = locator.nth(i);
|
|
363
|
+
// check if the tag matches
|
|
364
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
365
|
+
if (!tag.startsWith("*")) {
|
|
366
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
367
|
+
return false;
|
|
465
368
|
}
|
|
466
369
|
}
|
|
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);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
let elementCount = 0;
|
|
486
|
-
if (noChildElements.length > 0) {
|
|
487
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
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++;
|
|
370
|
+
if (!el.setAttribute) {
|
|
371
|
+
el = el.parentElement;
|
|
494
372
|
}
|
|
373
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
374
|
+
return true;
|
|
375
|
+
}, [tag1, randomToken]))) {
|
|
376
|
+
continue;
|
|
495
377
|
}
|
|
496
|
-
|
|
497
|
-
}
|
|
378
|
+
tagCount++;
|
|
379
|
+
}
|
|
380
|
+
return { elementCount: tagCount, randomToken };
|
|
498
381
|
}
|
|
499
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
382
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
500
383
|
if (!info) {
|
|
501
384
|
info = {};
|
|
502
385
|
}
|
|
@@ -505,10 +388,13 @@ class StableBrowser {
|
|
|
505
388
|
}
|
|
506
389
|
if (!info.log) {
|
|
507
390
|
info.log = "";
|
|
391
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
508
392
|
}
|
|
509
393
|
let locatorSearch = selectorHierarchy[index];
|
|
394
|
+
let originalLocatorSearch = "";
|
|
510
395
|
try {
|
|
511
|
-
|
|
396
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
397
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
512
398
|
}
|
|
513
399
|
catch (e) {
|
|
514
400
|
console.error(e);
|
|
@@ -516,30 +402,31 @@ class StableBrowser {
|
|
|
516
402
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
517
403
|
let locator = null;
|
|
518
404
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
519
|
-
|
|
405
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
406
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
520
407
|
if (!locatorString) {
|
|
521
408
|
info.failCause.textNotFound = true;
|
|
522
|
-
info.failCause.lastError =
|
|
409
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
523
410
|
return;
|
|
524
411
|
}
|
|
525
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
412
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
526
413
|
}
|
|
527
414
|
else if (locatorSearch.text) {
|
|
528
|
-
let text =
|
|
529
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
415
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
416
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
530
417
|
if (result.elementCount === 0) {
|
|
531
418
|
info.failCause.textNotFound = true;
|
|
532
|
-
info.failCause.lastError =
|
|
419
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
533
420
|
return;
|
|
534
421
|
}
|
|
535
|
-
locatorSearch.css = "[data-blinq-id
|
|
422
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
536
423
|
if (locatorSearch.childCss) {
|
|
537
424
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
538
425
|
}
|
|
539
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
426
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
540
427
|
}
|
|
541
428
|
else {
|
|
542
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
429
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
543
430
|
}
|
|
544
431
|
// let cssHref = false;
|
|
545
432
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -552,18 +439,27 @@ class StableBrowser {
|
|
|
552
439
|
//info.log += "total elements found " + count + "\n";
|
|
553
440
|
//let visibleCount = 0;
|
|
554
441
|
let visibleLocator = null;
|
|
555
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
442
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
556
443
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
444
|
+
if (info.locatorLog) {
|
|
445
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
446
|
+
}
|
|
557
447
|
return;
|
|
558
448
|
}
|
|
449
|
+
if (info.locatorLog && count === 0) {
|
|
450
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
451
|
+
}
|
|
559
452
|
for (let j = 0; j < count; j++) {
|
|
560
453
|
let visible = await locator.nth(j).isVisible();
|
|
561
454
|
const enabled = await locator.nth(j).isEnabled();
|
|
562
455
|
if (!visibleOnly) {
|
|
563
456
|
visible = true;
|
|
564
457
|
}
|
|
565
|
-
if (visible && enabled) {
|
|
458
|
+
if (visible && (allowDisabled || enabled)) {
|
|
566
459
|
foundLocators.push(locator.nth(j));
|
|
460
|
+
if (info.locatorLog) {
|
|
461
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
462
|
+
}
|
|
567
463
|
}
|
|
568
464
|
else {
|
|
569
465
|
info.failCause.visible = visible;
|
|
@@ -571,8 +467,16 @@ class StableBrowser {
|
|
|
571
467
|
if (!info.printMessages) {
|
|
572
468
|
info.printMessages = {};
|
|
573
469
|
}
|
|
470
|
+
if (info.locatorLog && !visible) {
|
|
471
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
472
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
473
|
+
}
|
|
474
|
+
if (info.locatorLog && !enabled) {
|
|
475
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
476
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
477
|
+
}
|
|
574
478
|
if (!info.printMessages[j.toString()]) {
|
|
575
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
479
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
576
480
|
info.printMessages[j.toString()] = true;
|
|
577
481
|
}
|
|
578
482
|
}
|
|
@@ -588,12 +492,12 @@ class StableBrowser {
|
|
|
588
492
|
if (!info) {
|
|
589
493
|
info = {};
|
|
590
494
|
}
|
|
591
|
-
info.log += "scan for popup handlers" + "\n";
|
|
495
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
592
496
|
const handlerGroup = [];
|
|
593
497
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
594
498
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
595
499
|
}
|
|
596
|
-
const scopes = this.page.frames()
|
|
500
|
+
const scopes = [this.page, ...this.page.frames()];
|
|
597
501
|
let result = null;
|
|
598
502
|
let scope = null;
|
|
599
503
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -636,7 +540,7 @@ class StableBrowser {
|
|
|
636
540
|
}
|
|
637
541
|
return { rerun: false };
|
|
638
542
|
}
|
|
639
|
-
async _locate(selectors, info, _params, timeout) {
|
|
543
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
640
544
|
if (!timeout) {
|
|
641
545
|
timeout = 30000;
|
|
642
546
|
}
|
|
@@ -646,9 +550,18 @@ class StableBrowser {
|
|
|
646
550
|
let selector = selectors.locators[j];
|
|
647
551
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
648
552
|
}
|
|
649
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
553
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
650
554
|
if (!element.rerun) {
|
|
651
|
-
|
|
555
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
556
|
+
element.evaluate((el, randomToken) => {
|
|
557
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
558
|
+
}, randomToken);
|
|
559
|
+
if (element._frame) {
|
|
560
|
+
return element;
|
|
561
|
+
}
|
|
562
|
+
const scope = element.page();
|
|
563
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
564
|
+
return newSelector;
|
|
652
565
|
}
|
|
653
566
|
}
|
|
654
567
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -659,6 +572,7 @@ class StableBrowser {
|
|
|
659
572
|
info.failCause = {};
|
|
660
573
|
info.log = "";
|
|
661
574
|
}
|
|
575
|
+
let startTime = Date.now();
|
|
662
576
|
let scope = this.page;
|
|
663
577
|
if (selectors.frame) {
|
|
664
578
|
return selectors.frame;
|
|
@@ -689,9 +603,11 @@ class StableBrowser {
|
|
|
689
603
|
}
|
|
690
604
|
return framescope;
|
|
691
605
|
};
|
|
606
|
+
let fLocator = null;
|
|
692
607
|
while (true) {
|
|
693
608
|
let frameFound = false;
|
|
694
609
|
if (selectors.nestFrmLoc) {
|
|
610
|
+
fLocator = selectors.nestFrmLoc;
|
|
695
611
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
696
612
|
frameFound = true;
|
|
697
613
|
break;
|
|
@@ -700,6 +616,7 @@ class StableBrowser {
|
|
|
700
616
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
701
617
|
let frameLocator = selectors.frameLocators[i];
|
|
702
618
|
if (frameLocator.css) {
|
|
619
|
+
fLocator = frameLocator.css;
|
|
703
620
|
scope = scope.frameLocator(frameLocator.css);
|
|
704
621
|
frameFound = true;
|
|
705
622
|
break;
|
|
@@ -707,18 +624,25 @@ class StableBrowser {
|
|
|
707
624
|
}
|
|
708
625
|
}
|
|
709
626
|
if (!frameFound && selectors.iframe_src) {
|
|
627
|
+
fLocator = selectors.iframe_src;
|
|
710
628
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
711
629
|
}
|
|
712
630
|
if (!scope) {
|
|
713
|
-
info
|
|
714
|
-
|
|
631
|
+
if (info && info.locatorLog) {
|
|
632
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
633
|
+
}
|
|
634
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
635
|
+
if (Date.now() - startTime > timeout) {
|
|
715
636
|
info.failCause.iframeNotFound = true;
|
|
716
|
-
info.failCause.lastError =
|
|
637
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
717
638
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
718
639
|
}
|
|
719
640
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
720
641
|
}
|
|
721
642
|
else {
|
|
643
|
+
if (info && info.locatorLog) {
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
645
|
+
}
|
|
722
646
|
break;
|
|
723
647
|
}
|
|
724
648
|
}
|
|
@@ -735,15 +659,16 @@ class StableBrowser {
|
|
|
735
659
|
return bodyContent;
|
|
736
660
|
});
|
|
737
661
|
}
|
|
738
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
662
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
739
663
|
if (!info) {
|
|
740
664
|
info = {};
|
|
741
665
|
info.failCause = {};
|
|
742
666
|
info.log = "";
|
|
667
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
743
668
|
}
|
|
744
669
|
let highPriorityTimeout = 5000;
|
|
745
670
|
let visibleOnlyTimeout = 6000;
|
|
746
|
-
let startTime =
|
|
671
|
+
let startTime = Date.now();
|
|
747
672
|
let locatorsCount = 0;
|
|
748
673
|
let lazy_scroll = false;
|
|
749
674
|
//let arrayMode = Array.isArray(selectors);
|
|
@@ -783,17 +708,17 @@ class StableBrowser {
|
|
|
783
708
|
}
|
|
784
709
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
785
710
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
786
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
711
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
787
712
|
if (result.foundElements.length === 0) {
|
|
788
713
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
789
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
714
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
790
715
|
}
|
|
791
716
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
792
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
717
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
793
718
|
}
|
|
794
719
|
else {
|
|
795
720
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
796
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
721
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
797
722
|
}
|
|
798
723
|
}
|
|
799
724
|
let foundElements = result.foundElements;
|
|
@@ -834,30 +759,38 @@ class StableBrowser {
|
|
|
834
759
|
return maxCountElement.locator;
|
|
835
760
|
}
|
|
836
761
|
}
|
|
837
|
-
if (
|
|
762
|
+
if (Date.now() - startTime > timeout) {
|
|
838
763
|
break;
|
|
839
764
|
}
|
|
840
|
-
if (
|
|
841
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
765
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
766
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
842
767
|
highPriorityOnly = false;
|
|
843
768
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
844
769
|
lazy_scroll = true;
|
|
845
|
-
await this.
|
|
770
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
846
771
|
}
|
|
847
772
|
}
|
|
848
|
-
if (
|
|
849
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
773
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
774
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
850
775
|
visibleOnly = false;
|
|
851
776
|
}
|
|
852
777
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
853
778
|
}
|
|
854
779
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
855
|
-
info.
|
|
780
|
+
// if (info.locatorLog) {
|
|
781
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
782
|
+
// for (let line of lines) {
|
|
783
|
+
// this.logger.debug(line);
|
|
784
|
+
// }
|
|
785
|
+
// }
|
|
786
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
856
787
|
info.failCause.locatorNotFound = true;
|
|
857
|
-
info
|
|
788
|
+
if (!info?.failCause?.lastError) {
|
|
789
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
790
|
+
}
|
|
858
791
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
859
792
|
}
|
|
860
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
793
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
861
794
|
let foundElements = [];
|
|
862
795
|
const result = {
|
|
863
796
|
foundElements: foundElements,
|
|
@@ -865,14 +798,15 @@ class StableBrowser {
|
|
|
865
798
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
866
799
|
let foundLocators = [];
|
|
867
800
|
try {
|
|
868
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
801
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
869
802
|
}
|
|
870
803
|
catch (e) {
|
|
871
|
-
this
|
|
872
|
-
this.logger.debug(
|
|
804
|
+
// this call can fail it the browser is navigating
|
|
805
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
806
|
+
// this.logger.debug(e);
|
|
873
807
|
foundLocators = [];
|
|
874
808
|
try {
|
|
875
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
809
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
876
810
|
}
|
|
877
811
|
catch (e) {
|
|
878
812
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -888,11 +822,27 @@ class StableBrowser {
|
|
|
888
822
|
}
|
|
889
823
|
if (foundLocators.length > 1) {
|
|
890
824
|
info.failCause.foundMultiple = true;
|
|
825
|
+
if (info.locatorLog) {
|
|
826
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
827
|
+
}
|
|
891
828
|
}
|
|
892
829
|
}
|
|
893
830
|
return result;
|
|
894
831
|
}
|
|
895
832
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
833
|
+
const state = {
|
|
834
|
+
locate: false,
|
|
835
|
+
scroll: false,
|
|
836
|
+
highlight: false,
|
|
837
|
+
_params,
|
|
838
|
+
options,
|
|
839
|
+
world,
|
|
840
|
+
type: Types.CLICK,
|
|
841
|
+
text: "Click element",
|
|
842
|
+
operation: "simpleClick",
|
|
843
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
844
|
+
};
|
|
845
|
+
_preCommand(state, this);
|
|
896
846
|
const startTime = Date.now();
|
|
897
847
|
let timeout = 30000;
|
|
898
848
|
if (options && options.timeout) {
|
|
@@ -917,13 +867,31 @@ class StableBrowser {
|
|
|
917
867
|
catch (e) {
|
|
918
868
|
if (performance.now() - startTime > timeout) {
|
|
919
869
|
// throw e;
|
|
920
|
-
|
|
870
|
+
try {
|
|
871
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
872
|
+
}
|
|
873
|
+
finally {
|
|
874
|
+
_commandFinally(state, this);
|
|
875
|
+
}
|
|
921
876
|
}
|
|
922
877
|
}
|
|
923
878
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
924
879
|
}
|
|
925
880
|
}
|
|
926
881
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
882
|
+
const state = {
|
|
883
|
+
locate: false,
|
|
884
|
+
scroll: false,
|
|
885
|
+
highlight: false,
|
|
886
|
+
_params,
|
|
887
|
+
options,
|
|
888
|
+
world,
|
|
889
|
+
type: Types.FILL,
|
|
890
|
+
text: "Fill element",
|
|
891
|
+
operation: "simpleClickType",
|
|
892
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
893
|
+
};
|
|
894
|
+
_preCommand(state, this);
|
|
927
895
|
const startTime = Date.now();
|
|
928
896
|
let timeout = 30000;
|
|
929
897
|
if (options && options.timeout) {
|
|
@@ -948,7 +916,12 @@ class StableBrowser {
|
|
|
948
916
|
catch (e) {
|
|
949
917
|
if (performance.now() - startTime > timeout) {
|
|
950
918
|
// throw e;
|
|
951
|
-
|
|
919
|
+
try {
|
|
920
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
921
|
+
}
|
|
922
|
+
finally {
|
|
923
|
+
_commandFinally(state, this);
|
|
924
|
+
}
|
|
952
925
|
}
|
|
953
926
|
}
|
|
954
927
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -967,9 +940,9 @@ class StableBrowser {
|
|
|
967
940
|
};
|
|
968
941
|
try {
|
|
969
942
|
await _preCommand(state, this);
|
|
970
|
-
if (state.options && state.options.context) {
|
|
971
|
-
|
|
972
|
-
}
|
|
943
|
+
// if (state.options && state.options.context) {
|
|
944
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
945
|
+
// }
|
|
973
946
|
try {
|
|
974
947
|
await state.element.click();
|
|
975
948
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1007,9 +980,15 @@ class StableBrowser {
|
|
|
1007
980
|
// let element = await this._locate(selectors, info, _params);
|
|
1008
981
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1009
982
|
try {
|
|
1010
|
-
//
|
|
983
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
984
|
+
// console.log(`Highlighting while running from recorder`);
|
|
985
|
+
await this._highlightElements(element);
|
|
1011
986
|
await state.element.setChecked(checked);
|
|
1012
987
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
988
|
+
// await this._unHighlightElements(element);
|
|
989
|
+
// }
|
|
990
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
991
|
+
// await this._unHighlightElements(element);
|
|
1013
992
|
}
|
|
1014
993
|
catch (e) {
|
|
1015
994
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1048,6 +1027,7 @@ class StableBrowser {
|
|
|
1048
1027
|
await _preCommand(state, this);
|
|
1049
1028
|
try {
|
|
1050
1029
|
await state.element.hover();
|
|
1030
|
+
// await _screenshot(state, this);
|
|
1051
1031
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1052
1032
|
}
|
|
1053
1033
|
catch (e) {
|
|
@@ -1055,6 +1035,7 @@ class StableBrowser {
|
|
|
1055
1035
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1056
1036
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1057
1037
|
await state.element.hover({ timeout: 10000 });
|
|
1038
|
+
// await _screenshot(state, this);
|
|
1058
1039
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
1040
|
}
|
|
1060
1041
|
await _screenshot(state, this);
|
|
@@ -1329,7 +1310,12 @@ class StableBrowser {
|
|
|
1329
1310
|
await this.waitForPageLoad();
|
|
1330
1311
|
}
|
|
1331
1312
|
else if (enter === false) {
|
|
1332
|
-
|
|
1313
|
+
try {
|
|
1314
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1315
|
+
}
|
|
1316
|
+
catch (e) {
|
|
1317
|
+
// ignore
|
|
1318
|
+
}
|
|
1333
1319
|
//await this.page.keyboard.press("Tab");
|
|
1334
1320
|
}
|
|
1335
1321
|
else {
|
|
@@ -1386,6 +1372,7 @@ class StableBrowser {
|
|
|
1386
1372
|
let screenshotPath = null;
|
|
1387
1373
|
if (!info.log) {
|
|
1388
1374
|
info.log = "";
|
|
1375
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1389
1376
|
}
|
|
1390
1377
|
info.operation = "getText";
|
|
1391
1378
|
info.selectors = selectors;
|
|
@@ -1408,6 +1395,18 @@ class StableBrowser {
|
|
|
1408
1395
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1409
1396
|
try {
|
|
1410
1397
|
await this._highlightElements(element);
|
|
1398
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1399
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1400
|
+
// this._highlightElements(element)
|
|
1401
|
+
// .then(async () => {
|
|
1402
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1403
|
+
// this._unhighlightElements(element).then(
|
|
1404
|
+
// () => {}
|
|
1405
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1406
|
+
// );
|
|
1407
|
+
// })
|
|
1408
|
+
// .catch(e);
|
|
1409
|
+
// }
|
|
1411
1410
|
const elementText = await element.innerText();
|
|
1412
1411
|
return {
|
|
1413
1412
|
text: elementText,
|
|
@@ -1419,7 +1418,7 @@ class StableBrowser {
|
|
|
1419
1418
|
}
|
|
1420
1419
|
catch (e) {
|
|
1421
1420
|
//await this.closeUnexpectedPopups();
|
|
1422
|
-
this.logger.info("no innerText will use textContent");
|
|
1421
|
+
this.logger.info("no innerText, will use textContent");
|
|
1423
1422
|
const elementText = await element.textContent();
|
|
1424
1423
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1425
1424
|
}
|
|
@@ -1545,22 +1544,6 @@ class StableBrowser {
|
|
|
1545
1544
|
_commandFinally(state, this);
|
|
1546
1545
|
}
|
|
1547
1546
|
}
|
|
1548
|
-
_getDataFile(world = null) {
|
|
1549
|
-
let dataFile = null;
|
|
1550
|
-
if (world && world.reportFolder) {
|
|
1551
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1552
|
-
}
|
|
1553
|
-
else if (this.reportFolder) {
|
|
1554
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1555
|
-
}
|
|
1556
|
-
else if (this.context && this.context.reportFolder) {
|
|
1557
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1558
|
-
}
|
|
1559
|
-
else {
|
|
1560
|
-
dataFile = "data.json";
|
|
1561
|
-
}
|
|
1562
|
-
return dataFile;
|
|
1563
|
-
}
|
|
1564
1547
|
async waitForUserInput(message, world = null) {
|
|
1565
1548
|
if (!message) {
|
|
1566
1549
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1589,7 +1572,7 @@ class StableBrowser {
|
|
|
1589
1572
|
return;
|
|
1590
1573
|
}
|
|
1591
1574
|
// if data file exists, load it
|
|
1592
|
-
const dataFile =
|
|
1575
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1593
1576
|
let data = this.getTestData(world);
|
|
1594
1577
|
// merge the testData with the existing data
|
|
1595
1578
|
Object.assign(data, testData);
|
|
@@ -1692,7 +1675,7 @@ class StableBrowser {
|
|
|
1692
1675
|
}
|
|
1693
1676
|
}
|
|
1694
1677
|
getTestData(world = null) {
|
|
1695
|
-
const dataFile =
|
|
1678
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1696
1679
|
let data = {};
|
|
1697
1680
|
if (fs.existsSync(dataFile)) {
|
|
1698
1681
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1779,6 +1762,15 @@ class StableBrowser {
|
|
|
1779
1762
|
document.documentElement.clientWidth,
|
|
1780
1763
|
])));
|
|
1781
1764
|
let screenshotBuffer = null;
|
|
1765
|
+
// if (focusedElement) {
|
|
1766
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1767
|
+
// await this._unhighlightElements(focusedElement);
|
|
1768
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1769
|
+
// console.log(`Unhighlighted previous element`);
|
|
1770
|
+
// }
|
|
1771
|
+
// if (focusedElement) {
|
|
1772
|
+
// await this._highlightElements(focusedElement);
|
|
1773
|
+
// }
|
|
1782
1774
|
if (this.context.browserName === "chromium") {
|
|
1783
1775
|
const client = await playContext.newCDPSession(this.page);
|
|
1784
1776
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1800,6 +1792,10 @@ class StableBrowser {
|
|
|
1800
1792
|
else {
|
|
1801
1793
|
screenshotBuffer = await this.page.screenshot();
|
|
1802
1794
|
}
|
|
1795
|
+
// if (focusedElement) {
|
|
1796
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1797
|
+
// await this._unhighlightElements(focusedElement);
|
|
1798
|
+
// }
|
|
1803
1799
|
let image = await Jimp.read(screenshotBuffer);
|
|
1804
1800
|
// Get the image dimensions
|
|
1805
1801
|
const { width, height } = image.bitmap;
|
|
@@ -1812,6 +1808,7 @@ class StableBrowser {
|
|
|
1812
1808
|
else {
|
|
1813
1809
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1814
1810
|
}
|
|
1811
|
+
return screenshotBuffer;
|
|
1815
1812
|
}
|
|
1816
1813
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1817
1814
|
const state = {
|
|
@@ -1849,6 +1846,7 @@ class StableBrowser {
|
|
|
1849
1846
|
text: `Extract attribute from element`,
|
|
1850
1847
|
operation: "extractAttribute",
|
|
1851
1848
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1849
|
+
allowDisabled: true,
|
|
1852
1850
|
};
|
|
1853
1851
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1854
1852
|
try {
|
|
@@ -1870,6 +1868,76 @@ class StableBrowser {
|
|
|
1870
1868
|
state.info.value = state.value;
|
|
1871
1869
|
this.setTestData({ [variable]: state.value }, world);
|
|
1872
1870
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1871
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1872
|
+
return state.info;
|
|
1873
|
+
}
|
|
1874
|
+
catch (e) {
|
|
1875
|
+
await _commandError(state, e, this);
|
|
1876
|
+
}
|
|
1877
|
+
finally {
|
|
1878
|
+
_commandFinally(state, this);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1882
|
+
const state = {
|
|
1883
|
+
selectors,
|
|
1884
|
+
_params,
|
|
1885
|
+
attribute,
|
|
1886
|
+
value,
|
|
1887
|
+
options,
|
|
1888
|
+
world,
|
|
1889
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1890
|
+
highlight: true,
|
|
1891
|
+
screenshot: true,
|
|
1892
|
+
text: `Verify element attribute`,
|
|
1893
|
+
operation: "verifyAttribute",
|
|
1894
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1895
|
+
allowDisabled: true,
|
|
1896
|
+
};
|
|
1897
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1898
|
+
let val;
|
|
1899
|
+
let expectedValue;
|
|
1900
|
+
try {
|
|
1901
|
+
await _preCommand(state, this);
|
|
1902
|
+
expectedValue = state.value;
|
|
1903
|
+
state.info.expectedValue = expectedValue;
|
|
1904
|
+
switch (attribute) {
|
|
1905
|
+
case "innerText":
|
|
1906
|
+
val = String(await state.element.innerText());
|
|
1907
|
+
break;
|
|
1908
|
+
case "value":
|
|
1909
|
+
val = String(await state.element.inputValue());
|
|
1910
|
+
break;
|
|
1911
|
+
case "checked":
|
|
1912
|
+
val = String(await state.element.isChecked());
|
|
1913
|
+
break;
|
|
1914
|
+
case "disabled":
|
|
1915
|
+
val = String(await state.element.isDisabled());
|
|
1916
|
+
break;
|
|
1917
|
+
case "readOnly":
|
|
1918
|
+
const isEditable = await state.element.isEditable();
|
|
1919
|
+
val = String(!isEditable);
|
|
1920
|
+
break;
|
|
1921
|
+
default:
|
|
1922
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1923
|
+
break;
|
|
1924
|
+
}
|
|
1925
|
+
state.info.value = val;
|
|
1926
|
+
let regex;
|
|
1927
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1928
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1929
|
+
regex = new RegExp(patternBody, "g");
|
|
1930
|
+
}
|
|
1931
|
+
else {
|
|
1932
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1933
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1934
|
+
}
|
|
1935
|
+
if (!val.match(regex)) {
|
|
1936
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1937
|
+
state.info.failCause.assertionFailed = true;
|
|
1938
|
+
state.info.failCause.lastError = errorMessage;
|
|
1939
|
+
throw new Error(errorMessage);
|
|
1940
|
+
}
|
|
1873
1941
|
return state.info;
|
|
1874
1942
|
}
|
|
1875
1943
|
catch (e) {
|
|
@@ -1897,7 +1965,7 @@ class StableBrowser {
|
|
|
1897
1965
|
if (options && options.timeout) {
|
|
1898
1966
|
timeout = options.timeout;
|
|
1899
1967
|
}
|
|
1900
|
-
const serviceUrl =
|
|
1968
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1901
1969
|
const request = {
|
|
1902
1970
|
method: "POST",
|
|
1903
1971
|
url: serviceUrl,
|
|
@@ -1968,27 +2036,32 @@ class StableBrowser {
|
|
|
1968
2036
|
async _highlightElements(scope, css) {
|
|
1969
2037
|
try {
|
|
1970
2038
|
if (!scope) {
|
|
2039
|
+
// console.log(`Scope is not defined`);
|
|
1971
2040
|
return;
|
|
1972
2041
|
}
|
|
1973
2042
|
if (!css) {
|
|
1974
2043
|
scope
|
|
1975
2044
|
.evaluate((node) => {
|
|
1976
2045
|
if (node && node.style) {
|
|
1977
|
-
let
|
|
1978
|
-
|
|
2046
|
+
let originalOutline = node.style.outline;
|
|
2047
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2048
|
+
// node.__previousOutline = originalOutline;
|
|
2049
|
+
node.style.outline = "2px solid red";
|
|
2050
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1979
2051
|
if (window) {
|
|
1980
2052
|
window.addEventListener("beforeunload", function (e) {
|
|
1981
|
-
node.style.
|
|
2053
|
+
node.style.outline = originalOutline;
|
|
1982
2054
|
});
|
|
1983
2055
|
}
|
|
1984
2056
|
setTimeout(function () {
|
|
1985
|
-
node.style.
|
|
2057
|
+
node.style.outline = originalOutline;
|
|
1986
2058
|
}, 2000);
|
|
1987
2059
|
}
|
|
1988
2060
|
})
|
|
1989
2061
|
.then(() => { })
|
|
1990
2062
|
.catch((e) => {
|
|
1991
2063
|
// ignore
|
|
2064
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1992
2065
|
});
|
|
1993
2066
|
}
|
|
1994
2067
|
else {
|
|
@@ -2004,17 +2077,18 @@ class StableBrowser {
|
|
|
2004
2077
|
if (!element.style) {
|
|
2005
2078
|
return;
|
|
2006
2079
|
}
|
|
2007
|
-
|
|
2080
|
+
let originalOutline = element.style.outline;
|
|
2081
|
+
element.__previousOutline = originalOutline;
|
|
2008
2082
|
// Set the new border to be red and 2px solid
|
|
2009
|
-
element.style.
|
|
2083
|
+
element.style.outline = "2px solid red";
|
|
2010
2084
|
if (window) {
|
|
2011
2085
|
window.addEventListener("beforeunload", function (e) {
|
|
2012
|
-
element.style.
|
|
2086
|
+
element.style.outline = originalOutline;
|
|
2013
2087
|
});
|
|
2014
2088
|
}
|
|
2015
2089
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2016
2090
|
setTimeout(function () {
|
|
2017
|
-
element.style.
|
|
2091
|
+
element.style.outline = originalOutline;
|
|
2018
2092
|
}, 2000);
|
|
2019
2093
|
}
|
|
2020
2094
|
return;
|
|
@@ -2022,6 +2096,7 @@ class StableBrowser {
|
|
|
2022
2096
|
.then(() => { })
|
|
2023
2097
|
.catch((e) => {
|
|
2024
2098
|
// ignore
|
|
2099
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2025
2100
|
});
|
|
2026
2101
|
}
|
|
2027
2102
|
}
|
|
@@ -2029,6 +2104,54 @@ class StableBrowser {
|
|
|
2029
2104
|
console.debug(error);
|
|
2030
2105
|
}
|
|
2031
2106
|
}
|
|
2107
|
+
// async _unhighlightElements(scope, css) {
|
|
2108
|
+
// try {
|
|
2109
|
+
// if (!scope) {
|
|
2110
|
+
// return;
|
|
2111
|
+
// }
|
|
2112
|
+
// if (!css) {
|
|
2113
|
+
// scope
|
|
2114
|
+
// .evaluate((node) => {
|
|
2115
|
+
// if (node && node.style) {
|
|
2116
|
+
// if (!node.__previousOutline) {
|
|
2117
|
+
// node.style.outline = "";
|
|
2118
|
+
// } else {
|
|
2119
|
+
// node.style.outline = node.__previousOutline;
|
|
2120
|
+
// }
|
|
2121
|
+
// }
|
|
2122
|
+
// })
|
|
2123
|
+
// .then(() => {})
|
|
2124
|
+
// .catch((e) => {
|
|
2125
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2126
|
+
// });
|
|
2127
|
+
// } else {
|
|
2128
|
+
// scope
|
|
2129
|
+
// .evaluate(([css]) => {
|
|
2130
|
+
// if (!css) {
|
|
2131
|
+
// return;
|
|
2132
|
+
// }
|
|
2133
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2134
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2135
|
+
// let element = elements[i];
|
|
2136
|
+
// if (!element.style) {
|
|
2137
|
+
// return;
|
|
2138
|
+
// }
|
|
2139
|
+
// if (!element.__previousOutline) {
|
|
2140
|
+
// element.style.outline = "";
|
|
2141
|
+
// } else {
|
|
2142
|
+
// element.style.outline = element.__previousOutline;
|
|
2143
|
+
// }
|
|
2144
|
+
// }
|
|
2145
|
+
// })
|
|
2146
|
+
// .then(() => {})
|
|
2147
|
+
// .catch((e) => {
|
|
2148
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2149
|
+
// });
|
|
2150
|
+
// }
|
|
2151
|
+
// } catch (error) {
|
|
2152
|
+
// // console.debug(error);
|
|
2153
|
+
// }
|
|
2154
|
+
// }
|
|
2032
2155
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2033
2156
|
const startTime = Date.now();
|
|
2034
2157
|
let error = null;
|
|
@@ -2090,6 +2213,35 @@ class StableBrowser {
|
|
|
2090
2213
|
});
|
|
2091
2214
|
}
|
|
2092
2215
|
}
|
|
2216
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2217
|
+
const frames = this.page.frames();
|
|
2218
|
+
let results = [];
|
|
2219
|
+
let ignoreCase = false;
|
|
2220
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2221
|
+
if (dateAlternatives.date) {
|
|
2222
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2223
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2224
|
+
result.frame = frames[i];
|
|
2225
|
+
results.push(result);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
else if (numberAlternatives.number) {
|
|
2229
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2230
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2231
|
+
result.frame = frames[i];
|
|
2232
|
+
results.push(result);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
else {
|
|
2236
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2237
|
+
result.frame = frames[i];
|
|
2238
|
+
results.push(result);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
state.info.results = results;
|
|
2242
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2243
|
+
return resultWithElementsFound;
|
|
2244
|
+
}
|
|
2093
2245
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2094
2246
|
text = unEscapeString(text);
|
|
2095
2247
|
const state = {
|
|
@@ -2099,7 +2251,7 @@ class StableBrowser {
|
|
|
2099
2251
|
locate: false,
|
|
2100
2252
|
scroll: false,
|
|
2101
2253
|
highlight: false,
|
|
2102
|
-
type: Types.
|
|
2254
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2103
2255
|
text: `Verify text exists in page`,
|
|
2104
2256
|
operation: "verifyTextExistInPage",
|
|
2105
2257
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2117,31 +2269,15 @@ class StableBrowser {
|
|
|
2117
2269
|
await _preCommand(state, this);
|
|
2118
2270
|
state.info.text = text;
|
|
2119
2271
|
while (true) {
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
else if (numberAlternatives.number) {
|
|
2131
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2132
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2133
|
-
result.frame = frames[i];
|
|
2134
|
-
results.push(result);
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
else {
|
|
2138
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2139
|
-
result.frame = frames[i];
|
|
2140
|
-
results.push(result);
|
|
2141
|
-
}
|
|
2272
|
+
let resultWithElementsFound = {
|
|
2273
|
+
length: 0,
|
|
2274
|
+
};
|
|
2275
|
+
try {
|
|
2276
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2277
|
+
}
|
|
2278
|
+
catch (error) {
|
|
2279
|
+
// ignore
|
|
2142
2280
|
}
|
|
2143
|
-
state.info.results = results;
|
|
2144
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2145
2281
|
if (resultWithElementsFound.length === 0) {
|
|
2146
2282
|
if (Date.now() - state.startTime > timeout) {
|
|
2147
2283
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2149,18 +2285,40 @@ class StableBrowser {
|
|
|
2149
2285
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2150
2286
|
continue;
|
|
2151
2287
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2288
|
+
try {
|
|
2289
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2290
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2291
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2292
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2293
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2294
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2295
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2296
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2297
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2298
|
+
// .then(async () => {
|
|
2299
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2300
|
+
// })
|
|
2301
|
+
// .catch(
|
|
2302
|
+
// (e) => {}
|
|
2303
|
+
// console.error(e)
|
|
2304
|
+
// );
|
|
2305
|
+
// });
|
|
2306
|
+
// }
|
|
2307
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2308
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2309
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2310
|
+
if (element) {
|
|
2311
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2312
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2313
|
+
// await _screenshot(state, this, element);
|
|
2314
|
+
}
|
|
2160
2315
|
}
|
|
2316
|
+
await _screenshot(state, this);
|
|
2317
|
+
return state.info;
|
|
2318
|
+
}
|
|
2319
|
+
catch (error) {
|
|
2320
|
+
console.error(error);
|
|
2161
2321
|
}
|
|
2162
|
-
await _screenshot(state, this);
|
|
2163
|
-
return state.info;
|
|
2164
2322
|
}
|
|
2165
2323
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2166
2324
|
}
|
|
@@ -2197,32 +2355,16 @@ class StableBrowser {
|
|
|
2197
2355
|
try {
|
|
2198
2356
|
await _preCommand(state, this);
|
|
2199
2357
|
state.info.text = text;
|
|
2358
|
+
let resultWithElementsFound = {
|
|
2359
|
+
length: null, // initial cannot be 0
|
|
2360
|
+
};
|
|
2200
2361
|
while (true) {
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2207
|
-
result.frame = frames[i];
|
|
2208
|
-
results.push(result);
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
else if (numberAlternatives.number) {
|
|
2212
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2213
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2214
|
-
result.frame = frames[i];
|
|
2215
|
-
results.push(result);
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
else {
|
|
2219
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2220
|
-
result.frame = frames[i];
|
|
2221
|
-
results.push(result);
|
|
2222
|
-
}
|
|
2362
|
+
try {
|
|
2363
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2364
|
+
}
|
|
2365
|
+
catch (error) {
|
|
2366
|
+
// ignore
|
|
2223
2367
|
}
|
|
2224
|
-
state.info.results = results;
|
|
2225
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2226
2368
|
if (resultWithElementsFound.length === 0) {
|
|
2227
2369
|
await _screenshot(state, this);
|
|
2228
2370
|
return state.info;
|
|
@@ -2240,15 +2382,115 @@ class StableBrowser {
|
|
|
2240
2382
|
_commandFinally(state, this);
|
|
2241
2383
|
}
|
|
2242
2384
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2385
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2386
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2387
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2388
|
+
const state = {
|
|
2389
|
+
text_search: textToVerify,
|
|
2390
|
+
options,
|
|
2391
|
+
world,
|
|
2392
|
+
locate: false,
|
|
2393
|
+
scroll: false,
|
|
2394
|
+
highlight: false,
|
|
2395
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2396
|
+
text: `Verify text with relation to another text`,
|
|
2397
|
+
operation: "verify_text_with_relation",
|
|
2398
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2399
|
+
};
|
|
2400
|
+
const timeout = this._getLoadTimeout(options);
|
|
2401
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2402
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2403
|
+
if (newValue !== textAnchor) {
|
|
2404
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2405
|
+
textAnchor = newValue;
|
|
2406
|
+
}
|
|
2407
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2408
|
+
if (newValue !== textToVerify) {
|
|
2409
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2410
|
+
textToVerify = newValue;
|
|
2411
|
+
}
|
|
2412
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2413
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2414
|
+
let foundAncore = false;
|
|
2415
|
+
try {
|
|
2416
|
+
await _preCommand(state, this);
|
|
2417
|
+
state.info.text = textToVerify;
|
|
2418
|
+
let resultWithElementsFound = {
|
|
2419
|
+
length: 0,
|
|
2420
|
+
};
|
|
2421
|
+
while (true) {
|
|
2422
|
+
try {
|
|
2423
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
// ignore
|
|
2427
|
+
}
|
|
2428
|
+
if (resultWithElementsFound.length === 0) {
|
|
2429
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2430
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2431
|
+
}
|
|
2432
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
try {
|
|
2436
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2437
|
+
foundAncore = true;
|
|
2438
|
+
const result = resultWithElementsFound[i];
|
|
2439
|
+
const token = result.randomToken;
|
|
2440
|
+
const frame = result.frame;
|
|
2441
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2442
|
+
const climbArray1 = [];
|
|
2443
|
+
for (let i = 0; i < climb; i++) {
|
|
2444
|
+
climbArray1.push("..");
|
|
2445
|
+
}
|
|
2446
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2447
|
+
css = css + " >> " + climbXpath;
|
|
2448
|
+
const count = await frame.locator(css).count();
|
|
2449
|
+
for (let j = 0; j < count; j++) {
|
|
2450
|
+
const continer = await frame.locator(css).nth(j);
|
|
2451
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2452
|
+
if (result.elementCount > 0) {
|
|
2453
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2454
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2455
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2456
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2457
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2458
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2459
|
+
// .then(async () => {
|
|
2460
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2461
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2462
|
+
// () => {}
|
|
2463
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2464
|
+
// );
|
|
2465
|
+
// })
|
|
2466
|
+
// .catch(e);
|
|
2467
|
+
// }
|
|
2468
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2469
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2470
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2471
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2472
|
+
if (element) {
|
|
2473
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2474
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2475
|
+
}
|
|
2476
|
+
await _screenshot(state, this);
|
|
2477
|
+
return state.info;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
catch (error) {
|
|
2483
|
+
console.error(error);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2247
2487
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2488
|
+
catch (e) {
|
|
2489
|
+
await _commandError(state, e, this);
|
|
2490
|
+
}
|
|
2491
|
+
finally {
|
|
2492
|
+
_commandFinally(state, this);
|
|
2250
2493
|
}
|
|
2251
|
-
return serviceUrl;
|
|
2252
2494
|
}
|
|
2253
2495
|
async visualVerification(text, options = {}, world = null) {
|
|
2254
2496
|
const startTime = Date.now();
|
|
@@ -2264,14 +2506,17 @@ class StableBrowser {
|
|
|
2264
2506
|
throw new Error("TOKEN is not set");
|
|
2265
2507
|
}
|
|
2266
2508
|
try {
|
|
2267
|
-
let serviceUrl =
|
|
2509
|
+
let serviceUrl = _getServerUrl();
|
|
2268
2510
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2269
2511
|
info.screenshotPath = screenshotPath;
|
|
2270
2512
|
const screenshot = await this.takeScreenshot();
|
|
2271
|
-
|
|
2272
|
-
method: "
|
|
2513
|
+
let request = {
|
|
2514
|
+
method: "post",
|
|
2515
|
+
maxBodyLength: Infinity,
|
|
2273
2516
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2274
2517
|
headers: {
|
|
2518
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2519
|
+
"x-source": "aaa",
|
|
2275
2520
|
"Content-Type": "application/json",
|
|
2276
2521
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2277
2522
|
},
|
|
@@ -2280,7 +2525,7 @@ class StableBrowser {
|
|
|
2280
2525
|
screenshot: screenshot,
|
|
2281
2526
|
}),
|
|
2282
2527
|
};
|
|
2283
|
-
|
|
2528
|
+
const result = await axios.request(request);
|
|
2284
2529
|
if (result.data.status !== true) {
|
|
2285
2530
|
throw new Error("Visual validation failed");
|
|
2286
2531
|
}
|
|
@@ -2353,6 +2598,7 @@ class StableBrowser {
|
|
|
2353
2598
|
let screenshotPath = null;
|
|
2354
2599
|
const info = {};
|
|
2355
2600
|
info.log = "";
|
|
2601
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2356
2602
|
info.operation = "getTableData";
|
|
2357
2603
|
info.selectors = selectors;
|
|
2358
2604
|
try {
|
|
@@ -2428,7 +2674,7 @@ class StableBrowser {
|
|
|
2428
2674
|
info.operation = "analyzeTable";
|
|
2429
2675
|
info.selectors = selectors;
|
|
2430
2676
|
info.query = query;
|
|
2431
|
-
query =
|
|
2677
|
+
query = _fixUsingParams(query, _params);
|
|
2432
2678
|
info.query_fixed = query;
|
|
2433
2679
|
info.operator = operator;
|
|
2434
2680
|
info.value = value;
|
|
@@ -2573,6 +2819,17 @@ class StableBrowser {
|
|
|
2573
2819
|
}
|
|
2574
2820
|
return timeout;
|
|
2575
2821
|
}
|
|
2822
|
+
async saveStoreState(path = null, world = null) {
|
|
2823
|
+
const storageState = await this.page.context().storageState();
|
|
2824
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2825
|
+
if (path) {
|
|
2826
|
+
// save { storageState: storageState } into the path
|
|
2827
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2828
|
+
}
|
|
2829
|
+
else {
|
|
2830
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2576
2833
|
async waitForPageLoad(options = {}, world = null) {
|
|
2577
2834
|
let timeout = this._getLoadTimeout(options);
|
|
2578
2835
|
const promiseArray = [];
|
|
@@ -2758,14 +3015,22 @@ class StableBrowser {
|
|
|
2758
3015
|
}
|
|
2759
3016
|
}
|
|
2760
3017
|
async beforeStep(world, step) {
|
|
2761
|
-
this.stepName = step.pickleStep.text;
|
|
2762
|
-
this.logger.info("step: " + this.stepName);
|
|
2763
3018
|
if (this.stepIndex === undefined) {
|
|
2764
3019
|
this.stepIndex = 0;
|
|
2765
3020
|
}
|
|
2766
3021
|
else {
|
|
2767
3022
|
this.stepIndex++;
|
|
2768
3023
|
}
|
|
3024
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3025
|
+
this.stepName = step.pickleStep.text;
|
|
3026
|
+
this.logger.info("step: " + this.stepName);
|
|
3027
|
+
}
|
|
3028
|
+
else if (step && step.text) {
|
|
3029
|
+
this.stepName = step.text;
|
|
3030
|
+
}
|
|
3031
|
+
else {
|
|
3032
|
+
this.stepName = "step " + this.stepIndex;
|
|
3033
|
+
}
|
|
2769
3034
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2770
3035
|
if (this.context.browserObject.context) {
|
|
2771
3036
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2795,156 +3060,5 @@ function createTimedPromise(promise, label) {
|
|
|
2795
3060
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2796
3061
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2797
3062
|
}
|
|
2798
|
-
const KEYBOARD_EVENTS = [
|
|
2799
|
-
"ALT",
|
|
2800
|
-
"AltGraph",
|
|
2801
|
-
"CapsLock",
|
|
2802
|
-
"Control",
|
|
2803
|
-
"Fn",
|
|
2804
|
-
"FnLock",
|
|
2805
|
-
"Hyper",
|
|
2806
|
-
"Meta",
|
|
2807
|
-
"NumLock",
|
|
2808
|
-
"ScrollLock",
|
|
2809
|
-
"Shift",
|
|
2810
|
-
"Super",
|
|
2811
|
-
"Symbol",
|
|
2812
|
-
"SymbolLock",
|
|
2813
|
-
"Enter",
|
|
2814
|
-
"Tab",
|
|
2815
|
-
"ArrowDown",
|
|
2816
|
-
"ArrowLeft",
|
|
2817
|
-
"ArrowRight",
|
|
2818
|
-
"ArrowUp",
|
|
2819
|
-
"End",
|
|
2820
|
-
"Home",
|
|
2821
|
-
"PageDown",
|
|
2822
|
-
"PageUp",
|
|
2823
|
-
"Backspace",
|
|
2824
|
-
"Clear",
|
|
2825
|
-
"Copy",
|
|
2826
|
-
"CrSel",
|
|
2827
|
-
"Cut",
|
|
2828
|
-
"Delete",
|
|
2829
|
-
"EraseEof",
|
|
2830
|
-
"ExSel",
|
|
2831
|
-
"Insert",
|
|
2832
|
-
"Paste",
|
|
2833
|
-
"Redo",
|
|
2834
|
-
"Undo",
|
|
2835
|
-
"Accept",
|
|
2836
|
-
"Again",
|
|
2837
|
-
"Attn",
|
|
2838
|
-
"Cancel",
|
|
2839
|
-
"ContextMenu",
|
|
2840
|
-
"Escape",
|
|
2841
|
-
"Execute",
|
|
2842
|
-
"Find",
|
|
2843
|
-
"Finish",
|
|
2844
|
-
"Help",
|
|
2845
|
-
"Pause",
|
|
2846
|
-
"Play",
|
|
2847
|
-
"Props",
|
|
2848
|
-
"Select",
|
|
2849
|
-
"ZoomIn",
|
|
2850
|
-
"ZoomOut",
|
|
2851
|
-
"BrightnessDown",
|
|
2852
|
-
"BrightnessUp",
|
|
2853
|
-
"Eject",
|
|
2854
|
-
"LogOff",
|
|
2855
|
-
"Power",
|
|
2856
|
-
"PowerOff",
|
|
2857
|
-
"PrintScreen",
|
|
2858
|
-
"Hibernate",
|
|
2859
|
-
"Standby",
|
|
2860
|
-
"WakeUp",
|
|
2861
|
-
"AllCandidates",
|
|
2862
|
-
"Alphanumeric",
|
|
2863
|
-
"CodeInput",
|
|
2864
|
-
"Compose",
|
|
2865
|
-
"Convert",
|
|
2866
|
-
"Dead",
|
|
2867
|
-
"FinalMode",
|
|
2868
|
-
"GroupFirst",
|
|
2869
|
-
"GroupLast",
|
|
2870
|
-
"GroupNext",
|
|
2871
|
-
"GroupPrevious",
|
|
2872
|
-
"ModeChange",
|
|
2873
|
-
"NextCandidate",
|
|
2874
|
-
"NonConvert",
|
|
2875
|
-
"PreviousCandidate",
|
|
2876
|
-
"Process",
|
|
2877
|
-
"SingleCandidate",
|
|
2878
|
-
"HangulMode",
|
|
2879
|
-
"HanjaMode",
|
|
2880
|
-
"JunjaMode",
|
|
2881
|
-
"Eisu",
|
|
2882
|
-
"Hankaku",
|
|
2883
|
-
"Hiragana",
|
|
2884
|
-
"HiraganaKatakana",
|
|
2885
|
-
"KanaMode",
|
|
2886
|
-
"KanjiMode",
|
|
2887
|
-
"Katakana",
|
|
2888
|
-
"Romaji",
|
|
2889
|
-
"Zenkaku",
|
|
2890
|
-
"ZenkakuHanaku",
|
|
2891
|
-
"F1",
|
|
2892
|
-
"F2",
|
|
2893
|
-
"F3",
|
|
2894
|
-
"F4",
|
|
2895
|
-
"F5",
|
|
2896
|
-
"F6",
|
|
2897
|
-
"F7",
|
|
2898
|
-
"F8",
|
|
2899
|
-
"F9",
|
|
2900
|
-
"F10",
|
|
2901
|
-
"F11",
|
|
2902
|
-
"F12",
|
|
2903
|
-
"Soft1",
|
|
2904
|
-
"Soft2",
|
|
2905
|
-
"Soft3",
|
|
2906
|
-
"Soft4",
|
|
2907
|
-
"ChannelDown",
|
|
2908
|
-
"ChannelUp",
|
|
2909
|
-
"Close",
|
|
2910
|
-
"MailForward",
|
|
2911
|
-
"MailReply",
|
|
2912
|
-
"MailSend",
|
|
2913
|
-
"MediaFastForward",
|
|
2914
|
-
"MediaPause",
|
|
2915
|
-
"MediaPlay",
|
|
2916
|
-
"MediaPlayPause",
|
|
2917
|
-
"MediaRecord",
|
|
2918
|
-
"MediaRewind",
|
|
2919
|
-
"MediaStop",
|
|
2920
|
-
"MediaTrackNext",
|
|
2921
|
-
"MediaTrackPrevious",
|
|
2922
|
-
"AudioBalanceLeft",
|
|
2923
|
-
"AudioBalanceRight",
|
|
2924
|
-
"AudioBassBoostDown",
|
|
2925
|
-
"AudioBassBoostToggle",
|
|
2926
|
-
"AudioBassBoostUp",
|
|
2927
|
-
"AudioFaderFront",
|
|
2928
|
-
"AudioFaderRear",
|
|
2929
|
-
"AudioSurroundModeNext",
|
|
2930
|
-
"AudioTrebleDown",
|
|
2931
|
-
"AudioTrebleUp",
|
|
2932
|
-
"AudioVolumeDown",
|
|
2933
|
-
"AudioVolumeMute",
|
|
2934
|
-
"AudioVolumeUp",
|
|
2935
|
-
"MicrophoneToggle",
|
|
2936
|
-
"MicrophoneVolumeDown",
|
|
2937
|
-
"MicrophoneVolumeMute",
|
|
2938
|
-
"MicrophoneVolumeUp",
|
|
2939
|
-
"TV",
|
|
2940
|
-
"TV3DMode",
|
|
2941
|
-
"TVAntennaCable",
|
|
2942
|
-
"TVAudioDescription",
|
|
2943
|
-
];
|
|
2944
|
-
function unEscapeString(str) {
|
|
2945
|
-
const placeholder = "__NEWLINE__";
|
|
2946
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2947
|
-
return str;
|
|
2948
|
-
}
|
|
2949
3063
|
export { StableBrowser };
|
|
2950
3064
|
//# sourceMappingURL=stable_browser.js.map
|