automation_model 1.0.532-dev → 1.0.532-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.d.ts +1 -0
- package/lib/api.js +8 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +90 -4
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +70 -27
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +51 -9
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +29 -5
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +51 -12
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +50 -18
- package/lib/stable_browser.js +632 -512
- 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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return new RegExp(pattern, flags);
|
|
400
|
-
}
|
|
401
|
-
document.getRegex = getRegex;
|
|
402
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
403
|
-
// Check and add the element if it has a shadow root
|
|
404
|
-
if (element.shadowRoot) {
|
|
405
|
-
result.push(element);
|
|
406
|
-
// Also search within the shadow root
|
|
407
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
408
|
-
}
|
|
409
|
-
// Iterate over child nodes
|
|
410
|
-
element.childNodes.forEach((child) => {
|
|
411
|
-
// Recursively call the function for each child node
|
|
412
|
-
document.collectAllShadowDomElements(child, result);
|
|
413
|
-
});
|
|
414
|
-
return result;
|
|
415
|
-
}
|
|
416
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
417
|
-
if (!tag) {
|
|
418
|
-
tag = "*:not(script, style, head)";
|
|
419
|
-
}
|
|
420
|
-
let regexpSearch = document.getRegex(text);
|
|
421
|
-
if (regexpSearch) {
|
|
422
|
-
regex = true;
|
|
423
|
-
}
|
|
424
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
425
|
-
let shadowHosts = [];
|
|
426
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
427
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
428
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
429
|
-
if (!shadowElement) {
|
|
430
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
434
|
-
elements = elements.concat(shadowElements);
|
|
435
|
-
}
|
|
436
|
-
let randomToken = null;
|
|
437
|
-
const foundElements = [];
|
|
438
|
-
if (regex) {
|
|
439
|
-
if (!regexpSearch) {
|
|
440
|
-
regexpSearch = new RegExp(text, "im");
|
|
441
|
-
}
|
|
442
|
-
for (let i = 0; i < elements.length; i++) {
|
|
443
|
-
const element = elements[i];
|
|
444
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
445
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
446
|
-
foundElements.push(element);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
text = text.trim();
|
|
452
|
-
for (let i = 0; i < elements.length; i++) {
|
|
453
|
-
const element = elements[i];
|
|
454
|
-
if (partial) {
|
|
455
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
456
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
457
|
-
foundElements.push(element);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
462
|
-
(element.value && element.value === text)) {
|
|
463
|
-
foundElements.push(element);
|
|
464
|
-
}
|
|
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;
|
|
465
365
|
}
|
|
466
366
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
470
|
-
let element = foundElements[i];
|
|
471
|
-
let hasChild = false;
|
|
472
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
473
|
-
if (i === j) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
if (isParent(element, foundElements[j])) {
|
|
477
|
-
hasChild = true;
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (!hasChild) {
|
|
482
|
-
noChildElements.push(element);
|
|
367
|
+
if (!el.setAttribute) {
|
|
368
|
+
el = el.parentElement;
|
|
483
369
|
}
|
|
370
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
371
|
+
return true;
|
|
372
|
+
}, [tag1, randomToken]))) {
|
|
373
|
+
continue;
|
|
484
374
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (randomToken === null) {
|
|
489
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
490
|
-
}
|
|
491
|
-
let element = noChildElements[i];
|
|
492
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
493
|
-
elementCount++;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
497
|
-
}, [text1, tag1, regex1, partial1]);
|
|
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,7 +489,7 @@ 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);
|
|
@@ -615,16 +516,28 @@ class StableBrowser {
|
|
|
615
516
|
}
|
|
616
517
|
if (result.foundElements.length > 0) {
|
|
617
518
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
519
|
+
try {
|
|
520
|
+
await scope?.evaluate(() => {
|
|
521
|
+
window.__isClosingPopups = true;
|
|
522
|
+
});
|
|
523
|
+
await dialogCloseLocator.click();
|
|
524
|
+
// wait for the dialog to close
|
|
525
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
}
|
|
529
|
+
finally {
|
|
530
|
+
await scope?.evaluate(() => {
|
|
531
|
+
window.__isClosingPopups = false;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
621
534
|
return { rerun: true };
|
|
622
535
|
}
|
|
623
536
|
}
|
|
624
537
|
}
|
|
625
538
|
return { rerun: false };
|
|
626
539
|
}
|
|
627
|
-
async _locate(selectors, info, _params, timeout) {
|
|
540
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
628
541
|
if (!timeout) {
|
|
629
542
|
timeout = 30000;
|
|
630
543
|
}
|
|
@@ -634,9 +547,15 @@ class StableBrowser {
|
|
|
634
547
|
let selector = selectors.locators[j];
|
|
635
548
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
636
549
|
}
|
|
637
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
550
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
638
551
|
if (!element.rerun) {
|
|
639
|
-
|
|
552
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
553
|
+
element.evaluate((el, randomToken) => {
|
|
554
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
555
|
+
}, randomToken);
|
|
556
|
+
const scope = element._frame ?? element.page();
|
|
557
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
558
|
+
return newSelector;
|
|
640
559
|
}
|
|
641
560
|
}
|
|
642
561
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -647,6 +566,7 @@ class StableBrowser {
|
|
|
647
566
|
info.failCause = {};
|
|
648
567
|
info.log = "";
|
|
649
568
|
}
|
|
569
|
+
let startTime = Date.now();
|
|
650
570
|
let scope = this.page;
|
|
651
571
|
if (selectors.frame) {
|
|
652
572
|
return selectors.frame;
|
|
@@ -677,9 +597,11 @@ class StableBrowser {
|
|
|
677
597
|
}
|
|
678
598
|
return framescope;
|
|
679
599
|
};
|
|
600
|
+
let fLocator = null;
|
|
680
601
|
while (true) {
|
|
681
602
|
let frameFound = false;
|
|
682
603
|
if (selectors.nestFrmLoc) {
|
|
604
|
+
fLocator = selectors.nestFrmLoc;
|
|
683
605
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
684
606
|
frameFound = true;
|
|
685
607
|
break;
|
|
@@ -688,6 +610,7 @@ class StableBrowser {
|
|
|
688
610
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
689
611
|
let frameLocator = selectors.frameLocators[i];
|
|
690
612
|
if (frameLocator.css) {
|
|
613
|
+
fLocator = frameLocator.css;
|
|
691
614
|
scope = scope.frameLocator(frameLocator.css);
|
|
692
615
|
frameFound = true;
|
|
693
616
|
break;
|
|
@@ -695,18 +618,25 @@ class StableBrowser {
|
|
|
695
618
|
}
|
|
696
619
|
}
|
|
697
620
|
if (!frameFound && selectors.iframe_src) {
|
|
621
|
+
fLocator = selectors.iframe_src;
|
|
698
622
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
699
623
|
}
|
|
700
624
|
if (!scope) {
|
|
701
|
-
info
|
|
702
|
-
|
|
625
|
+
if (info && info.locatorLog) {
|
|
626
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
627
|
+
}
|
|
628
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
629
|
+
if (Date.now() - startTime > timeout) {
|
|
703
630
|
info.failCause.iframeNotFound = true;
|
|
704
|
-
info.failCause.lastError =
|
|
631
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
705
632
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
706
633
|
}
|
|
707
634
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
708
635
|
}
|
|
709
636
|
else {
|
|
637
|
+
if (info && info.locatorLog) {
|
|
638
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
639
|
+
}
|
|
710
640
|
break;
|
|
711
641
|
}
|
|
712
642
|
}
|
|
@@ -723,15 +653,16 @@ class StableBrowser {
|
|
|
723
653
|
return bodyContent;
|
|
724
654
|
});
|
|
725
655
|
}
|
|
726
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
656
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
727
657
|
if (!info) {
|
|
728
658
|
info = {};
|
|
729
659
|
info.failCause = {};
|
|
730
660
|
info.log = "";
|
|
661
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
731
662
|
}
|
|
732
663
|
let highPriorityTimeout = 5000;
|
|
733
664
|
let visibleOnlyTimeout = 6000;
|
|
734
|
-
let startTime =
|
|
665
|
+
let startTime = Date.now();
|
|
735
666
|
let locatorsCount = 0;
|
|
736
667
|
let lazy_scroll = false;
|
|
737
668
|
//let arrayMode = Array.isArray(selectors);
|
|
@@ -771,17 +702,17 @@ class StableBrowser {
|
|
|
771
702
|
}
|
|
772
703
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
773
704
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
774
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
705
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
775
706
|
if (result.foundElements.length === 0) {
|
|
776
707
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
777
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
708
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
778
709
|
}
|
|
779
710
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
780
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
711
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
781
712
|
}
|
|
782
713
|
else {
|
|
783
714
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
784
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
715
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
785
716
|
}
|
|
786
717
|
}
|
|
787
718
|
let foundElements = result.foundElements;
|
|
@@ -822,30 +753,38 @@ class StableBrowser {
|
|
|
822
753
|
return maxCountElement.locator;
|
|
823
754
|
}
|
|
824
755
|
}
|
|
825
|
-
if (
|
|
756
|
+
if (Date.now() - startTime > timeout) {
|
|
826
757
|
break;
|
|
827
758
|
}
|
|
828
|
-
if (
|
|
829
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
759
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
760
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
830
761
|
highPriorityOnly = false;
|
|
831
762
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
832
763
|
lazy_scroll = true;
|
|
833
|
-
await this.
|
|
764
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
834
765
|
}
|
|
835
766
|
}
|
|
836
|
-
if (
|
|
837
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
767
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
768
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
838
769
|
visibleOnly = false;
|
|
839
770
|
}
|
|
840
771
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
841
772
|
}
|
|
842
773
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
843
|
-
info.
|
|
774
|
+
// if (info.locatorLog) {
|
|
775
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
776
|
+
// for (let line of lines) {
|
|
777
|
+
// this.logger.debug(line);
|
|
778
|
+
// }
|
|
779
|
+
// }
|
|
780
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
844
781
|
info.failCause.locatorNotFound = true;
|
|
845
|
-
info
|
|
782
|
+
if (!info?.failCause?.lastError) {
|
|
783
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
784
|
+
}
|
|
846
785
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
847
786
|
}
|
|
848
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
787
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
849
788
|
let foundElements = [];
|
|
850
789
|
const result = {
|
|
851
790
|
foundElements: foundElements,
|
|
@@ -853,14 +792,15 @@ class StableBrowser {
|
|
|
853
792
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
854
793
|
let foundLocators = [];
|
|
855
794
|
try {
|
|
856
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
795
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
857
796
|
}
|
|
858
797
|
catch (e) {
|
|
859
|
-
this
|
|
860
|
-
this.logger.debug(
|
|
798
|
+
// this call can fail it the browser is navigating
|
|
799
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
800
|
+
// this.logger.debug(e);
|
|
861
801
|
foundLocators = [];
|
|
862
802
|
try {
|
|
863
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
803
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
864
804
|
}
|
|
865
805
|
catch (e) {
|
|
866
806
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -876,11 +816,27 @@ class StableBrowser {
|
|
|
876
816
|
}
|
|
877
817
|
if (foundLocators.length > 1) {
|
|
878
818
|
info.failCause.foundMultiple = true;
|
|
819
|
+
if (info.locatorLog) {
|
|
820
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
821
|
+
}
|
|
879
822
|
}
|
|
880
823
|
}
|
|
881
824
|
return result;
|
|
882
825
|
}
|
|
883
826
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
827
|
+
const state = {
|
|
828
|
+
locate: false,
|
|
829
|
+
scroll: false,
|
|
830
|
+
highlight: false,
|
|
831
|
+
_params,
|
|
832
|
+
options,
|
|
833
|
+
world,
|
|
834
|
+
type: Types.CLICK,
|
|
835
|
+
text: "Click element",
|
|
836
|
+
operation: "simpleClick",
|
|
837
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
838
|
+
};
|
|
839
|
+
_preCommand(state, this);
|
|
884
840
|
const startTime = Date.now();
|
|
885
841
|
let timeout = 30000;
|
|
886
842
|
if (options && options.timeout) {
|
|
@@ -905,13 +861,31 @@ class StableBrowser {
|
|
|
905
861
|
catch (e) {
|
|
906
862
|
if (performance.now() - startTime > timeout) {
|
|
907
863
|
// throw e;
|
|
908
|
-
|
|
864
|
+
try {
|
|
865
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
866
|
+
}
|
|
867
|
+
finally {
|
|
868
|
+
_commandFinally(state, this);
|
|
869
|
+
}
|
|
909
870
|
}
|
|
910
871
|
}
|
|
911
872
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
912
873
|
}
|
|
913
874
|
}
|
|
914
875
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
876
|
+
const state = {
|
|
877
|
+
locate: false,
|
|
878
|
+
scroll: false,
|
|
879
|
+
highlight: false,
|
|
880
|
+
_params,
|
|
881
|
+
options,
|
|
882
|
+
world,
|
|
883
|
+
type: Types.FILL,
|
|
884
|
+
text: "Fill element",
|
|
885
|
+
operation: "simpleClickType",
|
|
886
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
887
|
+
};
|
|
888
|
+
_preCommand(state, this);
|
|
915
889
|
const startTime = Date.now();
|
|
916
890
|
let timeout = 30000;
|
|
917
891
|
if (options && options.timeout) {
|
|
@@ -936,7 +910,12 @@ class StableBrowser {
|
|
|
936
910
|
catch (e) {
|
|
937
911
|
if (performance.now() - startTime > timeout) {
|
|
938
912
|
// throw e;
|
|
939
|
-
|
|
913
|
+
try {
|
|
914
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
915
|
+
}
|
|
916
|
+
finally {
|
|
917
|
+
_commandFinally(state, this);
|
|
918
|
+
}
|
|
940
919
|
}
|
|
941
920
|
}
|
|
942
921
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -955,9 +934,9 @@ class StableBrowser {
|
|
|
955
934
|
};
|
|
956
935
|
try {
|
|
957
936
|
await _preCommand(state, this);
|
|
958
|
-
if (state.options && state.options.context) {
|
|
959
|
-
|
|
960
|
-
}
|
|
937
|
+
// if (state.options && state.options.context) {
|
|
938
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
939
|
+
// }
|
|
961
940
|
try {
|
|
962
941
|
await state.element.click();
|
|
963
942
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -995,9 +974,15 @@ class StableBrowser {
|
|
|
995
974
|
// let element = await this._locate(selectors, info, _params);
|
|
996
975
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
997
976
|
try {
|
|
998
|
-
//
|
|
977
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
978
|
+
// console.log(`Highlighting while running from recorder`);
|
|
979
|
+
await this._highlightElements(element);
|
|
999
980
|
await state.element.setChecked(checked);
|
|
1000
981
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
982
|
+
// await this._unHighlightElements(element);
|
|
983
|
+
// }
|
|
984
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
985
|
+
// await this._unHighlightElements(element);
|
|
1001
986
|
}
|
|
1002
987
|
catch (e) {
|
|
1003
988
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1036,6 +1021,7 @@ class StableBrowser {
|
|
|
1036
1021
|
await _preCommand(state, this);
|
|
1037
1022
|
try {
|
|
1038
1023
|
await state.element.hover();
|
|
1024
|
+
// await _screenshot(state, this);
|
|
1039
1025
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1040
1026
|
}
|
|
1041
1027
|
catch (e) {
|
|
@@ -1043,6 +1029,7 @@ class StableBrowser {
|
|
|
1043
1029
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1044
1030
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1045
1031
|
await state.element.hover({ timeout: 10000 });
|
|
1032
|
+
// await _screenshot(state, this);
|
|
1046
1033
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1047
1034
|
}
|
|
1048
1035
|
await _screenshot(state, this);
|
|
@@ -1317,7 +1304,12 @@ class StableBrowser {
|
|
|
1317
1304
|
await this.waitForPageLoad();
|
|
1318
1305
|
}
|
|
1319
1306
|
else if (enter === false) {
|
|
1320
|
-
|
|
1307
|
+
try {
|
|
1308
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1309
|
+
}
|
|
1310
|
+
catch (e) {
|
|
1311
|
+
// ignore
|
|
1312
|
+
}
|
|
1321
1313
|
//await this.page.keyboard.press("Tab");
|
|
1322
1314
|
}
|
|
1323
1315
|
else {
|
|
@@ -1374,6 +1366,7 @@ class StableBrowser {
|
|
|
1374
1366
|
let screenshotPath = null;
|
|
1375
1367
|
if (!info.log) {
|
|
1376
1368
|
info.log = "";
|
|
1369
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1377
1370
|
}
|
|
1378
1371
|
info.operation = "getText";
|
|
1379
1372
|
info.selectors = selectors;
|
|
@@ -1396,6 +1389,18 @@ class StableBrowser {
|
|
|
1396
1389
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1397
1390
|
try {
|
|
1398
1391
|
await this._highlightElements(element);
|
|
1392
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1393
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1394
|
+
// this._highlightElements(element)
|
|
1395
|
+
// .then(async () => {
|
|
1396
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1397
|
+
// this._unhighlightElements(element).then(
|
|
1398
|
+
// () => {}
|
|
1399
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1400
|
+
// );
|
|
1401
|
+
// })
|
|
1402
|
+
// .catch(e);
|
|
1403
|
+
// }
|
|
1399
1404
|
const elementText = await element.innerText();
|
|
1400
1405
|
return {
|
|
1401
1406
|
text: elementText,
|
|
@@ -1407,7 +1412,7 @@ class StableBrowser {
|
|
|
1407
1412
|
}
|
|
1408
1413
|
catch (e) {
|
|
1409
1414
|
//await this.closeUnexpectedPopups();
|
|
1410
|
-
this.logger.info("no innerText will use textContent");
|
|
1415
|
+
this.logger.info("no innerText, will use textContent");
|
|
1411
1416
|
const elementText = await element.textContent();
|
|
1412
1417
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1413
1418
|
}
|
|
@@ -1533,22 +1538,6 @@ class StableBrowser {
|
|
|
1533
1538
|
_commandFinally(state, this);
|
|
1534
1539
|
}
|
|
1535
1540
|
}
|
|
1536
|
-
_getDataFile(world = null) {
|
|
1537
|
-
let dataFile = null;
|
|
1538
|
-
if (world && world.reportFolder) {
|
|
1539
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1540
|
-
}
|
|
1541
|
-
else if (this.reportFolder) {
|
|
1542
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1543
|
-
}
|
|
1544
|
-
else if (this.context && this.context.reportFolder) {
|
|
1545
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1546
|
-
}
|
|
1547
|
-
else {
|
|
1548
|
-
dataFile = "data.json";
|
|
1549
|
-
}
|
|
1550
|
-
return dataFile;
|
|
1551
|
-
}
|
|
1552
1541
|
async waitForUserInput(message, world = null) {
|
|
1553
1542
|
if (!message) {
|
|
1554
1543
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1577,7 +1566,7 @@ class StableBrowser {
|
|
|
1577
1566
|
return;
|
|
1578
1567
|
}
|
|
1579
1568
|
// if data file exists, load it
|
|
1580
|
-
const dataFile =
|
|
1569
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1581
1570
|
let data = this.getTestData(world);
|
|
1582
1571
|
// merge the testData with the existing data
|
|
1583
1572
|
Object.assign(data, testData);
|
|
@@ -1680,7 +1669,7 @@ class StableBrowser {
|
|
|
1680
1669
|
}
|
|
1681
1670
|
}
|
|
1682
1671
|
getTestData(world = null) {
|
|
1683
|
-
const dataFile =
|
|
1672
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1684
1673
|
let data = {};
|
|
1685
1674
|
if (fs.existsSync(dataFile)) {
|
|
1686
1675
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1724,15 +1713,15 @@ class StableBrowser {
|
|
|
1724
1713
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1725
1714
|
// }
|
|
1726
1715
|
// });
|
|
1716
|
+
result.screenshotId = uuidStr;
|
|
1717
|
+
result.screenshotPath = screenshotPath;
|
|
1718
|
+
if (info && info.box) {
|
|
1719
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1720
|
+
}
|
|
1727
1721
|
}
|
|
1728
1722
|
catch (e) {
|
|
1729
1723
|
this.logger.info("unable to take screenshot, ignored");
|
|
1730
1724
|
}
|
|
1731
|
-
result.screenshotId = uuidStr;
|
|
1732
|
-
result.screenshotPath = screenshotPath;
|
|
1733
|
-
if (info && info.box) {
|
|
1734
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1735
|
-
}
|
|
1736
1725
|
}
|
|
1737
1726
|
else if (options && options.screenshot) {
|
|
1738
1727
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1767,6 +1756,15 @@ class StableBrowser {
|
|
|
1767
1756
|
document.documentElement.clientWidth,
|
|
1768
1757
|
])));
|
|
1769
1758
|
let screenshotBuffer = null;
|
|
1759
|
+
// if (focusedElement) {
|
|
1760
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1761
|
+
// await this._unhighlightElements(focusedElement);
|
|
1762
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1763
|
+
// console.log(`Unhighlighted previous element`);
|
|
1764
|
+
// }
|
|
1765
|
+
// if (focusedElement) {
|
|
1766
|
+
// await this._highlightElements(focusedElement);
|
|
1767
|
+
// }
|
|
1770
1768
|
if (this.context.browserName === "chromium") {
|
|
1771
1769
|
const client = await playContext.newCDPSession(this.page);
|
|
1772
1770
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1788,6 +1786,10 @@ class StableBrowser {
|
|
|
1788
1786
|
else {
|
|
1789
1787
|
screenshotBuffer = await this.page.screenshot();
|
|
1790
1788
|
}
|
|
1789
|
+
// if (focusedElement) {
|
|
1790
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1791
|
+
// await this._unhighlightElements(focusedElement);
|
|
1792
|
+
// }
|
|
1791
1793
|
let image = await Jimp.read(screenshotBuffer);
|
|
1792
1794
|
// Get the image dimensions
|
|
1793
1795
|
const { width, height } = image.bitmap;
|
|
@@ -1800,6 +1802,7 @@ class StableBrowser {
|
|
|
1800
1802
|
else {
|
|
1801
1803
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1802
1804
|
}
|
|
1805
|
+
return screenshotBuffer;
|
|
1803
1806
|
}
|
|
1804
1807
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1805
1808
|
const state = {
|
|
@@ -1837,6 +1840,7 @@ class StableBrowser {
|
|
|
1837
1840
|
text: `Extract attribute from element`,
|
|
1838
1841
|
operation: "extractAttribute",
|
|
1839
1842
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1843
|
+
allowDisabled: true,
|
|
1840
1844
|
};
|
|
1841
1845
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1842
1846
|
try {
|
|
@@ -1858,6 +1862,76 @@ class StableBrowser {
|
|
|
1858
1862
|
state.info.value = state.value;
|
|
1859
1863
|
this.setTestData({ [variable]: state.value }, world);
|
|
1860
1864
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1865
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1866
|
+
return state.info;
|
|
1867
|
+
}
|
|
1868
|
+
catch (e) {
|
|
1869
|
+
await _commandError(state, e, this);
|
|
1870
|
+
}
|
|
1871
|
+
finally {
|
|
1872
|
+
_commandFinally(state, this);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1876
|
+
const state = {
|
|
1877
|
+
selectors,
|
|
1878
|
+
_params,
|
|
1879
|
+
attribute,
|
|
1880
|
+
value,
|
|
1881
|
+
options,
|
|
1882
|
+
world,
|
|
1883
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1884
|
+
highlight: true,
|
|
1885
|
+
screenshot: true,
|
|
1886
|
+
text: `Verify element attribute`,
|
|
1887
|
+
operation: "verifyAttribute",
|
|
1888
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1889
|
+
allowDisabled: true,
|
|
1890
|
+
};
|
|
1891
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1892
|
+
let val;
|
|
1893
|
+
let expectedValue;
|
|
1894
|
+
try {
|
|
1895
|
+
await _preCommand(state, this);
|
|
1896
|
+
expectedValue = state.value;
|
|
1897
|
+
state.info.expectedValue = expectedValue;
|
|
1898
|
+
switch (attribute) {
|
|
1899
|
+
case "innerText":
|
|
1900
|
+
val = String(await state.element.innerText());
|
|
1901
|
+
break;
|
|
1902
|
+
case "value":
|
|
1903
|
+
val = String(await state.element.inputValue());
|
|
1904
|
+
break;
|
|
1905
|
+
case "checked":
|
|
1906
|
+
val = String(await state.element.isChecked());
|
|
1907
|
+
break;
|
|
1908
|
+
case "disabled":
|
|
1909
|
+
val = String(await state.element.isDisabled());
|
|
1910
|
+
break;
|
|
1911
|
+
case "readOnly":
|
|
1912
|
+
const isEditable = await state.element.isEditable();
|
|
1913
|
+
val = String(!isEditable);
|
|
1914
|
+
break;
|
|
1915
|
+
default:
|
|
1916
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
state.info.value = val;
|
|
1920
|
+
let regex;
|
|
1921
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1922
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1923
|
+
regex = new RegExp(patternBody, "g");
|
|
1924
|
+
}
|
|
1925
|
+
else {
|
|
1926
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1927
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1928
|
+
}
|
|
1929
|
+
if (!val.match(regex)) {
|
|
1930
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1931
|
+
state.info.failCause.assertionFailed = true;
|
|
1932
|
+
state.info.failCause.lastError = errorMessage;
|
|
1933
|
+
throw new Error(errorMessage);
|
|
1934
|
+
}
|
|
1861
1935
|
return state.info;
|
|
1862
1936
|
}
|
|
1863
1937
|
catch (e) {
|
|
@@ -1885,7 +1959,7 @@ class StableBrowser {
|
|
|
1885
1959
|
if (options && options.timeout) {
|
|
1886
1960
|
timeout = options.timeout;
|
|
1887
1961
|
}
|
|
1888
|
-
const serviceUrl =
|
|
1962
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1889
1963
|
const request = {
|
|
1890
1964
|
method: "POST",
|
|
1891
1965
|
url: serviceUrl,
|
|
@@ -1956,27 +2030,32 @@ class StableBrowser {
|
|
|
1956
2030
|
async _highlightElements(scope, css) {
|
|
1957
2031
|
try {
|
|
1958
2032
|
if (!scope) {
|
|
2033
|
+
// console.log(`Scope is not defined`);
|
|
1959
2034
|
return;
|
|
1960
2035
|
}
|
|
1961
2036
|
if (!css) {
|
|
1962
2037
|
scope
|
|
1963
2038
|
.evaluate((node) => {
|
|
1964
2039
|
if (node && node.style) {
|
|
1965
|
-
let
|
|
1966
|
-
|
|
2040
|
+
let originalOutline = node.style.outline;
|
|
2041
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2042
|
+
// node.__previousOutline = originalOutline;
|
|
2043
|
+
node.style.outline = "2px solid red";
|
|
2044
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1967
2045
|
if (window) {
|
|
1968
2046
|
window.addEventListener("beforeunload", function (e) {
|
|
1969
|
-
node.style.
|
|
2047
|
+
node.style.outline = originalOutline;
|
|
1970
2048
|
});
|
|
1971
2049
|
}
|
|
1972
2050
|
setTimeout(function () {
|
|
1973
|
-
node.style.
|
|
2051
|
+
node.style.outline = originalOutline;
|
|
1974
2052
|
}, 2000);
|
|
1975
2053
|
}
|
|
1976
2054
|
})
|
|
1977
2055
|
.then(() => { })
|
|
1978
2056
|
.catch((e) => {
|
|
1979
2057
|
// ignore
|
|
2058
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1980
2059
|
});
|
|
1981
2060
|
}
|
|
1982
2061
|
else {
|
|
@@ -1992,17 +2071,18 @@ class StableBrowser {
|
|
|
1992
2071
|
if (!element.style) {
|
|
1993
2072
|
return;
|
|
1994
2073
|
}
|
|
1995
|
-
|
|
2074
|
+
let originalOutline = element.style.outline;
|
|
2075
|
+
element.__previousOutline = originalOutline;
|
|
1996
2076
|
// Set the new border to be red and 2px solid
|
|
1997
|
-
element.style.
|
|
2077
|
+
element.style.outline = "2px solid red";
|
|
1998
2078
|
if (window) {
|
|
1999
2079
|
window.addEventListener("beforeunload", function (e) {
|
|
2000
|
-
element.style.
|
|
2080
|
+
element.style.outline = originalOutline;
|
|
2001
2081
|
});
|
|
2002
2082
|
}
|
|
2003
2083
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2004
2084
|
setTimeout(function () {
|
|
2005
|
-
element.style.
|
|
2085
|
+
element.style.outline = originalOutline;
|
|
2006
2086
|
}, 2000);
|
|
2007
2087
|
}
|
|
2008
2088
|
return;
|
|
@@ -2010,6 +2090,7 @@ class StableBrowser {
|
|
|
2010
2090
|
.then(() => { })
|
|
2011
2091
|
.catch((e) => {
|
|
2012
2092
|
// ignore
|
|
2093
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2013
2094
|
});
|
|
2014
2095
|
}
|
|
2015
2096
|
}
|
|
@@ -2017,6 +2098,54 @@ class StableBrowser {
|
|
|
2017
2098
|
console.debug(error);
|
|
2018
2099
|
}
|
|
2019
2100
|
}
|
|
2101
|
+
// async _unhighlightElements(scope, css) {
|
|
2102
|
+
// try {
|
|
2103
|
+
// if (!scope) {
|
|
2104
|
+
// return;
|
|
2105
|
+
// }
|
|
2106
|
+
// if (!css) {
|
|
2107
|
+
// scope
|
|
2108
|
+
// .evaluate((node) => {
|
|
2109
|
+
// if (node && node.style) {
|
|
2110
|
+
// if (!node.__previousOutline) {
|
|
2111
|
+
// node.style.outline = "";
|
|
2112
|
+
// } else {
|
|
2113
|
+
// node.style.outline = node.__previousOutline;
|
|
2114
|
+
// }
|
|
2115
|
+
// }
|
|
2116
|
+
// })
|
|
2117
|
+
// .then(() => {})
|
|
2118
|
+
// .catch((e) => {
|
|
2119
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2120
|
+
// });
|
|
2121
|
+
// } else {
|
|
2122
|
+
// scope
|
|
2123
|
+
// .evaluate(([css]) => {
|
|
2124
|
+
// if (!css) {
|
|
2125
|
+
// return;
|
|
2126
|
+
// }
|
|
2127
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2128
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2129
|
+
// let element = elements[i];
|
|
2130
|
+
// if (!element.style) {
|
|
2131
|
+
// return;
|
|
2132
|
+
// }
|
|
2133
|
+
// if (!element.__previousOutline) {
|
|
2134
|
+
// element.style.outline = "";
|
|
2135
|
+
// } else {
|
|
2136
|
+
// element.style.outline = element.__previousOutline;
|
|
2137
|
+
// }
|
|
2138
|
+
// }
|
|
2139
|
+
// })
|
|
2140
|
+
// .then(() => {})
|
|
2141
|
+
// .catch((e) => {
|
|
2142
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2143
|
+
// });
|
|
2144
|
+
// }
|
|
2145
|
+
// } catch (error) {
|
|
2146
|
+
// // console.debug(error);
|
|
2147
|
+
// }
|
|
2148
|
+
// }
|
|
2020
2149
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2021
2150
|
const startTime = Date.now();
|
|
2022
2151
|
let error = null;
|
|
@@ -2078,6 +2207,35 @@ class StableBrowser {
|
|
|
2078
2207
|
});
|
|
2079
2208
|
}
|
|
2080
2209
|
}
|
|
2210
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2211
|
+
const frames = this.page.frames();
|
|
2212
|
+
let results = [];
|
|
2213
|
+
let ignoreCase = false;
|
|
2214
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2215
|
+
if (dateAlternatives.date) {
|
|
2216
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2217
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2218
|
+
result.frame = frames[i];
|
|
2219
|
+
results.push(result);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
else if (numberAlternatives.number) {
|
|
2223
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2224
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2225
|
+
result.frame = frames[i];
|
|
2226
|
+
results.push(result);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
else {
|
|
2230
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2231
|
+
result.frame = frames[i];
|
|
2232
|
+
results.push(result);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
state.info.results = results;
|
|
2236
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2237
|
+
return resultWithElementsFound;
|
|
2238
|
+
}
|
|
2081
2239
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2082
2240
|
text = unEscapeString(text);
|
|
2083
2241
|
const state = {
|
|
@@ -2087,7 +2245,7 @@ class StableBrowser {
|
|
|
2087
2245
|
locate: false,
|
|
2088
2246
|
scroll: false,
|
|
2089
2247
|
highlight: false,
|
|
2090
|
-
type: Types.
|
|
2248
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2091
2249
|
text: `Verify text exists in page`,
|
|
2092
2250
|
operation: "verifyTextExistInPage",
|
|
2093
2251
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2105,31 +2263,15 @@ class StableBrowser {
|
|
|
2105
2263
|
await _preCommand(state, this);
|
|
2106
2264
|
state.info.text = text;
|
|
2107
2265
|
while (true) {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
else if (numberAlternatives.number) {
|
|
2119
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2120
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2121
|
-
result.frame = frames[i];
|
|
2122
|
-
results.push(result);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
else {
|
|
2126
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2127
|
-
result.frame = frames[i];
|
|
2128
|
-
results.push(result);
|
|
2129
|
-
}
|
|
2266
|
+
let resultWithElementsFound = {
|
|
2267
|
+
length: 0,
|
|
2268
|
+
};
|
|
2269
|
+
try {
|
|
2270
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2271
|
+
}
|
|
2272
|
+
catch (error) {
|
|
2273
|
+
// ignore
|
|
2130
2274
|
}
|
|
2131
|
-
state.info.results = results;
|
|
2132
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2133
2275
|
if (resultWithElementsFound.length === 0) {
|
|
2134
2276
|
if (Date.now() - state.startTime > timeout) {
|
|
2135
2277
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2137,18 +2279,40 @@ class StableBrowser {
|
|
|
2137
2279
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2138
2280
|
continue;
|
|
2139
2281
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2282
|
+
try {
|
|
2283
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2284
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2285
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2286
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2287
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2288
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2289
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2290
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2291
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2292
|
+
// .then(async () => {
|
|
2293
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2294
|
+
// })
|
|
2295
|
+
// .catch(
|
|
2296
|
+
// (e) => {}
|
|
2297
|
+
// console.error(e)
|
|
2298
|
+
// );
|
|
2299
|
+
// });
|
|
2300
|
+
// }
|
|
2301
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2302
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2303
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2304
|
+
if (element) {
|
|
2305
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2306
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2307
|
+
// await _screenshot(state, this, element);
|
|
2308
|
+
}
|
|
2148
2309
|
}
|
|
2310
|
+
await _screenshot(state, this);
|
|
2311
|
+
return state.info;
|
|
2312
|
+
}
|
|
2313
|
+
catch (error) {
|
|
2314
|
+
console.error(error);
|
|
2149
2315
|
}
|
|
2150
|
-
await _screenshot(state, this);
|
|
2151
|
-
return state.info;
|
|
2152
2316
|
}
|
|
2153
2317
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2154
2318
|
}
|
|
@@ -2185,32 +2349,16 @@ class StableBrowser {
|
|
|
2185
2349
|
try {
|
|
2186
2350
|
await _preCommand(state, this);
|
|
2187
2351
|
state.info.text = text;
|
|
2352
|
+
let resultWithElementsFound = {
|
|
2353
|
+
length: null, // initial cannot be 0
|
|
2354
|
+
};
|
|
2188
2355
|
while (true) {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2195
|
-
result.frame = frames[i];
|
|
2196
|
-
results.push(result);
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
else if (numberAlternatives.number) {
|
|
2200
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2201
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2202
|
-
result.frame = frames[i];
|
|
2203
|
-
results.push(result);
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
else {
|
|
2207
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2208
|
-
result.frame = frames[i];
|
|
2209
|
-
results.push(result);
|
|
2210
|
-
}
|
|
2356
|
+
try {
|
|
2357
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2358
|
+
}
|
|
2359
|
+
catch (error) {
|
|
2360
|
+
// ignore
|
|
2211
2361
|
}
|
|
2212
|
-
state.info.results = results;
|
|
2213
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2214
2362
|
if (resultWithElementsFound.length === 0) {
|
|
2215
2363
|
await _screenshot(state, this);
|
|
2216
2364
|
return state.info;
|
|
@@ -2228,15 +2376,115 @@ class StableBrowser {
|
|
|
2228
2376
|
_commandFinally(state, this);
|
|
2229
2377
|
}
|
|
2230
2378
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2379
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2380
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2381
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2382
|
+
const state = {
|
|
2383
|
+
text_search: textToVerify,
|
|
2384
|
+
options,
|
|
2385
|
+
world,
|
|
2386
|
+
locate: false,
|
|
2387
|
+
scroll: false,
|
|
2388
|
+
highlight: false,
|
|
2389
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2390
|
+
text: `Verify text with relation to another text`,
|
|
2391
|
+
operation: "verify_text_with_relation",
|
|
2392
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2393
|
+
};
|
|
2394
|
+
const timeout = this._getLoadTimeout(options);
|
|
2395
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2396
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2397
|
+
if (newValue !== textAnchor) {
|
|
2398
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2399
|
+
textAnchor = newValue;
|
|
2400
|
+
}
|
|
2401
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2402
|
+
if (newValue !== textToVerify) {
|
|
2403
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2404
|
+
textToVerify = newValue;
|
|
2405
|
+
}
|
|
2406
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2407
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2408
|
+
let foundAncore = false;
|
|
2409
|
+
try {
|
|
2410
|
+
await _preCommand(state, this);
|
|
2411
|
+
state.info.text = textToVerify;
|
|
2412
|
+
let resultWithElementsFound = {
|
|
2413
|
+
length: 0,
|
|
2414
|
+
};
|
|
2415
|
+
while (true) {
|
|
2416
|
+
try {
|
|
2417
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2418
|
+
}
|
|
2419
|
+
catch (error) {
|
|
2420
|
+
// ignore
|
|
2421
|
+
}
|
|
2422
|
+
if (resultWithElementsFound.length === 0) {
|
|
2423
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2424
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2425
|
+
}
|
|
2426
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
try {
|
|
2430
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2431
|
+
foundAncore = true;
|
|
2432
|
+
const result = resultWithElementsFound[i];
|
|
2433
|
+
const token = result.randomToken;
|
|
2434
|
+
const frame = result.frame;
|
|
2435
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2436
|
+
const climbArray1 = [];
|
|
2437
|
+
for (let i = 0; i < climb; i++) {
|
|
2438
|
+
climbArray1.push("..");
|
|
2439
|
+
}
|
|
2440
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2441
|
+
css = css + " >> " + climbXpath;
|
|
2442
|
+
const count = await frame.locator(css).count();
|
|
2443
|
+
for (let j = 0; j < count; j++) {
|
|
2444
|
+
const continer = await frame.locator(css).nth(j);
|
|
2445
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2446
|
+
if (result.elementCount > 0) {
|
|
2447
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2448
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2449
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2450
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2451
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2452
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2453
|
+
// .then(async () => {
|
|
2454
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2455
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2456
|
+
// () => {}
|
|
2457
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2458
|
+
// );
|
|
2459
|
+
// })
|
|
2460
|
+
// .catch(e);
|
|
2461
|
+
// }
|
|
2462
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2463
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2464
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2465
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2466
|
+
if (element) {
|
|
2467
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2468
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2469
|
+
}
|
|
2470
|
+
await _screenshot(state, this);
|
|
2471
|
+
return state.info;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
catch (error) {
|
|
2477
|
+
console.error(error);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2481
|
+
}
|
|
2482
|
+
catch (e) {
|
|
2483
|
+
await _commandError(state, e, this);
|
|
2235
2484
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2485
|
+
finally {
|
|
2486
|
+
_commandFinally(state, this);
|
|
2238
2487
|
}
|
|
2239
|
-
return serviceUrl;
|
|
2240
2488
|
}
|
|
2241
2489
|
async visualVerification(text, options = {}, world = null) {
|
|
2242
2490
|
const startTime = Date.now();
|
|
@@ -2252,14 +2500,17 @@ class StableBrowser {
|
|
|
2252
2500
|
throw new Error("TOKEN is not set");
|
|
2253
2501
|
}
|
|
2254
2502
|
try {
|
|
2255
|
-
let serviceUrl =
|
|
2503
|
+
let serviceUrl = _getServerUrl();
|
|
2256
2504
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2257
2505
|
info.screenshotPath = screenshotPath;
|
|
2258
2506
|
const screenshot = await this.takeScreenshot();
|
|
2259
|
-
|
|
2260
|
-
method: "
|
|
2507
|
+
let request = {
|
|
2508
|
+
method: "post",
|
|
2509
|
+
maxBodyLength: Infinity,
|
|
2261
2510
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2262
2511
|
headers: {
|
|
2512
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2513
|
+
"x-source": "aaa",
|
|
2263
2514
|
"Content-Type": "application/json",
|
|
2264
2515
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2265
2516
|
},
|
|
@@ -2268,7 +2519,7 @@ class StableBrowser {
|
|
|
2268
2519
|
screenshot: screenshot,
|
|
2269
2520
|
}),
|
|
2270
2521
|
};
|
|
2271
|
-
|
|
2522
|
+
const result = await axios.request(request);
|
|
2272
2523
|
if (result.data.status !== true) {
|
|
2273
2524
|
throw new Error("Visual validation failed");
|
|
2274
2525
|
}
|
|
@@ -2341,6 +2592,7 @@ class StableBrowser {
|
|
|
2341
2592
|
let screenshotPath = null;
|
|
2342
2593
|
const info = {};
|
|
2343
2594
|
info.log = "";
|
|
2595
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2344
2596
|
info.operation = "getTableData";
|
|
2345
2597
|
info.selectors = selectors;
|
|
2346
2598
|
try {
|
|
@@ -2416,7 +2668,7 @@ class StableBrowser {
|
|
|
2416
2668
|
info.operation = "analyzeTable";
|
|
2417
2669
|
info.selectors = selectors;
|
|
2418
2670
|
info.query = query;
|
|
2419
|
-
query =
|
|
2671
|
+
query = _fixUsingParams(query, _params);
|
|
2420
2672
|
info.query_fixed = query;
|
|
2421
2673
|
info.operator = operator;
|
|
2422
2674
|
info.value = value;
|
|
@@ -2561,6 +2813,17 @@ class StableBrowser {
|
|
|
2561
2813
|
}
|
|
2562
2814
|
return timeout;
|
|
2563
2815
|
}
|
|
2816
|
+
async saveStoreState(path = null, world = null) {
|
|
2817
|
+
const storageState = await this.page.context().storageState();
|
|
2818
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2819
|
+
if (path) {
|
|
2820
|
+
// save { storageState: storageState } into the path
|
|
2821
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2822
|
+
}
|
|
2823
|
+
else {
|
|
2824
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2564
2827
|
async waitForPageLoad(options = {}, world = null) {
|
|
2565
2828
|
let timeout = this._getLoadTimeout(options);
|
|
2566
2829
|
const promiseArray = [];
|
|
@@ -2746,14 +3009,22 @@ class StableBrowser {
|
|
|
2746
3009
|
}
|
|
2747
3010
|
}
|
|
2748
3011
|
async beforeStep(world, step) {
|
|
2749
|
-
this.stepName = step.pickleStep.text;
|
|
2750
|
-
this.logger.info("step: " + this.stepName);
|
|
2751
3012
|
if (this.stepIndex === undefined) {
|
|
2752
3013
|
this.stepIndex = 0;
|
|
2753
3014
|
}
|
|
2754
3015
|
else {
|
|
2755
3016
|
this.stepIndex++;
|
|
2756
3017
|
}
|
|
3018
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3019
|
+
this.stepName = step.pickleStep.text;
|
|
3020
|
+
this.logger.info("step: " + this.stepName);
|
|
3021
|
+
}
|
|
3022
|
+
else if (step && step.text) {
|
|
3023
|
+
this.stepName = step.text;
|
|
3024
|
+
}
|
|
3025
|
+
else {
|
|
3026
|
+
this.stepName = "step " + this.stepIndex;
|
|
3027
|
+
}
|
|
2757
3028
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2758
3029
|
if (this.context.browserObject.context) {
|
|
2759
3030
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2783,156 +3054,5 @@ function createTimedPromise(promise, label) {
|
|
|
2783
3054
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2784
3055
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2785
3056
|
}
|
|
2786
|
-
const KEYBOARD_EVENTS = [
|
|
2787
|
-
"ALT",
|
|
2788
|
-
"AltGraph",
|
|
2789
|
-
"CapsLock",
|
|
2790
|
-
"Control",
|
|
2791
|
-
"Fn",
|
|
2792
|
-
"FnLock",
|
|
2793
|
-
"Hyper",
|
|
2794
|
-
"Meta",
|
|
2795
|
-
"NumLock",
|
|
2796
|
-
"ScrollLock",
|
|
2797
|
-
"Shift",
|
|
2798
|
-
"Super",
|
|
2799
|
-
"Symbol",
|
|
2800
|
-
"SymbolLock",
|
|
2801
|
-
"Enter",
|
|
2802
|
-
"Tab",
|
|
2803
|
-
"ArrowDown",
|
|
2804
|
-
"ArrowLeft",
|
|
2805
|
-
"ArrowRight",
|
|
2806
|
-
"ArrowUp",
|
|
2807
|
-
"End",
|
|
2808
|
-
"Home",
|
|
2809
|
-
"PageDown",
|
|
2810
|
-
"PageUp",
|
|
2811
|
-
"Backspace",
|
|
2812
|
-
"Clear",
|
|
2813
|
-
"Copy",
|
|
2814
|
-
"CrSel",
|
|
2815
|
-
"Cut",
|
|
2816
|
-
"Delete",
|
|
2817
|
-
"EraseEof",
|
|
2818
|
-
"ExSel",
|
|
2819
|
-
"Insert",
|
|
2820
|
-
"Paste",
|
|
2821
|
-
"Redo",
|
|
2822
|
-
"Undo",
|
|
2823
|
-
"Accept",
|
|
2824
|
-
"Again",
|
|
2825
|
-
"Attn",
|
|
2826
|
-
"Cancel",
|
|
2827
|
-
"ContextMenu",
|
|
2828
|
-
"Escape",
|
|
2829
|
-
"Execute",
|
|
2830
|
-
"Find",
|
|
2831
|
-
"Finish",
|
|
2832
|
-
"Help",
|
|
2833
|
-
"Pause",
|
|
2834
|
-
"Play",
|
|
2835
|
-
"Props",
|
|
2836
|
-
"Select",
|
|
2837
|
-
"ZoomIn",
|
|
2838
|
-
"ZoomOut",
|
|
2839
|
-
"BrightnessDown",
|
|
2840
|
-
"BrightnessUp",
|
|
2841
|
-
"Eject",
|
|
2842
|
-
"LogOff",
|
|
2843
|
-
"Power",
|
|
2844
|
-
"PowerOff",
|
|
2845
|
-
"PrintScreen",
|
|
2846
|
-
"Hibernate",
|
|
2847
|
-
"Standby",
|
|
2848
|
-
"WakeUp",
|
|
2849
|
-
"AllCandidates",
|
|
2850
|
-
"Alphanumeric",
|
|
2851
|
-
"CodeInput",
|
|
2852
|
-
"Compose",
|
|
2853
|
-
"Convert",
|
|
2854
|
-
"Dead",
|
|
2855
|
-
"FinalMode",
|
|
2856
|
-
"GroupFirst",
|
|
2857
|
-
"GroupLast",
|
|
2858
|
-
"GroupNext",
|
|
2859
|
-
"GroupPrevious",
|
|
2860
|
-
"ModeChange",
|
|
2861
|
-
"NextCandidate",
|
|
2862
|
-
"NonConvert",
|
|
2863
|
-
"PreviousCandidate",
|
|
2864
|
-
"Process",
|
|
2865
|
-
"SingleCandidate",
|
|
2866
|
-
"HangulMode",
|
|
2867
|
-
"HanjaMode",
|
|
2868
|
-
"JunjaMode",
|
|
2869
|
-
"Eisu",
|
|
2870
|
-
"Hankaku",
|
|
2871
|
-
"Hiragana",
|
|
2872
|
-
"HiraganaKatakana",
|
|
2873
|
-
"KanaMode",
|
|
2874
|
-
"KanjiMode",
|
|
2875
|
-
"Katakana",
|
|
2876
|
-
"Romaji",
|
|
2877
|
-
"Zenkaku",
|
|
2878
|
-
"ZenkakuHanaku",
|
|
2879
|
-
"F1",
|
|
2880
|
-
"F2",
|
|
2881
|
-
"F3",
|
|
2882
|
-
"F4",
|
|
2883
|
-
"F5",
|
|
2884
|
-
"F6",
|
|
2885
|
-
"F7",
|
|
2886
|
-
"F8",
|
|
2887
|
-
"F9",
|
|
2888
|
-
"F10",
|
|
2889
|
-
"F11",
|
|
2890
|
-
"F12",
|
|
2891
|
-
"Soft1",
|
|
2892
|
-
"Soft2",
|
|
2893
|
-
"Soft3",
|
|
2894
|
-
"Soft4",
|
|
2895
|
-
"ChannelDown",
|
|
2896
|
-
"ChannelUp",
|
|
2897
|
-
"Close",
|
|
2898
|
-
"MailForward",
|
|
2899
|
-
"MailReply",
|
|
2900
|
-
"MailSend",
|
|
2901
|
-
"MediaFastForward",
|
|
2902
|
-
"MediaPause",
|
|
2903
|
-
"MediaPlay",
|
|
2904
|
-
"MediaPlayPause",
|
|
2905
|
-
"MediaRecord",
|
|
2906
|
-
"MediaRewind",
|
|
2907
|
-
"MediaStop",
|
|
2908
|
-
"MediaTrackNext",
|
|
2909
|
-
"MediaTrackPrevious",
|
|
2910
|
-
"AudioBalanceLeft",
|
|
2911
|
-
"AudioBalanceRight",
|
|
2912
|
-
"AudioBassBoostDown",
|
|
2913
|
-
"AudioBassBoostToggle",
|
|
2914
|
-
"AudioBassBoostUp",
|
|
2915
|
-
"AudioFaderFront",
|
|
2916
|
-
"AudioFaderRear",
|
|
2917
|
-
"AudioSurroundModeNext",
|
|
2918
|
-
"AudioTrebleDown",
|
|
2919
|
-
"AudioTrebleUp",
|
|
2920
|
-
"AudioVolumeDown",
|
|
2921
|
-
"AudioVolumeMute",
|
|
2922
|
-
"AudioVolumeUp",
|
|
2923
|
-
"MicrophoneToggle",
|
|
2924
|
-
"MicrophoneVolumeDown",
|
|
2925
|
-
"MicrophoneVolumeMute",
|
|
2926
|
-
"MicrophoneVolumeUp",
|
|
2927
|
-
"TV",
|
|
2928
|
-
"TV3DMode",
|
|
2929
|
-
"TVAntennaCable",
|
|
2930
|
-
"TVAudioDescription",
|
|
2931
|
-
];
|
|
2932
|
-
function unEscapeString(str) {
|
|
2933
|
-
const placeholder = "__NEWLINE__";
|
|
2934
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2935
|
-
return str;
|
|
2936
|
-
}
|
|
2937
3057
|
export { StableBrowser };
|
|
2938
3058
|
//# sourceMappingURL=stable_browser.js.map
|