automation_model 1.0.499-dev → 1.0.499-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 +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 +50 -18
- package/lib/stable_browser.js +637 -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
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, _params);
|
|
345
329
|
if (result.elementCount === 0) {
|
|
346
330
|
return;
|
|
347
331
|
}
|
|
@@ -352,121 +336,51 @@ 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
345
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
358
346
|
//const stringifyText = JSON.stringify(text);
|
|
359
347
|
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (currentNode === parent) {
|
|
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 = "*";
|
|
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 = [];
|
|
348
|
+
const options = {
|
|
349
|
+
innerText: true,
|
|
350
|
+
};
|
|
416
351
|
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
|
-
}
|
|
352
|
+
options.singleRegex = true;
|
|
427
353
|
}
|
|
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
|
-
}
|
|
354
|
+
if (tag) {
|
|
355
|
+
options.tag = tag;
|
|
445
356
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (isParent(element, foundElements[j])) {
|
|
455
|
-
hasChild = true;
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
357
|
+
if (!(partial === true)) {
|
|
358
|
+
options.exactMatch = true;
|
|
359
|
+
}
|
|
360
|
+
if (text.startsWith("/") && text.endsWith("/")) {
|
|
361
|
+
if (text.length < 3) {
|
|
362
|
+
throw new Error("Invalid regex pattern: empty pattern");
|
|
458
363
|
}
|
|
459
|
-
|
|
460
|
-
|
|
364
|
+
try {
|
|
365
|
+
const pattern = text.slice(1, -1);
|
|
366
|
+
new RegExp(pattern); // Validate pattern
|
|
367
|
+
text = pattern;
|
|
368
|
+
options.singleRegex = true;
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
461
372
|
}
|
|
462
373
|
}
|
|
374
|
+
const elements = window.findMatchingElements(text, options);
|
|
375
|
+
let randomToken = null;
|
|
376
|
+
const foundElements = [];
|
|
463
377
|
let elementCount = 0;
|
|
464
|
-
if (
|
|
465
|
-
for (let i = 0; i <
|
|
378
|
+
if (elements.length > 0) {
|
|
379
|
+
for (let i = 0; i < elements.length; i++) {
|
|
466
380
|
if (randomToken === null) {
|
|
467
381
|
randomToken = Math.random().toString(36).substring(7);
|
|
468
382
|
}
|
|
469
|
-
let element =
|
|
383
|
+
let element = elements[i];
|
|
470
384
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
471
385
|
elementCount++;
|
|
472
386
|
}
|
|
@@ -475,9 +389,21 @@ class StableBrowser {
|
|
|
475
389
|
}, [text1, tag1, regex1, partial1]);
|
|
476
390
|
}
|
|
477
391
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
392
|
+
if (!info) {
|
|
393
|
+
info = {};
|
|
394
|
+
}
|
|
395
|
+
if (!info.failCause) {
|
|
396
|
+
info.failCause = {};
|
|
397
|
+
}
|
|
398
|
+
if (!info.log) {
|
|
399
|
+
info.log = "";
|
|
400
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
401
|
+
}
|
|
478
402
|
let locatorSearch = selectorHierarchy[index];
|
|
403
|
+
let originalLocatorSearch = "";
|
|
479
404
|
try {
|
|
480
|
-
|
|
405
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
406
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
481
407
|
}
|
|
482
408
|
catch (e) {
|
|
483
409
|
console.error(e);
|
|
@@ -487,36 +413,50 @@ class StableBrowser {
|
|
|
487
413
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
488
414
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
489
415
|
if (!locatorString) {
|
|
416
|
+
info.failCause.textNotFound = true;
|
|
417
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
490
418
|
return;
|
|
491
419
|
}
|
|
492
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
420
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
493
421
|
}
|
|
494
422
|
else if (locatorSearch.text) {
|
|
495
|
-
let
|
|
423
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
424
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
496
425
|
if (result.elementCount === 0) {
|
|
426
|
+
info.failCause.textNotFound = true;
|
|
427
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
497
428
|
return;
|
|
498
429
|
}
|
|
499
430
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
500
431
|
if (locatorSearch.childCss) {
|
|
501
432
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
502
433
|
}
|
|
503
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
434
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
504
435
|
}
|
|
505
436
|
else {
|
|
506
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
437
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
507
438
|
}
|
|
508
439
|
// let cssHref = false;
|
|
509
440
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
510
441
|
// cssHref = true;
|
|
511
442
|
// }
|
|
512
443
|
let count = await locator.count();
|
|
444
|
+
if (count > 0 && !info.failCause.count) {
|
|
445
|
+
info.failCause.count = count;
|
|
446
|
+
}
|
|
513
447
|
//info.log += "total elements found " + count + "\n";
|
|
514
448
|
//let visibleCount = 0;
|
|
515
449
|
let visibleLocator = null;
|
|
516
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
450
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
517
451
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
452
|
+
if (info.locatorLog) {
|
|
453
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
454
|
+
}
|
|
518
455
|
return;
|
|
519
456
|
}
|
|
457
|
+
if (info.locatorLog && count === 0) {
|
|
458
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
459
|
+
}
|
|
520
460
|
for (let j = 0; j < count; j++) {
|
|
521
461
|
let visible = await locator.nth(j).isVisible();
|
|
522
462
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -525,24 +465,40 @@ class StableBrowser {
|
|
|
525
465
|
}
|
|
526
466
|
if (visible && enabled) {
|
|
527
467
|
foundLocators.push(locator.nth(j));
|
|
468
|
+
if (info.locatorLog) {
|
|
469
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
470
|
+
}
|
|
528
471
|
}
|
|
529
472
|
else {
|
|
473
|
+
info.failCause.visible = visible;
|
|
474
|
+
info.failCause.enabled = enabled;
|
|
530
475
|
if (!info.printMessages) {
|
|
531
476
|
info.printMessages = {};
|
|
532
477
|
}
|
|
478
|
+
if (info.locatorLog && !visible) {
|
|
479
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
480
|
+
}
|
|
481
|
+
if (info.locatorLog && !enabled) {
|
|
482
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
483
|
+
}
|
|
533
484
|
if (!info.printMessages[j.toString()]) {
|
|
534
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
485
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
535
486
|
info.printMessages[j.toString()] = true;
|
|
536
487
|
}
|
|
537
488
|
}
|
|
538
489
|
}
|
|
539
490
|
}
|
|
540
491
|
async closeUnexpectedPopups(info, _params) {
|
|
492
|
+
if (!info) {
|
|
493
|
+
info = {};
|
|
494
|
+
info.failCause = {};
|
|
495
|
+
info.log = "";
|
|
496
|
+
}
|
|
541
497
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
542
498
|
if (!info) {
|
|
543
499
|
info = {};
|
|
544
500
|
}
|
|
545
|
-
info.log += "scan for popup handlers" + "\n";
|
|
501
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
546
502
|
const handlerGroup = [];
|
|
547
503
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
548
504
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -569,9 +525,21 @@ class StableBrowser {
|
|
|
569
525
|
}
|
|
570
526
|
if (result.foundElements.length > 0) {
|
|
571
527
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
528
|
+
try {
|
|
529
|
+
await scope?.evaluate(() => {
|
|
530
|
+
window.__isClosingPopups = true;
|
|
531
|
+
});
|
|
532
|
+
await dialogCloseLocator.click();
|
|
533
|
+
// wait for the dialog to close
|
|
534
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
535
|
+
}
|
|
536
|
+
catch (e) {
|
|
537
|
+
}
|
|
538
|
+
finally {
|
|
539
|
+
await scope?.evaluate(() => {
|
|
540
|
+
window.__isClosingPopups = false;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
575
543
|
return { rerun: true };
|
|
576
544
|
}
|
|
577
545
|
}
|
|
@@ -595,7 +563,13 @@ class StableBrowser {
|
|
|
595
563
|
}
|
|
596
564
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
597
565
|
}
|
|
598
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
566
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
567
|
+
if (!info) {
|
|
568
|
+
info = {};
|
|
569
|
+
info.failCause = {};
|
|
570
|
+
info.log = "";
|
|
571
|
+
}
|
|
572
|
+
let startTime = Date.now();
|
|
599
573
|
let scope = this.page;
|
|
600
574
|
if (selectors.frame) {
|
|
601
575
|
return selectors.frame;
|
|
@@ -626,9 +600,11 @@ class StableBrowser {
|
|
|
626
600
|
}
|
|
627
601
|
return framescope;
|
|
628
602
|
};
|
|
603
|
+
let fLocator = null;
|
|
629
604
|
while (true) {
|
|
630
605
|
let frameFound = false;
|
|
631
606
|
if (selectors.nestFrmLoc) {
|
|
607
|
+
fLocator = selectors.nestFrmLoc;
|
|
632
608
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
633
609
|
frameFound = true;
|
|
634
610
|
break;
|
|
@@ -637,6 +613,7 @@ class StableBrowser {
|
|
|
637
613
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
638
614
|
let frameLocator = selectors.frameLocators[i];
|
|
639
615
|
if (frameLocator.css) {
|
|
616
|
+
fLocator = frameLocator.css;
|
|
640
617
|
scope = scope.frameLocator(frameLocator.css);
|
|
641
618
|
frameFound = true;
|
|
642
619
|
break;
|
|
@@ -644,16 +621,25 @@ class StableBrowser {
|
|
|
644
621
|
}
|
|
645
622
|
}
|
|
646
623
|
if (!frameFound && selectors.iframe_src) {
|
|
624
|
+
fLocator = selectors.iframe_src;
|
|
647
625
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
648
626
|
}
|
|
649
627
|
if (!scope) {
|
|
650
|
-
info
|
|
651
|
-
|
|
628
|
+
if (info && info.locatorLog) {
|
|
629
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
630
|
+
}
|
|
631
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
632
|
+
if (Date.now() - startTime > timeout) {
|
|
633
|
+
info.failCause.iframeNotFound = true;
|
|
634
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
652
635
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
653
636
|
}
|
|
654
637
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
655
638
|
}
|
|
656
639
|
else {
|
|
640
|
+
if (info && info.locatorLog) {
|
|
641
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
642
|
+
}
|
|
657
643
|
break;
|
|
658
644
|
}
|
|
659
645
|
}
|
|
@@ -663,20 +649,27 @@ class StableBrowser {
|
|
|
663
649
|
}
|
|
664
650
|
return scope;
|
|
665
651
|
}
|
|
666
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
667
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
652
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
653
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
668
654
|
return scope.evaluate(() => {
|
|
669
655
|
var bodyContent = document.body.innerHTML;
|
|
670
656
|
return bodyContent;
|
|
671
657
|
});
|
|
672
658
|
}
|
|
673
659
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
660
|
+
if (!info) {
|
|
661
|
+
info = {};
|
|
662
|
+
info.failCause = {};
|
|
663
|
+
info.log = "";
|
|
664
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
665
|
+
}
|
|
674
666
|
let highPriorityTimeout = 5000;
|
|
675
667
|
let visibleOnlyTimeout = 6000;
|
|
676
|
-
let startTime =
|
|
668
|
+
let startTime = Date.now();
|
|
677
669
|
let locatorsCount = 0;
|
|
670
|
+
let lazy_scroll = false;
|
|
678
671
|
//let arrayMode = Array.isArray(selectors);
|
|
679
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
672
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
680
673
|
let selectorsLocators = null;
|
|
681
674
|
selectorsLocators = selectors.locators;
|
|
682
675
|
// group selectors by priority
|
|
@@ -763,21 +756,33 @@ class StableBrowser {
|
|
|
763
756
|
return maxCountElement.locator;
|
|
764
757
|
}
|
|
765
758
|
}
|
|
766
|
-
if (
|
|
759
|
+
if (Date.now() - startTime > timeout) {
|
|
767
760
|
break;
|
|
768
761
|
}
|
|
769
|
-
if (
|
|
762
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
770
763
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
771
764
|
highPriorityOnly = false;
|
|
765
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
766
|
+
lazy_scroll = true;
|
|
767
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
768
|
+
}
|
|
772
769
|
}
|
|
773
|
-
if (
|
|
770
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
774
771
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
775
772
|
visibleOnly = false;
|
|
776
773
|
}
|
|
777
774
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
778
775
|
}
|
|
779
776
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
780
|
-
info.
|
|
777
|
+
// if (info.locatorLog) {
|
|
778
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
779
|
+
// for (let line of lines) {
|
|
780
|
+
// this.logger.debug(line);
|
|
781
|
+
// }
|
|
782
|
+
// }
|
|
783
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
784
|
+
info.failCause.locatorNotFound = true;
|
|
785
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
781
786
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
782
787
|
}
|
|
783
788
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -791,8 +796,9 @@ class StableBrowser {
|
|
|
791
796
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
792
797
|
}
|
|
793
798
|
catch (e) {
|
|
794
|
-
this
|
|
795
|
-
this.logger.debug(
|
|
799
|
+
// this call can fail it the browser is navigating
|
|
800
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
801
|
+
// this.logger.debug(e);
|
|
796
802
|
foundLocators = [];
|
|
797
803
|
try {
|
|
798
804
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -809,10 +815,29 @@ class StableBrowser {
|
|
|
809
815
|
});
|
|
810
816
|
result.locatorIndex = i;
|
|
811
817
|
}
|
|
818
|
+
if (foundLocators.length > 1) {
|
|
819
|
+
info.failCause.foundMultiple = true;
|
|
820
|
+
if (info.locatorLog) {
|
|
821
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
822
|
+
}
|
|
823
|
+
}
|
|
812
824
|
}
|
|
813
825
|
return result;
|
|
814
826
|
}
|
|
815
827
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
828
|
+
const state = {
|
|
829
|
+
locate: false,
|
|
830
|
+
scroll: false,
|
|
831
|
+
highlight: false,
|
|
832
|
+
_params,
|
|
833
|
+
options,
|
|
834
|
+
world,
|
|
835
|
+
type: Types.CLICK,
|
|
836
|
+
text: "Click element",
|
|
837
|
+
operation: "simpleClick",
|
|
838
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
839
|
+
};
|
|
840
|
+
_preCommand(state, this);
|
|
816
841
|
const startTime = Date.now();
|
|
817
842
|
let timeout = 30000;
|
|
818
843
|
if (options && options.timeout) {
|
|
@@ -836,13 +861,32 @@ class StableBrowser {
|
|
|
836
861
|
}
|
|
837
862
|
catch (e) {
|
|
838
863
|
if (performance.now() - startTime > timeout) {
|
|
839
|
-
throw e;
|
|
864
|
+
// throw e;
|
|
865
|
+
try {
|
|
866
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
867
|
+
}
|
|
868
|
+
finally {
|
|
869
|
+
_commandFinally(state, this);
|
|
870
|
+
}
|
|
840
871
|
}
|
|
841
872
|
}
|
|
842
873
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
843
874
|
}
|
|
844
875
|
}
|
|
845
876
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
877
|
+
const state = {
|
|
878
|
+
locate: false,
|
|
879
|
+
scroll: false,
|
|
880
|
+
highlight: false,
|
|
881
|
+
_params,
|
|
882
|
+
options,
|
|
883
|
+
world,
|
|
884
|
+
type: Types.FILL,
|
|
885
|
+
text: "Fill element",
|
|
886
|
+
operation: "simpleClickType",
|
|
887
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
888
|
+
};
|
|
889
|
+
_preCommand(state, this);
|
|
846
890
|
const startTime = Date.now();
|
|
847
891
|
let timeout = 30000;
|
|
848
892
|
if (options && options.timeout) {
|
|
@@ -866,7 +910,13 @@ class StableBrowser {
|
|
|
866
910
|
}
|
|
867
911
|
catch (e) {
|
|
868
912
|
if (performance.now() - startTime > timeout) {
|
|
869
|
-
throw e;
|
|
913
|
+
// throw e;
|
|
914
|
+
try {
|
|
915
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
916
|
+
}
|
|
917
|
+
finally {
|
|
918
|
+
_commandFinally(state, this);
|
|
919
|
+
}
|
|
870
920
|
}
|
|
871
921
|
}
|
|
872
922
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -885,18 +935,18 @@ class StableBrowser {
|
|
|
885
935
|
};
|
|
886
936
|
try {
|
|
887
937
|
await _preCommand(state, this);
|
|
888
|
-
if (state.options && state.options.context) {
|
|
889
|
-
|
|
890
|
-
}
|
|
938
|
+
// if (state.options && state.options.context) {
|
|
939
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
940
|
+
// }
|
|
891
941
|
try {
|
|
892
942
|
await state.element.click();
|
|
893
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
943
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
894
944
|
}
|
|
895
945
|
catch (e) {
|
|
896
946
|
// await this.closeUnexpectedPopups();
|
|
897
947
|
state.element = await this._locate(selectors, state.info, _params);
|
|
898
948
|
await state.element.dispatchEvent("click");
|
|
899
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
949
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
900
950
|
}
|
|
901
951
|
await this.waitForPageLoad();
|
|
902
952
|
return state.info;
|
|
@@ -975,7 +1025,6 @@ class StableBrowser {
|
|
|
975
1025
|
await state.element.hover({ timeout: 10000 });
|
|
976
1026
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
1027
|
}
|
|
978
|
-
await _screenshot(state, this);
|
|
979
1028
|
await this.waitForPageLoad();
|
|
980
1029
|
return state.info;
|
|
981
1030
|
}
|
|
@@ -1103,33 +1152,30 @@ class StableBrowser {
|
|
|
1103
1152
|
}
|
|
1104
1153
|
}
|
|
1105
1154
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1155
|
+
const state = {
|
|
1156
|
+
selectors,
|
|
1157
|
+
_params,
|
|
1158
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1159
|
+
options,
|
|
1160
|
+
world,
|
|
1161
|
+
type: Types.SET_DATE_TIME,
|
|
1162
|
+
text: `Set date time value: ${value}`,
|
|
1163
|
+
operation: "setDateTime",
|
|
1164
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1165
|
+
throwError: false,
|
|
1166
|
+
};
|
|
1116
1167
|
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);
|
|
1168
|
+
await _preCommand(state, this);
|
|
1123
1169
|
try {
|
|
1124
|
-
await element.click();
|
|
1170
|
+
await state.element.click();
|
|
1125
1171
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1126
1172
|
if (format) {
|
|
1127
|
-
value = dayjs(value).format(format);
|
|
1128
|
-
await element.fill(value);
|
|
1173
|
+
state.value = dayjs(state.value).format(format);
|
|
1174
|
+
await state.element.fill(state.value);
|
|
1129
1175
|
}
|
|
1130
1176
|
else {
|
|
1131
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1132
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1177
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1178
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1133
1179
|
el.value = ""; // clear input
|
|
1134
1180
|
el.value = dateTimeValue;
|
|
1135
1181
|
}, dateTimeValue);
|
|
@@ -1142,20 +1188,19 @@ class StableBrowser {
|
|
|
1142
1188
|
}
|
|
1143
1189
|
catch (err) {
|
|
1144
1190
|
//await this.closeUnexpectedPopups();
|
|
1145
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1191
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1146
1192
|
this.logger.info("Trying again");
|
|
1147
|
-
|
|
1148
|
-
info.
|
|
1149
|
-
Object.assign(err, { info: info });
|
|
1193
|
+
await _screenshot(state, this);
|
|
1194
|
+
Object.assign(err, { info: state.info });
|
|
1150
1195
|
await element.click();
|
|
1151
1196
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1152
1197
|
if (format) {
|
|
1153
|
-
value = dayjs(value).format(format);
|
|
1154
|
-
await element.fill(value);
|
|
1198
|
+
state.value = dayjs(state.value).format(format);
|
|
1199
|
+
await state.element.fill(state.value);
|
|
1155
1200
|
}
|
|
1156
1201
|
else {
|
|
1157
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1158
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1202
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1203
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1159
1204
|
el.value = ""; // clear input
|
|
1160
1205
|
el.value = dateTimeValue;
|
|
1161
1206
|
}, dateTimeValue);
|
|
@@ -1168,31 +1213,10 @@ class StableBrowser {
|
|
|
1168
1213
|
}
|
|
1169
1214
|
}
|
|
1170
1215
|
catch (e) {
|
|
1171
|
-
|
|
1172
|
-
throw e;
|
|
1216
|
+
await _commandError(state, e, this);
|
|
1173
1217
|
}
|
|
1174
1218
|
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
|
-
});
|
|
1219
|
+
_commandFinally(state, this);
|
|
1196
1220
|
}
|
|
1197
1221
|
}
|
|
1198
1222
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1329,6 +1353,7 @@ class StableBrowser {
|
|
|
1329
1353
|
let screenshotPath = null;
|
|
1330
1354
|
if (!info.log) {
|
|
1331
1355
|
info.log = "";
|
|
1356
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1332
1357
|
}
|
|
1333
1358
|
info.operation = "getText";
|
|
1334
1359
|
info.selectors = selectors;
|
|
@@ -1667,11 +1692,9 @@ class StableBrowser {
|
|
|
1667
1692
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1668
1693
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1669
1694
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1695
|
+
// to make sure the path doesn't start with -
|
|
1696
|
+
const uuidStr = "id_" + randomUUID();
|
|
1697
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1675
1698
|
try {
|
|
1676
1699
|
await this.takeScreenshot(screenshotPath);
|
|
1677
1700
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1685,7 +1708,7 @@ class StableBrowser {
|
|
|
1685
1708
|
catch (e) {
|
|
1686
1709
|
this.logger.info("unable to take screenshot, ignored");
|
|
1687
1710
|
}
|
|
1688
|
-
result.screenshotId =
|
|
1711
|
+
result.screenshotId = uuidStr;
|
|
1689
1712
|
result.screenshotPath = screenshotPath;
|
|
1690
1713
|
if (info && info.box) {
|
|
1691
1714
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1783,74 +1806,105 @@ class StableBrowser {
|
|
|
1783
1806
|
}
|
|
1784
1807
|
}
|
|
1785
1808
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1809
|
+
const state = {
|
|
1810
|
+
selectors,
|
|
1811
|
+
_params,
|
|
1812
|
+
attribute,
|
|
1813
|
+
variable,
|
|
1814
|
+
options,
|
|
1815
|
+
world,
|
|
1816
|
+
type: Types.EXTRACT,
|
|
1817
|
+
text: `Extract attribute from element`,
|
|
1818
|
+
operation: "extractAttribute",
|
|
1819
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1820
|
+
};
|
|
1791
1821
|
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
1822
|
try {
|
|
1797
|
-
|
|
1798
|
-
await this._highlightElements(element);
|
|
1799
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1823
|
+
await _preCommand(state, this);
|
|
1800
1824
|
switch (attribute) {
|
|
1801
1825
|
case "inner_text":
|
|
1802
|
-
|
|
1826
|
+
state.value = await state.element.innerText();
|
|
1803
1827
|
break;
|
|
1804
1828
|
case "href":
|
|
1805
|
-
|
|
1829
|
+
state.value = await state.element.getAttribute("href");
|
|
1830
|
+
break;
|
|
1831
|
+
case "value":
|
|
1832
|
+
state.value = await state.element.inputValue();
|
|
1833
|
+
break;
|
|
1834
|
+
default:
|
|
1835
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
state.info.value = state.value;
|
|
1839
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1840
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1841
|
+
return state.info;
|
|
1842
|
+
}
|
|
1843
|
+
catch (e) {
|
|
1844
|
+
await _commandError(state, e, this);
|
|
1845
|
+
}
|
|
1846
|
+
finally {
|
|
1847
|
+
_commandFinally(state, this);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1851
|
+
const state = {
|
|
1852
|
+
selectors,
|
|
1853
|
+
_params,
|
|
1854
|
+
attribute,
|
|
1855
|
+
value,
|
|
1856
|
+
options,
|
|
1857
|
+
world,
|
|
1858
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1859
|
+
text: `Verify element attribute`,
|
|
1860
|
+
operation: "verifyAttribute",
|
|
1861
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1862
|
+
};
|
|
1863
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1864
|
+
let val;
|
|
1865
|
+
try {
|
|
1866
|
+
await _preCommand(state, this);
|
|
1867
|
+
switch (attribute) {
|
|
1868
|
+
case "innerText":
|
|
1869
|
+
val = String(await state.element.innerText());
|
|
1806
1870
|
break;
|
|
1807
1871
|
case "value":
|
|
1808
|
-
|
|
1872
|
+
val = String(await state.element.inputValue());
|
|
1873
|
+
break;
|
|
1874
|
+
case "checked":
|
|
1875
|
+
val = String(await state.element.isChecked());
|
|
1876
|
+
break;
|
|
1877
|
+
case "disabled":
|
|
1878
|
+
val = String(await state.element.isDisabled());
|
|
1879
|
+
break;
|
|
1880
|
+
case "readOnly":
|
|
1881
|
+
const isEditable = await state.element.isEditable();
|
|
1882
|
+
val = String(!isEditable);
|
|
1809
1883
|
break;
|
|
1810
1884
|
default:
|
|
1811
|
-
|
|
1885
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1812
1886
|
break;
|
|
1813
1887
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1888
|
+
state.info.expectedValue = val;
|
|
1889
|
+
let regex;
|
|
1890
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1891
|
+
const patternBody = value.slice(1, -1);
|
|
1892
|
+
regex = new RegExp(patternBody, "g");
|
|
1817
1893
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1894
|
+
else {
|
|
1895
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1896
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1897
|
+
}
|
|
1898
|
+
if (!val.match(regex)) {
|
|
1899
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1900
|
+
}
|
|
1901
|
+
return state.info;
|
|
1821
1902
|
}
|
|
1822
1903
|
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;
|
|
1904
|
+
await _commandError(state, e, this);
|
|
1830
1905
|
}
|
|
1831
1906
|
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
|
-
});
|
|
1907
|
+
_commandFinally(state, this);
|
|
1854
1908
|
}
|
|
1855
1909
|
}
|
|
1856
1910
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1871,7 +1925,7 @@ class StableBrowser {
|
|
|
1871
1925
|
if (options && options.timeout) {
|
|
1872
1926
|
timeout = options.timeout;
|
|
1873
1927
|
}
|
|
1874
|
-
const serviceUrl =
|
|
1928
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1875
1929
|
const request = {
|
|
1876
1930
|
method: "POST",
|
|
1877
1931
|
url: serviceUrl,
|
|
@@ -1927,7 +1981,8 @@ class StableBrowser {
|
|
|
1927
1981
|
catch (e) {
|
|
1928
1982
|
errorCount++;
|
|
1929
1983
|
if (errorCount > 3) {
|
|
1930
|
-
throw e;
|
|
1984
|
+
// throw e;
|
|
1985
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1931
1986
|
}
|
|
1932
1987
|
// ignore
|
|
1933
1988
|
}
|
|
@@ -2038,11 +2093,12 @@ class StableBrowser {
|
|
|
2038
2093
|
info.screenshotPath = screenshotPath;
|
|
2039
2094
|
Object.assign(e, { info: info });
|
|
2040
2095
|
error = e;
|
|
2041
|
-
throw e;
|
|
2096
|
+
// throw e;
|
|
2097
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2042
2098
|
}
|
|
2043
2099
|
finally {
|
|
2044
2100
|
const endTime = Date.now();
|
|
2045
|
-
|
|
2101
|
+
_reportToWorld(world, {
|
|
2046
2102
|
type: Types.VERIFY_PAGE_PATH,
|
|
2047
2103
|
text: "Verify page path",
|
|
2048
2104
|
screenshotId,
|
|
@@ -2062,54 +2118,64 @@ class StableBrowser {
|
|
|
2062
2118
|
});
|
|
2063
2119
|
}
|
|
2064
2120
|
}
|
|
2121
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2122
|
+
const frames = this.page.frames();
|
|
2123
|
+
let results = [];
|
|
2124
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2125
|
+
if (dateAlternatives.date) {
|
|
2126
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2127
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2128
|
+
result.frame = frames[i];
|
|
2129
|
+
results.push(result);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
else if (numberAlternatives.number) {
|
|
2133
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2134
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2135
|
+
result.frame = frames[i];
|
|
2136
|
+
results.push(result);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
else {
|
|
2140
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2141
|
+
result.frame = frames[i];
|
|
2142
|
+
results.push(result);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
state.info.results = results;
|
|
2146
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2147
|
+
return resultWithElementsFound;
|
|
2148
|
+
}
|
|
2065
2149
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2066
2150
|
text = unEscapeString(text);
|
|
2067
|
-
const
|
|
2151
|
+
const state = {
|
|
2152
|
+
text_search: text,
|
|
2153
|
+
options,
|
|
2154
|
+
world,
|
|
2155
|
+
locate: false,
|
|
2156
|
+
scroll: false,
|
|
2157
|
+
highlight: false,
|
|
2158
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2159
|
+
text: `Verify text exists in page`,
|
|
2160
|
+
operation: "verifyTextExistInPage",
|
|
2161
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2162
|
+
};
|
|
2068
2163
|
const timeout = this._getLoadTimeout(options);
|
|
2069
|
-
let error = null;
|
|
2070
|
-
let screenshotId = null;
|
|
2071
|
-
let screenshotPath = null;
|
|
2072
2164
|
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
2165
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2077
2166
|
if (newValue !== text) {
|
|
2078
2167
|
this.logger.info(text + "=" + newValue);
|
|
2079
2168
|
text = newValue;
|
|
2080
2169
|
}
|
|
2081
|
-
info.text = text;
|
|
2082
2170
|
let dateAlternatives = findDateAlternatives(text);
|
|
2083
2171
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2084
2172
|
try {
|
|
2173
|
+
await _preCommand(state, this);
|
|
2174
|
+
state.info.text = text;
|
|
2085
2175
|
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], "*", 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], "*", true, true, {});
|
|
2099
|
-
result.frame = frames[i];
|
|
2100
|
-
results.push(result);
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
else {
|
|
2104
|
-
const result = await this._locateElementByText(frames[i], text, "*", 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);
|
|
2176
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2111
2177
|
if (resultWithElementsFound.length === 0) {
|
|
2112
|
-
if (Date.now() - startTime > timeout) {
|
|
2178
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2113
2179
|
throw new Error(`Text ${text} not found in page`);
|
|
2114
2180
|
}
|
|
2115
2181
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2119,57 +2185,165 @@ class StableBrowser {
|
|
|
2119
2185
|
const frame = resultWithElementsFound[0].frame;
|
|
2120
2186
|
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2121
2187
|
await this._highlightElements(frame, dataAttribute);
|
|
2122
|
-
const element = await frame
|
|
2188
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2123
2189
|
if (element) {
|
|
2124
|
-
await this.scrollIfNeeded(element, info);
|
|
2190
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2125
2191
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2126
2192
|
}
|
|
2127
2193
|
}
|
|
2128
|
-
|
|
2129
|
-
return info;
|
|
2194
|
+
await _screenshot(state, this);
|
|
2195
|
+
return state.info;
|
|
2130
2196
|
}
|
|
2131
2197
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2132
2198
|
}
|
|
2133
2199
|
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;
|
|
2200
|
+
await _commandError(state, e, this);
|
|
2141
2201
|
}
|
|
2142
2202
|
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
|
-
});
|
|
2203
|
+
_commandFinally(state, this);
|
|
2162
2204
|
}
|
|
2163
2205
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2206
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2207
|
+
text = unEscapeString(text);
|
|
2208
|
+
const state = {
|
|
2209
|
+
text_search: text,
|
|
2210
|
+
options,
|
|
2211
|
+
world,
|
|
2212
|
+
locate: false,
|
|
2213
|
+
scroll: false,
|
|
2214
|
+
highlight: false,
|
|
2215
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2216
|
+
text: `Verify text does not exist in page`,
|
|
2217
|
+
operation: "verifyTextNotExistInPage",
|
|
2218
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2219
|
+
};
|
|
2220
|
+
const timeout = this._getLoadTimeout(options);
|
|
2221
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2222
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2223
|
+
if (newValue !== text) {
|
|
2224
|
+
this.logger.info(text + "=" + newValue);
|
|
2225
|
+
text = newValue;
|
|
2226
|
+
}
|
|
2227
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2228
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2229
|
+
try {
|
|
2230
|
+
await _preCommand(state, this);
|
|
2231
|
+
state.info.text = text;
|
|
2232
|
+
while (true) {
|
|
2233
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2234
|
+
if (resultWithElementsFound.length === 0) {
|
|
2235
|
+
await _screenshot(state, this);
|
|
2236
|
+
return state.info;
|
|
2237
|
+
}
|
|
2238
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2239
|
+
throw new Error(`Text ${text} found in page`);
|
|
2240
|
+
}
|
|
2241
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2242
|
+
}
|
|
2168
2243
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2244
|
+
catch (e) {
|
|
2245
|
+
await _commandError(state, e, this);
|
|
2246
|
+
}
|
|
2247
|
+
finally {
|
|
2248
|
+
_commandFinally(state, this);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2252
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2253
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2254
|
+
const state = {
|
|
2255
|
+
text_search: textToVerify,
|
|
2256
|
+
options,
|
|
2257
|
+
world,
|
|
2258
|
+
locate: false,
|
|
2259
|
+
scroll: false,
|
|
2260
|
+
highlight: false,
|
|
2261
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2262
|
+
text: `Verify text with relation to another text`,
|
|
2263
|
+
operation: "verify_text_with_relation",
|
|
2264
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2265
|
+
};
|
|
2266
|
+
const timeout = this._getLoadTimeout(options);
|
|
2267
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2268
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2269
|
+
if (newValue !== textAnchor) {
|
|
2270
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2271
|
+
textAnchor = newValue;
|
|
2272
|
+
}
|
|
2273
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2274
|
+
if (newValue !== textToVerify) {
|
|
2275
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2276
|
+
textToVerify = newValue;
|
|
2277
|
+
}
|
|
2278
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2279
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2280
|
+
let foundAncore = false;
|
|
2281
|
+
try {
|
|
2282
|
+
await _preCommand(state, this);
|
|
2283
|
+
state.info.text = textToVerify;
|
|
2284
|
+
while (true) {
|
|
2285
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2286
|
+
if (resultWithElementsFound.length === 0) {
|
|
2287
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2288
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2289
|
+
}
|
|
2290
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2291
|
+
continue;
|
|
2292
|
+
}
|
|
2293
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2294
|
+
foundAncore = true;
|
|
2295
|
+
const result = resultWithElementsFound[i];
|
|
2296
|
+
const token = result.randomToken;
|
|
2297
|
+
const frame = result.frame;
|
|
2298
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2299
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2300
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2301
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2302
|
+
const element = elements[i];
|
|
2303
|
+
let climbParent = element;
|
|
2304
|
+
for (let j = 0; j < climb; j++) {
|
|
2305
|
+
climbParent = climbParent.parentElement;
|
|
2306
|
+
if (!climbParent) {
|
|
2307
|
+
break;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
if (!climbParent) {
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2314
|
+
if (foundElements.length > 0) {
|
|
2315
|
+
// set the container element attribute
|
|
2316
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2317
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2318
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2319
|
+
return { found: true };
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return { found: false };
|
|
2323
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2324
|
+
if (findResult.found === true) {
|
|
2325
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2326
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2327
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2328
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2329
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2330
|
+
if (element) {
|
|
2331
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2332
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2333
|
+
}
|
|
2334
|
+
await _screenshot(state, this);
|
|
2335
|
+
return state.info;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2340
|
+
}
|
|
2341
|
+
catch (e) {
|
|
2342
|
+
await _commandError(state, e, this);
|
|
2343
|
+
}
|
|
2344
|
+
finally {
|
|
2345
|
+
_commandFinally(state, this);
|
|
2171
2346
|
}
|
|
2172
|
-
return serviceUrl;
|
|
2173
2347
|
}
|
|
2174
2348
|
async visualVerification(text, options = {}, world = null) {
|
|
2175
2349
|
const startTime = Date.now();
|
|
@@ -2185,7 +2359,7 @@ class StableBrowser {
|
|
|
2185
2359
|
throw new Error("TOKEN is not set");
|
|
2186
2360
|
}
|
|
2187
2361
|
try {
|
|
2188
|
-
let serviceUrl =
|
|
2362
|
+
let serviceUrl = _getServerUrl();
|
|
2189
2363
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2190
2364
|
info.screenshotPath = screenshotPath;
|
|
2191
2365
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2221,11 +2395,12 @@ class StableBrowser {
|
|
|
2221
2395
|
info.screenshotPath = screenshotPath;
|
|
2222
2396
|
Object.assign(e, { info: info });
|
|
2223
2397
|
error = e;
|
|
2224
|
-
throw e;
|
|
2398
|
+
// throw e;
|
|
2399
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2225
2400
|
}
|
|
2226
2401
|
finally {
|
|
2227
2402
|
const endTime = Date.now();
|
|
2228
|
-
|
|
2403
|
+
_reportToWorld(world, {
|
|
2229
2404
|
type: Types.VERIFY_VISUAL,
|
|
2230
2405
|
text: "Visual verification",
|
|
2231
2406
|
screenshotId,
|
|
@@ -2273,6 +2448,7 @@ class StableBrowser {
|
|
|
2273
2448
|
let screenshotPath = null;
|
|
2274
2449
|
const info = {};
|
|
2275
2450
|
info.log = "";
|
|
2451
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2276
2452
|
info.operation = "getTableData";
|
|
2277
2453
|
info.selectors = selectors;
|
|
2278
2454
|
try {
|
|
@@ -2288,11 +2464,12 @@ class StableBrowser {
|
|
|
2288
2464
|
info.screenshotPath = screenshotPath;
|
|
2289
2465
|
Object.assign(e, { info: info });
|
|
2290
2466
|
error = e;
|
|
2291
|
-
throw e;
|
|
2467
|
+
// throw e;
|
|
2468
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2292
2469
|
}
|
|
2293
2470
|
finally {
|
|
2294
2471
|
const endTime = Date.now();
|
|
2295
|
-
|
|
2472
|
+
_reportToWorld(world, {
|
|
2296
2473
|
element_name: selectors.element_name,
|
|
2297
2474
|
type: Types.GET_TABLE_DATA,
|
|
2298
2475
|
text: "Get table data",
|
|
@@ -2347,7 +2524,7 @@ class StableBrowser {
|
|
|
2347
2524
|
info.operation = "analyzeTable";
|
|
2348
2525
|
info.selectors = selectors;
|
|
2349
2526
|
info.query = query;
|
|
2350
|
-
query =
|
|
2527
|
+
query = _fixUsingParams(query, _params);
|
|
2351
2528
|
info.query_fixed = query;
|
|
2352
2529
|
info.operator = operator;
|
|
2353
2530
|
info.value = value;
|
|
@@ -2453,11 +2630,12 @@ class StableBrowser {
|
|
|
2453
2630
|
info.screenshotPath = screenshotPath;
|
|
2454
2631
|
Object.assign(e, { info: info });
|
|
2455
2632
|
error = e;
|
|
2456
|
-
throw e;
|
|
2633
|
+
// throw e;
|
|
2634
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2457
2635
|
}
|
|
2458
2636
|
finally {
|
|
2459
2637
|
const endTime = Date.now();
|
|
2460
|
-
|
|
2638
|
+
_reportToWorld(world, {
|
|
2461
2639
|
element_name: selectors.element_name,
|
|
2462
2640
|
type: Types.ANALYZE_TABLE,
|
|
2463
2641
|
text: "Analyze table",
|
|
@@ -2530,7 +2708,7 @@ class StableBrowser {
|
|
|
2530
2708
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2531
2709
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2532
2710
|
const endTime = Date.now();
|
|
2533
|
-
|
|
2711
|
+
_reportToWorld(world, {
|
|
2534
2712
|
type: Types.GET_PAGE_STATUS,
|
|
2535
2713
|
text: "Wait for page load",
|
|
2536
2714
|
screenshotId,
|
|
@@ -2550,41 +2728,35 @@ class StableBrowser {
|
|
|
2550
2728
|
}
|
|
2551
2729
|
}
|
|
2552
2730
|
async closePage(options = {}, world = null) {
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2731
|
+
const state = {
|
|
2732
|
+
options,
|
|
2733
|
+
world,
|
|
2734
|
+
locate: false,
|
|
2735
|
+
scroll: false,
|
|
2736
|
+
highlight: false,
|
|
2737
|
+
type: Types.CLOSE_PAGE,
|
|
2738
|
+
text: `Close page`,
|
|
2739
|
+
operation: "closePage",
|
|
2740
|
+
log: "***** close page *****\n",
|
|
2741
|
+
throwError: false,
|
|
2742
|
+
};
|
|
2558
2743
|
try {
|
|
2744
|
+
await _preCommand(state, this);
|
|
2559
2745
|
await this.page.close();
|
|
2560
2746
|
}
|
|
2561
2747
|
catch (e) {
|
|
2562
2748
|
console.log(".");
|
|
2749
|
+
await _commandError(state, e, this);
|
|
2563
2750
|
}
|
|
2564
2751
|
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
|
-
});
|
|
2752
|
+
_commandFinally(state, this);
|
|
2586
2753
|
}
|
|
2587
2754
|
}
|
|
2755
|
+
saveTestDataAsGlobal(options, world) {
|
|
2756
|
+
const dataFile = this._getDataFile(world);
|
|
2757
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2758
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2759
|
+
}
|
|
2588
2760
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2589
2761
|
const startTime = Date.now();
|
|
2590
2762
|
let error = null;
|
|
@@ -2602,12 +2774,13 @@ class StableBrowser {
|
|
|
2602
2774
|
}
|
|
2603
2775
|
catch (e) {
|
|
2604
2776
|
console.log(".");
|
|
2777
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2605
2778
|
}
|
|
2606
2779
|
finally {
|
|
2607
2780
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2608
2781
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2609
2782
|
const endTime = Date.now();
|
|
2610
|
-
|
|
2783
|
+
_reportToWorld(world, {
|
|
2611
2784
|
type: Types.SET_VIEWPORT,
|
|
2612
2785
|
text: "set viewport size to " + width + "x" + hight,
|
|
2613
2786
|
screenshotId,
|
|
@@ -2638,12 +2811,13 @@ class StableBrowser {
|
|
|
2638
2811
|
}
|
|
2639
2812
|
catch (e) {
|
|
2640
2813
|
console.log(".");
|
|
2814
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2641
2815
|
}
|
|
2642
2816
|
finally {
|
|
2643
2817
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2644
2818
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2645
2819
|
const endTime = Date.now();
|
|
2646
|
-
|
|
2820
|
+
_reportToWorld(world, {
|
|
2647
2821
|
type: Types.GET_PAGE_STATUS,
|
|
2648
2822
|
text: "page relaod",
|
|
2649
2823
|
screenshotId,
|
|
@@ -2679,11 +2853,45 @@ class StableBrowser {
|
|
|
2679
2853
|
console.log("#-#");
|
|
2680
2854
|
}
|
|
2681
2855
|
}
|
|
2682
|
-
|
|
2683
|
-
if (
|
|
2684
|
-
|
|
2856
|
+
async beforeStep(world, step) {
|
|
2857
|
+
if (this.stepIndex === undefined) {
|
|
2858
|
+
this.stepIndex = 0;
|
|
2859
|
+
}
|
|
2860
|
+
else {
|
|
2861
|
+
this.stepIndex++;
|
|
2862
|
+
}
|
|
2863
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2864
|
+
this.stepName = step.pickleStep.text;
|
|
2865
|
+
this.logger.info("step: " + this.stepName);
|
|
2866
|
+
}
|
|
2867
|
+
else if (step && step.text) {
|
|
2868
|
+
this.stepName = step.text;
|
|
2869
|
+
}
|
|
2870
|
+
else {
|
|
2871
|
+
this.stepName = "step " + this.stepIndex;
|
|
2872
|
+
}
|
|
2873
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2874
|
+
if (this.context.browserObject.context) {
|
|
2875
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2879
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2880
|
+
// check if @global_test_data tag is present
|
|
2881
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2882
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
async afterStep(world, step) {
|
|
2887
|
+
this.stepName = null;
|
|
2888
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2889
|
+
if (this.context.browserObject.context) {
|
|
2890
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2891
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2685
2894
|
}
|
|
2686
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2687
2895
|
}
|
|
2688
2896
|
}
|
|
2689
2897
|
function createTimedPromise(promise, label) {
|
|
@@ -2691,156 +2899,5 @@ function createTimedPromise(promise, label) {
|
|
|
2691
2899
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2692
2900
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2693
2901
|
}
|
|
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
2902
|
export { StableBrowser };
|
|
2846
2903
|
//# sourceMappingURL=stable_browser.js.map
|