automation_model 1.0.543-dev → 1.0.543-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.d.ts +1 -0
- package/lib/api.js +8 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +101 -5
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +3 -2
- package/lib/browser_manager.js +85 -39
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -1
- package/lib/command_common.js +45 -13
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/init_browser.d.ts +4 -2
- package/lib/init_browser.js +52 -6
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +10 -10
- package/lib/locate_element.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.js +51 -12
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +51 -18
- package/lib/stable_browser.js +630 -541
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +344 -6
- package/lib/utils.js.map +1 -1
- package/package.json +7 -7
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,16 +10,18 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
|
-
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
export const Types = {
|
|
23
25
|
CLICK: "click_element",
|
|
24
26
|
NAVIGATE: "navigate",
|
|
25
27
|
FILL: "fill_element",
|
|
@@ -30,6 +32,8 @@ const Types = {
|
|
|
30
32
|
GET_PAGE_STATUS: "get_page_status",
|
|
31
33
|
CLICK_ROW_ACTION: "click_row_action",
|
|
32
34
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
36
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
33
37
|
ANALYZE_TABLE: "analyze_table",
|
|
34
38
|
SELECT: "select_combobox",
|
|
35
39
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -47,8 +51,12 @@ const Types = {
|
|
|
47
51
|
SET_INPUT: "set_input",
|
|
48
52
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
49
53
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
54
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
50
55
|
};
|
|
51
56
|
export const apps = {};
|
|
57
|
+
const formatElementName = (elementName) => {
|
|
58
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
59
|
+
};
|
|
52
60
|
class StableBrowser {
|
|
53
61
|
browser;
|
|
54
62
|
page;
|
|
@@ -61,7 +69,6 @@ class StableBrowser {
|
|
|
61
69
|
configuration = null;
|
|
62
70
|
appName = "main";
|
|
63
71
|
tags = null;
|
|
64
|
-
isRecording = false;
|
|
65
72
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
66
73
|
this.browser = browser;
|
|
67
74
|
this.page = page;
|
|
@@ -92,35 +99,17 @@ class StableBrowser {
|
|
|
92
99
|
catch (e) {
|
|
93
100
|
this.logger.error("unable to read ai_config.json");
|
|
94
101
|
}
|
|
95
|
-
context.pageLoading = { status: false };
|
|
96
|
-
context.pages = [this.page];
|
|
97
102
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
98
103
|
this.world = world;
|
|
104
|
+
context.pages = [this.page];
|
|
105
|
+
context.pageLoading = { status: false };
|
|
99
106
|
this.registerEventListeners(this.context);
|
|
100
107
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
101
108
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
102
109
|
}
|
|
103
|
-
async scrollPageToLoadLazyElements() {
|
|
104
|
-
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
105
|
-
let retry = 0;
|
|
106
|
-
while (true) {
|
|
107
|
-
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
108
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
109
|
-
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
110
|
-
if (newHeight === lastHeight) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
lastHeight = newHeight;
|
|
114
|
-
retry++;
|
|
115
|
-
if (retry > 10) {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
120
|
-
}
|
|
121
110
|
registerEventListeners(context) {
|
|
122
111
|
this.registerConsoleLogListener(this.page, context);
|
|
123
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
112
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
124
113
|
if (!context.pageLoading) {
|
|
125
114
|
context.pageLoading = { status: false };
|
|
126
115
|
}
|
|
@@ -165,7 +154,7 @@ class StableBrowser {
|
|
|
165
154
|
if (this.appName === appName) {
|
|
166
155
|
return;
|
|
167
156
|
}
|
|
168
|
-
let
|
|
157
|
+
let navigate = false;
|
|
169
158
|
if (!apps[appName]) {
|
|
170
159
|
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
171
160
|
newContextCreated = true;
|
|
@@ -176,32 +165,15 @@ class StableBrowser {
|
|
|
176
165
|
};
|
|
177
166
|
}
|
|
178
167
|
const tempContext = {};
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
_copyContext(this, tempContext);
|
|
169
|
+
_copyContext(apps[appName], this);
|
|
181
170
|
apps[this.appName] = tempContext;
|
|
182
171
|
this.appName = appName;
|
|
183
|
-
if (
|
|
184
|
-
this.registerEventListeners(this.context);
|
|
172
|
+
if (navigate) {
|
|
185
173
|
await this.goto(this.context.environment.baseUrl);
|
|
186
174
|
await this.waitForPageLoad();
|
|
187
175
|
}
|
|
188
176
|
}
|
|
189
|
-
_copyContext(from, to) {
|
|
190
|
-
to.browser = from.browser;
|
|
191
|
-
to.page = from.page;
|
|
192
|
-
to.context = from.context;
|
|
193
|
-
}
|
|
194
|
-
getWebLogFile(logFolder) {
|
|
195
|
-
if (!fs.existsSync(logFolder)) {
|
|
196
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
197
|
-
}
|
|
198
|
-
let nextIndex = 1;
|
|
199
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
200
|
-
nextIndex++;
|
|
201
|
-
}
|
|
202
|
-
const fileName = nextIndex + ".json";
|
|
203
|
-
return path.join(logFolder, fileName);
|
|
204
|
-
}
|
|
205
177
|
registerConsoleLogListener(page, context) {
|
|
206
178
|
if (!this.context.webLogger) {
|
|
207
179
|
this.context.webLogger = [];
|
|
@@ -265,55 +237,51 @@ class StableBrowser {
|
|
|
265
237
|
// async closeUnexpectedPopups() {
|
|
266
238
|
// await closeUnexpectedPopups(this.page);
|
|
267
239
|
// }
|
|
268
|
-
async goto(url) {
|
|
240
|
+
async goto(url, world = null) {
|
|
241
|
+
if (!url) {
|
|
242
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
243
|
+
}
|
|
269
244
|
if (!url.startsWith("http")) {
|
|
270
245
|
url = "https://" + url;
|
|
271
246
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
247
|
+
const state = {
|
|
248
|
+
value: url,
|
|
249
|
+
world: world,
|
|
250
|
+
type: Types.NAVIGATE,
|
|
251
|
+
text: `Navigate Page to: ${url}`,
|
|
252
|
+
operation: "goto",
|
|
253
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
254
|
+
info: {},
|
|
255
|
+
locate: false,
|
|
256
|
+
scroll: false,
|
|
257
|
+
screenshot: false,
|
|
258
|
+
highlight: false,
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
await _preCommand(state, this);
|
|
262
|
+
await this.page.goto(url, {
|
|
263
|
+
timeout: 60000,
|
|
264
|
+
});
|
|
265
|
+
await _screenshot(state, this);
|
|
279
266
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// remove the _ prefix
|
|
284
|
-
regValue = key.substring(1);
|
|
285
|
-
}
|
|
286
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error("Error on goto", error);
|
|
269
|
+
_commandError(state, error, this);
|
|
287
270
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
291
|
-
// check if not null
|
|
292
|
-
if (!locator) {
|
|
293
|
-
return locator;
|
|
271
|
+
finally {
|
|
272
|
+
_commandFinally(state, this);
|
|
294
273
|
}
|
|
295
|
-
// clone the locator
|
|
296
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
297
|
-
this.scanAndManipulate(locator, _params);
|
|
298
|
-
return locator;
|
|
299
274
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
else if (this._isObject(currentObj[key])) {
|
|
310
|
-
// Recursively scan nested objects
|
|
311
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
275
|
+
async _getLocator(locator, scope, _params) {
|
|
276
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
277
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
278
|
+
for (let key in locator) {
|
|
279
|
+
if (typeof locator[key] !== "string")
|
|
280
|
+
continue;
|
|
281
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
282
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
312
283
|
}
|
|
313
284
|
}
|
|
314
|
-
}
|
|
315
|
-
_getLocator(locator, scope, _params) {
|
|
316
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
317
285
|
let locatorReturn;
|
|
318
286
|
if (locator.role) {
|
|
319
287
|
if (locator.role[1].nameReg) {
|
|
@@ -321,7 +289,7 @@ class StableBrowser {
|
|
|
321
289
|
delete locator.role[1].nameReg;
|
|
322
290
|
}
|
|
323
291
|
// if (locator.role[1].name) {
|
|
324
|
-
// locator.role[1].name =
|
|
292
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
325
293
|
// }
|
|
326
294
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
327
295
|
}
|
|
@@ -364,140 +332,54 @@ class StableBrowser {
|
|
|
364
332
|
if (css && css.locator) {
|
|
365
333
|
css = css.locator;
|
|
366
334
|
}
|
|
367
|
-
let result = await this._locateElementByText(scope,
|
|
335
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
368
336
|
if (result.elementCount === 0) {
|
|
369
337
|
return;
|
|
370
338
|
}
|
|
371
|
-
let textElementCss = "[data-blinq-id
|
|
339
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
372
340
|
// css climb to parent element
|
|
373
341
|
const climbArray = [];
|
|
374
342
|
for (let i = 0; i < climb; i++) {
|
|
375
343
|
climbArray.push("..");
|
|
376
344
|
}
|
|
377
345
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
346
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
347
|
+
if (css) {
|
|
348
|
+
resultCss = resultCss + " >> " + css;
|
|
349
|
+
}
|
|
350
|
+
return resultCss;
|
|
351
|
+
}
|
|
352
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
353
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
354
|
+
const locator = scope.locator(query);
|
|
355
|
+
const count = await locator.count();
|
|
356
|
+
if (!tag1) {
|
|
357
|
+
tag1 = "*";
|
|
358
|
+
}
|
|
359
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
360
|
+
let tagCount = 0;
|
|
361
|
+
for (let i = 0; i < count; i++) {
|
|
362
|
+
const element = locator.nth(i);
|
|
363
|
+
// check if the tag matches
|
|
364
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
365
|
+
if (!tag.startsWith("*")) {
|
|
366
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
367
|
+
return false;
|
|
388
368
|
}
|
|
389
|
-
currentNode = currentNode.parentNode;
|
|
390
|
-
}
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
document.isParent = isParent;
|
|
394
|
-
function getRegex(str) {
|
|
395
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
396
|
-
if (!match) {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
let [_, pattern, flags] = match;
|
|
400
|
-
return new RegExp(pattern, flags);
|
|
401
|
-
}
|
|
402
|
-
document.getRegex = getRegex;
|
|
403
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
404
|
-
// Check and add the element if it has a shadow root
|
|
405
|
-
if (element.shadowRoot) {
|
|
406
|
-
result.push(element);
|
|
407
|
-
// Also search within the shadow root
|
|
408
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
409
|
-
}
|
|
410
|
-
// Iterate over child nodes
|
|
411
|
-
element.childNodes.forEach((child) => {
|
|
412
|
-
// Recursively call the function for each child node
|
|
413
|
-
document.collectAllShadowDomElements(child, result);
|
|
414
|
-
});
|
|
415
|
-
return result;
|
|
416
|
-
}
|
|
417
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
418
|
-
if (!tag) {
|
|
419
|
-
tag = "*:not(script, style, head)";
|
|
420
|
-
}
|
|
421
|
-
let regexpSearch = document.getRegex(text);
|
|
422
|
-
if (regexpSearch) {
|
|
423
|
-
regex = true;
|
|
424
|
-
}
|
|
425
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
426
|
-
let shadowHosts = [];
|
|
427
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
428
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
429
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
430
|
-
if (!shadowElement) {
|
|
431
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
435
|
-
elements = elements.concat(shadowElements);
|
|
436
|
-
}
|
|
437
|
-
let randomToken = null;
|
|
438
|
-
const foundElements = [];
|
|
439
|
-
if (regex) {
|
|
440
|
-
if (!regexpSearch) {
|
|
441
|
-
regexpSearch = new RegExp(text, "im");
|
|
442
369
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
446
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
447
|
-
foundElements.push(element);
|
|
448
|
-
}
|
|
370
|
+
if (!el.setAttribute) {
|
|
371
|
+
el = el.parentElement;
|
|
449
372
|
}
|
|
373
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
374
|
+
return true;
|
|
375
|
+
}, [tag1, randomToken]))) {
|
|
376
|
+
continue;
|
|
450
377
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const element = elements[i];
|
|
455
|
-
if (partial) {
|
|
456
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
457
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
458
|
-
foundElements.push(element);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
463
|
-
(element.value && element.value === text)) {
|
|
464
|
-
foundElements.push(element);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
let noChildElements = [];
|
|
470
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
471
|
-
let element = foundElements[i];
|
|
472
|
-
let hasChild = false;
|
|
473
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
474
|
-
if (i === j) {
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (isParent(element, foundElements[j])) {
|
|
478
|
-
hasChild = true;
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (!hasChild) {
|
|
483
|
-
noChildElements.push(element);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
let elementCount = 0;
|
|
487
|
-
if (noChildElements.length > 0) {
|
|
488
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
489
|
-
if (randomToken === null) {
|
|
490
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
491
|
-
}
|
|
492
|
-
let element = noChildElements[i];
|
|
493
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
494
|
-
elementCount++;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
498
|
-
}, [text1, tag1, regex1, partial1]);
|
|
378
|
+
tagCount++;
|
|
379
|
+
}
|
|
380
|
+
return { elementCount: tagCount, randomToken };
|
|
499
381
|
}
|
|
500
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
382
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
501
383
|
if (!info) {
|
|
502
384
|
info = {};
|
|
503
385
|
}
|
|
@@ -506,10 +388,13 @@ class StableBrowser {
|
|
|
506
388
|
}
|
|
507
389
|
if (!info.log) {
|
|
508
390
|
info.log = "";
|
|
391
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
509
392
|
}
|
|
510
393
|
let locatorSearch = selectorHierarchy[index];
|
|
394
|
+
let originalLocatorSearch = "";
|
|
511
395
|
try {
|
|
512
|
-
|
|
396
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
397
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
513
398
|
}
|
|
514
399
|
catch (e) {
|
|
515
400
|
console.error(e);
|
|
@@ -517,30 +402,31 @@ class StableBrowser {
|
|
|
517
402
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
518
403
|
let locator = null;
|
|
519
404
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
520
|
-
|
|
405
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
406
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
521
407
|
if (!locatorString) {
|
|
522
408
|
info.failCause.textNotFound = true;
|
|
523
|
-
info.failCause.lastError =
|
|
409
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
524
410
|
return;
|
|
525
411
|
}
|
|
526
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
412
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
527
413
|
}
|
|
528
414
|
else if (locatorSearch.text) {
|
|
529
|
-
let text =
|
|
530
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
415
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
416
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
531
417
|
if (result.elementCount === 0) {
|
|
532
418
|
info.failCause.textNotFound = true;
|
|
533
|
-
info.failCause.lastError =
|
|
419
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
534
420
|
return;
|
|
535
421
|
}
|
|
536
|
-
locatorSearch.css = "[data-blinq-id
|
|
422
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
537
423
|
if (locatorSearch.childCss) {
|
|
538
424
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
539
425
|
}
|
|
540
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
426
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
541
427
|
}
|
|
542
428
|
else {
|
|
543
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
429
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
544
430
|
}
|
|
545
431
|
// let cssHref = false;
|
|
546
432
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -553,18 +439,27 @@ class StableBrowser {
|
|
|
553
439
|
//info.log += "total elements found " + count + "\n";
|
|
554
440
|
//let visibleCount = 0;
|
|
555
441
|
let visibleLocator = null;
|
|
556
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
442
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
557
443
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
444
|
+
if (info.locatorLog) {
|
|
445
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
446
|
+
}
|
|
558
447
|
return;
|
|
559
448
|
}
|
|
449
|
+
if (info.locatorLog && count === 0) {
|
|
450
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
451
|
+
}
|
|
560
452
|
for (let j = 0; j < count; j++) {
|
|
561
453
|
let visible = await locator.nth(j).isVisible();
|
|
562
454
|
const enabled = await locator.nth(j).isEnabled();
|
|
563
455
|
if (!visibleOnly) {
|
|
564
456
|
visible = true;
|
|
565
457
|
}
|
|
566
|
-
if (visible && enabled) {
|
|
458
|
+
if (visible && (allowDisabled || enabled)) {
|
|
567
459
|
foundLocators.push(locator.nth(j));
|
|
460
|
+
if (info.locatorLog) {
|
|
461
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
462
|
+
}
|
|
568
463
|
}
|
|
569
464
|
else {
|
|
570
465
|
info.failCause.visible = visible;
|
|
@@ -572,8 +467,16 @@ class StableBrowser {
|
|
|
572
467
|
if (!info.printMessages) {
|
|
573
468
|
info.printMessages = {};
|
|
574
469
|
}
|
|
470
|
+
if (info.locatorLog && !visible) {
|
|
471
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
472
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
473
|
+
}
|
|
474
|
+
if (info.locatorLog && !enabled) {
|
|
475
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
476
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
477
|
+
}
|
|
575
478
|
if (!info.printMessages[j.toString()]) {
|
|
576
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
479
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
577
480
|
info.printMessages[j.toString()] = true;
|
|
578
481
|
}
|
|
579
482
|
}
|
|
@@ -589,12 +492,12 @@ class StableBrowser {
|
|
|
589
492
|
if (!info) {
|
|
590
493
|
info = {};
|
|
591
494
|
}
|
|
592
|
-
info.log += "scan for popup handlers" + "\n";
|
|
495
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
593
496
|
const handlerGroup = [];
|
|
594
497
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
595
498
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
596
499
|
}
|
|
597
|
-
const scopes = this.page.frames()
|
|
500
|
+
const scopes = [this.page, ...this.page.frames()];
|
|
598
501
|
let result = null;
|
|
599
502
|
let scope = null;
|
|
600
503
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -637,7 +540,7 @@ class StableBrowser {
|
|
|
637
540
|
}
|
|
638
541
|
return { rerun: false };
|
|
639
542
|
}
|
|
640
|
-
async _locate(selectors, info, _params, timeout) {
|
|
543
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
641
544
|
if (!timeout) {
|
|
642
545
|
timeout = 30000;
|
|
643
546
|
}
|
|
@@ -647,9 +550,18 @@ class StableBrowser {
|
|
|
647
550
|
let selector = selectors.locators[j];
|
|
648
551
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
649
552
|
}
|
|
650
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
553
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
651
554
|
if (!element.rerun) {
|
|
652
|
-
|
|
555
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
556
|
+
element.evaluate((el, randomToken) => {
|
|
557
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
558
|
+
}, randomToken);
|
|
559
|
+
if (element._frame) {
|
|
560
|
+
return element;
|
|
561
|
+
}
|
|
562
|
+
const scope = element.page();
|
|
563
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
564
|
+
return newSelector;
|
|
653
565
|
}
|
|
654
566
|
}
|
|
655
567
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -691,9 +603,11 @@ class StableBrowser {
|
|
|
691
603
|
}
|
|
692
604
|
return framescope;
|
|
693
605
|
};
|
|
606
|
+
let fLocator = null;
|
|
694
607
|
while (true) {
|
|
695
608
|
let frameFound = false;
|
|
696
609
|
if (selectors.nestFrmLoc) {
|
|
610
|
+
fLocator = selectors.nestFrmLoc;
|
|
697
611
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
698
612
|
frameFound = true;
|
|
699
613
|
break;
|
|
@@ -702,6 +616,7 @@ class StableBrowser {
|
|
|
702
616
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
703
617
|
let frameLocator = selectors.frameLocators[i];
|
|
704
618
|
if (frameLocator.css) {
|
|
619
|
+
fLocator = frameLocator.css;
|
|
705
620
|
scope = scope.frameLocator(frameLocator.css);
|
|
706
621
|
frameFound = true;
|
|
707
622
|
break;
|
|
@@ -709,18 +624,25 @@ class StableBrowser {
|
|
|
709
624
|
}
|
|
710
625
|
}
|
|
711
626
|
if (!frameFound && selectors.iframe_src) {
|
|
627
|
+
fLocator = selectors.iframe_src;
|
|
712
628
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
713
629
|
}
|
|
714
630
|
if (!scope) {
|
|
715
|
-
info
|
|
631
|
+
if (info && info.locatorLog) {
|
|
632
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
633
|
+
}
|
|
634
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
716
635
|
if (Date.now() - startTime > timeout) {
|
|
717
636
|
info.failCause.iframeNotFound = true;
|
|
718
|
-
info.failCause.lastError =
|
|
637
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
719
638
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
720
639
|
}
|
|
721
640
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
722
641
|
}
|
|
723
642
|
else {
|
|
643
|
+
if (info && info.locatorLog) {
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
645
|
+
}
|
|
724
646
|
break;
|
|
725
647
|
}
|
|
726
648
|
}
|
|
@@ -737,11 +659,12 @@ class StableBrowser {
|
|
|
737
659
|
return bodyContent;
|
|
738
660
|
});
|
|
739
661
|
}
|
|
740
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
662
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
741
663
|
if (!info) {
|
|
742
664
|
info = {};
|
|
743
665
|
info.failCause = {};
|
|
744
666
|
info.log = "";
|
|
667
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
745
668
|
}
|
|
746
669
|
let highPriorityTimeout = 5000;
|
|
747
670
|
let visibleOnlyTimeout = 6000;
|
|
@@ -785,17 +708,17 @@ class StableBrowser {
|
|
|
785
708
|
}
|
|
786
709
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
787
710
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
788
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
711
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
789
712
|
if (result.foundElements.length === 0) {
|
|
790
713
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
791
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
714
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
792
715
|
}
|
|
793
716
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
794
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
717
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
795
718
|
}
|
|
796
719
|
else {
|
|
797
720
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
798
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
721
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
799
722
|
}
|
|
800
723
|
}
|
|
801
724
|
let foundElements = result.foundElements;
|
|
@@ -840,26 +763,34 @@ class StableBrowser {
|
|
|
840
763
|
break;
|
|
841
764
|
}
|
|
842
765
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
843
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
766
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
844
767
|
highPriorityOnly = false;
|
|
845
768
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
846
769
|
lazy_scroll = true;
|
|
847
|
-
await this.
|
|
770
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
848
771
|
}
|
|
849
772
|
}
|
|
850
773
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
851
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
774
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
852
775
|
visibleOnly = false;
|
|
853
776
|
}
|
|
854
777
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
855
778
|
}
|
|
856
779
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
857
|
-
info.
|
|
780
|
+
// if (info.locatorLog) {
|
|
781
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
782
|
+
// for (let line of lines) {
|
|
783
|
+
// this.logger.debug(line);
|
|
784
|
+
// }
|
|
785
|
+
// }
|
|
786
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
858
787
|
info.failCause.locatorNotFound = true;
|
|
859
|
-
info
|
|
788
|
+
if (!info?.failCause?.lastError) {
|
|
789
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
790
|
+
}
|
|
860
791
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
861
792
|
}
|
|
862
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
793
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
863
794
|
let foundElements = [];
|
|
864
795
|
const result = {
|
|
865
796
|
foundElements: foundElements,
|
|
@@ -867,14 +798,15 @@ class StableBrowser {
|
|
|
867
798
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
868
799
|
let foundLocators = [];
|
|
869
800
|
try {
|
|
870
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
801
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
871
802
|
}
|
|
872
803
|
catch (e) {
|
|
873
|
-
this
|
|
874
|
-
this.logger.debug(
|
|
804
|
+
// this call can fail it the browser is navigating
|
|
805
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
806
|
+
// this.logger.debug(e);
|
|
875
807
|
foundLocators = [];
|
|
876
808
|
try {
|
|
877
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
809
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
878
810
|
}
|
|
879
811
|
catch (e) {
|
|
880
812
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -890,11 +822,27 @@ class StableBrowser {
|
|
|
890
822
|
}
|
|
891
823
|
if (foundLocators.length > 1) {
|
|
892
824
|
info.failCause.foundMultiple = true;
|
|
825
|
+
if (info.locatorLog) {
|
|
826
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
827
|
+
}
|
|
893
828
|
}
|
|
894
829
|
}
|
|
895
830
|
return result;
|
|
896
831
|
}
|
|
897
832
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
833
|
+
const state = {
|
|
834
|
+
locate: false,
|
|
835
|
+
scroll: false,
|
|
836
|
+
highlight: false,
|
|
837
|
+
_params,
|
|
838
|
+
options,
|
|
839
|
+
world,
|
|
840
|
+
type: Types.CLICK,
|
|
841
|
+
text: "Click element",
|
|
842
|
+
operation: "simpleClick",
|
|
843
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
844
|
+
};
|
|
845
|
+
_preCommand(state, this);
|
|
898
846
|
const startTime = Date.now();
|
|
899
847
|
let timeout = 30000;
|
|
900
848
|
if (options && options.timeout) {
|
|
@@ -917,15 +865,33 @@ class StableBrowser {
|
|
|
917
865
|
}
|
|
918
866
|
}
|
|
919
867
|
catch (e) {
|
|
920
|
-
if (
|
|
868
|
+
if (performance.now() - startTime > timeout) {
|
|
921
869
|
// throw e;
|
|
922
|
-
|
|
870
|
+
try {
|
|
871
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
872
|
+
}
|
|
873
|
+
finally {
|
|
874
|
+
_commandFinally(state, this);
|
|
875
|
+
}
|
|
923
876
|
}
|
|
924
877
|
}
|
|
925
878
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
926
879
|
}
|
|
927
880
|
}
|
|
928
881
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
882
|
+
const state = {
|
|
883
|
+
locate: false,
|
|
884
|
+
scroll: false,
|
|
885
|
+
highlight: false,
|
|
886
|
+
_params,
|
|
887
|
+
options,
|
|
888
|
+
world,
|
|
889
|
+
type: Types.FILL,
|
|
890
|
+
text: "Fill element",
|
|
891
|
+
operation: "simpleClickType",
|
|
892
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
893
|
+
};
|
|
894
|
+
_preCommand(state, this);
|
|
929
895
|
const startTime = Date.now();
|
|
930
896
|
let timeout = 30000;
|
|
931
897
|
if (options && options.timeout) {
|
|
@@ -948,9 +914,14 @@ class StableBrowser {
|
|
|
948
914
|
}
|
|
949
915
|
}
|
|
950
916
|
catch (e) {
|
|
951
|
-
if (
|
|
917
|
+
if (performance.now() - startTime > timeout) {
|
|
952
918
|
// throw e;
|
|
953
|
-
|
|
919
|
+
try {
|
|
920
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
921
|
+
}
|
|
922
|
+
finally {
|
|
923
|
+
_commandFinally(state, this);
|
|
924
|
+
}
|
|
954
925
|
}
|
|
955
926
|
}
|
|
956
927
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -969,9 +940,9 @@ class StableBrowser {
|
|
|
969
940
|
};
|
|
970
941
|
try {
|
|
971
942
|
await _preCommand(state, this);
|
|
972
|
-
if (state.options && state.options.context) {
|
|
973
|
-
|
|
974
|
-
}
|
|
943
|
+
// if (state.options && state.options.context) {
|
|
944
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
945
|
+
// }
|
|
975
946
|
try {
|
|
976
947
|
await state.element.click();
|
|
977
948
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1009,9 +980,15 @@ class StableBrowser {
|
|
|
1009
980
|
// let element = await this._locate(selectors, info, _params);
|
|
1010
981
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1011
982
|
try {
|
|
1012
|
-
//
|
|
983
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
984
|
+
// console.log(`Highlighting while running from recorder`);
|
|
985
|
+
await this._highlightElements(element);
|
|
1013
986
|
await state.element.setChecked(checked);
|
|
1014
987
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
988
|
+
// await this._unHighlightElements(element);
|
|
989
|
+
// }
|
|
990
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
991
|
+
// await this._unHighlightElements(element);
|
|
1015
992
|
}
|
|
1016
993
|
catch (e) {
|
|
1017
994
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1050,6 +1027,7 @@ class StableBrowser {
|
|
|
1050
1027
|
await _preCommand(state, this);
|
|
1051
1028
|
try {
|
|
1052
1029
|
await state.element.hover();
|
|
1030
|
+
// await _screenshot(state, this);
|
|
1053
1031
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1054
1032
|
}
|
|
1055
1033
|
catch (e) {
|
|
@@ -1057,6 +1035,7 @@ class StableBrowser {
|
|
|
1057
1035
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1058
1036
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1059
1037
|
await state.element.hover({ timeout: 10000 });
|
|
1038
|
+
// await _screenshot(state, this);
|
|
1060
1039
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1061
1040
|
}
|
|
1062
1041
|
await _screenshot(state, this);
|
|
@@ -1331,7 +1310,12 @@ class StableBrowser {
|
|
|
1331
1310
|
await this.waitForPageLoad();
|
|
1332
1311
|
}
|
|
1333
1312
|
else if (enter === false) {
|
|
1334
|
-
|
|
1313
|
+
try {
|
|
1314
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1315
|
+
}
|
|
1316
|
+
catch (e) {
|
|
1317
|
+
// ignore
|
|
1318
|
+
}
|
|
1335
1319
|
//await this.page.keyboard.press("Tab");
|
|
1336
1320
|
}
|
|
1337
1321
|
else {
|
|
@@ -1383,15 +1367,17 @@ class StableBrowser {
|
|
|
1383
1367
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1384
1368
|
}
|
|
1385
1369
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1370
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1386
1371
|
_validateSelectors(selectors);
|
|
1387
1372
|
let screenshotId = null;
|
|
1388
1373
|
let screenshotPath = null;
|
|
1389
1374
|
if (!info.log) {
|
|
1390
1375
|
info.log = "";
|
|
1376
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1391
1377
|
}
|
|
1392
1378
|
info.operation = "getText";
|
|
1393
1379
|
info.selectors = selectors;
|
|
1394
|
-
let element = await this._locate(selectors, info, _params);
|
|
1380
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1395
1381
|
if (climb > 0) {
|
|
1396
1382
|
const climbArray = [];
|
|
1397
1383
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1410,6 +1396,18 @@ class StableBrowser {
|
|
|
1410
1396
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1411
1397
|
try {
|
|
1412
1398
|
await this._highlightElements(element);
|
|
1399
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1400
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1401
|
+
// this._highlightElements(element)
|
|
1402
|
+
// .then(async () => {
|
|
1403
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1404
|
+
// this._unhighlightElements(element).then(
|
|
1405
|
+
// () => {}
|
|
1406
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1407
|
+
// );
|
|
1408
|
+
// })
|
|
1409
|
+
// .catch(e);
|
|
1410
|
+
// }
|
|
1413
1411
|
const elementText = await element.innerText();
|
|
1414
1412
|
return {
|
|
1415
1413
|
text: elementText,
|
|
@@ -1421,7 +1419,7 @@ class StableBrowser {
|
|
|
1421
1419
|
}
|
|
1422
1420
|
catch (e) {
|
|
1423
1421
|
//await this.closeUnexpectedPopups();
|
|
1424
|
-
this.logger.info("no innerText will use textContent");
|
|
1422
|
+
this.logger.info("no innerText, will use textContent");
|
|
1425
1423
|
const elementText = await element.textContent();
|
|
1426
1424
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1427
1425
|
}
|
|
@@ -1481,6 +1479,8 @@ class StableBrowser {
|
|
|
1481
1479
|
}
|
|
1482
1480
|
}
|
|
1483
1481
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1482
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1483
|
+
const startTime = Date.now();
|
|
1484
1484
|
const state = {
|
|
1485
1485
|
selectors,
|
|
1486
1486
|
_params,
|
|
@@ -1507,62 +1507,54 @@ class StableBrowser {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
let foundObj = null;
|
|
1509
1509
|
try {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1517
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1518
|
-
if (dateAlternatives.date) {
|
|
1519
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1520
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1521
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1522
|
-
return state.info;
|
|
1510
|
+
while (Date.now() - startTime < timeout) {
|
|
1511
|
+
try {
|
|
1512
|
+
await _preCommand(state, this);
|
|
1513
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1514
|
+
if (foundObj && foundObj.element) {
|
|
1515
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1523
1516
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1517
|
+
await _screenshot(state, this);
|
|
1518
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1519
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1520
|
+
if (dateAlternatives.date) {
|
|
1521
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1522
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1523
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1524
|
+
return state.info;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
else if (numberAlternatives.number) {
|
|
1529
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1530
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1531
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1532
|
+
return state.info;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1531
1537
|
return state.info;
|
|
1532
1538
|
}
|
|
1533
1539
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
throw new Error("element doesn't contain text " + text);
|
|
1540
|
+
catch (e) {
|
|
1541
|
+
// Log error but continue retrying until timeout is reached
|
|
1542
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1543
|
+
}
|
|
1544
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1540
1545
|
}
|
|
1541
|
-
|
|
1546
|
+
state.info.foundText = foundObj?.text;
|
|
1547
|
+
state.info.value = foundObj?.value;
|
|
1548
|
+
throw new Error("element doesn't contain text " + text);
|
|
1542
1549
|
}
|
|
1543
1550
|
catch (e) {
|
|
1544
1551
|
await _commandError(state, e, this);
|
|
1552
|
+
throw e;
|
|
1545
1553
|
}
|
|
1546
1554
|
finally {
|
|
1547
1555
|
_commandFinally(state, this);
|
|
1548
1556
|
}
|
|
1549
1557
|
}
|
|
1550
|
-
_getDataFile(world = null) {
|
|
1551
|
-
let dataFile = null;
|
|
1552
|
-
if (world && world.reportFolder) {
|
|
1553
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1554
|
-
}
|
|
1555
|
-
else if (this.reportFolder) {
|
|
1556
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1557
|
-
}
|
|
1558
|
-
else if (this.context && this.context.reportFolder) {
|
|
1559
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1560
|
-
}
|
|
1561
|
-
else {
|
|
1562
|
-
dataFile = "data.json";
|
|
1563
|
-
}
|
|
1564
|
-
return dataFile;
|
|
1565
|
-
}
|
|
1566
1558
|
async waitForUserInput(message, world = null) {
|
|
1567
1559
|
if (!message) {
|
|
1568
1560
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1591,7 +1583,7 @@ class StableBrowser {
|
|
|
1591
1583
|
return;
|
|
1592
1584
|
}
|
|
1593
1585
|
// if data file exists, load it
|
|
1594
|
-
const dataFile =
|
|
1586
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1595
1587
|
let data = this.getTestData(world);
|
|
1596
1588
|
// merge the testData with the existing data
|
|
1597
1589
|
Object.assign(data, testData);
|
|
@@ -1694,7 +1686,7 @@ class StableBrowser {
|
|
|
1694
1686
|
}
|
|
1695
1687
|
}
|
|
1696
1688
|
getTestData(world = null) {
|
|
1697
|
-
const dataFile =
|
|
1689
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1698
1690
|
let data = {};
|
|
1699
1691
|
if (fs.existsSync(dataFile)) {
|
|
1700
1692
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1781,6 +1773,15 @@ class StableBrowser {
|
|
|
1781
1773
|
document.documentElement.clientWidth,
|
|
1782
1774
|
])));
|
|
1783
1775
|
let screenshotBuffer = null;
|
|
1776
|
+
// if (focusedElement) {
|
|
1777
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1778
|
+
// await this._unhighlightElements(focusedElement);
|
|
1779
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1780
|
+
// console.log(`Unhighlighted previous element`);
|
|
1781
|
+
// }
|
|
1782
|
+
// if (focusedElement) {
|
|
1783
|
+
// await this._highlightElements(focusedElement);
|
|
1784
|
+
// }
|
|
1784
1785
|
if (this.context.browserName === "chromium") {
|
|
1785
1786
|
const client = await playContext.newCDPSession(this.page);
|
|
1786
1787
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1802,6 +1803,10 @@ class StableBrowser {
|
|
|
1802
1803
|
else {
|
|
1803
1804
|
screenshotBuffer = await this.page.screenshot();
|
|
1804
1805
|
}
|
|
1806
|
+
// if (focusedElement) {
|
|
1807
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1808
|
+
// await this._unhighlightElements(focusedElement);
|
|
1809
|
+
// }
|
|
1805
1810
|
let image = await Jimp.read(screenshotBuffer);
|
|
1806
1811
|
// Get the image dimensions
|
|
1807
1812
|
const { width, height } = image.bitmap;
|
|
@@ -1814,6 +1819,7 @@ class StableBrowser {
|
|
|
1814
1819
|
else {
|
|
1815
1820
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1816
1821
|
}
|
|
1822
|
+
return screenshotBuffer;
|
|
1817
1823
|
}
|
|
1818
1824
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1819
1825
|
const state = {
|
|
@@ -1851,6 +1857,7 @@ class StableBrowser {
|
|
|
1851
1857
|
text: `Extract attribute from element`,
|
|
1852
1858
|
operation: "extractAttribute",
|
|
1853
1859
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1860
|
+
allowDisabled: true,
|
|
1854
1861
|
};
|
|
1855
1862
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1856
1863
|
try {
|
|
@@ -1872,6 +1879,7 @@ class StableBrowser {
|
|
|
1872
1879
|
state.info.value = state.value;
|
|
1873
1880
|
this.setTestData({ [variable]: state.value }, world);
|
|
1874
1881
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1882
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1875
1883
|
return state.info;
|
|
1876
1884
|
}
|
|
1877
1885
|
catch (e) {
|
|
@@ -1890,38 +1898,58 @@ class StableBrowser {
|
|
|
1890
1898
|
options,
|
|
1891
1899
|
world,
|
|
1892
1900
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1901
|
+
highlight: true,
|
|
1902
|
+
screenshot: true,
|
|
1893
1903
|
text: `Verify element attribute`,
|
|
1904
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1894
1905
|
operation: "verifyAttribute",
|
|
1895
1906
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1907
|
+
allowDisabled: true,
|
|
1896
1908
|
};
|
|
1897
1909
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1898
1910
|
let val;
|
|
1911
|
+
let expectedValue;
|
|
1899
1912
|
try {
|
|
1900
1913
|
await _preCommand(state, this);
|
|
1914
|
+
expectedValue = state.value;
|
|
1915
|
+
state.info.expectedValue = expectedValue;
|
|
1901
1916
|
switch (attribute) {
|
|
1902
1917
|
case "innerText":
|
|
1903
|
-
val = await state.element.innerText();
|
|
1918
|
+
val = String(await state.element.innerText());
|
|
1904
1919
|
break;
|
|
1905
1920
|
case "value":
|
|
1906
|
-
val = await state.element.inputValue();
|
|
1921
|
+
val = String(await state.element.inputValue());
|
|
1922
|
+
break;
|
|
1923
|
+
case "checked":
|
|
1924
|
+
val = String(await state.element.isChecked());
|
|
1925
|
+
break;
|
|
1926
|
+
case "disabled":
|
|
1927
|
+
val = String(await state.element.isDisabled());
|
|
1928
|
+
break;
|
|
1929
|
+
case "readOnly":
|
|
1930
|
+
const isEditable = await state.element.isEditable();
|
|
1931
|
+
val = String(!isEditable);
|
|
1907
1932
|
break;
|
|
1908
1933
|
default:
|
|
1909
|
-
val = await state.element.getAttribute(attribute);
|
|
1934
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1910
1935
|
break;
|
|
1911
1936
|
}
|
|
1937
|
+
state.info.value = val;
|
|
1912
1938
|
let regex;
|
|
1913
|
-
if (
|
|
1914
|
-
const patternBody =
|
|
1939
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1940
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1915
1941
|
regex = new RegExp(patternBody, "g");
|
|
1916
1942
|
}
|
|
1917
1943
|
else {
|
|
1918
|
-
const escapedPattern =
|
|
1944
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1919
1945
|
regex = new RegExp(escapedPattern, "g");
|
|
1920
1946
|
}
|
|
1921
1947
|
if (!val.match(regex)) {
|
|
1922
|
-
|
|
1948
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
1949
|
+
state.info.failCause.assertionFailed = true;
|
|
1950
|
+
state.info.failCause.lastError = errorMessage;
|
|
1951
|
+
throw new Error(errorMessage);
|
|
1923
1952
|
}
|
|
1924
|
-
state.info.value = val;
|
|
1925
1953
|
return state.info;
|
|
1926
1954
|
}
|
|
1927
1955
|
catch (e) {
|
|
@@ -1949,7 +1977,7 @@ class StableBrowser {
|
|
|
1949
1977
|
if (options && options.timeout) {
|
|
1950
1978
|
timeout = options.timeout;
|
|
1951
1979
|
}
|
|
1952
|
-
const serviceUrl =
|
|
1980
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1953
1981
|
const request = {
|
|
1954
1982
|
method: "POST",
|
|
1955
1983
|
url: serviceUrl,
|
|
@@ -2020,27 +2048,32 @@ class StableBrowser {
|
|
|
2020
2048
|
async _highlightElements(scope, css) {
|
|
2021
2049
|
try {
|
|
2022
2050
|
if (!scope) {
|
|
2051
|
+
// console.log(`Scope is not defined`);
|
|
2023
2052
|
return;
|
|
2024
2053
|
}
|
|
2025
2054
|
if (!css) {
|
|
2026
2055
|
scope
|
|
2027
2056
|
.evaluate((node) => {
|
|
2028
2057
|
if (node && node.style) {
|
|
2029
|
-
let
|
|
2030
|
-
|
|
2058
|
+
let originalOutline = node.style.outline;
|
|
2059
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2060
|
+
// node.__previousOutline = originalOutline;
|
|
2061
|
+
node.style.outline = "2px solid red";
|
|
2062
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2031
2063
|
if (window) {
|
|
2032
2064
|
window.addEventListener("beforeunload", function (e) {
|
|
2033
|
-
node.style.
|
|
2065
|
+
node.style.outline = originalOutline;
|
|
2034
2066
|
});
|
|
2035
2067
|
}
|
|
2036
2068
|
setTimeout(function () {
|
|
2037
|
-
node.style.
|
|
2069
|
+
node.style.outline = originalOutline;
|
|
2038
2070
|
}, 2000);
|
|
2039
2071
|
}
|
|
2040
2072
|
})
|
|
2041
2073
|
.then(() => { })
|
|
2042
2074
|
.catch((e) => {
|
|
2043
2075
|
// ignore
|
|
2076
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2044
2077
|
});
|
|
2045
2078
|
}
|
|
2046
2079
|
else {
|
|
@@ -2056,17 +2089,18 @@ class StableBrowser {
|
|
|
2056
2089
|
if (!element.style) {
|
|
2057
2090
|
return;
|
|
2058
2091
|
}
|
|
2059
|
-
|
|
2092
|
+
let originalOutline = element.style.outline;
|
|
2093
|
+
element.__previousOutline = originalOutline;
|
|
2060
2094
|
// Set the new border to be red and 2px solid
|
|
2061
|
-
element.style.
|
|
2095
|
+
element.style.outline = "2px solid red";
|
|
2062
2096
|
if (window) {
|
|
2063
2097
|
window.addEventListener("beforeunload", function (e) {
|
|
2064
|
-
element.style.
|
|
2098
|
+
element.style.outline = originalOutline;
|
|
2065
2099
|
});
|
|
2066
2100
|
}
|
|
2067
2101
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2068
2102
|
setTimeout(function () {
|
|
2069
|
-
element.style.
|
|
2103
|
+
element.style.outline = originalOutline;
|
|
2070
2104
|
}, 2000);
|
|
2071
2105
|
}
|
|
2072
2106
|
return;
|
|
@@ -2074,6 +2108,7 @@ class StableBrowser {
|
|
|
2074
2108
|
.then(() => { })
|
|
2075
2109
|
.catch((e) => {
|
|
2076
2110
|
// ignore
|
|
2111
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2077
2112
|
});
|
|
2078
2113
|
}
|
|
2079
2114
|
}
|
|
@@ -2081,6 +2116,54 @@ class StableBrowser {
|
|
|
2081
2116
|
console.debug(error);
|
|
2082
2117
|
}
|
|
2083
2118
|
}
|
|
2119
|
+
// async _unhighlightElements(scope, css) {
|
|
2120
|
+
// try {
|
|
2121
|
+
// if (!scope) {
|
|
2122
|
+
// return;
|
|
2123
|
+
// }
|
|
2124
|
+
// if (!css) {
|
|
2125
|
+
// scope
|
|
2126
|
+
// .evaluate((node) => {
|
|
2127
|
+
// if (node && node.style) {
|
|
2128
|
+
// if (!node.__previousOutline) {
|
|
2129
|
+
// node.style.outline = "";
|
|
2130
|
+
// } else {
|
|
2131
|
+
// node.style.outline = node.__previousOutline;
|
|
2132
|
+
// }
|
|
2133
|
+
// }
|
|
2134
|
+
// })
|
|
2135
|
+
// .then(() => {})
|
|
2136
|
+
// .catch((e) => {
|
|
2137
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2138
|
+
// });
|
|
2139
|
+
// } else {
|
|
2140
|
+
// scope
|
|
2141
|
+
// .evaluate(([css]) => {
|
|
2142
|
+
// if (!css) {
|
|
2143
|
+
// return;
|
|
2144
|
+
// }
|
|
2145
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2146
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2147
|
+
// let element = elements[i];
|
|
2148
|
+
// if (!element.style) {
|
|
2149
|
+
// return;
|
|
2150
|
+
// }
|
|
2151
|
+
// if (!element.__previousOutline) {
|
|
2152
|
+
// element.style.outline = "";
|
|
2153
|
+
// } else {
|
|
2154
|
+
// element.style.outline = element.__previousOutline;
|
|
2155
|
+
// }
|
|
2156
|
+
// }
|
|
2157
|
+
// })
|
|
2158
|
+
// .then(() => {})
|
|
2159
|
+
// .catch((e) => {
|
|
2160
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2161
|
+
// });
|
|
2162
|
+
// }
|
|
2163
|
+
// } catch (error) {
|
|
2164
|
+
// // console.debug(error);
|
|
2165
|
+
// }
|
|
2166
|
+
// }
|
|
2084
2167
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2085
2168
|
const startTime = Date.now();
|
|
2086
2169
|
let error = null;
|
|
@@ -2142,6 +2225,35 @@ class StableBrowser {
|
|
|
2142
2225
|
});
|
|
2143
2226
|
}
|
|
2144
2227
|
}
|
|
2228
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2229
|
+
const frames = this.page.frames();
|
|
2230
|
+
let results = [];
|
|
2231
|
+
let ignoreCase = false;
|
|
2232
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2233
|
+
if (dateAlternatives.date) {
|
|
2234
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2235
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2236
|
+
result.frame = frames[i];
|
|
2237
|
+
results.push(result);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
else if (numberAlternatives.number) {
|
|
2241
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2242
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2243
|
+
result.frame = frames[i];
|
|
2244
|
+
results.push(result);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
else {
|
|
2248
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2249
|
+
result.frame = frames[i];
|
|
2250
|
+
results.push(result);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
state.info.results = results;
|
|
2254
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2255
|
+
return resultWithElementsFound;
|
|
2256
|
+
}
|
|
2145
2257
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2146
2258
|
text = unEscapeString(text);
|
|
2147
2259
|
const state = {
|
|
@@ -2151,12 +2263,12 @@ class StableBrowser {
|
|
|
2151
2263
|
locate: false,
|
|
2152
2264
|
scroll: false,
|
|
2153
2265
|
highlight: false,
|
|
2154
|
-
type: Types.
|
|
2266
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2155
2267
|
text: `Verify text exists in page`,
|
|
2156
2268
|
operation: "verifyTextExistInPage",
|
|
2157
2269
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2158
2270
|
};
|
|
2159
|
-
const timeout = this.
|
|
2271
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2160
2272
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2161
2273
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2162
2274
|
if (newValue !== text) {
|
|
@@ -2169,31 +2281,15 @@ class StableBrowser {
|
|
|
2169
2281
|
await _preCommand(state, this);
|
|
2170
2282
|
state.info.text = text;
|
|
2171
2283
|
while (true) {
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
else if (numberAlternatives.number) {
|
|
2183
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2184
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2185
|
-
result.frame = frames[i];
|
|
2186
|
-
results.push(result);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
else {
|
|
2190
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2191
|
-
result.frame = frames[i];
|
|
2192
|
-
results.push(result);
|
|
2193
|
-
}
|
|
2284
|
+
let resultWithElementsFound = {
|
|
2285
|
+
length: 0,
|
|
2286
|
+
};
|
|
2287
|
+
try {
|
|
2288
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2289
|
+
}
|
|
2290
|
+
catch (error) {
|
|
2291
|
+
// ignore
|
|
2194
2292
|
}
|
|
2195
|
-
state.info.results = results;
|
|
2196
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2197
2293
|
if (resultWithElementsFound.length === 0) {
|
|
2198
2294
|
if (Date.now() - state.startTime > timeout) {
|
|
2199
2295
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2201,18 +2297,40 @@ class StableBrowser {
|
|
|
2201
2297
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2202
2298
|
continue;
|
|
2203
2299
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2300
|
+
try {
|
|
2301
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2302
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2303
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2304
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2305
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2306
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2307
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2308
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2309
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2310
|
+
// .then(async () => {
|
|
2311
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2312
|
+
// })
|
|
2313
|
+
// .catch(
|
|
2314
|
+
// (e) => {}
|
|
2315
|
+
// console.error(e)
|
|
2316
|
+
// );
|
|
2317
|
+
// });
|
|
2318
|
+
// }
|
|
2319
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2320
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2321
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2322
|
+
if (element) {
|
|
2323
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2324
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2325
|
+
// await _screenshot(state, this, element);
|
|
2326
|
+
}
|
|
2212
2327
|
}
|
|
2328
|
+
await _screenshot(state, this);
|
|
2329
|
+
return state.info;
|
|
2330
|
+
}
|
|
2331
|
+
catch (error) {
|
|
2332
|
+
console.error(error);
|
|
2213
2333
|
}
|
|
2214
|
-
await _screenshot(state, this);
|
|
2215
|
-
return state.info;
|
|
2216
2334
|
}
|
|
2217
2335
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2218
2336
|
}
|
|
@@ -2237,7 +2355,7 @@ class StableBrowser {
|
|
|
2237
2355
|
operation: "verifyTextNotExistInPage",
|
|
2238
2356
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2239
2357
|
};
|
|
2240
|
-
const timeout = this.
|
|
2358
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2241
2359
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2242
2360
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2243
2361
|
if (newValue !== text) {
|
|
@@ -2249,32 +2367,16 @@ class StableBrowser {
|
|
|
2249
2367
|
try {
|
|
2250
2368
|
await _preCommand(state, this);
|
|
2251
2369
|
state.info.text = text;
|
|
2370
|
+
let resultWithElementsFound = {
|
|
2371
|
+
length: null, // initial cannot be 0
|
|
2372
|
+
};
|
|
2252
2373
|
while (true) {
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2259
|
-
result.frame = frames[i];
|
|
2260
|
-
results.push(result);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
else if (numberAlternatives.number) {
|
|
2264
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2265
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2266
|
-
result.frame = frames[i];
|
|
2267
|
-
results.push(result);
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
else {
|
|
2271
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2272
|
-
result.frame = frames[i];
|
|
2273
|
-
results.push(result);
|
|
2274
|
-
}
|
|
2374
|
+
try {
|
|
2375
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2376
|
+
}
|
|
2377
|
+
catch (error) {
|
|
2378
|
+
// ignore
|
|
2275
2379
|
}
|
|
2276
|
-
state.info.results = results;
|
|
2277
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2278
2380
|
if (resultWithElementsFound.length === 0) {
|
|
2279
2381
|
await _screenshot(state, this);
|
|
2280
2382
|
return state.info;
|
|
@@ -2292,15 +2394,115 @@ class StableBrowser {
|
|
|
2292
2394
|
_commandFinally(state, this);
|
|
2293
2395
|
}
|
|
2294
2396
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2397
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2398
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2399
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2400
|
+
const state = {
|
|
2401
|
+
text_search: textToVerify,
|
|
2402
|
+
options,
|
|
2403
|
+
world,
|
|
2404
|
+
locate: false,
|
|
2405
|
+
scroll: false,
|
|
2406
|
+
highlight: false,
|
|
2407
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2408
|
+
text: `Verify text with relation to another text`,
|
|
2409
|
+
operation: "verify_text_with_relation",
|
|
2410
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2411
|
+
};
|
|
2412
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2413
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2414
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2415
|
+
if (newValue !== textAnchor) {
|
|
2416
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2417
|
+
textAnchor = newValue;
|
|
2418
|
+
}
|
|
2419
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2420
|
+
if (newValue !== textToVerify) {
|
|
2421
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2422
|
+
textToVerify = newValue;
|
|
2423
|
+
}
|
|
2424
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2425
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2426
|
+
let foundAncore = false;
|
|
2427
|
+
try {
|
|
2428
|
+
await _preCommand(state, this);
|
|
2429
|
+
state.info.text = textToVerify;
|
|
2430
|
+
let resultWithElementsFound = {
|
|
2431
|
+
length: 0,
|
|
2432
|
+
};
|
|
2433
|
+
while (true) {
|
|
2434
|
+
try {
|
|
2435
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2436
|
+
}
|
|
2437
|
+
catch (error) {
|
|
2438
|
+
// ignore
|
|
2439
|
+
}
|
|
2440
|
+
if (resultWithElementsFound.length === 0) {
|
|
2441
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2442
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2443
|
+
}
|
|
2444
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
try {
|
|
2448
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2449
|
+
foundAncore = true;
|
|
2450
|
+
const result = resultWithElementsFound[i];
|
|
2451
|
+
const token = result.randomToken;
|
|
2452
|
+
const frame = result.frame;
|
|
2453
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2454
|
+
const climbArray1 = [];
|
|
2455
|
+
for (let i = 0; i < climb; i++) {
|
|
2456
|
+
climbArray1.push("..");
|
|
2457
|
+
}
|
|
2458
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2459
|
+
css = css + " >> " + climbXpath;
|
|
2460
|
+
const count = await frame.locator(css).count();
|
|
2461
|
+
for (let j = 0; j < count; j++) {
|
|
2462
|
+
const continer = await frame.locator(css).nth(j);
|
|
2463
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2464
|
+
if (result.elementCount > 0) {
|
|
2465
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2466
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2467
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2468
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2469
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2470
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2471
|
+
// .then(async () => {
|
|
2472
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2473
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2474
|
+
// () => {}
|
|
2475
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2476
|
+
// );
|
|
2477
|
+
// })
|
|
2478
|
+
// .catch(e);
|
|
2479
|
+
// }
|
|
2480
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2481
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2482
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2483
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2484
|
+
if (element) {
|
|
2485
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2486
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2487
|
+
}
|
|
2488
|
+
await _screenshot(state, this);
|
|
2489
|
+
return state.info;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
catch (error) {
|
|
2495
|
+
console.error(error);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2299
2499
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
2500
|
+
catch (e) {
|
|
2501
|
+
await _commandError(state, e, this);
|
|
2502
|
+
}
|
|
2503
|
+
finally {
|
|
2504
|
+
_commandFinally(state, this);
|
|
2302
2505
|
}
|
|
2303
|
-
return serviceUrl;
|
|
2304
2506
|
}
|
|
2305
2507
|
async visualVerification(text, options = {}, world = null) {
|
|
2306
2508
|
const startTime = Date.now();
|
|
@@ -2316,14 +2518,17 @@ class StableBrowser {
|
|
|
2316
2518
|
throw new Error("TOKEN is not set");
|
|
2317
2519
|
}
|
|
2318
2520
|
try {
|
|
2319
|
-
let serviceUrl =
|
|
2521
|
+
let serviceUrl = _getServerUrl();
|
|
2320
2522
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2321
2523
|
info.screenshotPath = screenshotPath;
|
|
2322
2524
|
const screenshot = await this.takeScreenshot();
|
|
2323
|
-
|
|
2324
|
-
method: "
|
|
2525
|
+
let request = {
|
|
2526
|
+
method: "post",
|
|
2527
|
+
maxBodyLength: Infinity,
|
|
2325
2528
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2326
2529
|
headers: {
|
|
2530
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2531
|
+
"x-source": "aaa",
|
|
2327
2532
|
"Content-Type": "application/json",
|
|
2328
2533
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2329
2534
|
},
|
|
@@ -2332,7 +2537,7 @@ class StableBrowser {
|
|
|
2332
2537
|
screenshot: screenshot,
|
|
2333
2538
|
}),
|
|
2334
2539
|
};
|
|
2335
|
-
|
|
2540
|
+
const result = await axios.request(request);
|
|
2336
2541
|
if (result.data.status !== true) {
|
|
2337
2542
|
throw new Error("Visual validation failed");
|
|
2338
2543
|
}
|
|
@@ -2405,6 +2610,7 @@ class StableBrowser {
|
|
|
2405
2610
|
let screenshotPath = null;
|
|
2406
2611
|
const info = {};
|
|
2407
2612
|
info.log = "";
|
|
2613
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2408
2614
|
info.operation = "getTableData";
|
|
2409
2615
|
info.selectors = selectors;
|
|
2410
2616
|
try {
|
|
@@ -2480,7 +2686,7 @@ class StableBrowser {
|
|
|
2480
2686
|
info.operation = "analyzeTable";
|
|
2481
2687
|
info.selectors = selectors;
|
|
2482
2688
|
info.query = query;
|
|
2483
|
-
query =
|
|
2689
|
+
query = _fixUsingParams(query, _params);
|
|
2484
2690
|
info.query_fixed = query;
|
|
2485
2691
|
info.operator = operator;
|
|
2486
2692
|
info.value = value;
|
|
@@ -2625,6 +2831,32 @@ class StableBrowser {
|
|
|
2625
2831
|
}
|
|
2626
2832
|
return timeout;
|
|
2627
2833
|
}
|
|
2834
|
+
_getFindElementTimeout(options) {
|
|
2835
|
+
if (options && options.timeout) {
|
|
2836
|
+
return options.timeout;
|
|
2837
|
+
}
|
|
2838
|
+
if (this.configuration.find_element_timeout) {
|
|
2839
|
+
return this.configuration.find_element_timeout;
|
|
2840
|
+
}
|
|
2841
|
+
return 30000;
|
|
2842
|
+
}
|
|
2843
|
+
async saveStoreState(path = null, world = null) {
|
|
2844
|
+
const storageState = await this.page.context().storageState();
|
|
2845
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2846
|
+
if (path) {
|
|
2847
|
+
// save { storageState: storageState } into the path
|
|
2848
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2849
|
+
}
|
|
2850
|
+
else {
|
|
2851
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
async restoreSaveState(path = null, world = null) {
|
|
2855
|
+
await refreshBrowser(this, path, world);
|
|
2856
|
+
this.registerEventListeners(this.context);
|
|
2857
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2858
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2859
|
+
}
|
|
2628
2860
|
async waitForPageLoad(options = {}, world = null) {
|
|
2629
2861
|
let timeout = this._getLoadTimeout(options);
|
|
2630
2862
|
const promiseArray = [];
|
|
@@ -2810,14 +3042,22 @@ class StableBrowser {
|
|
|
2810
3042
|
}
|
|
2811
3043
|
}
|
|
2812
3044
|
async beforeStep(world, step) {
|
|
2813
|
-
this.stepName = step.pickleStep.text;
|
|
2814
|
-
this.logger.info("step: " + this.stepName);
|
|
2815
3045
|
if (this.stepIndex === undefined) {
|
|
2816
3046
|
this.stepIndex = 0;
|
|
2817
3047
|
}
|
|
2818
3048
|
else {
|
|
2819
3049
|
this.stepIndex++;
|
|
2820
3050
|
}
|
|
3051
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3052
|
+
this.stepName = step.pickleStep.text;
|
|
3053
|
+
this.logger.info("step: " + this.stepName);
|
|
3054
|
+
}
|
|
3055
|
+
else if (step && step.text) {
|
|
3056
|
+
this.stepName = step.text;
|
|
3057
|
+
}
|
|
3058
|
+
else {
|
|
3059
|
+
this.stepName = "step " + this.stepIndex;
|
|
3060
|
+
}
|
|
2821
3061
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2822
3062
|
if (this.context.browserObject.context) {
|
|
2823
3063
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2847,156 +3087,5 @@ function createTimedPromise(promise, label) {
|
|
|
2847
3087
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2848
3088
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2849
3089
|
}
|
|
2850
|
-
const KEYBOARD_EVENTS = [
|
|
2851
|
-
"ALT",
|
|
2852
|
-
"AltGraph",
|
|
2853
|
-
"CapsLock",
|
|
2854
|
-
"Control",
|
|
2855
|
-
"Fn",
|
|
2856
|
-
"FnLock",
|
|
2857
|
-
"Hyper",
|
|
2858
|
-
"Meta",
|
|
2859
|
-
"NumLock",
|
|
2860
|
-
"ScrollLock",
|
|
2861
|
-
"Shift",
|
|
2862
|
-
"Super",
|
|
2863
|
-
"Symbol",
|
|
2864
|
-
"SymbolLock",
|
|
2865
|
-
"Enter",
|
|
2866
|
-
"Tab",
|
|
2867
|
-
"ArrowDown",
|
|
2868
|
-
"ArrowLeft",
|
|
2869
|
-
"ArrowRight",
|
|
2870
|
-
"ArrowUp",
|
|
2871
|
-
"End",
|
|
2872
|
-
"Home",
|
|
2873
|
-
"PageDown",
|
|
2874
|
-
"PageUp",
|
|
2875
|
-
"Backspace",
|
|
2876
|
-
"Clear",
|
|
2877
|
-
"Copy",
|
|
2878
|
-
"CrSel",
|
|
2879
|
-
"Cut",
|
|
2880
|
-
"Delete",
|
|
2881
|
-
"EraseEof",
|
|
2882
|
-
"ExSel",
|
|
2883
|
-
"Insert",
|
|
2884
|
-
"Paste",
|
|
2885
|
-
"Redo",
|
|
2886
|
-
"Undo",
|
|
2887
|
-
"Accept",
|
|
2888
|
-
"Again",
|
|
2889
|
-
"Attn",
|
|
2890
|
-
"Cancel",
|
|
2891
|
-
"ContextMenu",
|
|
2892
|
-
"Escape",
|
|
2893
|
-
"Execute",
|
|
2894
|
-
"Find",
|
|
2895
|
-
"Finish",
|
|
2896
|
-
"Help",
|
|
2897
|
-
"Pause",
|
|
2898
|
-
"Play",
|
|
2899
|
-
"Props",
|
|
2900
|
-
"Select",
|
|
2901
|
-
"ZoomIn",
|
|
2902
|
-
"ZoomOut",
|
|
2903
|
-
"BrightnessDown",
|
|
2904
|
-
"BrightnessUp",
|
|
2905
|
-
"Eject",
|
|
2906
|
-
"LogOff",
|
|
2907
|
-
"Power",
|
|
2908
|
-
"PowerOff",
|
|
2909
|
-
"PrintScreen",
|
|
2910
|
-
"Hibernate",
|
|
2911
|
-
"Standby",
|
|
2912
|
-
"WakeUp",
|
|
2913
|
-
"AllCandidates",
|
|
2914
|
-
"Alphanumeric",
|
|
2915
|
-
"CodeInput",
|
|
2916
|
-
"Compose",
|
|
2917
|
-
"Convert",
|
|
2918
|
-
"Dead",
|
|
2919
|
-
"FinalMode",
|
|
2920
|
-
"GroupFirst",
|
|
2921
|
-
"GroupLast",
|
|
2922
|
-
"GroupNext",
|
|
2923
|
-
"GroupPrevious",
|
|
2924
|
-
"ModeChange",
|
|
2925
|
-
"NextCandidate",
|
|
2926
|
-
"NonConvert",
|
|
2927
|
-
"PreviousCandidate",
|
|
2928
|
-
"Process",
|
|
2929
|
-
"SingleCandidate",
|
|
2930
|
-
"HangulMode",
|
|
2931
|
-
"HanjaMode",
|
|
2932
|
-
"JunjaMode",
|
|
2933
|
-
"Eisu",
|
|
2934
|
-
"Hankaku",
|
|
2935
|
-
"Hiragana",
|
|
2936
|
-
"HiraganaKatakana",
|
|
2937
|
-
"KanaMode",
|
|
2938
|
-
"KanjiMode",
|
|
2939
|
-
"Katakana",
|
|
2940
|
-
"Romaji",
|
|
2941
|
-
"Zenkaku",
|
|
2942
|
-
"ZenkakuHanaku",
|
|
2943
|
-
"F1",
|
|
2944
|
-
"F2",
|
|
2945
|
-
"F3",
|
|
2946
|
-
"F4",
|
|
2947
|
-
"F5",
|
|
2948
|
-
"F6",
|
|
2949
|
-
"F7",
|
|
2950
|
-
"F8",
|
|
2951
|
-
"F9",
|
|
2952
|
-
"F10",
|
|
2953
|
-
"F11",
|
|
2954
|
-
"F12",
|
|
2955
|
-
"Soft1",
|
|
2956
|
-
"Soft2",
|
|
2957
|
-
"Soft3",
|
|
2958
|
-
"Soft4",
|
|
2959
|
-
"ChannelDown",
|
|
2960
|
-
"ChannelUp",
|
|
2961
|
-
"Close",
|
|
2962
|
-
"MailForward",
|
|
2963
|
-
"MailReply",
|
|
2964
|
-
"MailSend",
|
|
2965
|
-
"MediaFastForward",
|
|
2966
|
-
"MediaPause",
|
|
2967
|
-
"MediaPlay",
|
|
2968
|
-
"MediaPlayPause",
|
|
2969
|
-
"MediaRecord",
|
|
2970
|
-
"MediaRewind",
|
|
2971
|
-
"MediaStop",
|
|
2972
|
-
"MediaTrackNext",
|
|
2973
|
-
"MediaTrackPrevious",
|
|
2974
|
-
"AudioBalanceLeft",
|
|
2975
|
-
"AudioBalanceRight",
|
|
2976
|
-
"AudioBassBoostDown",
|
|
2977
|
-
"AudioBassBoostToggle",
|
|
2978
|
-
"AudioBassBoostUp",
|
|
2979
|
-
"AudioFaderFront",
|
|
2980
|
-
"AudioFaderRear",
|
|
2981
|
-
"AudioSurroundModeNext",
|
|
2982
|
-
"AudioTrebleDown",
|
|
2983
|
-
"AudioTrebleUp",
|
|
2984
|
-
"AudioVolumeDown",
|
|
2985
|
-
"AudioVolumeMute",
|
|
2986
|
-
"AudioVolumeUp",
|
|
2987
|
-
"MicrophoneToggle",
|
|
2988
|
-
"MicrophoneVolumeDown",
|
|
2989
|
-
"MicrophoneVolumeMute",
|
|
2990
|
-
"MicrophoneVolumeUp",
|
|
2991
|
-
"TV",
|
|
2992
|
-
"TV3DMode",
|
|
2993
|
-
"TVAntennaCable",
|
|
2994
|
-
"TVAudioDescription",
|
|
2995
|
-
];
|
|
2996
|
-
function unEscapeString(str) {
|
|
2997
|
-
const placeholder = "__NEWLINE__";
|
|
2998
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2999
|
-
return str;
|
|
3000
|
-
}
|
|
3001
3090
|
export { StableBrowser };
|
|
3002
3091
|
//# sourceMappingURL=stable_browser.js.map
|