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