automation_model 1.0.533-dev → 1.0.533-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 +636 -513
- 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
|
-
}
|
|
299
|
-
_isObject(value) {
|
|
300
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
301
271
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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,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++) {
|
|
@@ -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,18 @@ 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
|
+
if (element._frame) {
|
|
557
|
+
return element;
|
|
558
|
+
}
|
|
559
|
+
const scope = element.page();
|
|
560
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
561
|
+
return newSelector;
|
|
640
562
|
}
|
|
641
563
|
}
|
|
642
564
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -647,6 +569,7 @@ class StableBrowser {
|
|
|
647
569
|
info.failCause = {};
|
|
648
570
|
info.log = "";
|
|
649
571
|
}
|
|
572
|
+
let startTime = Date.now();
|
|
650
573
|
let scope = this.page;
|
|
651
574
|
if (selectors.frame) {
|
|
652
575
|
return selectors.frame;
|
|
@@ -677,9 +600,11 @@ class StableBrowser {
|
|
|
677
600
|
}
|
|
678
601
|
return framescope;
|
|
679
602
|
};
|
|
603
|
+
let fLocator = null;
|
|
680
604
|
while (true) {
|
|
681
605
|
let frameFound = false;
|
|
682
606
|
if (selectors.nestFrmLoc) {
|
|
607
|
+
fLocator = selectors.nestFrmLoc;
|
|
683
608
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
684
609
|
frameFound = true;
|
|
685
610
|
break;
|
|
@@ -688,6 +613,7 @@ class StableBrowser {
|
|
|
688
613
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
689
614
|
let frameLocator = selectors.frameLocators[i];
|
|
690
615
|
if (frameLocator.css) {
|
|
616
|
+
fLocator = frameLocator.css;
|
|
691
617
|
scope = scope.frameLocator(frameLocator.css);
|
|
692
618
|
frameFound = true;
|
|
693
619
|
break;
|
|
@@ -695,18 +621,25 @@ class StableBrowser {
|
|
|
695
621
|
}
|
|
696
622
|
}
|
|
697
623
|
if (!frameFound && selectors.iframe_src) {
|
|
624
|
+
fLocator = selectors.iframe_src;
|
|
698
625
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
699
626
|
}
|
|
700
627
|
if (!scope) {
|
|
701
|
-
info
|
|
702
|
-
|
|
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) {
|
|
703
633
|
info.failCause.iframeNotFound = true;
|
|
704
|
-
info.failCause.lastError =
|
|
634
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
705
635
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
706
636
|
}
|
|
707
637
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
708
638
|
}
|
|
709
639
|
else {
|
|
640
|
+
if (info && info.locatorLog) {
|
|
641
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
642
|
+
}
|
|
710
643
|
break;
|
|
711
644
|
}
|
|
712
645
|
}
|
|
@@ -723,15 +656,16 @@ class StableBrowser {
|
|
|
723
656
|
return bodyContent;
|
|
724
657
|
});
|
|
725
658
|
}
|
|
726
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
659
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
727
660
|
if (!info) {
|
|
728
661
|
info = {};
|
|
729
662
|
info.failCause = {};
|
|
730
663
|
info.log = "";
|
|
664
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
731
665
|
}
|
|
732
666
|
let highPriorityTimeout = 5000;
|
|
733
667
|
let visibleOnlyTimeout = 6000;
|
|
734
|
-
let startTime =
|
|
668
|
+
let startTime = Date.now();
|
|
735
669
|
let locatorsCount = 0;
|
|
736
670
|
let lazy_scroll = false;
|
|
737
671
|
//let arrayMode = Array.isArray(selectors);
|
|
@@ -771,17 +705,17 @@ class StableBrowser {
|
|
|
771
705
|
}
|
|
772
706
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
773
707
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
774
|
-
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);
|
|
775
709
|
if (result.foundElements.length === 0) {
|
|
776
710
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
777
|
-
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);
|
|
778
712
|
}
|
|
779
713
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
780
|
-
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);
|
|
781
715
|
}
|
|
782
716
|
else {
|
|
783
717
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
784
|
-
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);
|
|
785
719
|
}
|
|
786
720
|
}
|
|
787
721
|
let foundElements = result.foundElements;
|
|
@@ -822,30 +756,38 @@ class StableBrowser {
|
|
|
822
756
|
return maxCountElement.locator;
|
|
823
757
|
}
|
|
824
758
|
}
|
|
825
|
-
if (
|
|
759
|
+
if (Date.now() - startTime > timeout) {
|
|
826
760
|
break;
|
|
827
761
|
}
|
|
828
|
-
if (
|
|
829
|
-
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";
|
|
830
764
|
highPriorityOnly = false;
|
|
831
765
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
832
766
|
lazy_scroll = true;
|
|
833
|
-
await this.
|
|
767
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
834
768
|
}
|
|
835
769
|
}
|
|
836
|
-
if (
|
|
837
|
-
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";
|
|
838
772
|
visibleOnly = false;
|
|
839
773
|
}
|
|
840
774
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
841
775
|
}
|
|
842
776
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
843
|
-
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";
|
|
844
784
|
info.failCause.locatorNotFound = true;
|
|
845
|
-
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
|
+
}
|
|
846
788
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
847
789
|
}
|
|
848
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
790
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
849
791
|
let foundElements = [];
|
|
850
792
|
const result = {
|
|
851
793
|
foundElements: foundElements,
|
|
@@ -853,14 +795,15 @@ class StableBrowser {
|
|
|
853
795
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
854
796
|
let foundLocators = [];
|
|
855
797
|
try {
|
|
856
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
798
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
857
799
|
}
|
|
858
800
|
catch (e) {
|
|
859
|
-
this
|
|
860
|
-
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);
|
|
861
804
|
foundLocators = [];
|
|
862
805
|
try {
|
|
863
|
-
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);
|
|
864
807
|
}
|
|
865
808
|
catch (e) {
|
|
866
809
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -876,11 +819,27 @@ class StableBrowser {
|
|
|
876
819
|
}
|
|
877
820
|
if (foundLocators.length > 1) {
|
|
878
821
|
info.failCause.foundMultiple = true;
|
|
822
|
+
if (info.locatorLog) {
|
|
823
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
824
|
+
}
|
|
879
825
|
}
|
|
880
826
|
}
|
|
881
827
|
return result;
|
|
882
828
|
}
|
|
883
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);
|
|
884
843
|
const startTime = Date.now();
|
|
885
844
|
let timeout = 30000;
|
|
886
845
|
if (options && options.timeout) {
|
|
@@ -905,13 +864,31 @@ class StableBrowser {
|
|
|
905
864
|
catch (e) {
|
|
906
865
|
if (performance.now() - startTime > timeout) {
|
|
907
866
|
// throw e;
|
|
908
|
-
|
|
867
|
+
try {
|
|
868
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
869
|
+
}
|
|
870
|
+
finally {
|
|
871
|
+
_commandFinally(state, this);
|
|
872
|
+
}
|
|
909
873
|
}
|
|
910
874
|
}
|
|
911
875
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
912
876
|
}
|
|
913
877
|
}
|
|
914
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);
|
|
915
892
|
const startTime = Date.now();
|
|
916
893
|
let timeout = 30000;
|
|
917
894
|
if (options && options.timeout) {
|
|
@@ -936,7 +913,12 @@ class StableBrowser {
|
|
|
936
913
|
catch (e) {
|
|
937
914
|
if (performance.now() - startTime > timeout) {
|
|
938
915
|
// throw e;
|
|
939
|
-
|
|
916
|
+
try {
|
|
917
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
918
|
+
}
|
|
919
|
+
finally {
|
|
920
|
+
_commandFinally(state, this);
|
|
921
|
+
}
|
|
940
922
|
}
|
|
941
923
|
}
|
|
942
924
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -955,9 +937,9 @@ class StableBrowser {
|
|
|
955
937
|
};
|
|
956
938
|
try {
|
|
957
939
|
await _preCommand(state, this);
|
|
958
|
-
if (state.options && state.options.context) {
|
|
959
|
-
|
|
960
|
-
}
|
|
940
|
+
// if (state.options && state.options.context) {
|
|
941
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
942
|
+
// }
|
|
961
943
|
try {
|
|
962
944
|
await state.element.click();
|
|
963
945
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -995,9 +977,15 @@ class StableBrowser {
|
|
|
995
977
|
// let element = await this._locate(selectors, info, _params);
|
|
996
978
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
997
979
|
try {
|
|
998
|
-
//
|
|
980
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
981
|
+
// console.log(`Highlighting while running from recorder`);
|
|
982
|
+
await this._highlightElements(element);
|
|
999
983
|
await state.element.setChecked(checked);
|
|
1000
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);
|
|
1001
989
|
}
|
|
1002
990
|
catch (e) {
|
|
1003
991
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1036,6 +1024,7 @@ class StableBrowser {
|
|
|
1036
1024
|
await _preCommand(state, this);
|
|
1037
1025
|
try {
|
|
1038
1026
|
await state.element.hover();
|
|
1027
|
+
// await _screenshot(state, this);
|
|
1039
1028
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1040
1029
|
}
|
|
1041
1030
|
catch (e) {
|
|
@@ -1043,6 +1032,7 @@ class StableBrowser {
|
|
|
1043
1032
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1044
1033
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1045
1034
|
await state.element.hover({ timeout: 10000 });
|
|
1035
|
+
// await _screenshot(state, this);
|
|
1046
1036
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1047
1037
|
}
|
|
1048
1038
|
await _screenshot(state, this);
|
|
@@ -1317,7 +1307,12 @@ class StableBrowser {
|
|
|
1317
1307
|
await this.waitForPageLoad();
|
|
1318
1308
|
}
|
|
1319
1309
|
else if (enter === false) {
|
|
1320
|
-
|
|
1310
|
+
try {
|
|
1311
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1312
|
+
}
|
|
1313
|
+
catch (e) {
|
|
1314
|
+
// ignore
|
|
1315
|
+
}
|
|
1321
1316
|
//await this.page.keyboard.press("Tab");
|
|
1322
1317
|
}
|
|
1323
1318
|
else {
|
|
@@ -1374,6 +1369,7 @@ class StableBrowser {
|
|
|
1374
1369
|
let screenshotPath = null;
|
|
1375
1370
|
if (!info.log) {
|
|
1376
1371
|
info.log = "";
|
|
1372
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1377
1373
|
}
|
|
1378
1374
|
info.operation = "getText";
|
|
1379
1375
|
info.selectors = selectors;
|
|
@@ -1396,6 +1392,18 @@ class StableBrowser {
|
|
|
1396
1392
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1397
1393
|
try {
|
|
1398
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
|
+
// }
|
|
1399
1407
|
const elementText = await element.innerText();
|
|
1400
1408
|
return {
|
|
1401
1409
|
text: elementText,
|
|
@@ -1407,7 +1415,7 @@ class StableBrowser {
|
|
|
1407
1415
|
}
|
|
1408
1416
|
catch (e) {
|
|
1409
1417
|
//await this.closeUnexpectedPopups();
|
|
1410
|
-
this.logger.info("no innerText will use textContent");
|
|
1418
|
+
this.logger.info("no innerText, will use textContent");
|
|
1411
1419
|
const elementText = await element.textContent();
|
|
1412
1420
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1413
1421
|
}
|
|
@@ -1533,22 +1541,6 @@ class StableBrowser {
|
|
|
1533
1541
|
_commandFinally(state, this);
|
|
1534
1542
|
}
|
|
1535
1543
|
}
|
|
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
1544
|
async waitForUserInput(message, world = null) {
|
|
1553
1545
|
if (!message) {
|
|
1554
1546
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1577,7 +1569,7 @@ class StableBrowser {
|
|
|
1577
1569
|
return;
|
|
1578
1570
|
}
|
|
1579
1571
|
// if data file exists, load it
|
|
1580
|
-
const dataFile =
|
|
1572
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1581
1573
|
let data = this.getTestData(world);
|
|
1582
1574
|
// merge the testData with the existing data
|
|
1583
1575
|
Object.assign(data, testData);
|
|
@@ -1680,7 +1672,7 @@ class StableBrowser {
|
|
|
1680
1672
|
}
|
|
1681
1673
|
}
|
|
1682
1674
|
getTestData(world = null) {
|
|
1683
|
-
const dataFile =
|
|
1675
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1684
1676
|
let data = {};
|
|
1685
1677
|
if (fs.existsSync(dataFile)) {
|
|
1686
1678
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1724,15 +1716,15 @@ class StableBrowser {
|
|
|
1724
1716
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1725
1717
|
// }
|
|
1726
1718
|
// });
|
|
1719
|
+
result.screenshotId = uuidStr;
|
|
1720
|
+
result.screenshotPath = screenshotPath;
|
|
1721
|
+
if (info && info.box) {
|
|
1722
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1723
|
+
}
|
|
1727
1724
|
}
|
|
1728
1725
|
catch (e) {
|
|
1729
1726
|
this.logger.info("unable to take screenshot, ignored");
|
|
1730
1727
|
}
|
|
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
1728
|
}
|
|
1737
1729
|
else if (options && options.screenshot) {
|
|
1738
1730
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1767,6 +1759,15 @@ class StableBrowser {
|
|
|
1767
1759
|
document.documentElement.clientWidth,
|
|
1768
1760
|
])));
|
|
1769
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
|
+
// }
|
|
1770
1771
|
if (this.context.browserName === "chromium") {
|
|
1771
1772
|
const client = await playContext.newCDPSession(this.page);
|
|
1772
1773
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1788,6 +1789,10 @@ class StableBrowser {
|
|
|
1788
1789
|
else {
|
|
1789
1790
|
screenshotBuffer = await this.page.screenshot();
|
|
1790
1791
|
}
|
|
1792
|
+
// if (focusedElement) {
|
|
1793
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1794
|
+
// await this._unhighlightElements(focusedElement);
|
|
1795
|
+
// }
|
|
1791
1796
|
let image = await Jimp.read(screenshotBuffer);
|
|
1792
1797
|
// Get the image dimensions
|
|
1793
1798
|
const { width, height } = image.bitmap;
|
|
@@ -1800,6 +1805,7 @@ class StableBrowser {
|
|
|
1800
1805
|
else {
|
|
1801
1806
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1802
1807
|
}
|
|
1808
|
+
return screenshotBuffer;
|
|
1803
1809
|
}
|
|
1804
1810
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1805
1811
|
const state = {
|
|
@@ -1837,6 +1843,7 @@ class StableBrowser {
|
|
|
1837
1843
|
text: `Extract attribute from element`,
|
|
1838
1844
|
operation: "extractAttribute",
|
|
1839
1845
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1846
|
+
allowDisabled: true,
|
|
1840
1847
|
};
|
|
1841
1848
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1842
1849
|
try {
|
|
@@ -1858,6 +1865,76 @@ class StableBrowser {
|
|
|
1858
1865
|
state.info.value = state.value;
|
|
1859
1866
|
this.setTestData({ [variable]: state.value }, world);
|
|
1860
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
|
+
}
|
|
1861
1938
|
return state.info;
|
|
1862
1939
|
}
|
|
1863
1940
|
catch (e) {
|
|
@@ -1885,7 +1962,7 @@ class StableBrowser {
|
|
|
1885
1962
|
if (options && options.timeout) {
|
|
1886
1963
|
timeout = options.timeout;
|
|
1887
1964
|
}
|
|
1888
|
-
const serviceUrl =
|
|
1965
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1889
1966
|
const request = {
|
|
1890
1967
|
method: "POST",
|
|
1891
1968
|
url: serviceUrl,
|
|
@@ -1956,27 +2033,32 @@ class StableBrowser {
|
|
|
1956
2033
|
async _highlightElements(scope, css) {
|
|
1957
2034
|
try {
|
|
1958
2035
|
if (!scope) {
|
|
2036
|
+
// console.log(`Scope is not defined`);
|
|
1959
2037
|
return;
|
|
1960
2038
|
}
|
|
1961
2039
|
if (!css) {
|
|
1962
2040
|
scope
|
|
1963
2041
|
.evaluate((node) => {
|
|
1964
2042
|
if (node && node.style) {
|
|
1965
|
-
let
|
|
1966
|
-
|
|
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}`);
|
|
1967
2048
|
if (window) {
|
|
1968
2049
|
window.addEventListener("beforeunload", function (e) {
|
|
1969
|
-
node.style.
|
|
2050
|
+
node.style.outline = originalOutline;
|
|
1970
2051
|
});
|
|
1971
2052
|
}
|
|
1972
2053
|
setTimeout(function () {
|
|
1973
|
-
node.style.
|
|
2054
|
+
node.style.outline = originalOutline;
|
|
1974
2055
|
}, 2000);
|
|
1975
2056
|
}
|
|
1976
2057
|
})
|
|
1977
2058
|
.then(() => { })
|
|
1978
2059
|
.catch((e) => {
|
|
1979
2060
|
// ignore
|
|
2061
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1980
2062
|
});
|
|
1981
2063
|
}
|
|
1982
2064
|
else {
|
|
@@ -1992,17 +2074,18 @@ class StableBrowser {
|
|
|
1992
2074
|
if (!element.style) {
|
|
1993
2075
|
return;
|
|
1994
2076
|
}
|
|
1995
|
-
|
|
2077
|
+
let originalOutline = element.style.outline;
|
|
2078
|
+
element.__previousOutline = originalOutline;
|
|
1996
2079
|
// Set the new border to be red and 2px solid
|
|
1997
|
-
element.style.
|
|
2080
|
+
element.style.outline = "2px solid red";
|
|
1998
2081
|
if (window) {
|
|
1999
2082
|
window.addEventListener("beforeunload", function (e) {
|
|
2000
|
-
element.style.
|
|
2083
|
+
element.style.outline = originalOutline;
|
|
2001
2084
|
});
|
|
2002
2085
|
}
|
|
2003
2086
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2004
2087
|
setTimeout(function () {
|
|
2005
|
-
element.style.
|
|
2088
|
+
element.style.outline = originalOutline;
|
|
2006
2089
|
}, 2000);
|
|
2007
2090
|
}
|
|
2008
2091
|
return;
|
|
@@ -2010,6 +2093,7 @@ class StableBrowser {
|
|
|
2010
2093
|
.then(() => { })
|
|
2011
2094
|
.catch((e) => {
|
|
2012
2095
|
// ignore
|
|
2096
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2013
2097
|
});
|
|
2014
2098
|
}
|
|
2015
2099
|
}
|
|
@@ -2017,6 +2101,54 @@ class StableBrowser {
|
|
|
2017
2101
|
console.debug(error);
|
|
2018
2102
|
}
|
|
2019
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
|
+
// }
|
|
2020
2152
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2021
2153
|
const startTime = Date.now();
|
|
2022
2154
|
let error = null;
|
|
@@ -2078,6 +2210,35 @@ class StableBrowser {
|
|
|
2078
2210
|
});
|
|
2079
2211
|
}
|
|
2080
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
|
+
}
|
|
2081
2242
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2082
2243
|
text = unEscapeString(text);
|
|
2083
2244
|
const state = {
|
|
@@ -2087,7 +2248,7 @@ class StableBrowser {
|
|
|
2087
2248
|
locate: false,
|
|
2088
2249
|
scroll: false,
|
|
2089
2250
|
highlight: false,
|
|
2090
|
-
type: Types.
|
|
2251
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2091
2252
|
text: `Verify text exists in page`,
|
|
2092
2253
|
operation: "verifyTextExistInPage",
|
|
2093
2254
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2105,31 +2266,15 @@ class StableBrowser {
|
|
|
2105
2266
|
await _preCommand(state, this);
|
|
2106
2267
|
state.info.text = text;
|
|
2107
2268
|
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
|
-
}
|
|
2269
|
+
let resultWithElementsFound = {
|
|
2270
|
+
length: 0,
|
|
2271
|
+
};
|
|
2272
|
+
try {
|
|
2273
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2274
|
+
}
|
|
2275
|
+
catch (error) {
|
|
2276
|
+
// ignore
|
|
2130
2277
|
}
|
|
2131
|
-
state.info.results = results;
|
|
2132
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2133
2278
|
if (resultWithElementsFound.length === 0) {
|
|
2134
2279
|
if (Date.now() - state.startTime > timeout) {
|
|
2135
2280
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2137,18 +2282,40 @@ class StableBrowser {
|
|
|
2137
2282
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2138
2283
|
continue;
|
|
2139
2284
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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
|
+
}
|
|
2148
2312
|
}
|
|
2313
|
+
await _screenshot(state, this);
|
|
2314
|
+
return state.info;
|
|
2315
|
+
}
|
|
2316
|
+
catch (error) {
|
|
2317
|
+
console.error(error);
|
|
2149
2318
|
}
|
|
2150
|
-
await _screenshot(state, this);
|
|
2151
|
-
return state.info;
|
|
2152
2319
|
}
|
|
2153
2320
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2154
2321
|
}
|
|
@@ -2185,32 +2352,16 @@ class StableBrowser {
|
|
|
2185
2352
|
try {
|
|
2186
2353
|
await _preCommand(state, this);
|
|
2187
2354
|
state.info.text = text;
|
|
2355
|
+
let resultWithElementsFound = {
|
|
2356
|
+
length: null, // initial cannot be 0
|
|
2357
|
+
};
|
|
2188
2358
|
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
|
-
}
|
|
2359
|
+
try {
|
|
2360
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2361
|
+
}
|
|
2362
|
+
catch (error) {
|
|
2363
|
+
// ignore
|
|
2211
2364
|
}
|
|
2212
|
-
state.info.results = results;
|
|
2213
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2214
2365
|
if (resultWithElementsFound.length === 0) {
|
|
2215
2366
|
await _screenshot(state, this);
|
|
2216
2367
|
return state.info;
|
|
@@ -2228,15 +2379,115 @@ class StableBrowser {
|
|
|
2228
2379
|
_commandFinally(state, this);
|
|
2229
2380
|
}
|
|
2230
2381
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
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 });
|
|
2484
|
+
}
|
|
2485
|
+
catch (e) {
|
|
2486
|
+
await _commandError(state, e, this);
|
|
2235
2487
|
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2488
|
+
finally {
|
|
2489
|
+
_commandFinally(state, this);
|
|
2238
2490
|
}
|
|
2239
|
-
return serviceUrl;
|
|
2240
2491
|
}
|
|
2241
2492
|
async visualVerification(text, options = {}, world = null) {
|
|
2242
2493
|
const startTime = Date.now();
|
|
@@ -2252,14 +2503,17 @@ class StableBrowser {
|
|
|
2252
2503
|
throw new Error("TOKEN is not set");
|
|
2253
2504
|
}
|
|
2254
2505
|
try {
|
|
2255
|
-
let serviceUrl =
|
|
2506
|
+
let serviceUrl = _getServerUrl();
|
|
2256
2507
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2257
2508
|
info.screenshotPath = screenshotPath;
|
|
2258
2509
|
const screenshot = await this.takeScreenshot();
|
|
2259
|
-
|
|
2260
|
-
method: "
|
|
2510
|
+
let request = {
|
|
2511
|
+
method: "post",
|
|
2512
|
+
maxBodyLength: Infinity,
|
|
2261
2513
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2262
2514
|
headers: {
|
|
2515
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2516
|
+
"x-source": "aaa",
|
|
2263
2517
|
"Content-Type": "application/json",
|
|
2264
2518
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2265
2519
|
},
|
|
@@ -2268,7 +2522,7 @@ class StableBrowser {
|
|
|
2268
2522
|
screenshot: screenshot,
|
|
2269
2523
|
}),
|
|
2270
2524
|
};
|
|
2271
|
-
|
|
2525
|
+
const result = await axios.request(request);
|
|
2272
2526
|
if (result.data.status !== true) {
|
|
2273
2527
|
throw new Error("Visual validation failed");
|
|
2274
2528
|
}
|
|
@@ -2341,6 +2595,7 @@ class StableBrowser {
|
|
|
2341
2595
|
let screenshotPath = null;
|
|
2342
2596
|
const info = {};
|
|
2343
2597
|
info.log = "";
|
|
2598
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2344
2599
|
info.operation = "getTableData";
|
|
2345
2600
|
info.selectors = selectors;
|
|
2346
2601
|
try {
|
|
@@ -2416,7 +2671,7 @@ class StableBrowser {
|
|
|
2416
2671
|
info.operation = "analyzeTable";
|
|
2417
2672
|
info.selectors = selectors;
|
|
2418
2673
|
info.query = query;
|
|
2419
|
-
query =
|
|
2674
|
+
query = _fixUsingParams(query, _params);
|
|
2420
2675
|
info.query_fixed = query;
|
|
2421
2676
|
info.operator = operator;
|
|
2422
2677
|
info.value = value;
|
|
@@ -2561,6 +2816,17 @@ class StableBrowser {
|
|
|
2561
2816
|
}
|
|
2562
2817
|
return timeout;
|
|
2563
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
|
+
}
|
|
2564
2830
|
async waitForPageLoad(options = {}, world = null) {
|
|
2565
2831
|
let timeout = this._getLoadTimeout(options);
|
|
2566
2832
|
const promiseArray = [];
|
|
@@ -2746,14 +3012,22 @@ class StableBrowser {
|
|
|
2746
3012
|
}
|
|
2747
3013
|
}
|
|
2748
3014
|
async beforeStep(world, step) {
|
|
2749
|
-
this.stepName = step.pickleStep.text;
|
|
2750
|
-
this.logger.info("step: " + this.stepName);
|
|
2751
3015
|
if (this.stepIndex === undefined) {
|
|
2752
3016
|
this.stepIndex = 0;
|
|
2753
3017
|
}
|
|
2754
3018
|
else {
|
|
2755
3019
|
this.stepIndex++;
|
|
2756
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
|
+
}
|
|
2757
3031
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2758
3032
|
if (this.context.browserObject.context) {
|
|
2759
3033
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2783,156 +3057,5 @@ function createTimedPromise(promise, label) {
|
|
|
2783
3057
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2784
3058
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2785
3059
|
}
|
|
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
3060
|
export { StableBrowser };
|
|
2938
3061
|
//# sourceMappingURL=stable_browser.js.map
|