automation_model 1.0.540-dev → 1.0.540-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 +101 -5
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +48 -24
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +45 -13
- 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 +20 -5
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +10 -10
- 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 +615 -502
- 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 +7 -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
|
-
|
|
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;
|
|
387
368
|
}
|
|
388
|
-
currentNode = currentNode.parentNode;
|
|
389
|
-
}
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
document.isParent = isParent;
|
|
393
|
-
function getRegex(str) {
|
|
394
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
395
|
-
if (!match) {
|
|
396
|
-
return null;
|
|
397
|
-
}
|
|
398
|
-
let [_, pattern, flags] = match;
|
|
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
369
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
445
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
446
|
-
foundElements.push(element);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
text = text.trim();
|
|
452
|
-
for (let i = 0; i < elements.length; i++) {
|
|
453
|
-
const element = elements[i];
|
|
454
|
-
if (partial) {
|
|
455
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
456
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
457
|
-
foundElements.push(element);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
462
|
-
(element.value && element.value === text)) {
|
|
463
|
-
foundElements.push(element);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
let noChildElements = [];
|
|
469
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
470
|
-
let element = foundElements[i];
|
|
471
|
-
let hasChild = false;
|
|
472
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
473
|
-
if (i === j) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
if (isParent(element, foundElements[j])) {
|
|
477
|
-
hasChild = true;
|
|
478
|
-
break;
|
|
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));
|
|
@@ -690,9 +603,11 @@ class StableBrowser {
|
|
|
690
603
|
}
|
|
691
604
|
return framescope;
|
|
692
605
|
};
|
|
606
|
+
let fLocator = null;
|
|
693
607
|
while (true) {
|
|
694
608
|
let frameFound = false;
|
|
695
609
|
if (selectors.nestFrmLoc) {
|
|
610
|
+
fLocator = selectors.nestFrmLoc;
|
|
696
611
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
697
612
|
frameFound = true;
|
|
698
613
|
break;
|
|
@@ -701,6 +616,7 @@ class StableBrowser {
|
|
|
701
616
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
702
617
|
let frameLocator = selectors.frameLocators[i];
|
|
703
618
|
if (frameLocator.css) {
|
|
619
|
+
fLocator = frameLocator.css;
|
|
704
620
|
scope = scope.frameLocator(frameLocator.css);
|
|
705
621
|
frameFound = true;
|
|
706
622
|
break;
|
|
@@ -708,18 +624,25 @@ class StableBrowser {
|
|
|
708
624
|
}
|
|
709
625
|
}
|
|
710
626
|
if (!frameFound && selectors.iframe_src) {
|
|
627
|
+
fLocator = selectors.iframe_src;
|
|
711
628
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
712
629
|
}
|
|
713
630
|
if (!scope) {
|
|
714
|
-
info
|
|
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";
|
|
715
635
|
if (Date.now() - startTime > timeout) {
|
|
716
636
|
info.failCause.iframeNotFound = true;
|
|
717
|
-
info.failCause.lastError =
|
|
637
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
718
638
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
719
639
|
}
|
|
720
640
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
721
641
|
}
|
|
722
642
|
else {
|
|
643
|
+
if (info && info.locatorLog) {
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
645
|
+
}
|
|
723
646
|
break;
|
|
724
647
|
}
|
|
725
648
|
}
|
|
@@ -736,11 +659,12 @@ class StableBrowser {
|
|
|
736
659
|
return bodyContent;
|
|
737
660
|
});
|
|
738
661
|
}
|
|
739
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
662
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
740
663
|
if (!info) {
|
|
741
664
|
info = {};
|
|
742
665
|
info.failCause = {};
|
|
743
666
|
info.log = "";
|
|
667
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
744
668
|
}
|
|
745
669
|
let highPriorityTimeout = 5000;
|
|
746
670
|
let visibleOnlyTimeout = 6000;
|
|
@@ -784,17 +708,17 @@ class StableBrowser {
|
|
|
784
708
|
}
|
|
785
709
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
786
710
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
787
|
-
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);
|
|
788
712
|
if (result.foundElements.length === 0) {
|
|
789
713
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
790
|
-
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);
|
|
791
715
|
}
|
|
792
716
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
793
|
-
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);
|
|
794
718
|
}
|
|
795
719
|
else {
|
|
796
720
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
797
|
-
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);
|
|
798
722
|
}
|
|
799
723
|
}
|
|
800
724
|
let foundElements = result.foundElements;
|
|
@@ -839,26 +763,34 @@ class StableBrowser {
|
|
|
839
763
|
break;
|
|
840
764
|
}
|
|
841
765
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
842
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
766
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
843
767
|
highPriorityOnly = false;
|
|
844
768
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
845
769
|
lazy_scroll = true;
|
|
846
|
-
await this.
|
|
770
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
847
771
|
}
|
|
848
772
|
}
|
|
849
773
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
850
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
774
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
851
775
|
visibleOnly = false;
|
|
852
776
|
}
|
|
853
777
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
854
778
|
}
|
|
855
779
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
856
|
-
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";
|
|
857
787
|
info.failCause.locatorNotFound = true;
|
|
858
|
-
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
|
+
}
|
|
859
791
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
860
792
|
}
|
|
861
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
793
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
862
794
|
let foundElements = [];
|
|
863
795
|
const result = {
|
|
864
796
|
foundElements: foundElements,
|
|
@@ -866,14 +798,15 @@ class StableBrowser {
|
|
|
866
798
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
867
799
|
let foundLocators = [];
|
|
868
800
|
try {
|
|
869
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
801
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
870
802
|
}
|
|
871
803
|
catch (e) {
|
|
872
|
-
this
|
|
873
|
-
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);
|
|
874
807
|
foundLocators = [];
|
|
875
808
|
try {
|
|
876
|
-
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);
|
|
877
810
|
}
|
|
878
811
|
catch (e) {
|
|
879
812
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -889,11 +822,27 @@ class StableBrowser {
|
|
|
889
822
|
}
|
|
890
823
|
if (foundLocators.length > 1) {
|
|
891
824
|
info.failCause.foundMultiple = true;
|
|
825
|
+
if (info.locatorLog) {
|
|
826
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
827
|
+
}
|
|
892
828
|
}
|
|
893
829
|
}
|
|
894
830
|
return result;
|
|
895
831
|
}
|
|
896
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);
|
|
897
846
|
const startTime = Date.now();
|
|
898
847
|
let timeout = 30000;
|
|
899
848
|
if (options && options.timeout) {
|
|
@@ -916,15 +865,33 @@ class StableBrowser {
|
|
|
916
865
|
}
|
|
917
866
|
}
|
|
918
867
|
catch (e) {
|
|
919
|
-
if (
|
|
868
|
+
if (performance.now() - startTime > timeout) {
|
|
920
869
|
// throw e;
|
|
921
|
-
|
|
870
|
+
try {
|
|
871
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
872
|
+
}
|
|
873
|
+
finally {
|
|
874
|
+
_commandFinally(state, this);
|
|
875
|
+
}
|
|
922
876
|
}
|
|
923
877
|
}
|
|
924
878
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
925
879
|
}
|
|
926
880
|
}
|
|
927
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);
|
|
928
895
|
const startTime = Date.now();
|
|
929
896
|
let timeout = 30000;
|
|
930
897
|
if (options && options.timeout) {
|
|
@@ -947,9 +914,14 @@ class StableBrowser {
|
|
|
947
914
|
}
|
|
948
915
|
}
|
|
949
916
|
catch (e) {
|
|
950
|
-
if (
|
|
917
|
+
if (performance.now() - startTime > timeout) {
|
|
951
918
|
// throw e;
|
|
952
|
-
|
|
919
|
+
try {
|
|
920
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
921
|
+
}
|
|
922
|
+
finally {
|
|
923
|
+
_commandFinally(state, this);
|
|
924
|
+
}
|
|
953
925
|
}
|
|
954
926
|
}
|
|
955
927
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -968,9 +940,9 @@ class StableBrowser {
|
|
|
968
940
|
};
|
|
969
941
|
try {
|
|
970
942
|
await _preCommand(state, this);
|
|
971
|
-
if (state.options && state.options.context) {
|
|
972
|
-
|
|
973
|
-
}
|
|
943
|
+
// if (state.options && state.options.context) {
|
|
944
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
945
|
+
// }
|
|
974
946
|
try {
|
|
975
947
|
await state.element.click();
|
|
976
948
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1008,9 +980,15 @@ class StableBrowser {
|
|
|
1008
980
|
// let element = await this._locate(selectors, info, _params);
|
|
1009
981
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1010
982
|
try {
|
|
1011
|
-
//
|
|
983
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
984
|
+
// console.log(`Highlighting while running from recorder`);
|
|
985
|
+
await this._highlightElements(element);
|
|
1012
986
|
await state.element.setChecked(checked);
|
|
1013
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);
|
|
1014
992
|
}
|
|
1015
993
|
catch (e) {
|
|
1016
994
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1049,6 +1027,7 @@ class StableBrowser {
|
|
|
1049
1027
|
await _preCommand(state, this);
|
|
1050
1028
|
try {
|
|
1051
1029
|
await state.element.hover();
|
|
1030
|
+
// await _screenshot(state, this);
|
|
1052
1031
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
1032
|
}
|
|
1054
1033
|
catch (e) {
|
|
@@ -1056,6 +1035,7 @@ class StableBrowser {
|
|
|
1056
1035
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1057
1036
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1058
1037
|
await state.element.hover({ timeout: 10000 });
|
|
1038
|
+
// await _screenshot(state, this);
|
|
1059
1039
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1060
1040
|
}
|
|
1061
1041
|
await _screenshot(state, this);
|
|
@@ -1330,7 +1310,12 @@ class StableBrowser {
|
|
|
1330
1310
|
await this.waitForPageLoad();
|
|
1331
1311
|
}
|
|
1332
1312
|
else if (enter === false) {
|
|
1333
|
-
|
|
1313
|
+
try {
|
|
1314
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1315
|
+
}
|
|
1316
|
+
catch (e) {
|
|
1317
|
+
// ignore
|
|
1318
|
+
}
|
|
1334
1319
|
//await this.page.keyboard.press("Tab");
|
|
1335
1320
|
}
|
|
1336
1321
|
else {
|
|
@@ -1387,6 +1372,7 @@ class StableBrowser {
|
|
|
1387
1372
|
let screenshotPath = null;
|
|
1388
1373
|
if (!info.log) {
|
|
1389
1374
|
info.log = "";
|
|
1375
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1390
1376
|
}
|
|
1391
1377
|
info.operation = "getText";
|
|
1392
1378
|
info.selectors = selectors;
|
|
@@ -1409,6 +1395,18 @@ class StableBrowser {
|
|
|
1409
1395
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1410
1396
|
try {
|
|
1411
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
|
+
// }
|
|
1412
1410
|
const elementText = await element.innerText();
|
|
1413
1411
|
return {
|
|
1414
1412
|
text: elementText,
|
|
@@ -1420,7 +1418,7 @@ class StableBrowser {
|
|
|
1420
1418
|
}
|
|
1421
1419
|
catch (e) {
|
|
1422
1420
|
//await this.closeUnexpectedPopups();
|
|
1423
|
-
this.logger.info("no innerText will use textContent");
|
|
1421
|
+
this.logger.info("no innerText, will use textContent");
|
|
1424
1422
|
const elementText = await element.textContent();
|
|
1425
1423
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1426
1424
|
}
|
|
@@ -1546,22 +1544,6 @@ class StableBrowser {
|
|
|
1546
1544
|
_commandFinally(state, this);
|
|
1547
1545
|
}
|
|
1548
1546
|
}
|
|
1549
|
-
_getDataFile(world = null) {
|
|
1550
|
-
let dataFile = null;
|
|
1551
|
-
if (world && world.reportFolder) {
|
|
1552
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1553
|
-
}
|
|
1554
|
-
else if (this.reportFolder) {
|
|
1555
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1556
|
-
}
|
|
1557
|
-
else if (this.context && this.context.reportFolder) {
|
|
1558
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1559
|
-
}
|
|
1560
|
-
else {
|
|
1561
|
-
dataFile = "data.json";
|
|
1562
|
-
}
|
|
1563
|
-
return dataFile;
|
|
1564
|
-
}
|
|
1565
1547
|
async waitForUserInput(message, world = null) {
|
|
1566
1548
|
if (!message) {
|
|
1567
1549
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1590,7 +1572,7 @@ class StableBrowser {
|
|
|
1590
1572
|
return;
|
|
1591
1573
|
}
|
|
1592
1574
|
// if data file exists, load it
|
|
1593
|
-
const dataFile =
|
|
1575
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1594
1576
|
let data = this.getTestData(world);
|
|
1595
1577
|
// merge the testData with the existing data
|
|
1596
1578
|
Object.assign(data, testData);
|
|
@@ -1693,7 +1675,7 @@ class StableBrowser {
|
|
|
1693
1675
|
}
|
|
1694
1676
|
}
|
|
1695
1677
|
getTestData(world = null) {
|
|
1696
|
-
const dataFile =
|
|
1678
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1697
1679
|
let data = {};
|
|
1698
1680
|
if (fs.existsSync(dataFile)) {
|
|
1699
1681
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1780,6 +1762,15 @@ class StableBrowser {
|
|
|
1780
1762
|
document.documentElement.clientWidth,
|
|
1781
1763
|
])));
|
|
1782
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
|
+
// }
|
|
1783
1774
|
if (this.context.browserName === "chromium") {
|
|
1784
1775
|
const client = await playContext.newCDPSession(this.page);
|
|
1785
1776
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1801,6 +1792,10 @@ class StableBrowser {
|
|
|
1801
1792
|
else {
|
|
1802
1793
|
screenshotBuffer = await this.page.screenshot();
|
|
1803
1794
|
}
|
|
1795
|
+
// if (focusedElement) {
|
|
1796
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1797
|
+
// await this._unhighlightElements(focusedElement);
|
|
1798
|
+
// }
|
|
1804
1799
|
let image = await Jimp.read(screenshotBuffer);
|
|
1805
1800
|
// Get the image dimensions
|
|
1806
1801
|
const { width, height } = image.bitmap;
|
|
@@ -1813,6 +1808,7 @@ class StableBrowser {
|
|
|
1813
1808
|
else {
|
|
1814
1809
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1815
1810
|
}
|
|
1811
|
+
return screenshotBuffer;
|
|
1816
1812
|
}
|
|
1817
1813
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1818
1814
|
const state = {
|
|
@@ -1850,6 +1846,7 @@ class StableBrowser {
|
|
|
1850
1846
|
text: `Extract attribute from element`,
|
|
1851
1847
|
operation: "extractAttribute",
|
|
1852
1848
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1849
|
+
allowDisabled: true,
|
|
1853
1850
|
};
|
|
1854
1851
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1855
1852
|
try {
|
|
@@ -1871,6 +1868,76 @@ class StableBrowser {
|
|
|
1871
1868
|
state.info.value = state.value;
|
|
1872
1869
|
this.setTestData({ [variable]: state.value }, world);
|
|
1873
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
|
+
}
|
|
1874
1941
|
return state.info;
|
|
1875
1942
|
}
|
|
1876
1943
|
catch (e) {
|
|
@@ -1898,7 +1965,7 @@ class StableBrowser {
|
|
|
1898
1965
|
if (options && options.timeout) {
|
|
1899
1966
|
timeout = options.timeout;
|
|
1900
1967
|
}
|
|
1901
|
-
const serviceUrl =
|
|
1968
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1902
1969
|
const request = {
|
|
1903
1970
|
method: "POST",
|
|
1904
1971
|
url: serviceUrl,
|
|
@@ -1969,27 +2036,32 @@ class StableBrowser {
|
|
|
1969
2036
|
async _highlightElements(scope, css) {
|
|
1970
2037
|
try {
|
|
1971
2038
|
if (!scope) {
|
|
2039
|
+
// console.log(`Scope is not defined`);
|
|
1972
2040
|
return;
|
|
1973
2041
|
}
|
|
1974
2042
|
if (!css) {
|
|
1975
2043
|
scope
|
|
1976
2044
|
.evaluate((node) => {
|
|
1977
2045
|
if (node && node.style) {
|
|
1978
|
-
let
|
|
1979
|
-
|
|
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}`);
|
|
1980
2051
|
if (window) {
|
|
1981
2052
|
window.addEventListener("beforeunload", function (e) {
|
|
1982
|
-
node.style.
|
|
2053
|
+
node.style.outline = originalOutline;
|
|
1983
2054
|
});
|
|
1984
2055
|
}
|
|
1985
2056
|
setTimeout(function () {
|
|
1986
|
-
node.style.
|
|
2057
|
+
node.style.outline = originalOutline;
|
|
1987
2058
|
}, 2000);
|
|
1988
2059
|
}
|
|
1989
2060
|
})
|
|
1990
2061
|
.then(() => { })
|
|
1991
2062
|
.catch((e) => {
|
|
1992
2063
|
// ignore
|
|
2064
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1993
2065
|
});
|
|
1994
2066
|
}
|
|
1995
2067
|
else {
|
|
@@ -2005,17 +2077,18 @@ class StableBrowser {
|
|
|
2005
2077
|
if (!element.style) {
|
|
2006
2078
|
return;
|
|
2007
2079
|
}
|
|
2008
|
-
|
|
2080
|
+
let originalOutline = element.style.outline;
|
|
2081
|
+
element.__previousOutline = originalOutline;
|
|
2009
2082
|
// Set the new border to be red and 2px solid
|
|
2010
|
-
element.style.
|
|
2083
|
+
element.style.outline = "2px solid red";
|
|
2011
2084
|
if (window) {
|
|
2012
2085
|
window.addEventListener("beforeunload", function (e) {
|
|
2013
|
-
element.style.
|
|
2086
|
+
element.style.outline = originalOutline;
|
|
2014
2087
|
});
|
|
2015
2088
|
}
|
|
2016
2089
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2017
2090
|
setTimeout(function () {
|
|
2018
|
-
element.style.
|
|
2091
|
+
element.style.outline = originalOutline;
|
|
2019
2092
|
}, 2000);
|
|
2020
2093
|
}
|
|
2021
2094
|
return;
|
|
@@ -2023,6 +2096,7 @@ class StableBrowser {
|
|
|
2023
2096
|
.then(() => { })
|
|
2024
2097
|
.catch((e) => {
|
|
2025
2098
|
// ignore
|
|
2099
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2026
2100
|
});
|
|
2027
2101
|
}
|
|
2028
2102
|
}
|
|
@@ -2030,6 +2104,54 @@ class StableBrowser {
|
|
|
2030
2104
|
console.debug(error);
|
|
2031
2105
|
}
|
|
2032
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
|
+
// }
|
|
2033
2155
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2034
2156
|
const startTime = Date.now();
|
|
2035
2157
|
let error = null;
|
|
@@ -2091,6 +2213,35 @@ class StableBrowser {
|
|
|
2091
2213
|
});
|
|
2092
2214
|
}
|
|
2093
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
|
+
}
|
|
2094
2245
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2095
2246
|
text = unEscapeString(text);
|
|
2096
2247
|
const state = {
|
|
@@ -2100,7 +2251,7 @@ class StableBrowser {
|
|
|
2100
2251
|
locate: false,
|
|
2101
2252
|
scroll: false,
|
|
2102
2253
|
highlight: false,
|
|
2103
|
-
type: Types.
|
|
2254
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2104
2255
|
text: `Verify text exists in page`,
|
|
2105
2256
|
operation: "verifyTextExistInPage",
|
|
2106
2257
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2118,31 +2269,15 @@ class StableBrowser {
|
|
|
2118
2269
|
await _preCommand(state, this);
|
|
2119
2270
|
state.info.text = text;
|
|
2120
2271
|
while (true) {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
else if (numberAlternatives.number) {
|
|
2132
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2133
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2134
|
-
result.frame = frames[i];
|
|
2135
|
-
results.push(result);
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
else {
|
|
2139
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2140
|
-
result.frame = frames[i];
|
|
2141
|
-
results.push(result);
|
|
2142
|
-
}
|
|
2272
|
+
let resultWithElementsFound = {
|
|
2273
|
+
length: 0,
|
|
2274
|
+
};
|
|
2275
|
+
try {
|
|
2276
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2277
|
+
}
|
|
2278
|
+
catch (error) {
|
|
2279
|
+
// ignore
|
|
2143
2280
|
}
|
|
2144
|
-
state.info.results = results;
|
|
2145
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2146
2281
|
if (resultWithElementsFound.length === 0) {
|
|
2147
2282
|
if (Date.now() - state.startTime > timeout) {
|
|
2148
2283
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2150,18 +2285,40 @@ class StableBrowser {
|
|
|
2150
2285
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2151
2286
|
continue;
|
|
2152
2287
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
+
}
|
|
2161
2315
|
}
|
|
2316
|
+
await _screenshot(state, this);
|
|
2317
|
+
return state.info;
|
|
2318
|
+
}
|
|
2319
|
+
catch (error) {
|
|
2320
|
+
console.error(error);
|
|
2162
2321
|
}
|
|
2163
|
-
await _screenshot(state, this);
|
|
2164
|
-
return state.info;
|
|
2165
2322
|
}
|
|
2166
2323
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2167
2324
|
}
|
|
@@ -2198,32 +2355,16 @@ class StableBrowser {
|
|
|
2198
2355
|
try {
|
|
2199
2356
|
await _preCommand(state, this);
|
|
2200
2357
|
state.info.text = text;
|
|
2358
|
+
let resultWithElementsFound = {
|
|
2359
|
+
length: null, // initial cannot be 0
|
|
2360
|
+
};
|
|
2201
2361
|
while (true) {
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2208
|
-
result.frame = frames[i];
|
|
2209
|
-
results.push(result);
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
else if (numberAlternatives.number) {
|
|
2213
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2214
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2215
|
-
result.frame = frames[i];
|
|
2216
|
-
results.push(result);
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
else {
|
|
2220
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2221
|
-
result.frame = frames[i];
|
|
2222
|
-
results.push(result);
|
|
2223
|
-
}
|
|
2362
|
+
try {
|
|
2363
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2364
|
+
}
|
|
2365
|
+
catch (error) {
|
|
2366
|
+
// ignore
|
|
2224
2367
|
}
|
|
2225
|
-
state.info.results = results;
|
|
2226
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2227
2368
|
if (resultWithElementsFound.length === 0) {
|
|
2228
2369
|
await _screenshot(state, this);
|
|
2229
2370
|
return state.info;
|
|
@@ -2241,15 +2382,115 @@ class StableBrowser {
|
|
|
2241
2382
|
_commandFinally(state, this);
|
|
2242
2383
|
}
|
|
2243
2384
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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 });
|
|
2248
2487
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2488
|
+
catch (e) {
|
|
2489
|
+
await _commandError(state, e, this);
|
|
2490
|
+
}
|
|
2491
|
+
finally {
|
|
2492
|
+
_commandFinally(state, this);
|
|
2251
2493
|
}
|
|
2252
|
-
return serviceUrl;
|
|
2253
2494
|
}
|
|
2254
2495
|
async visualVerification(text, options = {}, world = null) {
|
|
2255
2496
|
const startTime = Date.now();
|
|
@@ -2265,14 +2506,17 @@ class StableBrowser {
|
|
|
2265
2506
|
throw new Error("TOKEN is not set");
|
|
2266
2507
|
}
|
|
2267
2508
|
try {
|
|
2268
|
-
let serviceUrl =
|
|
2509
|
+
let serviceUrl = _getServerUrl();
|
|
2269
2510
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2270
2511
|
info.screenshotPath = screenshotPath;
|
|
2271
2512
|
const screenshot = await this.takeScreenshot();
|
|
2272
|
-
|
|
2273
|
-
method: "
|
|
2513
|
+
let request = {
|
|
2514
|
+
method: "post",
|
|
2515
|
+
maxBodyLength: Infinity,
|
|
2274
2516
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2275
2517
|
headers: {
|
|
2518
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2519
|
+
"x-source": "aaa",
|
|
2276
2520
|
"Content-Type": "application/json",
|
|
2277
2521
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2278
2522
|
},
|
|
@@ -2281,7 +2525,7 @@ class StableBrowser {
|
|
|
2281
2525
|
screenshot: screenshot,
|
|
2282
2526
|
}),
|
|
2283
2527
|
};
|
|
2284
|
-
|
|
2528
|
+
const result = await axios.request(request);
|
|
2285
2529
|
if (result.data.status !== true) {
|
|
2286
2530
|
throw new Error("Visual validation failed");
|
|
2287
2531
|
}
|
|
@@ -2354,6 +2598,7 @@ class StableBrowser {
|
|
|
2354
2598
|
let screenshotPath = null;
|
|
2355
2599
|
const info = {};
|
|
2356
2600
|
info.log = "";
|
|
2601
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2357
2602
|
info.operation = "getTableData";
|
|
2358
2603
|
info.selectors = selectors;
|
|
2359
2604
|
try {
|
|
@@ -2429,7 +2674,7 @@ class StableBrowser {
|
|
|
2429
2674
|
info.operation = "analyzeTable";
|
|
2430
2675
|
info.selectors = selectors;
|
|
2431
2676
|
info.query = query;
|
|
2432
|
-
query =
|
|
2677
|
+
query = _fixUsingParams(query, _params);
|
|
2433
2678
|
info.query_fixed = query;
|
|
2434
2679
|
info.operator = operator;
|
|
2435
2680
|
info.value = value;
|
|
@@ -2574,6 +2819,17 @@ class StableBrowser {
|
|
|
2574
2819
|
}
|
|
2575
2820
|
return timeout;
|
|
2576
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
|
+
}
|
|
2577
2833
|
async waitForPageLoad(options = {}, world = null) {
|
|
2578
2834
|
let timeout = this._getLoadTimeout(options);
|
|
2579
2835
|
const promiseArray = [];
|
|
@@ -2759,14 +3015,22 @@ class StableBrowser {
|
|
|
2759
3015
|
}
|
|
2760
3016
|
}
|
|
2761
3017
|
async beforeStep(world, step) {
|
|
2762
|
-
this.stepName = step.pickleStep.text;
|
|
2763
|
-
this.logger.info("step: " + this.stepName);
|
|
2764
3018
|
if (this.stepIndex === undefined) {
|
|
2765
3019
|
this.stepIndex = 0;
|
|
2766
3020
|
}
|
|
2767
3021
|
else {
|
|
2768
3022
|
this.stepIndex++;
|
|
2769
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
|
+
}
|
|
2770
3034
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2771
3035
|
if (this.context.browserObject.context) {
|
|
2772
3036
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2796,156 +3060,5 @@ function createTimedPromise(promise, label) {
|
|
|
2796
3060
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2797
3061
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2798
3062
|
}
|
|
2799
|
-
const KEYBOARD_EVENTS = [
|
|
2800
|
-
"ALT",
|
|
2801
|
-
"AltGraph",
|
|
2802
|
-
"CapsLock",
|
|
2803
|
-
"Control",
|
|
2804
|
-
"Fn",
|
|
2805
|
-
"FnLock",
|
|
2806
|
-
"Hyper",
|
|
2807
|
-
"Meta",
|
|
2808
|
-
"NumLock",
|
|
2809
|
-
"ScrollLock",
|
|
2810
|
-
"Shift",
|
|
2811
|
-
"Super",
|
|
2812
|
-
"Symbol",
|
|
2813
|
-
"SymbolLock",
|
|
2814
|
-
"Enter",
|
|
2815
|
-
"Tab",
|
|
2816
|
-
"ArrowDown",
|
|
2817
|
-
"ArrowLeft",
|
|
2818
|
-
"ArrowRight",
|
|
2819
|
-
"ArrowUp",
|
|
2820
|
-
"End",
|
|
2821
|
-
"Home",
|
|
2822
|
-
"PageDown",
|
|
2823
|
-
"PageUp",
|
|
2824
|
-
"Backspace",
|
|
2825
|
-
"Clear",
|
|
2826
|
-
"Copy",
|
|
2827
|
-
"CrSel",
|
|
2828
|
-
"Cut",
|
|
2829
|
-
"Delete",
|
|
2830
|
-
"EraseEof",
|
|
2831
|
-
"ExSel",
|
|
2832
|
-
"Insert",
|
|
2833
|
-
"Paste",
|
|
2834
|
-
"Redo",
|
|
2835
|
-
"Undo",
|
|
2836
|
-
"Accept",
|
|
2837
|
-
"Again",
|
|
2838
|
-
"Attn",
|
|
2839
|
-
"Cancel",
|
|
2840
|
-
"ContextMenu",
|
|
2841
|
-
"Escape",
|
|
2842
|
-
"Execute",
|
|
2843
|
-
"Find",
|
|
2844
|
-
"Finish",
|
|
2845
|
-
"Help",
|
|
2846
|
-
"Pause",
|
|
2847
|
-
"Play",
|
|
2848
|
-
"Props",
|
|
2849
|
-
"Select",
|
|
2850
|
-
"ZoomIn",
|
|
2851
|
-
"ZoomOut",
|
|
2852
|
-
"BrightnessDown",
|
|
2853
|
-
"BrightnessUp",
|
|
2854
|
-
"Eject",
|
|
2855
|
-
"LogOff",
|
|
2856
|
-
"Power",
|
|
2857
|
-
"PowerOff",
|
|
2858
|
-
"PrintScreen",
|
|
2859
|
-
"Hibernate",
|
|
2860
|
-
"Standby",
|
|
2861
|
-
"WakeUp",
|
|
2862
|
-
"AllCandidates",
|
|
2863
|
-
"Alphanumeric",
|
|
2864
|
-
"CodeInput",
|
|
2865
|
-
"Compose",
|
|
2866
|
-
"Convert",
|
|
2867
|
-
"Dead",
|
|
2868
|
-
"FinalMode",
|
|
2869
|
-
"GroupFirst",
|
|
2870
|
-
"GroupLast",
|
|
2871
|
-
"GroupNext",
|
|
2872
|
-
"GroupPrevious",
|
|
2873
|
-
"ModeChange",
|
|
2874
|
-
"NextCandidate",
|
|
2875
|
-
"NonConvert",
|
|
2876
|
-
"PreviousCandidate",
|
|
2877
|
-
"Process",
|
|
2878
|
-
"SingleCandidate",
|
|
2879
|
-
"HangulMode",
|
|
2880
|
-
"HanjaMode",
|
|
2881
|
-
"JunjaMode",
|
|
2882
|
-
"Eisu",
|
|
2883
|
-
"Hankaku",
|
|
2884
|
-
"Hiragana",
|
|
2885
|
-
"HiraganaKatakana",
|
|
2886
|
-
"KanaMode",
|
|
2887
|
-
"KanjiMode",
|
|
2888
|
-
"Katakana",
|
|
2889
|
-
"Romaji",
|
|
2890
|
-
"Zenkaku",
|
|
2891
|
-
"ZenkakuHanaku",
|
|
2892
|
-
"F1",
|
|
2893
|
-
"F2",
|
|
2894
|
-
"F3",
|
|
2895
|
-
"F4",
|
|
2896
|
-
"F5",
|
|
2897
|
-
"F6",
|
|
2898
|
-
"F7",
|
|
2899
|
-
"F8",
|
|
2900
|
-
"F9",
|
|
2901
|
-
"F10",
|
|
2902
|
-
"F11",
|
|
2903
|
-
"F12",
|
|
2904
|
-
"Soft1",
|
|
2905
|
-
"Soft2",
|
|
2906
|
-
"Soft3",
|
|
2907
|
-
"Soft4",
|
|
2908
|
-
"ChannelDown",
|
|
2909
|
-
"ChannelUp",
|
|
2910
|
-
"Close",
|
|
2911
|
-
"MailForward",
|
|
2912
|
-
"MailReply",
|
|
2913
|
-
"MailSend",
|
|
2914
|
-
"MediaFastForward",
|
|
2915
|
-
"MediaPause",
|
|
2916
|
-
"MediaPlay",
|
|
2917
|
-
"MediaPlayPause",
|
|
2918
|
-
"MediaRecord",
|
|
2919
|
-
"MediaRewind",
|
|
2920
|
-
"MediaStop",
|
|
2921
|
-
"MediaTrackNext",
|
|
2922
|
-
"MediaTrackPrevious",
|
|
2923
|
-
"AudioBalanceLeft",
|
|
2924
|
-
"AudioBalanceRight",
|
|
2925
|
-
"AudioBassBoostDown",
|
|
2926
|
-
"AudioBassBoostToggle",
|
|
2927
|
-
"AudioBassBoostUp",
|
|
2928
|
-
"AudioFaderFront",
|
|
2929
|
-
"AudioFaderRear",
|
|
2930
|
-
"AudioSurroundModeNext",
|
|
2931
|
-
"AudioTrebleDown",
|
|
2932
|
-
"AudioTrebleUp",
|
|
2933
|
-
"AudioVolumeDown",
|
|
2934
|
-
"AudioVolumeMute",
|
|
2935
|
-
"AudioVolumeUp",
|
|
2936
|
-
"MicrophoneToggle",
|
|
2937
|
-
"MicrophoneVolumeDown",
|
|
2938
|
-
"MicrophoneVolumeMute",
|
|
2939
|
-
"MicrophoneVolumeUp",
|
|
2940
|
-
"TV",
|
|
2941
|
-
"TV3DMode",
|
|
2942
|
-
"TVAntennaCable",
|
|
2943
|
-
"TVAudioDescription",
|
|
2944
|
-
];
|
|
2945
|
-
function unEscapeString(str) {
|
|
2946
|
-
const placeholder = "__NEWLINE__";
|
|
2947
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2948
|
-
return str;
|
|
2949
|
-
}
|
|
2950
3063
|
export { StableBrowser };
|
|
2951
3064
|
//# sourceMappingURL=stable_browser.js.map
|