automation_model 1.0.501-dev → 1.0.501-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 +2 -1
- package/lib/api.js +98 -98
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +19 -2
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +100 -32
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +54 -6
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- 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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +20 -6
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.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 +16 -3
- package/lib/network.js.map +1 -1
- package/lib/scripts/find_text.js +126 -0
- package/lib/stable_browser.d.ts +51 -19
- package/lib/stable_browser.js +642 -583
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +12 -1
- package/lib/utils.js +235 -3
- package/lib/utils.js.map +1 -1
- package/package.json +7 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,15 +10,17 @@ 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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
17
|
import { getContext } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
|
-
import {
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -29,6 +31,8 @@ const Types = {
|
|
|
29
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
36
|
ANALYZE_TABLE: "analyze_table",
|
|
33
37
|
SELECT: "select_combobox",
|
|
34
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -44,6 +48,9 @@ const Types = {
|
|
|
44
48
|
VERIFY_VISUAL: "verify_visual",
|
|
45
49
|
LOAD_DATA: "load_data",
|
|
46
50
|
SET_INPUT: "set_input",
|
|
51
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
47
54
|
};
|
|
48
55
|
export const apps = {};
|
|
49
56
|
class StableBrowser {
|
|
@@ -57,6 +64,7 @@ class StableBrowser {
|
|
|
57
64
|
networkLogger = null;
|
|
58
65
|
configuration = null;
|
|
59
66
|
appName = "main";
|
|
67
|
+
tags = null;
|
|
60
68
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
61
69
|
this.browser = browser;
|
|
62
70
|
this.page = page;
|
|
@@ -87,17 +95,17 @@ class StableBrowser {
|
|
|
87
95
|
catch (e) {
|
|
88
96
|
this.logger.error("unable to read ai_config.json");
|
|
89
97
|
}
|
|
90
|
-
context.pageLoading = { status: false };
|
|
91
|
-
context.pages = [this.page];
|
|
92
98
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
93
99
|
this.world = world;
|
|
100
|
+
context.pages = [this.page];
|
|
101
|
+
context.pageLoading = { status: false };
|
|
94
102
|
this.registerEventListeners(this.context);
|
|
95
103
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
104
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
97
105
|
}
|
|
98
106
|
registerEventListeners(context) {
|
|
99
107
|
this.registerConsoleLogListener(this.page, context);
|
|
100
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
109
|
if (!context.pageLoading) {
|
|
102
110
|
context.pageLoading = { status: false };
|
|
103
111
|
}
|
|
@@ -142,9 +150,9 @@ class StableBrowser {
|
|
|
142
150
|
if (this.appName === appName) {
|
|
143
151
|
return;
|
|
144
152
|
}
|
|
145
|
-
let
|
|
153
|
+
let navigate = false;
|
|
146
154
|
if (!apps[appName]) {
|
|
147
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
155
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
148
156
|
newContextCreated = true;
|
|
149
157
|
apps[appName] = {
|
|
150
158
|
context: newContext,
|
|
@@ -153,32 +161,15 @@ class StableBrowser {
|
|
|
153
161
|
};
|
|
154
162
|
}
|
|
155
163
|
const tempContext = {};
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
158
166
|
apps[this.appName] = tempContext;
|
|
159
167
|
this.appName = appName;
|
|
160
|
-
if (
|
|
161
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
162
169
|
await this.goto(this.context.environment.baseUrl);
|
|
163
170
|
await this.waitForPageLoad();
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
|
-
_copyContext(from, to) {
|
|
167
|
-
to.browser = from.browser;
|
|
168
|
-
to.page = from.page;
|
|
169
|
-
to.context = from.context;
|
|
170
|
-
}
|
|
171
|
-
getWebLogFile(logFolder) {
|
|
172
|
-
if (!fs.existsSync(logFolder)) {
|
|
173
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
174
|
-
}
|
|
175
|
-
let nextIndex = 1;
|
|
176
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
177
|
-
nextIndex++;
|
|
178
|
-
}
|
|
179
|
-
const fileName = nextIndex + ".json";
|
|
180
|
-
return path.join(logFolder, fileName);
|
|
181
|
-
}
|
|
182
173
|
registerConsoleLogListener(page, context) {
|
|
183
174
|
if (!this.context.webLogger) {
|
|
184
175
|
this.context.webLogger = [];
|
|
@@ -228,7 +219,7 @@ class StableBrowser {
|
|
|
228
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
229
220
|
}
|
|
230
221
|
catch (error) {
|
|
231
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
232
223
|
context.networkLogger.push({
|
|
233
224
|
error: "not able to listen",
|
|
234
225
|
message: error.message,
|
|
@@ -242,55 +233,48 @@ class StableBrowser {
|
|
|
242
233
|
// async closeUnexpectedPopups() {
|
|
243
234
|
// await closeUnexpectedPopups(this.page);
|
|
244
235
|
// }
|
|
245
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
246
237
|
if (!url.startsWith("http")) {
|
|
247
238
|
url = "https://" + url;
|
|
248
239
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
const state = {
|
|
241
|
+
value: url,
|
|
242
|
+
world: world,
|
|
243
|
+
type: Types.NAVIGATE,
|
|
244
|
+
text: `Navigate Page to: ${url}`,
|
|
245
|
+
operation: "goto",
|
|
246
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
247
|
+
info: {},
|
|
248
|
+
locate: false,
|
|
249
|
+
scroll: false,
|
|
250
|
+
screenshot: false,
|
|
251
|
+
highlight: false,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
await _preCommand(state, this);
|
|
255
|
+
await this.page.goto(url, {
|
|
256
|
+
timeout: 60000,
|
|
257
|
+
});
|
|
258
|
+
await _screenshot(state, this);
|
|
256
259
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// remove the _ prefix
|
|
261
|
-
regValue = key.substring(1);
|
|
262
|
-
}
|
|
263
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
268
|
-
// check if not null
|
|
269
|
-
if (!locator) {
|
|
270
|
-
return locator;
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
271
266
|
}
|
|
272
|
-
// clone the locator
|
|
273
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
274
|
-
this.scanAndManipulate(locator, _params);
|
|
275
|
-
return locator;
|
|
276
|
-
}
|
|
277
|
-
_isObject(value) {
|
|
278
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
279
267
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
268
|
+
async _getLocator(locator, scope, _params) {
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
270
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
271
|
+
for (let key in locator) {
|
|
272
|
+
if (typeof locator[key] !== "string")
|
|
273
|
+
continue;
|
|
274
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
275
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
289
276
|
}
|
|
290
277
|
}
|
|
291
|
-
}
|
|
292
|
-
_getLocator(locator, scope, _params) {
|
|
293
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
294
278
|
let locatorReturn;
|
|
295
279
|
if (locator.role) {
|
|
296
280
|
if (locator.role[1].nameReg) {
|
|
@@ -298,7 +282,7 @@ class StableBrowser {
|
|
|
298
282
|
delete locator.role[1].nameReg;
|
|
299
283
|
}
|
|
300
284
|
// if (locator.role[1].name) {
|
|
301
|
-
// locator.role[1].name =
|
|
285
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
302
286
|
// }
|
|
303
287
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
304
288
|
}
|
|
@@ -341,7 +325,7 @@ class StableBrowser {
|
|
|
341
325
|
if (css && css.locator) {
|
|
342
326
|
css = css.locator;
|
|
343
327
|
}
|
|
344
|
-
let result = await this._locateElementByText(scope,
|
|
328
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
345
329
|
if (result.elementCount === 0) {
|
|
346
330
|
return;
|
|
347
331
|
}
|
|
@@ -352,132 +336,75 @@ class StableBrowser {
|
|
|
352
336
|
climbArray.push("..");
|
|
353
337
|
}
|
|
354
338
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
355
|
-
|
|
339
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
340
|
+
if (css) {
|
|
341
|
+
resultCss = resultCss + " >> " + css;
|
|
342
|
+
}
|
|
343
|
+
return resultCss;
|
|
356
344
|
}
|
|
357
|
-
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
345
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
358
346
|
//const stringifyText = JSON.stringify(text);
|
|
359
|
-
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
currentNode = currentNode.parentNode;
|
|
367
|
-
}
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
document.isParent = isParent;
|
|
371
|
-
function getRegex(str) {
|
|
372
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
373
|
-
if (!match) {
|
|
374
|
-
return null;
|
|
375
|
-
}
|
|
376
|
-
let [_, pattern, flags] = match;
|
|
377
|
-
return new RegExp(pattern, flags);
|
|
378
|
-
}
|
|
379
|
-
document.getRegex = getRegex;
|
|
380
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
381
|
-
// Check and add the element if it has a shadow root
|
|
382
|
-
if (element.shadowRoot) {
|
|
383
|
-
result.push(element);
|
|
384
|
-
// Also search within the shadow root
|
|
385
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
386
|
-
}
|
|
387
|
-
// Iterate over child nodes
|
|
388
|
-
element.childNodes.forEach((child) => {
|
|
389
|
-
// Recursively call the function for each child node
|
|
390
|
-
document.collectAllShadowDomElements(child, result);
|
|
391
|
-
});
|
|
392
|
-
return result;
|
|
393
|
-
}
|
|
394
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
395
|
-
if (!tag) {
|
|
396
|
-
tag = "*:not(script, style, head)";
|
|
397
|
-
}
|
|
398
|
-
let regexpSearch = document.getRegex(text);
|
|
399
|
-
if (regexpSearch) {
|
|
400
|
-
regex = true;
|
|
401
|
-
}
|
|
402
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
403
|
-
let shadowHosts = [];
|
|
404
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
405
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
406
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
407
|
-
if (!shadowElement) {
|
|
408
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
412
|
-
elements = elements.concat(shadowElements);
|
|
413
|
-
}
|
|
414
|
-
let randomToken = null;
|
|
415
|
-
const foundElements = [];
|
|
347
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial, ignoreCase]) => {
|
|
348
|
+
const options = {
|
|
349
|
+
innerText: true,
|
|
350
|
+
ignoreCase: ignoreCase,
|
|
351
|
+
};
|
|
416
352
|
if (regex) {
|
|
417
|
-
|
|
418
|
-
regexpSearch = new RegExp(text, "im");
|
|
419
|
-
}
|
|
420
|
-
for (let i = 0; i < elements.length; i++) {
|
|
421
|
-
const element = elements[i];
|
|
422
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
423
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
424
|
-
foundElements.push(element);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
353
|
+
options.singleRegex = true;
|
|
427
354
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
for (let i = 0; i < elements.length; i++) {
|
|
431
|
-
const element = elements[i];
|
|
432
|
-
if (partial) {
|
|
433
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
434
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
435
|
-
foundElements.push(element);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
440
|
-
(element.value && element.value === text)) {
|
|
441
|
-
foundElements.push(element);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
355
|
+
if (tag) {
|
|
356
|
+
options.tag = tag;
|
|
445
357
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (isParent(element, foundElements[j])) {
|
|
455
|
-
hasChild = true;
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
358
|
+
if (!(partial === true)) {
|
|
359
|
+
options.exactMatch = true;
|
|
360
|
+
}
|
|
361
|
+
if (text.startsWith("/") && text.endsWith("/")) {
|
|
362
|
+
if (text.length < 3) {
|
|
363
|
+
throw new Error("Invalid regex pattern: empty pattern");
|
|
458
364
|
}
|
|
459
|
-
|
|
460
|
-
|
|
365
|
+
try {
|
|
366
|
+
const pattern = text.slice(1, -1);
|
|
367
|
+
new RegExp(pattern); // Validate pattern
|
|
368
|
+
text = pattern;
|
|
369
|
+
options.singleRegex = true;
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
372
|
+
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
461
373
|
}
|
|
462
374
|
}
|
|
375
|
+
const elements = window.findMatchingElements(text, options);
|
|
376
|
+
let randomToken = null;
|
|
377
|
+
const foundElements = [];
|
|
463
378
|
let elementCount = 0;
|
|
464
|
-
if (
|
|
465
|
-
for (let i = 0; i <
|
|
379
|
+
if (elements.length > 0) {
|
|
380
|
+
for (let i = 0; i < elements.length; i++) {
|
|
466
381
|
if (randomToken === null) {
|
|
467
382
|
randomToken = Math.random().toString(36).substring(7);
|
|
468
383
|
}
|
|
469
|
-
let element =
|
|
384
|
+
let element = elements[i];
|
|
470
385
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
471
386
|
elementCount++;
|
|
472
387
|
}
|
|
473
388
|
}
|
|
474
389
|
return { elementCount: elementCount, randomToken: randomToken };
|
|
475
|
-
}, [text1, tag1, regex1, partial1]);
|
|
390
|
+
}, [text1, tag1, regex1, partial1, ignoreCase]);
|
|
476
391
|
}
|
|
477
392
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
393
|
+
if (!info) {
|
|
394
|
+
info = {};
|
|
395
|
+
}
|
|
396
|
+
if (!info.failCause) {
|
|
397
|
+
info.failCause = {};
|
|
398
|
+
}
|
|
399
|
+
if (!info.log) {
|
|
400
|
+
info.log = "";
|
|
401
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
402
|
+
}
|
|
478
403
|
let locatorSearch = selectorHierarchy[index];
|
|
404
|
+
let originalLocatorSearch = "";
|
|
479
405
|
try {
|
|
480
|
-
|
|
406
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
407
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
481
408
|
}
|
|
482
409
|
catch (e) {
|
|
483
410
|
console.error(e);
|
|
@@ -487,36 +414,50 @@ class StableBrowser {
|
|
|
487
414
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
488
415
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
489
416
|
if (!locatorString) {
|
|
417
|
+
info.failCause.textNotFound = true;
|
|
418
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
490
419
|
return;
|
|
491
420
|
}
|
|
492
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
421
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
493
422
|
}
|
|
494
423
|
else if (locatorSearch.text) {
|
|
495
|
-
let
|
|
424
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
425
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
496
426
|
if (result.elementCount === 0) {
|
|
427
|
+
info.failCause.textNotFound = true;
|
|
428
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
497
429
|
return;
|
|
498
430
|
}
|
|
499
431
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
500
432
|
if (locatorSearch.childCss) {
|
|
501
433
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
502
434
|
}
|
|
503
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
435
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
504
436
|
}
|
|
505
437
|
else {
|
|
506
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
507
439
|
}
|
|
508
440
|
// let cssHref = false;
|
|
509
441
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
510
442
|
// cssHref = true;
|
|
511
443
|
// }
|
|
512
444
|
let count = await locator.count();
|
|
445
|
+
if (count > 0 && !info.failCause.count) {
|
|
446
|
+
info.failCause.count = count;
|
|
447
|
+
}
|
|
513
448
|
//info.log += "total elements found " + count + "\n";
|
|
514
449
|
//let visibleCount = 0;
|
|
515
450
|
let visibleLocator = null;
|
|
516
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
451
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
517
452
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
453
|
+
if (info.locatorLog) {
|
|
454
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
455
|
+
}
|
|
518
456
|
return;
|
|
519
457
|
}
|
|
458
|
+
if (info.locatorLog && count === 0) {
|
|
459
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
460
|
+
}
|
|
520
461
|
for (let j = 0; j < count; j++) {
|
|
521
462
|
let visible = await locator.nth(j).isVisible();
|
|
522
463
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -525,24 +466,40 @@ class StableBrowser {
|
|
|
525
466
|
}
|
|
526
467
|
if (visible && enabled) {
|
|
527
468
|
foundLocators.push(locator.nth(j));
|
|
469
|
+
if (info.locatorLog) {
|
|
470
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
471
|
+
}
|
|
528
472
|
}
|
|
529
473
|
else {
|
|
474
|
+
info.failCause.visible = visible;
|
|
475
|
+
info.failCause.enabled = enabled;
|
|
530
476
|
if (!info.printMessages) {
|
|
531
477
|
info.printMessages = {};
|
|
532
478
|
}
|
|
479
|
+
if (info.locatorLog && !visible) {
|
|
480
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
481
|
+
}
|
|
482
|
+
if (info.locatorLog && !enabled) {
|
|
483
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
484
|
+
}
|
|
533
485
|
if (!info.printMessages[j.toString()]) {
|
|
534
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
486
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
535
487
|
info.printMessages[j.toString()] = true;
|
|
536
488
|
}
|
|
537
489
|
}
|
|
538
490
|
}
|
|
539
491
|
}
|
|
540
492
|
async closeUnexpectedPopups(info, _params) {
|
|
493
|
+
if (!info) {
|
|
494
|
+
info = {};
|
|
495
|
+
info.failCause = {};
|
|
496
|
+
info.log = "";
|
|
497
|
+
}
|
|
541
498
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
542
499
|
if (!info) {
|
|
543
500
|
info = {};
|
|
544
501
|
}
|
|
545
|
-
info.log += "scan for popup handlers" + "\n";
|
|
502
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
546
503
|
const handlerGroup = [];
|
|
547
504
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
548
505
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -569,9 +526,21 @@ class StableBrowser {
|
|
|
569
526
|
}
|
|
570
527
|
if (result.foundElements.length > 0) {
|
|
571
528
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
529
|
+
try {
|
|
530
|
+
await scope?.evaluate(() => {
|
|
531
|
+
window.__isClosingPopups = true;
|
|
532
|
+
});
|
|
533
|
+
await dialogCloseLocator.click();
|
|
534
|
+
// wait for the dialog to close
|
|
535
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
await scope?.evaluate(() => {
|
|
541
|
+
window.__isClosingPopups = false;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
575
544
|
return { rerun: true };
|
|
576
545
|
}
|
|
577
546
|
}
|
|
@@ -595,7 +564,13 @@ class StableBrowser {
|
|
|
595
564
|
}
|
|
596
565
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
597
566
|
}
|
|
598
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
567
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
568
|
+
if (!info) {
|
|
569
|
+
info = {};
|
|
570
|
+
info.failCause = {};
|
|
571
|
+
info.log = "";
|
|
572
|
+
}
|
|
573
|
+
let startTime = Date.now();
|
|
599
574
|
let scope = this.page;
|
|
600
575
|
if (selectors.frame) {
|
|
601
576
|
return selectors.frame;
|
|
@@ -626,9 +601,11 @@ class StableBrowser {
|
|
|
626
601
|
}
|
|
627
602
|
return framescope;
|
|
628
603
|
};
|
|
604
|
+
let fLocator = null;
|
|
629
605
|
while (true) {
|
|
630
606
|
let frameFound = false;
|
|
631
607
|
if (selectors.nestFrmLoc) {
|
|
608
|
+
fLocator = selectors.nestFrmLoc;
|
|
632
609
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
633
610
|
frameFound = true;
|
|
634
611
|
break;
|
|
@@ -637,6 +614,7 @@ class StableBrowser {
|
|
|
637
614
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
638
615
|
let frameLocator = selectors.frameLocators[i];
|
|
639
616
|
if (frameLocator.css) {
|
|
617
|
+
fLocator = frameLocator.css;
|
|
640
618
|
scope = scope.frameLocator(frameLocator.css);
|
|
641
619
|
frameFound = true;
|
|
642
620
|
break;
|
|
@@ -644,16 +622,25 @@ class StableBrowser {
|
|
|
644
622
|
}
|
|
645
623
|
}
|
|
646
624
|
if (!frameFound && selectors.iframe_src) {
|
|
625
|
+
fLocator = selectors.iframe_src;
|
|
647
626
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
648
627
|
}
|
|
649
628
|
if (!scope) {
|
|
650
|
-
info
|
|
651
|
-
|
|
629
|
+
if (info && info.locatorLog) {
|
|
630
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
631
|
+
}
|
|
632
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
633
|
+
if (Date.now() - startTime > timeout) {
|
|
634
|
+
info.failCause.iframeNotFound = true;
|
|
635
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
652
636
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
653
637
|
}
|
|
654
638
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
655
639
|
}
|
|
656
640
|
else {
|
|
641
|
+
if (info && info.locatorLog) {
|
|
642
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
643
|
+
}
|
|
657
644
|
break;
|
|
658
645
|
}
|
|
659
646
|
}
|
|
@@ -663,20 +650,27 @@ class StableBrowser {
|
|
|
663
650
|
}
|
|
664
651
|
return scope;
|
|
665
652
|
}
|
|
666
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
667
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
653
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
654
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
668
655
|
return scope.evaluate(() => {
|
|
669
656
|
var bodyContent = document.body.innerHTML;
|
|
670
657
|
return bodyContent;
|
|
671
658
|
});
|
|
672
659
|
}
|
|
673
660
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
661
|
+
if (!info) {
|
|
662
|
+
info = {};
|
|
663
|
+
info.failCause = {};
|
|
664
|
+
info.log = "";
|
|
665
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
666
|
+
}
|
|
674
667
|
let highPriorityTimeout = 5000;
|
|
675
668
|
let visibleOnlyTimeout = 6000;
|
|
676
|
-
let startTime =
|
|
669
|
+
let startTime = Date.now();
|
|
677
670
|
let locatorsCount = 0;
|
|
671
|
+
let lazy_scroll = false;
|
|
678
672
|
//let arrayMode = Array.isArray(selectors);
|
|
679
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
673
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
680
674
|
let selectorsLocators = null;
|
|
681
675
|
selectorsLocators = selectors.locators;
|
|
682
676
|
// group selectors by priority
|
|
@@ -763,21 +757,33 @@ class StableBrowser {
|
|
|
763
757
|
return maxCountElement.locator;
|
|
764
758
|
}
|
|
765
759
|
}
|
|
766
|
-
if (
|
|
760
|
+
if (Date.now() - startTime > timeout) {
|
|
767
761
|
break;
|
|
768
762
|
}
|
|
769
|
-
if (
|
|
763
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
770
764
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
771
765
|
highPriorityOnly = false;
|
|
766
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
767
|
+
lazy_scroll = true;
|
|
768
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
769
|
+
}
|
|
772
770
|
}
|
|
773
|
-
if (
|
|
771
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
774
772
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
775
773
|
visibleOnly = false;
|
|
776
774
|
}
|
|
777
775
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
778
776
|
}
|
|
779
777
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
780
|
-
info.
|
|
778
|
+
// if (info.locatorLog) {
|
|
779
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
780
|
+
// for (let line of lines) {
|
|
781
|
+
// this.logger.debug(line);
|
|
782
|
+
// }
|
|
783
|
+
// }
|
|
784
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
785
|
+
info.failCause.locatorNotFound = true;
|
|
786
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
781
787
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
782
788
|
}
|
|
783
789
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -791,8 +797,9 @@ class StableBrowser {
|
|
|
791
797
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
792
798
|
}
|
|
793
799
|
catch (e) {
|
|
794
|
-
this
|
|
795
|
-
this.logger.debug(
|
|
800
|
+
// this call can fail it the browser is navigating
|
|
801
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
802
|
+
// this.logger.debug(e);
|
|
796
803
|
foundLocators = [];
|
|
797
804
|
try {
|
|
798
805
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -809,10 +816,29 @@ class StableBrowser {
|
|
|
809
816
|
});
|
|
810
817
|
result.locatorIndex = i;
|
|
811
818
|
}
|
|
819
|
+
if (foundLocators.length > 1) {
|
|
820
|
+
info.failCause.foundMultiple = true;
|
|
821
|
+
if (info.locatorLog) {
|
|
822
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
812
825
|
}
|
|
813
826
|
return result;
|
|
814
827
|
}
|
|
815
828
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
829
|
+
const state = {
|
|
830
|
+
locate: false,
|
|
831
|
+
scroll: false,
|
|
832
|
+
highlight: false,
|
|
833
|
+
_params,
|
|
834
|
+
options,
|
|
835
|
+
world,
|
|
836
|
+
type: Types.CLICK,
|
|
837
|
+
text: "Click element",
|
|
838
|
+
operation: "simpleClick",
|
|
839
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
840
|
+
};
|
|
841
|
+
_preCommand(state, this);
|
|
816
842
|
const startTime = Date.now();
|
|
817
843
|
let timeout = 30000;
|
|
818
844
|
if (options && options.timeout) {
|
|
@@ -836,13 +862,32 @@ class StableBrowser {
|
|
|
836
862
|
}
|
|
837
863
|
catch (e) {
|
|
838
864
|
if (performance.now() - startTime > timeout) {
|
|
839
|
-
throw e;
|
|
865
|
+
// throw e;
|
|
866
|
+
try {
|
|
867
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
868
|
+
}
|
|
869
|
+
finally {
|
|
870
|
+
_commandFinally(state, this);
|
|
871
|
+
}
|
|
840
872
|
}
|
|
841
873
|
}
|
|
842
874
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
843
875
|
}
|
|
844
876
|
}
|
|
845
877
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
878
|
+
const state = {
|
|
879
|
+
locate: false,
|
|
880
|
+
scroll: false,
|
|
881
|
+
highlight: false,
|
|
882
|
+
_params,
|
|
883
|
+
options,
|
|
884
|
+
world,
|
|
885
|
+
type: Types.FILL,
|
|
886
|
+
text: "Fill element",
|
|
887
|
+
operation: "simpleClickType",
|
|
888
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
889
|
+
};
|
|
890
|
+
_preCommand(state, this);
|
|
846
891
|
const startTime = Date.now();
|
|
847
892
|
let timeout = 30000;
|
|
848
893
|
if (options && options.timeout) {
|
|
@@ -866,7 +911,13 @@ class StableBrowser {
|
|
|
866
911
|
}
|
|
867
912
|
catch (e) {
|
|
868
913
|
if (performance.now() - startTime > timeout) {
|
|
869
|
-
throw e;
|
|
914
|
+
// throw e;
|
|
915
|
+
try {
|
|
916
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
917
|
+
}
|
|
918
|
+
finally {
|
|
919
|
+
_commandFinally(state, this);
|
|
920
|
+
}
|
|
870
921
|
}
|
|
871
922
|
}
|
|
872
923
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -885,18 +936,18 @@ class StableBrowser {
|
|
|
885
936
|
};
|
|
886
937
|
try {
|
|
887
938
|
await _preCommand(state, this);
|
|
888
|
-
if (state.options && state.options.context) {
|
|
889
|
-
|
|
890
|
-
}
|
|
939
|
+
// if (state.options && state.options.context) {
|
|
940
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
941
|
+
// }
|
|
891
942
|
try {
|
|
892
943
|
await state.element.click();
|
|
893
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
944
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
894
945
|
}
|
|
895
946
|
catch (e) {
|
|
896
947
|
// await this.closeUnexpectedPopups();
|
|
897
948
|
state.element = await this._locate(selectors, state.info, _params);
|
|
898
949
|
await state.element.dispatchEvent("click");
|
|
899
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
950
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
900
951
|
}
|
|
901
952
|
await this.waitForPageLoad();
|
|
902
953
|
return state.info;
|
|
@@ -975,7 +1026,6 @@ class StableBrowser {
|
|
|
975
1026
|
await state.element.hover({ timeout: 10000 });
|
|
976
1027
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
1028
|
}
|
|
978
|
-
await _screenshot(state, this);
|
|
979
1029
|
await this.waitForPageLoad();
|
|
980
1030
|
return state.info;
|
|
981
1031
|
}
|
|
@@ -1103,33 +1153,30 @@ class StableBrowser {
|
|
|
1103
1153
|
}
|
|
1104
1154
|
}
|
|
1105
1155
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1156
|
+
const state = {
|
|
1157
|
+
selectors,
|
|
1158
|
+
_params,
|
|
1159
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1160
|
+
options,
|
|
1161
|
+
world,
|
|
1162
|
+
type: Types.SET_DATE_TIME,
|
|
1163
|
+
text: `Set date time value: ${value}`,
|
|
1164
|
+
operation: "setDateTime",
|
|
1165
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1166
|
+
throwError: false,
|
|
1167
|
+
};
|
|
1116
1168
|
try {
|
|
1117
|
-
|
|
1118
|
-
let element = await this._locate(selectors, info, _params);
|
|
1119
|
-
//insert red border around the element
|
|
1120
|
-
await this.scrollIfNeeded(element, info);
|
|
1121
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1122
|
-
await this._highlightElements(element);
|
|
1169
|
+
await _preCommand(state, this);
|
|
1123
1170
|
try {
|
|
1124
|
-
await element.click();
|
|
1171
|
+
await state.element.click();
|
|
1125
1172
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1126
1173
|
if (format) {
|
|
1127
|
-
value = dayjs(value).format(format);
|
|
1128
|
-
await element.fill(value);
|
|
1174
|
+
state.value = dayjs(state.value).format(format);
|
|
1175
|
+
await state.element.fill(state.value);
|
|
1129
1176
|
}
|
|
1130
1177
|
else {
|
|
1131
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1132
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1178
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1179
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1133
1180
|
el.value = ""; // clear input
|
|
1134
1181
|
el.value = dateTimeValue;
|
|
1135
1182
|
}, dateTimeValue);
|
|
@@ -1142,20 +1189,19 @@ class StableBrowser {
|
|
|
1142
1189
|
}
|
|
1143
1190
|
catch (err) {
|
|
1144
1191
|
//await this.closeUnexpectedPopups();
|
|
1145
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1192
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1146
1193
|
this.logger.info("Trying again");
|
|
1147
|
-
|
|
1148
|
-
info.
|
|
1149
|
-
Object.assign(err, { info: info });
|
|
1194
|
+
await _screenshot(state, this);
|
|
1195
|
+
Object.assign(err, { info: state.info });
|
|
1150
1196
|
await element.click();
|
|
1151
1197
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1152
1198
|
if (format) {
|
|
1153
|
-
value = dayjs(value).format(format);
|
|
1154
|
-
await element.fill(value);
|
|
1199
|
+
state.value = dayjs(state.value).format(format);
|
|
1200
|
+
await state.element.fill(state.value);
|
|
1155
1201
|
}
|
|
1156
1202
|
else {
|
|
1157
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1158
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1203
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1204
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1159
1205
|
el.value = ""; // clear input
|
|
1160
1206
|
el.value = dateTimeValue;
|
|
1161
1207
|
}, dateTimeValue);
|
|
@@ -1168,31 +1214,10 @@ class StableBrowser {
|
|
|
1168
1214
|
}
|
|
1169
1215
|
}
|
|
1170
1216
|
catch (e) {
|
|
1171
|
-
|
|
1172
|
-
throw e;
|
|
1217
|
+
await _commandError(state, e, this);
|
|
1173
1218
|
}
|
|
1174
1219
|
finally {
|
|
1175
|
-
|
|
1176
|
-
this._reportToWorld(world, {
|
|
1177
|
-
element_name: selectors.element_name,
|
|
1178
|
-
type: Types.SET_DATE_TIME,
|
|
1179
|
-
screenshotId,
|
|
1180
|
-
value: value,
|
|
1181
|
-
text: `setDateTime input with value: ${value}`,
|
|
1182
|
-
result: error
|
|
1183
|
-
? {
|
|
1184
|
-
status: "FAILED",
|
|
1185
|
-
startTime,
|
|
1186
|
-
endTime,
|
|
1187
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1188
|
-
}
|
|
1189
|
-
: {
|
|
1190
|
-
status: "PASSED",
|
|
1191
|
-
startTime,
|
|
1192
|
-
endTime,
|
|
1193
|
-
},
|
|
1194
|
-
info: info,
|
|
1195
|
-
});
|
|
1220
|
+
_commandFinally(state, this);
|
|
1196
1221
|
}
|
|
1197
1222
|
}
|
|
1198
1223
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1329,6 +1354,7 @@ class StableBrowser {
|
|
|
1329
1354
|
let screenshotPath = null;
|
|
1330
1355
|
if (!info.log) {
|
|
1331
1356
|
info.log = "";
|
|
1357
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1332
1358
|
}
|
|
1333
1359
|
info.operation = "getText";
|
|
1334
1360
|
info.selectors = selectors;
|
|
@@ -1667,11 +1693,9 @@ class StableBrowser {
|
|
|
1667
1693
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1668
1694
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1669
1695
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1696
|
+
// to make sure the path doesn't start with -
|
|
1697
|
+
const uuidStr = "id_" + randomUUID();
|
|
1698
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1675
1699
|
try {
|
|
1676
1700
|
await this.takeScreenshot(screenshotPath);
|
|
1677
1701
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1685,7 +1709,7 @@ class StableBrowser {
|
|
|
1685
1709
|
catch (e) {
|
|
1686
1710
|
this.logger.info("unable to take screenshot, ignored");
|
|
1687
1711
|
}
|
|
1688
|
-
result.screenshotId =
|
|
1712
|
+
result.screenshotId = uuidStr;
|
|
1689
1713
|
result.screenshotPath = screenshotPath;
|
|
1690
1714
|
if (info && info.box) {
|
|
1691
1715
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1783,74 +1807,105 @@ class StableBrowser {
|
|
|
1783
1807
|
}
|
|
1784
1808
|
}
|
|
1785
1809
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1810
|
+
const state = {
|
|
1811
|
+
selectors,
|
|
1812
|
+
_params,
|
|
1813
|
+
attribute,
|
|
1814
|
+
variable,
|
|
1815
|
+
options,
|
|
1816
|
+
world,
|
|
1817
|
+
type: Types.EXTRACT,
|
|
1818
|
+
text: `Extract attribute from element`,
|
|
1819
|
+
operation: "extractAttribute",
|
|
1820
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1821
|
+
};
|
|
1791
1822
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1792
|
-
const info = {};
|
|
1793
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1794
|
-
info.operation = "extract";
|
|
1795
|
-
info.selectors = selectors;
|
|
1796
1823
|
try {
|
|
1797
|
-
|
|
1798
|
-
await this._highlightElements(element);
|
|
1799
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1824
|
+
await _preCommand(state, this);
|
|
1800
1825
|
switch (attribute) {
|
|
1801
1826
|
case "inner_text":
|
|
1802
|
-
|
|
1827
|
+
state.value = await state.element.innerText();
|
|
1803
1828
|
break;
|
|
1804
1829
|
case "href":
|
|
1805
|
-
|
|
1830
|
+
state.value = await state.element.getAttribute("href");
|
|
1831
|
+
break;
|
|
1832
|
+
case "value":
|
|
1833
|
+
state.value = await state.element.inputValue();
|
|
1834
|
+
break;
|
|
1835
|
+
default:
|
|
1836
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1839
|
+
state.info.value = state.value;
|
|
1840
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1841
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1842
|
+
return state.info;
|
|
1843
|
+
}
|
|
1844
|
+
catch (e) {
|
|
1845
|
+
await _commandError(state, e, this);
|
|
1846
|
+
}
|
|
1847
|
+
finally {
|
|
1848
|
+
_commandFinally(state, this);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1852
|
+
const state = {
|
|
1853
|
+
selectors,
|
|
1854
|
+
_params,
|
|
1855
|
+
attribute,
|
|
1856
|
+
value,
|
|
1857
|
+
options,
|
|
1858
|
+
world,
|
|
1859
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1860
|
+
text: `Verify element attribute`,
|
|
1861
|
+
operation: "verifyAttribute",
|
|
1862
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1863
|
+
};
|
|
1864
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1865
|
+
let val;
|
|
1866
|
+
try {
|
|
1867
|
+
await _preCommand(state, this);
|
|
1868
|
+
switch (attribute) {
|
|
1869
|
+
case "innerText":
|
|
1870
|
+
val = String(await state.element.innerText());
|
|
1806
1871
|
break;
|
|
1807
1872
|
case "value":
|
|
1808
|
-
|
|
1873
|
+
val = String(await state.element.inputValue());
|
|
1874
|
+
break;
|
|
1875
|
+
case "checked":
|
|
1876
|
+
val = String(await state.element.isChecked());
|
|
1877
|
+
break;
|
|
1878
|
+
case "disabled":
|
|
1879
|
+
val = String(await state.element.isDisabled());
|
|
1880
|
+
break;
|
|
1881
|
+
case "readOnly":
|
|
1882
|
+
const isEditable = await state.element.isEditable();
|
|
1883
|
+
val = String(!isEditable);
|
|
1809
1884
|
break;
|
|
1810
1885
|
default:
|
|
1811
|
-
|
|
1886
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1812
1887
|
break;
|
|
1813
1888
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1889
|
+
state.info.expectedValue = val;
|
|
1890
|
+
let regex;
|
|
1891
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1892
|
+
const patternBody = value.slice(1, -1);
|
|
1893
|
+
regex = new RegExp(patternBody, "g");
|
|
1817
1894
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1895
|
+
else {
|
|
1896
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1897
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1898
|
+
}
|
|
1899
|
+
if (!val.match(regex)) {
|
|
1900
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1901
|
+
}
|
|
1902
|
+
return state.info;
|
|
1821
1903
|
}
|
|
1822
1904
|
catch (e) {
|
|
1823
|
-
|
|
1824
|
-
this.logger.error("extract failed " + info.log);
|
|
1825
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1826
|
-
info.screenshotPath = screenshotPath;
|
|
1827
|
-
Object.assign(e, { info: info });
|
|
1828
|
-
error = e;
|
|
1829
|
-
throw e;
|
|
1905
|
+
await _commandError(state, e, this);
|
|
1830
1906
|
}
|
|
1831
1907
|
finally {
|
|
1832
|
-
|
|
1833
|
-
this._reportToWorld(world, {
|
|
1834
|
-
element_name: selectors.element_name,
|
|
1835
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1836
|
-
variable: variable,
|
|
1837
|
-
value: info.value,
|
|
1838
|
-
text: "Extract attribute from element",
|
|
1839
|
-
screenshotId,
|
|
1840
|
-
result: error
|
|
1841
|
-
? {
|
|
1842
|
-
status: "FAILED",
|
|
1843
|
-
startTime,
|
|
1844
|
-
endTime,
|
|
1845
|
-
message: error?.message,
|
|
1846
|
-
}
|
|
1847
|
-
: {
|
|
1848
|
-
status: "PASSED",
|
|
1849
|
-
startTime,
|
|
1850
|
-
endTime,
|
|
1851
|
-
},
|
|
1852
|
-
info: info,
|
|
1853
|
-
});
|
|
1908
|
+
_commandFinally(state, this);
|
|
1854
1909
|
}
|
|
1855
1910
|
}
|
|
1856
1911
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1871,7 +1926,7 @@ class StableBrowser {
|
|
|
1871
1926
|
if (options && options.timeout) {
|
|
1872
1927
|
timeout = options.timeout;
|
|
1873
1928
|
}
|
|
1874
|
-
const serviceUrl =
|
|
1929
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1875
1930
|
const request = {
|
|
1876
1931
|
method: "POST",
|
|
1877
1932
|
url: serviceUrl,
|
|
@@ -1927,7 +1982,8 @@ class StableBrowser {
|
|
|
1927
1982
|
catch (e) {
|
|
1928
1983
|
errorCount++;
|
|
1929
1984
|
if (errorCount > 3) {
|
|
1930
|
-
throw e;
|
|
1985
|
+
// throw e;
|
|
1986
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1931
1987
|
}
|
|
1932
1988
|
// ignore
|
|
1933
1989
|
}
|
|
@@ -2038,11 +2094,12 @@ class StableBrowser {
|
|
|
2038
2094
|
info.screenshotPath = screenshotPath;
|
|
2039
2095
|
Object.assign(e, { info: info });
|
|
2040
2096
|
error = e;
|
|
2041
|
-
throw e;
|
|
2097
|
+
// throw e;
|
|
2098
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2042
2099
|
}
|
|
2043
2100
|
finally {
|
|
2044
2101
|
const endTime = Date.now();
|
|
2045
|
-
|
|
2102
|
+
_reportToWorld(world, {
|
|
2046
2103
|
type: Types.VERIFY_PAGE_PATH,
|
|
2047
2104
|
text: "Verify page path",
|
|
2048
2105
|
screenshotId,
|
|
@@ -2062,54 +2119,65 @@ class StableBrowser {
|
|
|
2062
2119
|
});
|
|
2063
2120
|
}
|
|
2064
2121
|
}
|
|
2122
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2123
|
+
const frames = this.page.frames();
|
|
2124
|
+
let results = [];
|
|
2125
|
+
let ignoreCase = !(text.startsWith("/") && text.endsWith("/"));
|
|
2126
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2127
|
+
if (dateAlternatives.date) {
|
|
2128
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2129
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2130
|
+
result.frame = frames[i];
|
|
2131
|
+
results.push(result);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
else if (numberAlternatives.number) {
|
|
2135
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2136
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2137
|
+
result.frame = frames[i];
|
|
2138
|
+
results.push(result);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
else {
|
|
2142
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2143
|
+
result.frame = frames[i];
|
|
2144
|
+
results.push(result);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
state.info.results = results;
|
|
2148
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2149
|
+
return resultWithElementsFound;
|
|
2150
|
+
}
|
|
2065
2151
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2066
2152
|
text = unEscapeString(text);
|
|
2067
|
-
const
|
|
2153
|
+
const state = {
|
|
2154
|
+
text_search: text,
|
|
2155
|
+
options,
|
|
2156
|
+
world,
|
|
2157
|
+
locate: false,
|
|
2158
|
+
scroll: false,
|
|
2159
|
+
highlight: false,
|
|
2160
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2161
|
+
text: `Verify text exists in page`,
|
|
2162
|
+
operation: "verifyTextExistInPage",
|
|
2163
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2164
|
+
};
|
|
2068
2165
|
const timeout = this._getLoadTimeout(options);
|
|
2069
|
-
let error = null;
|
|
2070
|
-
let screenshotId = null;
|
|
2071
|
-
let screenshotPath = null;
|
|
2072
2166
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2073
|
-
const info = {};
|
|
2074
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2075
|
-
info.operation = "verifyTextExistInPage";
|
|
2076
2167
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2077
2168
|
if (newValue !== text) {
|
|
2078
2169
|
this.logger.info(text + "=" + newValue);
|
|
2079
2170
|
text = newValue;
|
|
2080
2171
|
}
|
|
2081
|
-
info.text = text;
|
|
2082
2172
|
let dateAlternatives = findDateAlternatives(text);
|
|
2083
2173
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2084
2174
|
try {
|
|
2175
|
+
await _preCommand(state, this);
|
|
2176
|
+
state.info.text = text;
|
|
2085
2177
|
while (true) {
|
|
2086
|
-
const
|
|
2087
|
-
let results = [];
|
|
2088
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2089
|
-
if (dateAlternatives.date) {
|
|
2090
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2092
|
-
result.frame = frames[i];
|
|
2093
|
-
results.push(result);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
else if (numberAlternatives.number) {
|
|
2097
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2098
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2099
|
-
result.frame = frames[i];
|
|
2100
|
-
results.push(result);
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
else {
|
|
2104
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2105
|
-
result.frame = frames[i];
|
|
2106
|
-
results.push(result);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
info.results = results;
|
|
2110
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2178
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2111
2179
|
if (resultWithElementsFound.length === 0) {
|
|
2112
|
-
if (Date.now() - startTime > timeout) {
|
|
2180
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2113
2181
|
throw new Error(`Text ${text} not found in page`);
|
|
2114
2182
|
}
|
|
2115
2183
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2119,57 +2187,165 @@ class StableBrowser {
|
|
|
2119
2187
|
const frame = resultWithElementsFound[0].frame;
|
|
2120
2188
|
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2121
2189
|
await this._highlightElements(frame, dataAttribute);
|
|
2122
|
-
const element = await frame
|
|
2190
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2123
2191
|
if (element) {
|
|
2124
|
-
await this.scrollIfNeeded(element, info);
|
|
2192
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2125
2193
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2126
2194
|
}
|
|
2127
2195
|
}
|
|
2128
|
-
|
|
2129
|
-
return info;
|
|
2196
|
+
await _screenshot(state, this);
|
|
2197
|
+
return state.info;
|
|
2130
2198
|
}
|
|
2131
2199
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2132
2200
|
}
|
|
2133
2201
|
catch (e) {
|
|
2134
|
-
|
|
2135
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2136
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2137
|
-
info.screenshotPath = screenshotPath;
|
|
2138
|
-
Object.assign(e, { info: info });
|
|
2139
|
-
error = e;
|
|
2140
|
-
throw e;
|
|
2202
|
+
await _commandError(state, e, this);
|
|
2141
2203
|
}
|
|
2142
2204
|
finally {
|
|
2143
|
-
|
|
2144
|
-
this._reportToWorld(world, {
|
|
2145
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2146
|
-
text: "Verify text exists in page",
|
|
2147
|
-
screenshotId,
|
|
2148
|
-
result: error
|
|
2149
|
-
? {
|
|
2150
|
-
status: "FAILED",
|
|
2151
|
-
startTime,
|
|
2152
|
-
endTime,
|
|
2153
|
-
message: error?.message,
|
|
2154
|
-
}
|
|
2155
|
-
: {
|
|
2156
|
-
status: "PASSED",
|
|
2157
|
-
startTime,
|
|
2158
|
-
endTime,
|
|
2159
|
-
},
|
|
2160
|
-
info: info,
|
|
2161
|
-
});
|
|
2205
|
+
_commandFinally(state, this);
|
|
2162
2206
|
}
|
|
2163
2207
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2208
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2209
|
+
text = unEscapeString(text);
|
|
2210
|
+
const state = {
|
|
2211
|
+
text_search: text,
|
|
2212
|
+
options,
|
|
2213
|
+
world,
|
|
2214
|
+
locate: false,
|
|
2215
|
+
scroll: false,
|
|
2216
|
+
highlight: false,
|
|
2217
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2218
|
+
text: `Verify text does not exist in page`,
|
|
2219
|
+
operation: "verifyTextNotExistInPage",
|
|
2220
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2221
|
+
};
|
|
2222
|
+
const timeout = this._getLoadTimeout(options);
|
|
2223
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2224
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2225
|
+
if (newValue !== text) {
|
|
2226
|
+
this.logger.info(text + "=" + newValue);
|
|
2227
|
+
text = newValue;
|
|
2228
|
+
}
|
|
2229
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2230
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2231
|
+
try {
|
|
2232
|
+
await _preCommand(state, this);
|
|
2233
|
+
state.info.text = text;
|
|
2234
|
+
while (true) {
|
|
2235
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2236
|
+
if (resultWithElementsFound.length === 0) {
|
|
2237
|
+
await _screenshot(state, this);
|
|
2238
|
+
return state.info;
|
|
2239
|
+
}
|
|
2240
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2241
|
+
throw new Error(`Text ${text} found in page`);
|
|
2242
|
+
}
|
|
2243
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2244
|
+
}
|
|
2168
2245
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2246
|
+
catch (e) {
|
|
2247
|
+
await _commandError(state, e, this);
|
|
2248
|
+
}
|
|
2249
|
+
finally {
|
|
2250
|
+
_commandFinally(state, this);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2254
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2255
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2256
|
+
const state = {
|
|
2257
|
+
text_search: textToVerify,
|
|
2258
|
+
options,
|
|
2259
|
+
world,
|
|
2260
|
+
locate: false,
|
|
2261
|
+
scroll: false,
|
|
2262
|
+
highlight: false,
|
|
2263
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2264
|
+
text: `Verify text with relation to another text`,
|
|
2265
|
+
operation: "verify_text_with_relation",
|
|
2266
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2267
|
+
};
|
|
2268
|
+
const timeout = this._getLoadTimeout(options);
|
|
2269
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2270
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2271
|
+
if (newValue !== textAnchor) {
|
|
2272
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2273
|
+
textAnchor = newValue;
|
|
2274
|
+
}
|
|
2275
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2276
|
+
if (newValue !== textToVerify) {
|
|
2277
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2278
|
+
textToVerify = newValue;
|
|
2279
|
+
}
|
|
2280
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2281
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2282
|
+
let foundAncore = false;
|
|
2283
|
+
try {
|
|
2284
|
+
await _preCommand(state, this);
|
|
2285
|
+
state.info.text = textToVerify;
|
|
2286
|
+
while (true) {
|
|
2287
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2288
|
+
if (resultWithElementsFound.length === 0) {
|
|
2289
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2290
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2291
|
+
}
|
|
2292
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2296
|
+
foundAncore = true;
|
|
2297
|
+
const result = resultWithElementsFound[i];
|
|
2298
|
+
const token = result.randomToken;
|
|
2299
|
+
const frame = result.frame;
|
|
2300
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2301
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2302
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2303
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2304
|
+
const element = elements[i];
|
|
2305
|
+
let climbParent = element;
|
|
2306
|
+
for (let j = 0; j < climb; j++) {
|
|
2307
|
+
climbParent = climbParent.parentElement;
|
|
2308
|
+
if (!climbParent) {
|
|
2309
|
+
break;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
if (!climbParent) {
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2316
|
+
if (foundElements.length > 0) {
|
|
2317
|
+
// set the container element attribute
|
|
2318
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2319
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2320
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2321
|
+
return { found: true };
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
return { found: false };
|
|
2325
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2326
|
+
if (findResult.found === true) {
|
|
2327
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2328
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2329
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2330
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2331
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2332
|
+
if (element) {
|
|
2333
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2334
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2335
|
+
}
|
|
2336
|
+
await _screenshot(state, this);
|
|
2337
|
+
return state.info;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2342
|
+
}
|
|
2343
|
+
catch (e) {
|
|
2344
|
+
await _commandError(state, e, this);
|
|
2345
|
+
}
|
|
2346
|
+
finally {
|
|
2347
|
+
_commandFinally(state, this);
|
|
2171
2348
|
}
|
|
2172
|
-
return serviceUrl;
|
|
2173
2349
|
}
|
|
2174
2350
|
async visualVerification(text, options = {}, world = null) {
|
|
2175
2351
|
const startTime = Date.now();
|
|
@@ -2185,7 +2361,7 @@ class StableBrowser {
|
|
|
2185
2361
|
throw new Error("TOKEN is not set");
|
|
2186
2362
|
}
|
|
2187
2363
|
try {
|
|
2188
|
-
let serviceUrl =
|
|
2364
|
+
let serviceUrl = _getServerUrl();
|
|
2189
2365
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2190
2366
|
info.screenshotPath = screenshotPath;
|
|
2191
2367
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2221,11 +2397,12 @@ class StableBrowser {
|
|
|
2221
2397
|
info.screenshotPath = screenshotPath;
|
|
2222
2398
|
Object.assign(e, { info: info });
|
|
2223
2399
|
error = e;
|
|
2224
|
-
throw e;
|
|
2400
|
+
// throw e;
|
|
2401
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2225
2402
|
}
|
|
2226
2403
|
finally {
|
|
2227
2404
|
const endTime = Date.now();
|
|
2228
|
-
|
|
2405
|
+
_reportToWorld(world, {
|
|
2229
2406
|
type: Types.VERIFY_VISUAL,
|
|
2230
2407
|
text: "Visual verification",
|
|
2231
2408
|
screenshotId,
|
|
@@ -2273,6 +2450,7 @@ class StableBrowser {
|
|
|
2273
2450
|
let screenshotPath = null;
|
|
2274
2451
|
const info = {};
|
|
2275
2452
|
info.log = "";
|
|
2453
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2276
2454
|
info.operation = "getTableData";
|
|
2277
2455
|
info.selectors = selectors;
|
|
2278
2456
|
try {
|
|
@@ -2288,11 +2466,12 @@ class StableBrowser {
|
|
|
2288
2466
|
info.screenshotPath = screenshotPath;
|
|
2289
2467
|
Object.assign(e, { info: info });
|
|
2290
2468
|
error = e;
|
|
2291
|
-
throw e;
|
|
2469
|
+
// throw e;
|
|
2470
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2292
2471
|
}
|
|
2293
2472
|
finally {
|
|
2294
2473
|
const endTime = Date.now();
|
|
2295
|
-
|
|
2474
|
+
_reportToWorld(world, {
|
|
2296
2475
|
element_name: selectors.element_name,
|
|
2297
2476
|
type: Types.GET_TABLE_DATA,
|
|
2298
2477
|
text: "Get table data",
|
|
@@ -2347,7 +2526,7 @@ class StableBrowser {
|
|
|
2347
2526
|
info.operation = "analyzeTable";
|
|
2348
2527
|
info.selectors = selectors;
|
|
2349
2528
|
info.query = query;
|
|
2350
|
-
query =
|
|
2529
|
+
query = _fixUsingParams(query, _params);
|
|
2351
2530
|
info.query_fixed = query;
|
|
2352
2531
|
info.operator = operator;
|
|
2353
2532
|
info.value = value;
|
|
@@ -2453,11 +2632,12 @@ class StableBrowser {
|
|
|
2453
2632
|
info.screenshotPath = screenshotPath;
|
|
2454
2633
|
Object.assign(e, { info: info });
|
|
2455
2634
|
error = e;
|
|
2456
|
-
throw e;
|
|
2635
|
+
// throw e;
|
|
2636
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2457
2637
|
}
|
|
2458
2638
|
finally {
|
|
2459
2639
|
const endTime = Date.now();
|
|
2460
|
-
|
|
2640
|
+
_reportToWorld(world, {
|
|
2461
2641
|
element_name: selectors.element_name,
|
|
2462
2642
|
type: Types.ANALYZE_TABLE,
|
|
2463
2643
|
text: "Analyze table",
|
|
@@ -2530,7 +2710,7 @@ class StableBrowser {
|
|
|
2530
2710
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2531
2711
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2532
2712
|
const endTime = Date.now();
|
|
2533
|
-
|
|
2713
|
+
_reportToWorld(world, {
|
|
2534
2714
|
type: Types.GET_PAGE_STATUS,
|
|
2535
2715
|
text: "Wait for page load",
|
|
2536
2716
|
screenshotId,
|
|
@@ -2550,41 +2730,35 @@ class StableBrowser {
|
|
|
2550
2730
|
}
|
|
2551
2731
|
}
|
|
2552
2732
|
async closePage(options = {}, world = null) {
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2733
|
+
const state = {
|
|
2734
|
+
options,
|
|
2735
|
+
world,
|
|
2736
|
+
locate: false,
|
|
2737
|
+
scroll: false,
|
|
2738
|
+
highlight: false,
|
|
2739
|
+
type: Types.CLOSE_PAGE,
|
|
2740
|
+
text: `Close page`,
|
|
2741
|
+
operation: "closePage",
|
|
2742
|
+
log: "***** close page *****\n",
|
|
2743
|
+
throwError: false,
|
|
2744
|
+
};
|
|
2558
2745
|
try {
|
|
2746
|
+
await _preCommand(state, this);
|
|
2559
2747
|
await this.page.close();
|
|
2560
2748
|
}
|
|
2561
2749
|
catch (e) {
|
|
2562
2750
|
console.log(".");
|
|
2751
|
+
await _commandError(state, e, this);
|
|
2563
2752
|
}
|
|
2564
2753
|
finally {
|
|
2565
|
-
|
|
2566
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2567
|
-
const endTime = Date.now();
|
|
2568
|
-
this._reportToWorld(world, {
|
|
2569
|
-
type: Types.CLOSE_PAGE,
|
|
2570
|
-
text: "close page",
|
|
2571
|
-
screenshotId,
|
|
2572
|
-
result: error
|
|
2573
|
-
? {
|
|
2574
|
-
status: "FAILED",
|
|
2575
|
-
startTime,
|
|
2576
|
-
endTime,
|
|
2577
|
-
message: error?.message,
|
|
2578
|
-
}
|
|
2579
|
-
: {
|
|
2580
|
-
status: "PASSED",
|
|
2581
|
-
startTime,
|
|
2582
|
-
endTime,
|
|
2583
|
-
},
|
|
2584
|
-
info: info,
|
|
2585
|
-
});
|
|
2754
|
+
_commandFinally(state, this);
|
|
2586
2755
|
}
|
|
2587
2756
|
}
|
|
2757
|
+
saveTestDataAsGlobal(options, world) {
|
|
2758
|
+
const dataFile = this._getDataFile(world);
|
|
2759
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2760
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2761
|
+
}
|
|
2588
2762
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2589
2763
|
const startTime = Date.now();
|
|
2590
2764
|
let error = null;
|
|
@@ -2602,12 +2776,13 @@ class StableBrowser {
|
|
|
2602
2776
|
}
|
|
2603
2777
|
catch (e) {
|
|
2604
2778
|
console.log(".");
|
|
2779
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2605
2780
|
}
|
|
2606
2781
|
finally {
|
|
2607
2782
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2608
2783
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2609
2784
|
const endTime = Date.now();
|
|
2610
|
-
|
|
2785
|
+
_reportToWorld(world, {
|
|
2611
2786
|
type: Types.SET_VIEWPORT,
|
|
2612
2787
|
text: "set viewport size to " + width + "x" + hight,
|
|
2613
2788
|
screenshotId,
|
|
@@ -2638,12 +2813,13 @@ class StableBrowser {
|
|
|
2638
2813
|
}
|
|
2639
2814
|
catch (e) {
|
|
2640
2815
|
console.log(".");
|
|
2816
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2641
2817
|
}
|
|
2642
2818
|
finally {
|
|
2643
2819
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2644
2820
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2645
2821
|
const endTime = Date.now();
|
|
2646
|
-
|
|
2822
|
+
_reportToWorld(world, {
|
|
2647
2823
|
type: Types.GET_PAGE_STATUS,
|
|
2648
2824
|
text: "page relaod",
|
|
2649
2825
|
screenshotId,
|
|
@@ -2679,11 +2855,45 @@ class StableBrowser {
|
|
|
2679
2855
|
console.log("#-#");
|
|
2680
2856
|
}
|
|
2681
2857
|
}
|
|
2682
|
-
|
|
2683
|
-
if (
|
|
2684
|
-
|
|
2858
|
+
async beforeStep(world, step) {
|
|
2859
|
+
if (this.stepIndex === undefined) {
|
|
2860
|
+
this.stepIndex = 0;
|
|
2861
|
+
}
|
|
2862
|
+
else {
|
|
2863
|
+
this.stepIndex++;
|
|
2864
|
+
}
|
|
2865
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2866
|
+
this.stepName = step.pickleStep.text;
|
|
2867
|
+
this.logger.info("step: " + this.stepName);
|
|
2868
|
+
}
|
|
2869
|
+
else if (step && step.text) {
|
|
2870
|
+
this.stepName = step.text;
|
|
2871
|
+
}
|
|
2872
|
+
else {
|
|
2873
|
+
this.stepName = "step " + this.stepIndex;
|
|
2874
|
+
}
|
|
2875
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2876
|
+
if (this.context.browserObject.context) {
|
|
2877
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2881
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2882
|
+
// check if @global_test_data tag is present
|
|
2883
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2884
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
async afterStep(world, step) {
|
|
2889
|
+
this.stepName = null;
|
|
2890
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2891
|
+
if (this.context.browserObject.context) {
|
|
2892
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2893
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
2685
2896
|
}
|
|
2686
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2687
2897
|
}
|
|
2688
2898
|
}
|
|
2689
2899
|
function createTimedPromise(promise, label) {
|
|
@@ -2691,156 +2901,5 @@ function createTimedPromise(promise, label) {
|
|
|
2691
2901
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2692
2902
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2693
2903
|
}
|
|
2694
|
-
const KEYBOARD_EVENTS = [
|
|
2695
|
-
"ALT",
|
|
2696
|
-
"AltGraph",
|
|
2697
|
-
"CapsLock",
|
|
2698
|
-
"Control",
|
|
2699
|
-
"Fn",
|
|
2700
|
-
"FnLock",
|
|
2701
|
-
"Hyper",
|
|
2702
|
-
"Meta",
|
|
2703
|
-
"NumLock",
|
|
2704
|
-
"ScrollLock",
|
|
2705
|
-
"Shift",
|
|
2706
|
-
"Super",
|
|
2707
|
-
"Symbol",
|
|
2708
|
-
"SymbolLock",
|
|
2709
|
-
"Enter",
|
|
2710
|
-
"Tab",
|
|
2711
|
-
"ArrowDown",
|
|
2712
|
-
"ArrowLeft",
|
|
2713
|
-
"ArrowRight",
|
|
2714
|
-
"ArrowUp",
|
|
2715
|
-
"End",
|
|
2716
|
-
"Home",
|
|
2717
|
-
"PageDown",
|
|
2718
|
-
"PageUp",
|
|
2719
|
-
"Backspace",
|
|
2720
|
-
"Clear",
|
|
2721
|
-
"Copy",
|
|
2722
|
-
"CrSel",
|
|
2723
|
-
"Cut",
|
|
2724
|
-
"Delete",
|
|
2725
|
-
"EraseEof",
|
|
2726
|
-
"ExSel",
|
|
2727
|
-
"Insert",
|
|
2728
|
-
"Paste",
|
|
2729
|
-
"Redo",
|
|
2730
|
-
"Undo",
|
|
2731
|
-
"Accept",
|
|
2732
|
-
"Again",
|
|
2733
|
-
"Attn",
|
|
2734
|
-
"Cancel",
|
|
2735
|
-
"ContextMenu",
|
|
2736
|
-
"Escape",
|
|
2737
|
-
"Execute",
|
|
2738
|
-
"Find",
|
|
2739
|
-
"Finish",
|
|
2740
|
-
"Help",
|
|
2741
|
-
"Pause",
|
|
2742
|
-
"Play",
|
|
2743
|
-
"Props",
|
|
2744
|
-
"Select",
|
|
2745
|
-
"ZoomIn",
|
|
2746
|
-
"ZoomOut",
|
|
2747
|
-
"BrightnessDown",
|
|
2748
|
-
"BrightnessUp",
|
|
2749
|
-
"Eject",
|
|
2750
|
-
"LogOff",
|
|
2751
|
-
"Power",
|
|
2752
|
-
"PowerOff",
|
|
2753
|
-
"PrintScreen",
|
|
2754
|
-
"Hibernate",
|
|
2755
|
-
"Standby",
|
|
2756
|
-
"WakeUp",
|
|
2757
|
-
"AllCandidates",
|
|
2758
|
-
"Alphanumeric",
|
|
2759
|
-
"CodeInput",
|
|
2760
|
-
"Compose",
|
|
2761
|
-
"Convert",
|
|
2762
|
-
"Dead",
|
|
2763
|
-
"FinalMode",
|
|
2764
|
-
"GroupFirst",
|
|
2765
|
-
"GroupLast",
|
|
2766
|
-
"GroupNext",
|
|
2767
|
-
"GroupPrevious",
|
|
2768
|
-
"ModeChange",
|
|
2769
|
-
"NextCandidate",
|
|
2770
|
-
"NonConvert",
|
|
2771
|
-
"PreviousCandidate",
|
|
2772
|
-
"Process",
|
|
2773
|
-
"SingleCandidate",
|
|
2774
|
-
"HangulMode",
|
|
2775
|
-
"HanjaMode",
|
|
2776
|
-
"JunjaMode",
|
|
2777
|
-
"Eisu",
|
|
2778
|
-
"Hankaku",
|
|
2779
|
-
"Hiragana",
|
|
2780
|
-
"HiraganaKatakana",
|
|
2781
|
-
"KanaMode",
|
|
2782
|
-
"KanjiMode",
|
|
2783
|
-
"Katakana",
|
|
2784
|
-
"Romaji",
|
|
2785
|
-
"Zenkaku",
|
|
2786
|
-
"ZenkakuHanaku",
|
|
2787
|
-
"F1",
|
|
2788
|
-
"F2",
|
|
2789
|
-
"F3",
|
|
2790
|
-
"F4",
|
|
2791
|
-
"F5",
|
|
2792
|
-
"F6",
|
|
2793
|
-
"F7",
|
|
2794
|
-
"F8",
|
|
2795
|
-
"F9",
|
|
2796
|
-
"F10",
|
|
2797
|
-
"F11",
|
|
2798
|
-
"F12",
|
|
2799
|
-
"Soft1",
|
|
2800
|
-
"Soft2",
|
|
2801
|
-
"Soft3",
|
|
2802
|
-
"Soft4",
|
|
2803
|
-
"ChannelDown",
|
|
2804
|
-
"ChannelUp",
|
|
2805
|
-
"Close",
|
|
2806
|
-
"MailForward",
|
|
2807
|
-
"MailReply",
|
|
2808
|
-
"MailSend",
|
|
2809
|
-
"MediaFastForward",
|
|
2810
|
-
"MediaPause",
|
|
2811
|
-
"MediaPlay",
|
|
2812
|
-
"MediaPlayPause",
|
|
2813
|
-
"MediaRecord",
|
|
2814
|
-
"MediaRewind",
|
|
2815
|
-
"MediaStop",
|
|
2816
|
-
"MediaTrackNext",
|
|
2817
|
-
"MediaTrackPrevious",
|
|
2818
|
-
"AudioBalanceLeft",
|
|
2819
|
-
"AudioBalanceRight",
|
|
2820
|
-
"AudioBassBoostDown",
|
|
2821
|
-
"AudioBassBoostToggle",
|
|
2822
|
-
"AudioBassBoostUp",
|
|
2823
|
-
"AudioFaderFront",
|
|
2824
|
-
"AudioFaderRear",
|
|
2825
|
-
"AudioSurroundModeNext",
|
|
2826
|
-
"AudioTrebleDown",
|
|
2827
|
-
"AudioTrebleUp",
|
|
2828
|
-
"AudioVolumeDown",
|
|
2829
|
-
"AudioVolumeMute",
|
|
2830
|
-
"AudioVolumeUp",
|
|
2831
|
-
"MicrophoneToggle",
|
|
2832
|
-
"MicrophoneVolumeDown",
|
|
2833
|
-
"MicrophoneVolumeMute",
|
|
2834
|
-
"MicrophoneVolumeUp",
|
|
2835
|
-
"TV",
|
|
2836
|
-
"TV3DMode",
|
|
2837
|
-
"TVAntennaCable",
|
|
2838
|
-
"TVAudioDescription",
|
|
2839
|
-
];
|
|
2840
|
-
function unEscapeString(str) {
|
|
2841
|
-
const placeholder = "__NEWLINE__";
|
|
2842
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2843
|
-
return str;
|
|
2844
|
-
}
|
|
2845
2904
|
export { StableBrowser };
|
|
2846
2905
|
//# sourceMappingURL=stable_browser.js.map
|