automation_model 1.0.536-dev → 1.0.536-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 +66 -44
- 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 +24 -9
- 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 +616 -505
- package/lib/stable_browser.js.map +1 -1
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +344 -6
- package/lib/utils.js.map +1 -1
- package/package.json +6 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,7 +10,7 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
@@ -19,7 +19,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,48 @@ class StableBrowser {
|
|
|
264
237
|
// async closeUnexpectedPopups() {
|
|
265
238
|
// await closeUnexpectedPopups(this.page);
|
|
266
239
|
// }
|
|
267
|
-
async goto(url) {
|
|
240
|
+
async goto(url, world = null) {
|
|
268
241
|
if (!url.startsWith("http")) {
|
|
269
242
|
url = "https://" + url;
|
|
270
243
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
244
|
+
const state = {
|
|
245
|
+
value: url,
|
|
246
|
+
world: world,
|
|
247
|
+
type: Types.NAVIGATE,
|
|
248
|
+
text: `Navigate Page to: ${url}`,
|
|
249
|
+
operation: "goto",
|
|
250
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
251
|
+
info: {},
|
|
252
|
+
locate: false,
|
|
253
|
+
scroll: false,
|
|
254
|
+
screenshot: false,
|
|
255
|
+
highlight: false,
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
await _preCommand(state, this);
|
|
259
|
+
await this.page.goto(url, {
|
|
260
|
+
timeout: 60000,
|
|
261
|
+
});
|
|
262
|
+
await _screenshot(state, this);
|
|
278
263
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// remove the _ prefix
|
|
283
|
-
regValue = key.substring(1);
|
|
284
|
-
}
|
|
285
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error("Error on goto", error);
|
|
266
|
+
_commandError(state, error, this);
|
|
286
267
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
290
|
-
// check if not null
|
|
291
|
-
if (!locator) {
|
|
292
|
-
return locator;
|
|
268
|
+
finally {
|
|
269
|
+
_commandFinally(state, this);
|
|
293
270
|
}
|
|
294
|
-
// clone the locator
|
|
295
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
296
|
-
this.scanAndManipulate(locator, _params);
|
|
297
|
-
return locator;
|
|
298
271
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
else if (this._isObject(currentObj[key])) {
|
|
309
|
-
// Recursively scan nested objects
|
|
310
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
272
|
+
async _getLocator(locator, scope, _params) {
|
|
273
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
274
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
275
|
+
for (let key in locator) {
|
|
276
|
+
if (typeof locator[key] !== "string")
|
|
277
|
+
continue;
|
|
278
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
279
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
311
280
|
}
|
|
312
281
|
}
|
|
313
|
-
}
|
|
314
|
-
_getLocator(locator, scope, _params) {
|
|
315
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
316
282
|
let locatorReturn;
|
|
317
283
|
if (locator.role) {
|
|
318
284
|
if (locator.role[1].nameReg) {
|
|
@@ -320,7 +286,7 @@ class StableBrowser {
|
|
|
320
286
|
delete locator.role[1].nameReg;
|
|
321
287
|
}
|
|
322
288
|
// if (locator.role[1].name) {
|
|
323
|
-
// locator.role[1].name =
|
|
289
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
324
290
|
// }
|
|
325
291
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
326
292
|
}
|
|
@@ -363,140 +329,54 @@ class StableBrowser {
|
|
|
363
329
|
if (css && css.locator) {
|
|
364
330
|
css = css.locator;
|
|
365
331
|
}
|
|
366
|
-
let result = await this._locateElementByText(scope,
|
|
332
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
367
333
|
if (result.elementCount === 0) {
|
|
368
334
|
return;
|
|
369
335
|
}
|
|
370
|
-
let textElementCss = "[data-blinq-id
|
|
336
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
371
337
|
// css climb to parent element
|
|
372
338
|
const climbArray = [];
|
|
373
339
|
for (let i = 0; i < climb; i++) {
|
|
374
340
|
climbArray.push("..");
|
|
375
341
|
}
|
|
376
342
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
343
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
344
|
+
if (css) {
|
|
345
|
+
resultCss = resultCss + " >> " + css;
|
|
346
|
+
}
|
|
347
|
+
return resultCss;
|
|
348
|
+
}
|
|
349
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
350
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
351
|
+
const locator = scope.locator(query);
|
|
352
|
+
const count = await locator.count();
|
|
353
|
+
if (!tag1) {
|
|
354
|
+
tag1 = "*";
|
|
355
|
+
}
|
|
356
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
357
|
+
let tagCount = 0;
|
|
358
|
+
for (let i = 0; i < count; i++) {
|
|
359
|
+
const element = locator.nth(i);
|
|
360
|
+
// check if the tag matches
|
|
361
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
362
|
+
if (!tag.startsWith("*")) {
|
|
363
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
364
|
+
return false;
|
|
387
365
|
}
|
|
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
366
|
}
|
|
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++;
|
|
367
|
+
if (!el.setAttribute) {
|
|
368
|
+
el = el.parentElement;
|
|
494
369
|
}
|
|
370
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
371
|
+
return true;
|
|
372
|
+
}, [tag1, randomToken]))) {
|
|
373
|
+
continue;
|
|
495
374
|
}
|
|
496
|
-
|
|
497
|
-
}
|
|
375
|
+
tagCount++;
|
|
376
|
+
}
|
|
377
|
+
return { elementCount: tagCount, randomToken };
|
|
498
378
|
}
|
|
499
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
379
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
500
380
|
if (!info) {
|
|
501
381
|
info = {};
|
|
502
382
|
}
|
|
@@ -505,10 +385,13 @@ class StableBrowser {
|
|
|
505
385
|
}
|
|
506
386
|
if (!info.log) {
|
|
507
387
|
info.log = "";
|
|
388
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
508
389
|
}
|
|
509
390
|
let locatorSearch = selectorHierarchy[index];
|
|
391
|
+
let originalLocatorSearch = "";
|
|
510
392
|
try {
|
|
511
|
-
|
|
393
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
394
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
512
395
|
}
|
|
513
396
|
catch (e) {
|
|
514
397
|
console.error(e);
|
|
@@ -516,30 +399,31 @@ class StableBrowser {
|
|
|
516
399
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
517
400
|
let locator = null;
|
|
518
401
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
519
|
-
|
|
402
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
403
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
520
404
|
if (!locatorString) {
|
|
521
405
|
info.failCause.textNotFound = true;
|
|
522
|
-
info.failCause.lastError =
|
|
406
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
523
407
|
return;
|
|
524
408
|
}
|
|
525
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
409
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
526
410
|
}
|
|
527
411
|
else if (locatorSearch.text) {
|
|
528
|
-
let text =
|
|
529
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
412
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
413
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
530
414
|
if (result.elementCount === 0) {
|
|
531
415
|
info.failCause.textNotFound = true;
|
|
532
|
-
info.failCause.lastError =
|
|
416
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
533
417
|
return;
|
|
534
418
|
}
|
|
535
|
-
locatorSearch.css = "[data-blinq-id
|
|
419
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
536
420
|
if (locatorSearch.childCss) {
|
|
537
421
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
538
422
|
}
|
|
539
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
423
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
540
424
|
}
|
|
541
425
|
else {
|
|
542
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
426
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
543
427
|
}
|
|
544
428
|
// let cssHref = false;
|
|
545
429
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -552,18 +436,27 @@ class StableBrowser {
|
|
|
552
436
|
//info.log += "total elements found " + count + "\n";
|
|
553
437
|
//let visibleCount = 0;
|
|
554
438
|
let visibleLocator = null;
|
|
555
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
439
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
556
440
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
441
|
+
if (info.locatorLog) {
|
|
442
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
443
|
+
}
|
|
557
444
|
return;
|
|
558
445
|
}
|
|
446
|
+
if (info.locatorLog && count === 0) {
|
|
447
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
448
|
+
}
|
|
559
449
|
for (let j = 0; j < count; j++) {
|
|
560
450
|
let visible = await locator.nth(j).isVisible();
|
|
561
451
|
const enabled = await locator.nth(j).isEnabled();
|
|
562
452
|
if (!visibleOnly) {
|
|
563
453
|
visible = true;
|
|
564
454
|
}
|
|
565
|
-
if (visible && enabled) {
|
|
455
|
+
if (visible && (allowDisabled || enabled)) {
|
|
566
456
|
foundLocators.push(locator.nth(j));
|
|
457
|
+
if (info.locatorLog) {
|
|
458
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
459
|
+
}
|
|
567
460
|
}
|
|
568
461
|
else {
|
|
569
462
|
info.failCause.visible = visible;
|
|
@@ -571,8 +464,16 @@ class StableBrowser {
|
|
|
571
464
|
if (!info.printMessages) {
|
|
572
465
|
info.printMessages = {};
|
|
573
466
|
}
|
|
467
|
+
if (info.locatorLog && !visible) {
|
|
468
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
469
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
470
|
+
}
|
|
471
|
+
if (info.locatorLog && !enabled) {
|
|
472
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
473
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
474
|
+
}
|
|
574
475
|
if (!info.printMessages[j.toString()]) {
|
|
575
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
476
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
576
477
|
info.printMessages[j.toString()] = true;
|
|
577
478
|
}
|
|
578
479
|
}
|
|
@@ -588,12 +489,12 @@ class StableBrowser {
|
|
|
588
489
|
if (!info) {
|
|
589
490
|
info = {};
|
|
590
491
|
}
|
|
591
|
-
info.log += "scan for popup handlers" + "\n";
|
|
492
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
592
493
|
const handlerGroup = [];
|
|
593
494
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
594
495
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
595
496
|
}
|
|
596
|
-
const scopes = this.page.frames()
|
|
497
|
+
const scopes = [this.page, ...this.page.frames()];
|
|
597
498
|
let result = null;
|
|
598
499
|
let scope = null;
|
|
599
500
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -636,7 +537,7 @@ class StableBrowser {
|
|
|
636
537
|
}
|
|
637
538
|
return { rerun: false };
|
|
638
539
|
}
|
|
639
|
-
async _locate(selectors, info, _params, timeout) {
|
|
540
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
640
541
|
if (!timeout) {
|
|
641
542
|
timeout = 30000;
|
|
642
543
|
}
|
|
@@ -646,9 +547,18 @@ class StableBrowser {
|
|
|
646
547
|
let selector = selectors.locators[j];
|
|
647
548
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
648
549
|
}
|
|
649
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
550
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
650
551
|
if (!element.rerun) {
|
|
651
|
-
|
|
552
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
553
|
+
element.evaluate((el, randomToken) => {
|
|
554
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
555
|
+
}, randomToken);
|
|
556
|
+
if (element._frame) {
|
|
557
|
+
return element;
|
|
558
|
+
}
|
|
559
|
+
const scope = element.page();
|
|
560
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
561
|
+
return newSelector;
|
|
652
562
|
}
|
|
653
563
|
}
|
|
654
564
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -659,6 +569,7 @@ class StableBrowser {
|
|
|
659
569
|
info.failCause = {};
|
|
660
570
|
info.log = "";
|
|
661
571
|
}
|
|
572
|
+
let startTime = Date.now();
|
|
662
573
|
let scope = this.page;
|
|
663
574
|
if (selectors.frame) {
|
|
664
575
|
return selectors.frame;
|
|
@@ -689,9 +600,11 @@ class StableBrowser {
|
|
|
689
600
|
}
|
|
690
601
|
return framescope;
|
|
691
602
|
};
|
|
603
|
+
let fLocator = null;
|
|
692
604
|
while (true) {
|
|
693
605
|
let frameFound = false;
|
|
694
606
|
if (selectors.nestFrmLoc) {
|
|
607
|
+
fLocator = selectors.nestFrmLoc;
|
|
695
608
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
696
609
|
frameFound = true;
|
|
697
610
|
break;
|
|
@@ -700,6 +613,7 @@ class StableBrowser {
|
|
|
700
613
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
701
614
|
let frameLocator = selectors.frameLocators[i];
|
|
702
615
|
if (frameLocator.css) {
|
|
616
|
+
fLocator = frameLocator.css;
|
|
703
617
|
scope = scope.frameLocator(frameLocator.css);
|
|
704
618
|
frameFound = true;
|
|
705
619
|
break;
|
|
@@ -707,18 +621,25 @@ class StableBrowser {
|
|
|
707
621
|
}
|
|
708
622
|
}
|
|
709
623
|
if (!frameFound && selectors.iframe_src) {
|
|
624
|
+
fLocator = selectors.iframe_src;
|
|
710
625
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
711
626
|
}
|
|
712
627
|
if (!scope) {
|
|
713
|
-
info
|
|
714
|
-
|
|
628
|
+
if (info && info.locatorLog) {
|
|
629
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
630
|
+
}
|
|
631
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
632
|
+
if (Date.now() - startTime > timeout) {
|
|
715
633
|
info.failCause.iframeNotFound = true;
|
|
716
|
-
info.failCause.lastError =
|
|
634
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
717
635
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
718
636
|
}
|
|
719
637
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
720
638
|
}
|
|
721
639
|
else {
|
|
640
|
+
if (info && info.locatorLog) {
|
|
641
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
642
|
+
}
|
|
722
643
|
break;
|
|
723
644
|
}
|
|
724
645
|
}
|
|
@@ -735,15 +656,16 @@ class StableBrowser {
|
|
|
735
656
|
return bodyContent;
|
|
736
657
|
});
|
|
737
658
|
}
|
|
738
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
659
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
739
660
|
if (!info) {
|
|
740
661
|
info = {};
|
|
741
662
|
info.failCause = {};
|
|
742
663
|
info.log = "";
|
|
664
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
743
665
|
}
|
|
744
666
|
let highPriorityTimeout = 5000;
|
|
745
667
|
let visibleOnlyTimeout = 6000;
|
|
746
|
-
let startTime =
|
|
668
|
+
let startTime = Date.now();
|
|
747
669
|
let locatorsCount = 0;
|
|
748
670
|
let lazy_scroll = false;
|
|
749
671
|
//let arrayMode = Array.isArray(selectors);
|
|
@@ -783,17 +705,17 @@ class StableBrowser {
|
|
|
783
705
|
}
|
|
784
706
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
785
707
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
786
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
708
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
787
709
|
if (result.foundElements.length === 0) {
|
|
788
710
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
789
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
711
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
790
712
|
}
|
|
791
713
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
792
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
714
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
793
715
|
}
|
|
794
716
|
else {
|
|
795
717
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
796
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
718
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
797
719
|
}
|
|
798
720
|
}
|
|
799
721
|
let foundElements = result.foundElements;
|
|
@@ -834,30 +756,38 @@ class StableBrowser {
|
|
|
834
756
|
return maxCountElement.locator;
|
|
835
757
|
}
|
|
836
758
|
}
|
|
837
|
-
if (
|
|
759
|
+
if (Date.now() - startTime > timeout) {
|
|
838
760
|
break;
|
|
839
761
|
}
|
|
840
|
-
if (
|
|
841
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
762
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
763
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
842
764
|
highPriorityOnly = false;
|
|
843
765
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
844
766
|
lazy_scroll = true;
|
|
845
|
-
await this.
|
|
767
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
846
768
|
}
|
|
847
769
|
}
|
|
848
|
-
if (
|
|
849
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
770
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
771
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
850
772
|
visibleOnly = false;
|
|
851
773
|
}
|
|
852
774
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
853
775
|
}
|
|
854
776
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
855
|
-
info.
|
|
777
|
+
// if (info.locatorLog) {
|
|
778
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
779
|
+
// for (let line of lines) {
|
|
780
|
+
// this.logger.debug(line);
|
|
781
|
+
// }
|
|
782
|
+
// }
|
|
783
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
856
784
|
info.failCause.locatorNotFound = true;
|
|
857
|
-
info
|
|
785
|
+
if (!info?.failCause?.lastError) {
|
|
786
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
787
|
+
}
|
|
858
788
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
859
789
|
}
|
|
860
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
790
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
861
791
|
let foundElements = [];
|
|
862
792
|
const result = {
|
|
863
793
|
foundElements: foundElements,
|
|
@@ -865,14 +795,15 @@ class StableBrowser {
|
|
|
865
795
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
866
796
|
let foundLocators = [];
|
|
867
797
|
try {
|
|
868
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
798
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
869
799
|
}
|
|
870
800
|
catch (e) {
|
|
871
|
-
this
|
|
872
|
-
this.logger.debug(
|
|
801
|
+
// this call can fail it the browser is navigating
|
|
802
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
803
|
+
// this.logger.debug(e);
|
|
873
804
|
foundLocators = [];
|
|
874
805
|
try {
|
|
875
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
806
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
876
807
|
}
|
|
877
808
|
catch (e) {
|
|
878
809
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -888,11 +819,27 @@ class StableBrowser {
|
|
|
888
819
|
}
|
|
889
820
|
if (foundLocators.length > 1) {
|
|
890
821
|
info.failCause.foundMultiple = true;
|
|
822
|
+
if (info.locatorLog) {
|
|
823
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
824
|
+
}
|
|
891
825
|
}
|
|
892
826
|
}
|
|
893
827
|
return result;
|
|
894
828
|
}
|
|
895
829
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
830
|
+
const state = {
|
|
831
|
+
locate: false,
|
|
832
|
+
scroll: false,
|
|
833
|
+
highlight: false,
|
|
834
|
+
_params,
|
|
835
|
+
options,
|
|
836
|
+
world,
|
|
837
|
+
type: Types.CLICK,
|
|
838
|
+
text: "Click element",
|
|
839
|
+
operation: "simpleClick",
|
|
840
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
841
|
+
};
|
|
842
|
+
_preCommand(state, this);
|
|
896
843
|
const startTime = Date.now();
|
|
897
844
|
let timeout = 30000;
|
|
898
845
|
if (options && options.timeout) {
|
|
@@ -917,13 +864,31 @@ class StableBrowser {
|
|
|
917
864
|
catch (e) {
|
|
918
865
|
if (performance.now() - startTime > timeout) {
|
|
919
866
|
// throw e;
|
|
920
|
-
|
|
867
|
+
try {
|
|
868
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
869
|
+
}
|
|
870
|
+
finally {
|
|
871
|
+
_commandFinally(state, this);
|
|
872
|
+
}
|
|
921
873
|
}
|
|
922
874
|
}
|
|
923
875
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
924
876
|
}
|
|
925
877
|
}
|
|
926
878
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
879
|
+
const state = {
|
|
880
|
+
locate: false,
|
|
881
|
+
scroll: false,
|
|
882
|
+
highlight: false,
|
|
883
|
+
_params,
|
|
884
|
+
options,
|
|
885
|
+
world,
|
|
886
|
+
type: Types.FILL,
|
|
887
|
+
text: "Fill element",
|
|
888
|
+
operation: "simpleClickType",
|
|
889
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
890
|
+
};
|
|
891
|
+
_preCommand(state, this);
|
|
927
892
|
const startTime = Date.now();
|
|
928
893
|
let timeout = 30000;
|
|
929
894
|
if (options && options.timeout) {
|
|
@@ -948,7 +913,12 @@ class StableBrowser {
|
|
|
948
913
|
catch (e) {
|
|
949
914
|
if (performance.now() - startTime > timeout) {
|
|
950
915
|
// throw e;
|
|
951
|
-
|
|
916
|
+
try {
|
|
917
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
918
|
+
}
|
|
919
|
+
finally {
|
|
920
|
+
_commandFinally(state, this);
|
|
921
|
+
}
|
|
952
922
|
}
|
|
953
923
|
}
|
|
954
924
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -967,9 +937,9 @@ class StableBrowser {
|
|
|
967
937
|
};
|
|
968
938
|
try {
|
|
969
939
|
await _preCommand(state, this);
|
|
970
|
-
if (state.options && state.options.context) {
|
|
971
|
-
|
|
972
|
-
}
|
|
940
|
+
// if (state.options && state.options.context) {
|
|
941
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
942
|
+
// }
|
|
973
943
|
try {
|
|
974
944
|
await state.element.click();
|
|
975
945
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1007,9 +977,15 @@ class StableBrowser {
|
|
|
1007
977
|
// let element = await this._locate(selectors, info, _params);
|
|
1008
978
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1009
979
|
try {
|
|
1010
|
-
//
|
|
980
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
981
|
+
// console.log(`Highlighting while running from recorder`);
|
|
982
|
+
await this._highlightElements(element);
|
|
1011
983
|
await state.element.setChecked(checked);
|
|
1012
984
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
985
|
+
// await this._unHighlightElements(element);
|
|
986
|
+
// }
|
|
987
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
988
|
+
// await this._unHighlightElements(element);
|
|
1013
989
|
}
|
|
1014
990
|
catch (e) {
|
|
1015
991
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1048,6 +1024,7 @@ class StableBrowser {
|
|
|
1048
1024
|
await _preCommand(state, this);
|
|
1049
1025
|
try {
|
|
1050
1026
|
await state.element.hover();
|
|
1027
|
+
// await _screenshot(state, this);
|
|
1051
1028
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1052
1029
|
}
|
|
1053
1030
|
catch (e) {
|
|
@@ -1055,6 +1032,7 @@ class StableBrowser {
|
|
|
1055
1032
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1056
1033
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1057
1034
|
await state.element.hover({ timeout: 10000 });
|
|
1035
|
+
// await _screenshot(state, this);
|
|
1058
1036
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
1037
|
}
|
|
1060
1038
|
await _screenshot(state, this);
|
|
@@ -1329,7 +1307,12 @@ class StableBrowser {
|
|
|
1329
1307
|
await this.waitForPageLoad();
|
|
1330
1308
|
}
|
|
1331
1309
|
else if (enter === false) {
|
|
1332
|
-
|
|
1310
|
+
try {
|
|
1311
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1312
|
+
}
|
|
1313
|
+
catch (e) {
|
|
1314
|
+
// ignore
|
|
1315
|
+
}
|
|
1333
1316
|
//await this.page.keyboard.press("Tab");
|
|
1334
1317
|
}
|
|
1335
1318
|
else {
|
|
@@ -1386,6 +1369,7 @@ class StableBrowser {
|
|
|
1386
1369
|
let screenshotPath = null;
|
|
1387
1370
|
if (!info.log) {
|
|
1388
1371
|
info.log = "";
|
|
1372
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1389
1373
|
}
|
|
1390
1374
|
info.operation = "getText";
|
|
1391
1375
|
info.selectors = selectors;
|
|
@@ -1408,6 +1392,18 @@ class StableBrowser {
|
|
|
1408
1392
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1409
1393
|
try {
|
|
1410
1394
|
await this._highlightElements(element);
|
|
1395
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1396
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1397
|
+
// this._highlightElements(element)
|
|
1398
|
+
// .then(async () => {
|
|
1399
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1400
|
+
// this._unhighlightElements(element).then(
|
|
1401
|
+
// () => {}
|
|
1402
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1403
|
+
// );
|
|
1404
|
+
// })
|
|
1405
|
+
// .catch(e);
|
|
1406
|
+
// }
|
|
1411
1407
|
const elementText = await element.innerText();
|
|
1412
1408
|
return {
|
|
1413
1409
|
text: elementText,
|
|
@@ -1419,7 +1415,7 @@ class StableBrowser {
|
|
|
1419
1415
|
}
|
|
1420
1416
|
catch (e) {
|
|
1421
1417
|
//await this.closeUnexpectedPopups();
|
|
1422
|
-
this.logger.info("no innerText will use textContent");
|
|
1418
|
+
this.logger.info("no innerText, will use textContent");
|
|
1423
1419
|
const elementText = await element.textContent();
|
|
1424
1420
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1425
1421
|
}
|
|
@@ -1545,22 +1541,6 @@ class StableBrowser {
|
|
|
1545
1541
|
_commandFinally(state, this);
|
|
1546
1542
|
}
|
|
1547
1543
|
}
|
|
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
1544
|
async waitForUserInput(message, world = null) {
|
|
1565
1545
|
if (!message) {
|
|
1566
1546
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1589,7 +1569,7 @@ class StableBrowser {
|
|
|
1589
1569
|
return;
|
|
1590
1570
|
}
|
|
1591
1571
|
// if data file exists, load it
|
|
1592
|
-
const dataFile =
|
|
1572
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1593
1573
|
let data = this.getTestData(world);
|
|
1594
1574
|
// merge the testData with the existing data
|
|
1595
1575
|
Object.assign(data, testData);
|
|
@@ -1692,7 +1672,7 @@ class StableBrowser {
|
|
|
1692
1672
|
}
|
|
1693
1673
|
}
|
|
1694
1674
|
getTestData(world = null) {
|
|
1695
|
-
const dataFile =
|
|
1675
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1696
1676
|
let data = {};
|
|
1697
1677
|
if (fs.existsSync(dataFile)) {
|
|
1698
1678
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1779,6 +1759,15 @@ class StableBrowser {
|
|
|
1779
1759
|
document.documentElement.clientWidth,
|
|
1780
1760
|
])));
|
|
1781
1761
|
let screenshotBuffer = null;
|
|
1762
|
+
// if (focusedElement) {
|
|
1763
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1764
|
+
// await this._unhighlightElements(focusedElement);
|
|
1765
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1766
|
+
// console.log(`Unhighlighted previous element`);
|
|
1767
|
+
// }
|
|
1768
|
+
// if (focusedElement) {
|
|
1769
|
+
// await this._highlightElements(focusedElement);
|
|
1770
|
+
// }
|
|
1782
1771
|
if (this.context.browserName === "chromium") {
|
|
1783
1772
|
const client = await playContext.newCDPSession(this.page);
|
|
1784
1773
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1800,6 +1789,10 @@ class StableBrowser {
|
|
|
1800
1789
|
else {
|
|
1801
1790
|
screenshotBuffer = await this.page.screenshot();
|
|
1802
1791
|
}
|
|
1792
|
+
// if (focusedElement) {
|
|
1793
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1794
|
+
// await this._unhighlightElements(focusedElement);
|
|
1795
|
+
// }
|
|
1803
1796
|
let image = await Jimp.read(screenshotBuffer);
|
|
1804
1797
|
// Get the image dimensions
|
|
1805
1798
|
const { width, height } = image.bitmap;
|
|
@@ -1812,6 +1805,7 @@ class StableBrowser {
|
|
|
1812
1805
|
else {
|
|
1813
1806
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1814
1807
|
}
|
|
1808
|
+
return screenshotBuffer;
|
|
1815
1809
|
}
|
|
1816
1810
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1817
1811
|
const state = {
|
|
@@ -1849,6 +1843,7 @@ class StableBrowser {
|
|
|
1849
1843
|
text: `Extract attribute from element`,
|
|
1850
1844
|
operation: "extractAttribute",
|
|
1851
1845
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1846
|
+
allowDisabled: true,
|
|
1852
1847
|
};
|
|
1853
1848
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1854
1849
|
try {
|
|
@@ -1870,6 +1865,76 @@ class StableBrowser {
|
|
|
1870
1865
|
state.info.value = state.value;
|
|
1871
1866
|
this.setTestData({ [variable]: state.value }, world);
|
|
1872
1867
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1868
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1869
|
+
return state.info;
|
|
1870
|
+
}
|
|
1871
|
+
catch (e) {
|
|
1872
|
+
await _commandError(state, e, this);
|
|
1873
|
+
}
|
|
1874
|
+
finally {
|
|
1875
|
+
_commandFinally(state, this);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1879
|
+
const state = {
|
|
1880
|
+
selectors,
|
|
1881
|
+
_params,
|
|
1882
|
+
attribute,
|
|
1883
|
+
value,
|
|
1884
|
+
options,
|
|
1885
|
+
world,
|
|
1886
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1887
|
+
highlight: true,
|
|
1888
|
+
screenshot: true,
|
|
1889
|
+
text: `Verify element attribute`,
|
|
1890
|
+
operation: "verifyAttribute",
|
|
1891
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1892
|
+
allowDisabled: true,
|
|
1893
|
+
};
|
|
1894
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1895
|
+
let val;
|
|
1896
|
+
let expectedValue;
|
|
1897
|
+
try {
|
|
1898
|
+
await _preCommand(state, this);
|
|
1899
|
+
expectedValue = state.value;
|
|
1900
|
+
state.info.expectedValue = expectedValue;
|
|
1901
|
+
switch (attribute) {
|
|
1902
|
+
case "innerText":
|
|
1903
|
+
val = String(await state.element.innerText());
|
|
1904
|
+
break;
|
|
1905
|
+
case "value":
|
|
1906
|
+
val = String(await state.element.inputValue());
|
|
1907
|
+
break;
|
|
1908
|
+
case "checked":
|
|
1909
|
+
val = String(await state.element.isChecked());
|
|
1910
|
+
break;
|
|
1911
|
+
case "disabled":
|
|
1912
|
+
val = String(await state.element.isDisabled());
|
|
1913
|
+
break;
|
|
1914
|
+
case "readOnly":
|
|
1915
|
+
const isEditable = await state.element.isEditable();
|
|
1916
|
+
val = String(!isEditable);
|
|
1917
|
+
break;
|
|
1918
|
+
default:
|
|
1919
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
state.info.value = val;
|
|
1923
|
+
let regex;
|
|
1924
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1925
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1926
|
+
regex = new RegExp(patternBody, "g");
|
|
1927
|
+
}
|
|
1928
|
+
else {
|
|
1929
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1930
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1931
|
+
}
|
|
1932
|
+
if (!val.match(regex)) {
|
|
1933
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1934
|
+
state.info.failCause.assertionFailed = true;
|
|
1935
|
+
state.info.failCause.lastError = errorMessage;
|
|
1936
|
+
throw new Error(errorMessage);
|
|
1937
|
+
}
|
|
1873
1938
|
return state.info;
|
|
1874
1939
|
}
|
|
1875
1940
|
catch (e) {
|
|
@@ -1897,7 +1962,7 @@ class StableBrowser {
|
|
|
1897
1962
|
if (options && options.timeout) {
|
|
1898
1963
|
timeout = options.timeout;
|
|
1899
1964
|
}
|
|
1900
|
-
const serviceUrl =
|
|
1965
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1901
1966
|
const request = {
|
|
1902
1967
|
method: "POST",
|
|
1903
1968
|
url: serviceUrl,
|
|
@@ -1968,27 +2033,32 @@ class StableBrowser {
|
|
|
1968
2033
|
async _highlightElements(scope, css) {
|
|
1969
2034
|
try {
|
|
1970
2035
|
if (!scope) {
|
|
2036
|
+
// console.log(`Scope is not defined`);
|
|
1971
2037
|
return;
|
|
1972
2038
|
}
|
|
1973
2039
|
if (!css) {
|
|
1974
2040
|
scope
|
|
1975
2041
|
.evaluate((node) => {
|
|
1976
2042
|
if (node && node.style) {
|
|
1977
|
-
let
|
|
1978
|
-
|
|
2043
|
+
let originalOutline = node.style.outline;
|
|
2044
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2045
|
+
// node.__previousOutline = originalOutline;
|
|
2046
|
+
node.style.outline = "2px solid red";
|
|
2047
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1979
2048
|
if (window) {
|
|
1980
2049
|
window.addEventListener("beforeunload", function (e) {
|
|
1981
|
-
node.style.
|
|
2050
|
+
node.style.outline = originalOutline;
|
|
1982
2051
|
});
|
|
1983
2052
|
}
|
|
1984
2053
|
setTimeout(function () {
|
|
1985
|
-
node.style.
|
|
2054
|
+
node.style.outline = originalOutline;
|
|
1986
2055
|
}, 2000);
|
|
1987
2056
|
}
|
|
1988
2057
|
})
|
|
1989
2058
|
.then(() => { })
|
|
1990
2059
|
.catch((e) => {
|
|
1991
2060
|
// ignore
|
|
2061
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1992
2062
|
});
|
|
1993
2063
|
}
|
|
1994
2064
|
else {
|
|
@@ -2004,17 +2074,18 @@ class StableBrowser {
|
|
|
2004
2074
|
if (!element.style) {
|
|
2005
2075
|
return;
|
|
2006
2076
|
}
|
|
2007
|
-
|
|
2077
|
+
let originalOutline = element.style.outline;
|
|
2078
|
+
element.__previousOutline = originalOutline;
|
|
2008
2079
|
// Set the new border to be red and 2px solid
|
|
2009
|
-
element.style.
|
|
2080
|
+
element.style.outline = "2px solid red";
|
|
2010
2081
|
if (window) {
|
|
2011
2082
|
window.addEventListener("beforeunload", function (e) {
|
|
2012
|
-
element.style.
|
|
2083
|
+
element.style.outline = originalOutline;
|
|
2013
2084
|
});
|
|
2014
2085
|
}
|
|
2015
2086
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2016
2087
|
setTimeout(function () {
|
|
2017
|
-
element.style.
|
|
2088
|
+
element.style.outline = originalOutline;
|
|
2018
2089
|
}, 2000);
|
|
2019
2090
|
}
|
|
2020
2091
|
return;
|
|
@@ -2022,6 +2093,7 @@ class StableBrowser {
|
|
|
2022
2093
|
.then(() => { })
|
|
2023
2094
|
.catch((e) => {
|
|
2024
2095
|
// ignore
|
|
2096
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2025
2097
|
});
|
|
2026
2098
|
}
|
|
2027
2099
|
}
|
|
@@ -2029,6 +2101,54 @@ class StableBrowser {
|
|
|
2029
2101
|
console.debug(error);
|
|
2030
2102
|
}
|
|
2031
2103
|
}
|
|
2104
|
+
// async _unhighlightElements(scope, css) {
|
|
2105
|
+
// try {
|
|
2106
|
+
// if (!scope) {
|
|
2107
|
+
// return;
|
|
2108
|
+
// }
|
|
2109
|
+
// if (!css) {
|
|
2110
|
+
// scope
|
|
2111
|
+
// .evaluate((node) => {
|
|
2112
|
+
// if (node && node.style) {
|
|
2113
|
+
// if (!node.__previousOutline) {
|
|
2114
|
+
// node.style.outline = "";
|
|
2115
|
+
// } else {
|
|
2116
|
+
// node.style.outline = node.__previousOutline;
|
|
2117
|
+
// }
|
|
2118
|
+
// }
|
|
2119
|
+
// })
|
|
2120
|
+
// .then(() => {})
|
|
2121
|
+
// .catch((e) => {
|
|
2122
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2123
|
+
// });
|
|
2124
|
+
// } else {
|
|
2125
|
+
// scope
|
|
2126
|
+
// .evaluate(([css]) => {
|
|
2127
|
+
// if (!css) {
|
|
2128
|
+
// return;
|
|
2129
|
+
// }
|
|
2130
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2131
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2132
|
+
// let element = elements[i];
|
|
2133
|
+
// if (!element.style) {
|
|
2134
|
+
// return;
|
|
2135
|
+
// }
|
|
2136
|
+
// if (!element.__previousOutline) {
|
|
2137
|
+
// element.style.outline = "";
|
|
2138
|
+
// } else {
|
|
2139
|
+
// element.style.outline = element.__previousOutline;
|
|
2140
|
+
// }
|
|
2141
|
+
// }
|
|
2142
|
+
// })
|
|
2143
|
+
// .then(() => {})
|
|
2144
|
+
// .catch((e) => {
|
|
2145
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2146
|
+
// });
|
|
2147
|
+
// }
|
|
2148
|
+
// } catch (error) {
|
|
2149
|
+
// // console.debug(error);
|
|
2150
|
+
// }
|
|
2151
|
+
// }
|
|
2032
2152
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2033
2153
|
const startTime = Date.now();
|
|
2034
2154
|
let error = null;
|
|
@@ -2090,6 +2210,35 @@ class StableBrowser {
|
|
|
2090
2210
|
});
|
|
2091
2211
|
}
|
|
2092
2212
|
}
|
|
2213
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2214
|
+
const frames = this.page.frames();
|
|
2215
|
+
let results = [];
|
|
2216
|
+
let ignoreCase = false;
|
|
2217
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2218
|
+
if (dateAlternatives.date) {
|
|
2219
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2220
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2221
|
+
result.frame = frames[i];
|
|
2222
|
+
results.push(result);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
else if (numberAlternatives.number) {
|
|
2226
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2227
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2228
|
+
result.frame = frames[i];
|
|
2229
|
+
results.push(result);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
else {
|
|
2233
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2234
|
+
result.frame = frames[i];
|
|
2235
|
+
results.push(result);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
state.info.results = results;
|
|
2239
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2240
|
+
return resultWithElementsFound;
|
|
2241
|
+
}
|
|
2093
2242
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2094
2243
|
text = unEscapeString(text);
|
|
2095
2244
|
const state = {
|
|
@@ -2099,7 +2248,7 @@ class StableBrowser {
|
|
|
2099
2248
|
locate: false,
|
|
2100
2249
|
scroll: false,
|
|
2101
2250
|
highlight: false,
|
|
2102
|
-
type: Types.
|
|
2251
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2103
2252
|
text: `Verify text exists in page`,
|
|
2104
2253
|
operation: "verifyTextExistInPage",
|
|
2105
2254
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2117,31 +2266,15 @@ class StableBrowser {
|
|
|
2117
2266
|
await _preCommand(state, this);
|
|
2118
2267
|
state.info.text = text;
|
|
2119
2268
|
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
|
-
}
|
|
2269
|
+
let resultWithElementsFound = {
|
|
2270
|
+
length: 0,
|
|
2271
|
+
};
|
|
2272
|
+
try {
|
|
2273
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2274
|
+
}
|
|
2275
|
+
catch (error) {
|
|
2276
|
+
// ignore
|
|
2142
2277
|
}
|
|
2143
|
-
state.info.results = results;
|
|
2144
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2145
2278
|
if (resultWithElementsFound.length === 0) {
|
|
2146
2279
|
if (Date.now() - state.startTime > timeout) {
|
|
2147
2280
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2149,18 +2282,40 @@ class StableBrowser {
|
|
|
2149
2282
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2150
2283
|
continue;
|
|
2151
2284
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2285
|
+
try {
|
|
2286
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2287
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2288
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2289
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2290
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2291
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2292
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2293
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2294
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2295
|
+
// .then(async () => {
|
|
2296
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2297
|
+
// })
|
|
2298
|
+
// .catch(
|
|
2299
|
+
// (e) => {}
|
|
2300
|
+
// console.error(e)
|
|
2301
|
+
// );
|
|
2302
|
+
// });
|
|
2303
|
+
// }
|
|
2304
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2305
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2306
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2307
|
+
if (element) {
|
|
2308
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2309
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2310
|
+
// await _screenshot(state, this, element);
|
|
2311
|
+
}
|
|
2160
2312
|
}
|
|
2313
|
+
await _screenshot(state, this);
|
|
2314
|
+
return state.info;
|
|
2315
|
+
}
|
|
2316
|
+
catch (error) {
|
|
2317
|
+
console.error(error);
|
|
2161
2318
|
}
|
|
2162
|
-
await _screenshot(state, this);
|
|
2163
|
-
return state.info;
|
|
2164
2319
|
}
|
|
2165
2320
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2166
2321
|
}
|
|
@@ -2197,32 +2352,16 @@ class StableBrowser {
|
|
|
2197
2352
|
try {
|
|
2198
2353
|
await _preCommand(state, this);
|
|
2199
2354
|
state.info.text = text;
|
|
2355
|
+
let resultWithElementsFound = {
|
|
2356
|
+
length: null, // initial cannot be 0
|
|
2357
|
+
};
|
|
2200
2358
|
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
|
-
}
|
|
2359
|
+
try {
|
|
2360
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2361
|
+
}
|
|
2362
|
+
catch (error) {
|
|
2363
|
+
// ignore
|
|
2223
2364
|
}
|
|
2224
|
-
state.info.results = results;
|
|
2225
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2226
2365
|
if (resultWithElementsFound.length === 0) {
|
|
2227
2366
|
await _screenshot(state, this);
|
|
2228
2367
|
return state.info;
|
|
@@ -2240,15 +2379,115 @@ class StableBrowser {
|
|
|
2240
2379
|
_commandFinally(state, this);
|
|
2241
2380
|
}
|
|
2242
2381
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2382
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2383
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2384
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2385
|
+
const state = {
|
|
2386
|
+
text_search: textToVerify,
|
|
2387
|
+
options,
|
|
2388
|
+
world,
|
|
2389
|
+
locate: false,
|
|
2390
|
+
scroll: false,
|
|
2391
|
+
highlight: false,
|
|
2392
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2393
|
+
text: `Verify text with relation to another text`,
|
|
2394
|
+
operation: "verify_text_with_relation",
|
|
2395
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2396
|
+
};
|
|
2397
|
+
const timeout = this._getLoadTimeout(options);
|
|
2398
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2399
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2400
|
+
if (newValue !== textAnchor) {
|
|
2401
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2402
|
+
textAnchor = newValue;
|
|
2403
|
+
}
|
|
2404
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2405
|
+
if (newValue !== textToVerify) {
|
|
2406
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2407
|
+
textToVerify = newValue;
|
|
2408
|
+
}
|
|
2409
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2410
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2411
|
+
let foundAncore = false;
|
|
2412
|
+
try {
|
|
2413
|
+
await _preCommand(state, this);
|
|
2414
|
+
state.info.text = textToVerify;
|
|
2415
|
+
let resultWithElementsFound = {
|
|
2416
|
+
length: 0,
|
|
2417
|
+
};
|
|
2418
|
+
while (true) {
|
|
2419
|
+
try {
|
|
2420
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2421
|
+
}
|
|
2422
|
+
catch (error) {
|
|
2423
|
+
// ignore
|
|
2424
|
+
}
|
|
2425
|
+
if (resultWithElementsFound.length === 0) {
|
|
2426
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2427
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2428
|
+
}
|
|
2429
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2430
|
+
continue;
|
|
2431
|
+
}
|
|
2432
|
+
try {
|
|
2433
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2434
|
+
foundAncore = true;
|
|
2435
|
+
const result = resultWithElementsFound[i];
|
|
2436
|
+
const token = result.randomToken;
|
|
2437
|
+
const frame = result.frame;
|
|
2438
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2439
|
+
const climbArray1 = [];
|
|
2440
|
+
for (let i = 0; i < climb; i++) {
|
|
2441
|
+
climbArray1.push("..");
|
|
2442
|
+
}
|
|
2443
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2444
|
+
css = css + " >> " + climbXpath;
|
|
2445
|
+
const count = await frame.locator(css).count();
|
|
2446
|
+
for (let j = 0; j < count; j++) {
|
|
2447
|
+
const continer = await frame.locator(css).nth(j);
|
|
2448
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2449
|
+
if (result.elementCount > 0) {
|
|
2450
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2451
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2452
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2453
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2454
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2455
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2456
|
+
// .then(async () => {
|
|
2457
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2458
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2459
|
+
// () => {}
|
|
2460
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2461
|
+
// );
|
|
2462
|
+
// })
|
|
2463
|
+
// .catch(e);
|
|
2464
|
+
// }
|
|
2465
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2466
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2467
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2468
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2469
|
+
if (element) {
|
|
2470
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2471
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2472
|
+
}
|
|
2473
|
+
await _screenshot(state, this);
|
|
2474
|
+
return state.info;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
catch (error) {
|
|
2480
|
+
console.error(error);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2247
2484
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2485
|
+
catch (e) {
|
|
2486
|
+
await _commandError(state, e, this);
|
|
2487
|
+
}
|
|
2488
|
+
finally {
|
|
2489
|
+
_commandFinally(state, this);
|
|
2250
2490
|
}
|
|
2251
|
-
return serviceUrl;
|
|
2252
2491
|
}
|
|
2253
2492
|
async visualVerification(text, options = {}, world = null) {
|
|
2254
2493
|
const startTime = Date.now();
|
|
@@ -2264,14 +2503,17 @@ class StableBrowser {
|
|
|
2264
2503
|
throw new Error("TOKEN is not set");
|
|
2265
2504
|
}
|
|
2266
2505
|
try {
|
|
2267
|
-
let serviceUrl =
|
|
2506
|
+
let serviceUrl = _getServerUrl();
|
|
2268
2507
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2269
2508
|
info.screenshotPath = screenshotPath;
|
|
2270
2509
|
const screenshot = await this.takeScreenshot();
|
|
2271
|
-
|
|
2272
|
-
method: "
|
|
2510
|
+
let request = {
|
|
2511
|
+
method: "post",
|
|
2512
|
+
maxBodyLength: Infinity,
|
|
2273
2513
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2274
2514
|
headers: {
|
|
2515
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2516
|
+
"x-source": "aaa",
|
|
2275
2517
|
"Content-Type": "application/json",
|
|
2276
2518
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2277
2519
|
},
|
|
@@ -2280,7 +2522,7 @@ class StableBrowser {
|
|
|
2280
2522
|
screenshot: screenshot,
|
|
2281
2523
|
}),
|
|
2282
2524
|
};
|
|
2283
|
-
|
|
2525
|
+
const result = await axios.request(request);
|
|
2284
2526
|
if (result.data.status !== true) {
|
|
2285
2527
|
throw new Error("Visual validation failed");
|
|
2286
2528
|
}
|
|
@@ -2353,6 +2595,7 @@ class StableBrowser {
|
|
|
2353
2595
|
let screenshotPath = null;
|
|
2354
2596
|
const info = {};
|
|
2355
2597
|
info.log = "";
|
|
2598
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2356
2599
|
info.operation = "getTableData";
|
|
2357
2600
|
info.selectors = selectors;
|
|
2358
2601
|
try {
|
|
@@ -2428,7 +2671,7 @@ class StableBrowser {
|
|
|
2428
2671
|
info.operation = "analyzeTable";
|
|
2429
2672
|
info.selectors = selectors;
|
|
2430
2673
|
info.query = query;
|
|
2431
|
-
query =
|
|
2674
|
+
query = _fixUsingParams(query, _params);
|
|
2432
2675
|
info.query_fixed = query;
|
|
2433
2676
|
info.operator = operator;
|
|
2434
2677
|
info.value = value;
|
|
@@ -2573,6 +2816,17 @@ class StableBrowser {
|
|
|
2573
2816
|
}
|
|
2574
2817
|
return timeout;
|
|
2575
2818
|
}
|
|
2819
|
+
async saveStoreState(path = null, world = null) {
|
|
2820
|
+
const storageState = await this.page.context().storageState();
|
|
2821
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2822
|
+
if (path) {
|
|
2823
|
+
// save { storageState: storageState } into the path
|
|
2824
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2825
|
+
}
|
|
2826
|
+
else {
|
|
2827
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2576
2830
|
async waitForPageLoad(options = {}, world = null) {
|
|
2577
2831
|
let timeout = this._getLoadTimeout(options);
|
|
2578
2832
|
const promiseArray = [];
|
|
@@ -2758,14 +3012,22 @@ class StableBrowser {
|
|
|
2758
3012
|
}
|
|
2759
3013
|
}
|
|
2760
3014
|
async beforeStep(world, step) {
|
|
2761
|
-
this.stepName = step.pickleStep.text;
|
|
2762
|
-
this.logger.info("step: " + this.stepName);
|
|
2763
3015
|
if (this.stepIndex === undefined) {
|
|
2764
3016
|
this.stepIndex = 0;
|
|
2765
3017
|
}
|
|
2766
3018
|
else {
|
|
2767
3019
|
this.stepIndex++;
|
|
2768
3020
|
}
|
|
3021
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3022
|
+
this.stepName = step.pickleStep.text;
|
|
3023
|
+
this.logger.info("step: " + this.stepName);
|
|
3024
|
+
}
|
|
3025
|
+
else if (step && step.text) {
|
|
3026
|
+
this.stepName = step.text;
|
|
3027
|
+
}
|
|
3028
|
+
else {
|
|
3029
|
+
this.stepName = "step " + this.stepIndex;
|
|
3030
|
+
}
|
|
2769
3031
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2770
3032
|
if (this.context.browserObject.context) {
|
|
2771
3033
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2795,156 +3057,5 @@ function createTimedPromise(promise, label) {
|
|
|
2795
3057
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2796
3058
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2797
3059
|
}
|
|
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
3060
|
export { StableBrowser };
|
|
2950
3061
|
//# sourceMappingURL=stable_browser.js.map
|