automation_model 1.0.520-dev → 1.0.520-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 +90 -88
- 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 +3 -2
- package/lib/browser_manager.js +70 -27
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +2 -2
- package/lib/command_common.js +77 -17
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +20 -2
- 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/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 +15 -4
- 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 +51 -12
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +54 -17
- package/lib/stable_browser.js +624 -513
- 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/utils.d.ts +13 -1
- package/lib/utils.js +344 -6
- package/lib/utils.js.map +1 -1
- package/package.json +6 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -7,18 +7,19 @@ import path from "path";
|
|
|
7
7
|
import reg_parser from "regex-parser";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
|
-
import drawRectangle from "./drawRect.js";
|
|
11
10
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
11
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
12
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
14
13
|
import csv from "csv-parser";
|
|
15
14
|
import { Readable } from "node:stream";
|
|
16
15
|
import readline from "readline";
|
|
17
16
|
import { getContext } from "./init_browser.js";
|
|
18
17
|
import { locate_element } from "./locate_element.js";
|
|
18
|
+
import { randomUUID } from "crypto";
|
|
19
19
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
20
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
|
-
|
|
21
|
+
import { LocatorLog } from "./locator_log.js";
|
|
22
|
+
export const Types = {
|
|
22
23
|
CLICK: "click_element",
|
|
23
24
|
NAVIGATE: "navigate",
|
|
24
25
|
FILL: "fill_element",
|
|
@@ -29,6 +30,8 @@ const Types = {
|
|
|
29
30
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
31
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
32
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
33
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
35
|
ANALYZE_TABLE: "analyze_table",
|
|
33
36
|
SELECT: "select_combobox",
|
|
34
37
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -45,8 +48,13 @@ const Types = {
|
|
|
45
48
|
LOAD_DATA: "load_data",
|
|
46
49
|
SET_INPUT: "set_input",
|
|
47
50
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
51
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
52
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
48
53
|
};
|
|
49
54
|
export const apps = {};
|
|
55
|
+
const formatElementName = (elementName) => {
|
|
56
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
57
|
+
};
|
|
50
58
|
class StableBrowser {
|
|
51
59
|
browser;
|
|
52
60
|
page;
|
|
@@ -58,6 +66,7 @@ class StableBrowser {
|
|
|
58
66
|
networkLogger = null;
|
|
59
67
|
configuration = null;
|
|
60
68
|
appName = "main";
|
|
69
|
+
tags = null;
|
|
61
70
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
62
71
|
this.browser = browser;
|
|
63
72
|
this.page = page;
|
|
@@ -88,17 +97,17 @@ class StableBrowser {
|
|
|
88
97
|
catch (e) {
|
|
89
98
|
this.logger.error("unable to read ai_config.json");
|
|
90
99
|
}
|
|
91
|
-
context.pageLoading = { status: false };
|
|
92
|
-
context.pages = [this.page];
|
|
93
100
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
94
101
|
this.world = world;
|
|
102
|
+
context.pages = [this.page];
|
|
103
|
+
context.pageLoading = { status: false };
|
|
95
104
|
this.registerEventListeners(this.context);
|
|
96
105
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
97
106
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
98
107
|
}
|
|
99
108
|
registerEventListeners(context) {
|
|
100
109
|
this.registerConsoleLogListener(this.page, context);
|
|
101
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
110
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
102
111
|
if (!context.pageLoading) {
|
|
103
112
|
context.pageLoading = { status: false };
|
|
104
113
|
}
|
|
@@ -143,7 +152,7 @@ class StableBrowser {
|
|
|
143
152
|
if (this.appName === appName) {
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
146
|
-
let
|
|
155
|
+
let navigate = false;
|
|
147
156
|
if (!apps[appName]) {
|
|
148
157
|
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
149
158
|
newContextCreated = true;
|
|
@@ -154,32 +163,15 @@ class StableBrowser {
|
|
|
154
163
|
};
|
|
155
164
|
}
|
|
156
165
|
const tempContext = {};
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
_copyContext(this, tempContext);
|
|
167
|
+
_copyContext(apps[appName], this);
|
|
159
168
|
apps[this.appName] = tempContext;
|
|
160
169
|
this.appName = appName;
|
|
161
|
-
if (
|
|
162
|
-
this.registerEventListeners(this.context);
|
|
170
|
+
if (navigate) {
|
|
163
171
|
await this.goto(this.context.environment.baseUrl);
|
|
164
172
|
await this.waitForPageLoad();
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
|
-
_copyContext(from, to) {
|
|
168
|
-
to.browser = from.browser;
|
|
169
|
-
to.page = from.page;
|
|
170
|
-
to.context = from.context;
|
|
171
|
-
}
|
|
172
|
-
getWebLogFile(logFolder) {
|
|
173
|
-
if (!fs.existsSync(logFolder)) {
|
|
174
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
175
|
-
}
|
|
176
|
-
let nextIndex = 1;
|
|
177
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
178
|
-
nextIndex++;
|
|
179
|
-
}
|
|
180
|
-
const fileName = nextIndex + ".json";
|
|
181
|
-
return path.join(logFolder, fileName);
|
|
182
|
-
}
|
|
183
175
|
registerConsoleLogListener(page, context) {
|
|
184
176
|
if (!this.context.webLogger) {
|
|
185
177
|
this.context.webLogger = [];
|
|
@@ -243,55 +235,48 @@ class StableBrowser {
|
|
|
243
235
|
// async closeUnexpectedPopups() {
|
|
244
236
|
// await closeUnexpectedPopups(this.page);
|
|
245
237
|
// }
|
|
246
|
-
async goto(url) {
|
|
238
|
+
async goto(url, world = null) {
|
|
247
239
|
if (!url.startsWith("http")) {
|
|
248
240
|
url = "https://" + url;
|
|
249
241
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
242
|
+
const state = {
|
|
243
|
+
value: url,
|
|
244
|
+
world: world,
|
|
245
|
+
type: Types.NAVIGATE,
|
|
246
|
+
text: `Navigate Page to: ${url}`,
|
|
247
|
+
operation: "goto",
|
|
248
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
249
|
+
info: {},
|
|
250
|
+
locate: false,
|
|
251
|
+
scroll: false,
|
|
252
|
+
screenshot: false,
|
|
253
|
+
highlight: false,
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
await _preCommand(state, this, world);
|
|
257
|
+
await this.page.goto(url, {
|
|
258
|
+
timeout: 60000,
|
|
259
|
+
});
|
|
260
|
+
await _screenshot(state, this);
|
|
257
261
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// remove the _ prefix
|
|
262
|
-
regValue = key.substring(1);
|
|
263
|
-
}
|
|
264
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
262
|
+
catch (error) {
|
|
263
|
+
console.error("Error on goto", error);
|
|
264
|
+
_commandError(state, error, this);
|
|
265
265
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
269
|
-
// check if not null
|
|
270
|
-
if (!locator) {
|
|
271
|
-
return locator;
|
|
266
|
+
finally {
|
|
267
|
+
_commandFinally(state, this);
|
|
272
268
|
}
|
|
273
|
-
// clone the locator
|
|
274
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
275
|
-
this.scanAndManipulate(locator, _params);
|
|
276
|
-
return locator;
|
|
277
269
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
else if (this._isObject(currentObj[key])) {
|
|
288
|
-
// Recursively scan nested objects
|
|
289
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
270
|
+
async _getLocator(locator, scope, _params) {
|
|
271
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
272
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
273
|
+
for (let key in locator) {
|
|
274
|
+
if (typeof locator[key] !== "string")
|
|
275
|
+
continue;
|
|
276
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
277
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
290
278
|
}
|
|
291
279
|
}
|
|
292
|
-
}
|
|
293
|
-
_getLocator(locator, scope, _params) {
|
|
294
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
295
280
|
let locatorReturn;
|
|
296
281
|
if (locator.role) {
|
|
297
282
|
if (locator.role[1].nameReg) {
|
|
@@ -299,7 +284,7 @@ class StableBrowser {
|
|
|
299
284
|
delete locator.role[1].nameReg;
|
|
300
285
|
}
|
|
301
286
|
// if (locator.role[1].name) {
|
|
302
|
-
// locator.role[1].name =
|
|
287
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
303
288
|
// }
|
|
304
289
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
305
290
|
}
|
|
@@ -342,140 +327,54 @@ class StableBrowser {
|
|
|
342
327
|
if (css && css.locator) {
|
|
343
328
|
css = css.locator;
|
|
344
329
|
}
|
|
345
|
-
let result = await this._locateElementByText(scope,
|
|
330
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
346
331
|
if (result.elementCount === 0) {
|
|
347
332
|
return;
|
|
348
333
|
}
|
|
349
|
-
let textElementCss = "[data-blinq-id
|
|
334
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
350
335
|
// css climb to parent element
|
|
351
336
|
const climbArray = [];
|
|
352
337
|
for (let i = 0; i < climb; i++) {
|
|
353
338
|
climbArray.push("..");
|
|
354
339
|
}
|
|
355
340
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return new RegExp(pattern, flags);
|
|
379
|
-
}
|
|
380
|
-
document.getRegex = getRegex;
|
|
381
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
382
|
-
// Check and add the element if it has a shadow root
|
|
383
|
-
if (element.shadowRoot) {
|
|
384
|
-
result.push(element);
|
|
385
|
-
// Also search within the shadow root
|
|
386
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
387
|
-
}
|
|
388
|
-
// Iterate over child nodes
|
|
389
|
-
element.childNodes.forEach((child) => {
|
|
390
|
-
// Recursively call the function for each child node
|
|
391
|
-
document.collectAllShadowDomElements(child, result);
|
|
392
|
-
});
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
396
|
-
if (!tag) {
|
|
397
|
-
tag = "*:not(script, style, head)";
|
|
398
|
-
}
|
|
399
|
-
let regexpSearch = document.getRegex(text);
|
|
400
|
-
if (regexpSearch) {
|
|
401
|
-
regex = true;
|
|
402
|
-
}
|
|
403
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
404
|
-
let shadowHosts = [];
|
|
405
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
406
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
407
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
408
|
-
if (!shadowElement) {
|
|
409
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
413
|
-
elements = elements.concat(shadowElements);
|
|
414
|
-
}
|
|
415
|
-
let randomToken = null;
|
|
416
|
-
const foundElements = [];
|
|
417
|
-
if (regex) {
|
|
418
|
-
if (!regexpSearch) {
|
|
419
|
-
regexpSearch = new RegExp(text, "im");
|
|
420
|
-
}
|
|
421
|
-
for (let i = 0; i < elements.length; i++) {
|
|
422
|
-
const element = elements[i];
|
|
423
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
424
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
425
|
-
foundElements.push(element);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
text = text.trim();
|
|
431
|
-
for (let i = 0; i < elements.length; i++) {
|
|
432
|
-
const element = elements[i];
|
|
433
|
-
if (partial) {
|
|
434
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
435
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
436
|
-
foundElements.push(element);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
441
|
-
(element.value && element.value === text)) {
|
|
442
|
-
foundElements.push(element);
|
|
443
|
-
}
|
|
341
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
342
|
+
if (css) {
|
|
343
|
+
resultCss = resultCss + " >> " + css;
|
|
344
|
+
}
|
|
345
|
+
return resultCss;
|
|
346
|
+
}
|
|
347
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
348
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
349
|
+
const locator = scope.locator(query);
|
|
350
|
+
const count = await locator.count();
|
|
351
|
+
if (!tag1) {
|
|
352
|
+
tag1 = "*";
|
|
353
|
+
}
|
|
354
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
355
|
+
let tagCount = 0;
|
|
356
|
+
for (let i = 0; i < count; i++) {
|
|
357
|
+
const element = locator.nth(i);
|
|
358
|
+
// check if the tag matches
|
|
359
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
360
|
+
if (!tag.startsWith("*")) {
|
|
361
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
362
|
+
return false;
|
|
444
363
|
}
|
|
445
364
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
449
|
-
let element = foundElements[i];
|
|
450
|
-
let hasChild = false;
|
|
451
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
452
|
-
if (i === j) {
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
if (isParent(element, foundElements[j])) {
|
|
456
|
-
hasChild = true;
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (!hasChild) {
|
|
461
|
-
noChildElements.push(element);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
let elementCount = 0;
|
|
465
|
-
if (noChildElements.length > 0) {
|
|
466
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
467
|
-
if (randomToken === null) {
|
|
468
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
469
|
-
}
|
|
470
|
-
let element = noChildElements[i];
|
|
471
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
472
|
-
elementCount++;
|
|
365
|
+
if (!el.setAttribute) {
|
|
366
|
+
el = el.parentElement;
|
|
473
367
|
}
|
|
368
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
369
|
+
return true;
|
|
370
|
+
}, [tag1, randomToken]))) {
|
|
371
|
+
continue;
|
|
474
372
|
}
|
|
475
|
-
|
|
476
|
-
}
|
|
373
|
+
tagCount++;
|
|
374
|
+
}
|
|
375
|
+
return { elementCount: tagCount, randomToken };
|
|
477
376
|
}
|
|
478
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
377
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
479
378
|
if (!info) {
|
|
480
379
|
info = {};
|
|
481
380
|
}
|
|
@@ -484,10 +383,13 @@ class StableBrowser {
|
|
|
484
383
|
}
|
|
485
384
|
if (!info.log) {
|
|
486
385
|
info.log = "";
|
|
386
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
487
387
|
}
|
|
488
388
|
let locatorSearch = selectorHierarchy[index];
|
|
389
|
+
let originalLocatorSearch = "";
|
|
489
390
|
try {
|
|
490
|
-
|
|
391
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
392
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
491
393
|
}
|
|
492
394
|
catch (e) {
|
|
493
395
|
console.error(e);
|
|
@@ -495,30 +397,31 @@ class StableBrowser {
|
|
|
495
397
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
496
398
|
let locator = null;
|
|
497
399
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
498
|
-
|
|
400
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
401
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
499
402
|
if (!locatorString) {
|
|
500
403
|
info.failCause.textNotFound = true;
|
|
501
|
-
info.failCause.lastError =
|
|
404
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
502
405
|
return;
|
|
503
406
|
}
|
|
504
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
407
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
505
408
|
}
|
|
506
409
|
else if (locatorSearch.text) {
|
|
507
|
-
let text =
|
|
508
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
410
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
411
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
509
412
|
if (result.elementCount === 0) {
|
|
510
413
|
info.failCause.textNotFound = true;
|
|
511
|
-
info.failCause.lastError =
|
|
414
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
512
415
|
return;
|
|
513
416
|
}
|
|
514
|
-
locatorSearch.css = "[data-blinq-id
|
|
417
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
515
418
|
if (locatorSearch.childCss) {
|
|
516
419
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
517
420
|
}
|
|
518
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
421
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
519
422
|
}
|
|
520
423
|
else {
|
|
521
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
424
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
522
425
|
}
|
|
523
426
|
// let cssHref = false;
|
|
524
427
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -531,18 +434,27 @@ class StableBrowser {
|
|
|
531
434
|
//info.log += "total elements found " + count + "\n";
|
|
532
435
|
//let visibleCount = 0;
|
|
533
436
|
let visibleLocator = null;
|
|
534
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
437
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
535
438
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
439
|
+
if (info.locatorLog) {
|
|
440
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
441
|
+
}
|
|
536
442
|
return;
|
|
537
443
|
}
|
|
444
|
+
if (info.locatorLog && count === 0) {
|
|
445
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
446
|
+
}
|
|
538
447
|
for (let j = 0; j < count; j++) {
|
|
539
448
|
let visible = await locator.nth(j).isVisible();
|
|
540
449
|
const enabled = await locator.nth(j).isEnabled();
|
|
541
450
|
if (!visibleOnly) {
|
|
542
451
|
visible = true;
|
|
543
452
|
}
|
|
544
|
-
if (visible && enabled) {
|
|
453
|
+
if (visible && (allowDisabled || enabled)) {
|
|
545
454
|
foundLocators.push(locator.nth(j));
|
|
455
|
+
if (info.locatorLog) {
|
|
456
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
457
|
+
}
|
|
546
458
|
}
|
|
547
459
|
else {
|
|
548
460
|
info.failCause.visible = visible;
|
|
@@ -550,8 +462,16 @@ class StableBrowser {
|
|
|
550
462
|
if (!info.printMessages) {
|
|
551
463
|
info.printMessages = {};
|
|
552
464
|
}
|
|
465
|
+
if (info.locatorLog && !visible) {
|
|
466
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
467
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
468
|
+
}
|
|
469
|
+
if (info.locatorLog && !enabled) {
|
|
470
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
471
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
472
|
+
}
|
|
553
473
|
if (!info.printMessages[j.toString()]) {
|
|
554
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
474
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
555
475
|
info.printMessages[j.toString()] = true;
|
|
556
476
|
}
|
|
557
477
|
}
|
|
@@ -567,7 +487,7 @@ class StableBrowser {
|
|
|
567
487
|
if (!info) {
|
|
568
488
|
info = {};
|
|
569
489
|
}
|
|
570
|
-
info.log += "scan for popup handlers" + "\n";
|
|
490
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
571
491
|
const handlerGroup = [];
|
|
572
492
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
573
493
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -594,16 +514,28 @@ class StableBrowser {
|
|
|
594
514
|
}
|
|
595
515
|
if (result.foundElements.length > 0) {
|
|
596
516
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
517
|
+
try {
|
|
518
|
+
await scope?.evaluate(() => {
|
|
519
|
+
window.__isClosingPopups = true;
|
|
520
|
+
});
|
|
521
|
+
await dialogCloseLocator.click();
|
|
522
|
+
// wait for the dialog to close
|
|
523
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
524
|
+
}
|
|
525
|
+
catch (e) {
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
await scope?.evaluate(() => {
|
|
529
|
+
window.__isClosingPopups = false;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
600
532
|
return { rerun: true };
|
|
601
533
|
}
|
|
602
534
|
}
|
|
603
535
|
}
|
|
604
536
|
return { rerun: false };
|
|
605
537
|
}
|
|
606
|
-
async _locate(selectors, info, _params, timeout) {
|
|
538
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
607
539
|
if (!timeout) {
|
|
608
540
|
timeout = 30000;
|
|
609
541
|
}
|
|
@@ -613,7 +545,7 @@ class StableBrowser {
|
|
|
613
545
|
let selector = selectors.locators[j];
|
|
614
546
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
615
547
|
}
|
|
616
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
548
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
617
549
|
if (!element.rerun) {
|
|
618
550
|
return element;
|
|
619
551
|
}
|
|
@@ -626,6 +558,7 @@ class StableBrowser {
|
|
|
626
558
|
info.failCause = {};
|
|
627
559
|
info.log = "";
|
|
628
560
|
}
|
|
561
|
+
let startTime = Date.now();
|
|
629
562
|
let scope = this.page;
|
|
630
563
|
if (selectors.frame) {
|
|
631
564
|
return selectors.frame;
|
|
@@ -656,9 +589,11 @@ class StableBrowser {
|
|
|
656
589
|
}
|
|
657
590
|
return framescope;
|
|
658
591
|
};
|
|
592
|
+
let fLocator = null;
|
|
659
593
|
while (true) {
|
|
660
594
|
let frameFound = false;
|
|
661
595
|
if (selectors.nestFrmLoc) {
|
|
596
|
+
fLocator = selectors.nestFrmLoc;
|
|
662
597
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
663
598
|
frameFound = true;
|
|
664
599
|
break;
|
|
@@ -667,6 +602,7 @@ class StableBrowser {
|
|
|
667
602
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
668
603
|
let frameLocator = selectors.frameLocators[i];
|
|
669
604
|
if (frameLocator.css) {
|
|
605
|
+
fLocator = frameLocator.css;
|
|
670
606
|
scope = scope.frameLocator(frameLocator.css);
|
|
671
607
|
frameFound = true;
|
|
672
608
|
break;
|
|
@@ -674,18 +610,25 @@ class StableBrowser {
|
|
|
674
610
|
}
|
|
675
611
|
}
|
|
676
612
|
if (!frameFound && selectors.iframe_src) {
|
|
613
|
+
fLocator = selectors.iframe_src;
|
|
677
614
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
678
615
|
}
|
|
679
616
|
if (!scope) {
|
|
680
|
-
info
|
|
681
|
-
|
|
617
|
+
if (info && info.locatorLog) {
|
|
618
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
619
|
+
}
|
|
620
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
621
|
+
if (Date.now() - startTime > timeout) {
|
|
682
622
|
info.failCause.iframeNotFound = true;
|
|
683
|
-
info.failCause.lastError =
|
|
623
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
684
624
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
685
625
|
}
|
|
686
626
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
687
627
|
}
|
|
688
628
|
else {
|
|
629
|
+
if (info && info.locatorLog) {
|
|
630
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
631
|
+
}
|
|
689
632
|
break;
|
|
690
633
|
}
|
|
691
634
|
}
|
|
@@ -702,16 +645,18 @@ class StableBrowser {
|
|
|
702
645
|
return bodyContent;
|
|
703
646
|
});
|
|
704
647
|
}
|
|
705
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
648
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
706
649
|
if (!info) {
|
|
707
650
|
info = {};
|
|
708
651
|
info.failCause = {};
|
|
709
652
|
info.log = "";
|
|
653
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
710
654
|
}
|
|
711
655
|
let highPriorityTimeout = 5000;
|
|
712
656
|
let visibleOnlyTimeout = 6000;
|
|
713
|
-
let startTime =
|
|
657
|
+
let startTime = Date.now();
|
|
714
658
|
let locatorsCount = 0;
|
|
659
|
+
let lazy_scroll = false;
|
|
715
660
|
//let arrayMode = Array.isArray(selectors);
|
|
716
661
|
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
717
662
|
let selectorsLocators = null;
|
|
@@ -749,17 +694,17 @@ class StableBrowser {
|
|
|
749
694
|
}
|
|
750
695
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
751
696
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
752
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
697
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
753
698
|
if (result.foundElements.length === 0) {
|
|
754
699
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
755
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
700
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
756
701
|
}
|
|
757
702
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
758
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
703
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
759
704
|
}
|
|
760
705
|
else {
|
|
761
706
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
762
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
707
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
763
708
|
}
|
|
764
709
|
}
|
|
765
710
|
let foundElements = result.foundElements;
|
|
@@ -800,26 +745,38 @@ class StableBrowser {
|
|
|
800
745
|
return maxCountElement.locator;
|
|
801
746
|
}
|
|
802
747
|
}
|
|
803
|
-
if (
|
|
748
|
+
if (Date.now() - startTime > timeout) {
|
|
804
749
|
break;
|
|
805
750
|
}
|
|
806
|
-
if (
|
|
807
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
751
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
752
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
808
753
|
highPriorityOnly = false;
|
|
754
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
755
|
+
lazy_scroll = true;
|
|
756
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
757
|
+
}
|
|
809
758
|
}
|
|
810
|
-
if (
|
|
811
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
759
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
760
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
812
761
|
visibleOnly = false;
|
|
813
762
|
}
|
|
814
763
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
815
764
|
}
|
|
816
765
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
817
|
-
info.
|
|
766
|
+
// if (info.locatorLog) {
|
|
767
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
768
|
+
// for (let line of lines) {
|
|
769
|
+
// this.logger.debug(line);
|
|
770
|
+
// }
|
|
771
|
+
// }
|
|
772
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
818
773
|
info.failCause.locatorNotFound = true;
|
|
819
|
-
info
|
|
774
|
+
if (!info?.failCause?.lastError) {
|
|
775
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
776
|
+
}
|
|
820
777
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
821
778
|
}
|
|
822
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
779
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
823
780
|
let foundElements = [];
|
|
824
781
|
const result = {
|
|
825
782
|
foundElements: foundElements,
|
|
@@ -827,14 +784,15 @@ class StableBrowser {
|
|
|
827
784
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
828
785
|
let foundLocators = [];
|
|
829
786
|
try {
|
|
830
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
787
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
831
788
|
}
|
|
832
789
|
catch (e) {
|
|
833
|
-
this
|
|
834
|
-
this.logger.debug(
|
|
790
|
+
// this call can fail it the browser is navigating
|
|
791
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
792
|
+
// this.logger.debug(e);
|
|
835
793
|
foundLocators = [];
|
|
836
794
|
try {
|
|
837
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
795
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
838
796
|
}
|
|
839
797
|
catch (e) {
|
|
840
798
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -850,11 +808,27 @@ class StableBrowser {
|
|
|
850
808
|
}
|
|
851
809
|
if (foundLocators.length > 1) {
|
|
852
810
|
info.failCause.foundMultiple = true;
|
|
811
|
+
if (info.locatorLog) {
|
|
812
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
813
|
+
}
|
|
853
814
|
}
|
|
854
815
|
}
|
|
855
816
|
return result;
|
|
856
817
|
}
|
|
857
818
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
819
|
+
const state = {
|
|
820
|
+
locate: false,
|
|
821
|
+
scroll: false,
|
|
822
|
+
highlight: false,
|
|
823
|
+
_params,
|
|
824
|
+
options,
|
|
825
|
+
world,
|
|
826
|
+
type: Types.CLICK,
|
|
827
|
+
text: "Click element",
|
|
828
|
+
operation: "simpleClick",
|
|
829
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
830
|
+
};
|
|
831
|
+
_preCommand(state, this, world);
|
|
858
832
|
const startTime = Date.now();
|
|
859
833
|
let timeout = 30000;
|
|
860
834
|
if (options && options.timeout) {
|
|
@@ -879,13 +853,31 @@ class StableBrowser {
|
|
|
879
853
|
catch (e) {
|
|
880
854
|
if (performance.now() - startTime > timeout) {
|
|
881
855
|
// throw e;
|
|
882
|
-
|
|
856
|
+
try {
|
|
857
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
858
|
+
}
|
|
859
|
+
finally {
|
|
860
|
+
_commandFinally(state, this);
|
|
861
|
+
}
|
|
883
862
|
}
|
|
884
863
|
}
|
|
885
864
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
886
865
|
}
|
|
887
866
|
}
|
|
888
867
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
868
|
+
const state = {
|
|
869
|
+
locate: false,
|
|
870
|
+
scroll: false,
|
|
871
|
+
highlight: false,
|
|
872
|
+
_params,
|
|
873
|
+
options,
|
|
874
|
+
world,
|
|
875
|
+
type: Types.FILL,
|
|
876
|
+
text: "Fill element",
|
|
877
|
+
operation: "simpleClickType",
|
|
878
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
879
|
+
};
|
|
880
|
+
_preCommand(state, this, world);
|
|
889
881
|
const startTime = Date.now();
|
|
890
882
|
let timeout = 30000;
|
|
891
883
|
if (options && options.timeout) {
|
|
@@ -910,7 +902,12 @@ class StableBrowser {
|
|
|
910
902
|
catch (e) {
|
|
911
903
|
if (performance.now() - startTime > timeout) {
|
|
912
904
|
// throw e;
|
|
913
|
-
|
|
905
|
+
try {
|
|
906
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
907
|
+
}
|
|
908
|
+
finally {
|
|
909
|
+
_commandFinally(state, this);
|
|
910
|
+
}
|
|
914
911
|
}
|
|
915
912
|
}
|
|
916
913
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -928,10 +925,10 @@ class StableBrowser {
|
|
|
928
925
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
929
926
|
};
|
|
930
927
|
try {
|
|
931
|
-
await _preCommand(state, this);
|
|
932
|
-
if (state.options && state.options.context) {
|
|
933
|
-
|
|
934
|
-
}
|
|
928
|
+
await _preCommand(state, this, world);
|
|
929
|
+
// if (state.options && state.options.context) {
|
|
930
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
931
|
+
// }
|
|
935
932
|
try {
|
|
936
933
|
await state.element.click();
|
|
937
934
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -964,14 +961,20 @@ class StableBrowser {
|
|
|
964
961
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
965
962
|
};
|
|
966
963
|
try {
|
|
967
|
-
await _preCommand(state, this);
|
|
964
|
+
await _preCommand(state, this, world);
|
|
968
965
|
state.info.checked = checked;
|
|
969
966
|
// let element = await this._locate(selectors, info, _params);
|
|
970
967
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
971
968
|
try {
|
|
972
|
-
|
|
969
|
+
if (world && world.screenshot && !world.screenshotPath) {
|
|
970
|
+
// console.log(`Highlighting while running from recorder`);
|
|
971
|
+
await this._highlightElements(element);
|
|
972
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
973
|
+
await this._unHighlightElements(element);
|
|
974
|
+
}
|
|
973
975
|
await state.element.setChecked(checked);
|
|
974
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
976
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
|
+
// await this._unHighlightElements(element);
|
|
975
978
|
}
|
|
976
979
|
catch (e) {
|
|
977
980
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -1007,9 +1010,10 @@ class StableBrowser {
|
|
|
1007
1010
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1008
1011
|
};
|
|
1009
1012
|
try {
|
|
1010
|
-
await _preCommand(state, this);
|
|
1013
|
+
await _preCommand(state, this, world);
|
|
1011
1014
|
try {
|
|
1012
1015
|
await state.element.hover();
|
|
1016
|
+
await _screenshot(state, this);
|
|
1013
1017
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1014
1018
|
}
|
|
1015
1019
|
catch (e) {
|
|
@@ -1017,9 +1021,10 @@ class StableBrowser {
|
|
|
1017
1021
|
state.info.log += "hover failed, will try again" + "\n";
|
|
1018
1022
|
state.element = await this._locate(selectors, state.info, _params);
|
|
1019
1023
|
await state.element.hover({ timeout: 10000 });
|
|
1024
|
+
await _screenshot(state, this);
|
|
1020
1025
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1021
1026
|
}
|
|
1022
|
-
await _screenshot(state, this);
|
|
1027
|
+
// await _screenshot(state, this);
|
|
1023
1028
|
await this.waitForPageLoad();
|
|
1024
1029
|
return state.info;
|
|
1025
1030
|
}
|
|
@@ -1046,7 +1051,7 @@ class StableBrowser {
|
|
|
1046
1051
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1047
1052
|
};
|
|
1048
1053
|
try {
|
|
1049
|
-
await _preCommand(state, this);
|
|
1054
|
+
await _preCommand(state, this, world);
|
|
1050
1055
|
try {
|
|
1051
1056
|
await state.element.selectOption(values);
|
|
1052
1057
|
}
|
|
@@ -1080,7 +1085,7 @@ class StableBrowser {
|
|
|
1080
1085
|
log: "",
|
|
1081
1086
|
};
|
|
1082
1087
|
try {
|
|
1083
|
-
await _preCommand(state, this);
|
|
1088
|
+
await _preCommand(state, this, world);
|
|
1084
1089
|
const valueSegment = state.value.split("&&");
|
|
1085
1090
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1086
1091
|
if (i > 0) {
|
|
@@ -1123,7 +1128,7 @@ class StableBrowser {
|
|
|
1123
1128
|
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1124
1129
|
};
|
|
1125
1130
|
try {
|
|
1126
|
-
await _preCommand(state, this);
|
|
1131
|
+
await _preCommand(state, this, world);
|
|
1127
1132
|
let value = await this._replaceWithLocalData(state.value, this);
|
|
1128
1133
|
try {
|
|
1129
1134
|
await state.element.evaluateHandle((el, value) => {
|
|
@@ -1160,7 +1165,7 @@ class StableBrowser {
|
|
|
1160
1165
|
throwError: false,
|
|
1161
1166
|
};
|
|
1162
1167
|
try {
|
|
1163
|
-
await _preCommand(state, this);
|
|
1168
|
+
await _preCommand(state, this, world);
|
|
1164
1169
|
try {
|
|
1165
1170
|
await state.element.click();
|
|
1166
1171
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -1234,7 +1239,7 @@ class StableBrowser {
|
|
|
1234
1239
|
_value = newValue;
|
|
1235
1240
|
}
|
|
1236
1241
|
try {
|
|
1237
|
-
await _preCommand(state, this);
|
|
1242
|
+
await _preCommand(state, this, world);
|
|
1238
1243
|
state.info.value = _value;
|
|
1239
1244
|
if (options === null || options === undefined || !options.press) {
|
|
1240
1245
|
try {
|
|
@@ -1291,7 +1296,12 @@ class StableBrowser {
|
|
|
1291
1296
|
await this.waitForPageLoad();
|
|
1292
1297
|
}
|
|
1293
1298
|
else if (enter === false) {
|
|
1294
|
-
|
|
1299
|
+
try {
|
|
1300
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1301
|
+
}
|
|
1302
|
+
catch (e) {
|
|
1303
|
+
// ignore
|
|
1304
|
+
}
|
|
1295
1305
|
//await this.page.keyboard.press("Tab");
|
|
1296
1306
|
}
|
|
1297
1307
|
else {
|
|
@@ -1322,7 +1332,7 @@ class StableBrowser {
|
|
|
1322
1332
|
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1323
1333
|
};
|
|
1324
1334
|
try {
|
|
1325
|
-
await _preCommand(state, this);
|
|
1335
|
+
await _preCommand(state, this, world);
|
|
1326
1336
|
await state.element.fill(value);
|
|
1327
1337
|
await state.element.dispatchEvent("change");
|
|
1328
1338
|
if (enter) {
|
|
@@ -1348,6 +1358,7 @@ class StableBrowser {
|
|
|
1348
1358
|
let screenshotPath = null;
|
|
1349
1359
|
if (!info.log) {
|
|
1350
1360
|
info.log = "";
|
|
1361
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1351
1362
|
}
|
|
1352
1363
|
info.operation = "getText";
|
|
1353
1364
|
info.selectors = selectors;
|
|
@@ -1367,9 +1378,19 @@ class StableBrowser {
|
|
|
1367
1378
|
catch (e) {
|
|
1368
1379
|
//ignore
|
|
1369
1380
|
}
|
|
1370
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1381
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, element));
|
|
1371
1382
|
try {
|
|
1372
|
-
|
|
1383
|
+
if (world && world.screenshot && !world.screenshotPath) {
|
|
1384
|
+
// console.log(`Highlighting for get text while running from recorder`);
|
|
1385
|
+
this._highlightElements(element)
|
|
1386
|
+
.then(async () => {
|
|
1387
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1388
|
+
this._unhighlightElements(element).then(() => { }
|
|
1389
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1390
|
+
);
|
|
1391
|
+
})
|
|
1392
|
+
.catch(e);
|
|
1393
|
+
}
|
|
1373
1394
|
const elementText = await element.innerText();
|
|
1374
1395
|
return {
|
|
1375
1396
|
text: elementText,
|
|
@@ -1381,7 +1402,7 @@ class StableBrowser {
|
|
|
1381
1402
|
}
|
|
1382
1403
|
catch (e) {
|
|
1383
1404
|
//await this.closeUnexpectedPopups();
|
|
1384
|
-
this.logger.info("no innerText will use textContent");
|
|
1405
|
+
this.logger.info("no innerText, will use textContent");
|
|
1385
1406
|
const elementText = await element.textContent();
|
|
1386
1407
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1387
1408
|
}
|
|
@@ -1416,13 +1437,13 @@ class StableBrowser {
|
|
|
1416
1437
|
}
|
|
1417
1438
|
let foundObj = null;
|
|
1418
1439
|
try {
|
|
1419
|
-
await _preCommand(state, this);
|
|
1440
|
+
await _preCommand(state, this, world);
|
|
1420
1441
|
state.info.pattern = pattern;
|
|
1421
1442
|
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1422
1443
|
if (foundObj && foundObj.element) {
|
|
1423
1444
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1424
1445
|
}
|
|
1425
|
-
await _screenshot(state, this);
|
|
1446
|
+
await _screenshot(state, this, foundObj.element);
|
|
1426
1447
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1427
1448
|
pattern = pattern.replace("{text}", escapedText);
|
|
1428
1449
|
let regex = new RegExp(pattern, "im");
|
|
@@ -1467,12 +1488,12 @@ class StableBrowser {
|
|
|
1467
1488
|
}
|
|
1468
1489
|
let foundObj = null;
|
|
1469
1490
|
try {
|
|
1470
|
-
await _preCommand(state, this);
|
|
1491
|
+
await _preCommand(state, this, world);
|
|
1471
1492
|
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1472
1493
|
if (foundObj && foundObj.element) {
|
|
1473
1494
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1474
1495
|
}
|
|
1475
|
-
await _screenshot(state, this);
|
|
1496
|
+
await _screenshot(state, this, foundObj.element);
|
|
1476
1497
|
const dateAlternatives = findDateAlternatives(text);
|
|
1477
1498
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1478
1499
|
if (dateAlternatives.date) {
|
|
@@ -1661,7 +1682,7 @@ class StableBrowser {
|
|
|
1661
1682
|
}
|
|
1662
1683
|
return data;
|
|
1663
1684
|
}
|
|
1664
|
-
async _screenShot(options = {}, world = null, info = null) {
|
|
1685
|
+
async _screenShot(options = {}, world = null, info = null, focusedElement = null) {
|
|
1665
1686
|
// collect url/path/title
|
|
1666
1687
|
if (info) {
|
|
1667
1688
|
if (!info.title) {
|
|
@@ -1686,13 +1707,11 @@ class StableBrowser {
|
|
|
1686
1707
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1687
1708
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1688
1709
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
}
|
|
1693
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1710
|
+
// to make sure the path doesn't start with -
|
|
1711
|
+
const uuidStr = "id_" + randomUUID();
|
|
1712
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1694
1713
|
try {
|
|
1695
|
-
await this.takeScreenshot(screenshotPath);
|
|
1714
|
+
await this.takeScreenshot(screenshotPath, focusedElement);
|
|
1696
1715
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1697
1716
|
// // save the buffer to the screenshot path asynchrously
|
|
1698
1717
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -1700,20 +1719,20 @@ class StableBrowser {
|
|
|
1700
1719
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1701
1720
|
// }
|
|
1702
1721
|
// });
|
|
1722
|
+
result.screenshotId = uuidStr;
|
|
1723
|
+
result.screenshotPath = screenshotPath;
|
|
1724
|
+
if (info && info.box) {
|
|
1725
|
+
// await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1726
|
+
}
|
|
1703
1727
|
}
|
|
1704
1728
|
catch (e) {
|
|
1705
1729
|
this.logger.info("unable to take screenshot, ignored");
|
|
1706
1730
|
}
|
|
1707
|
-
result.screenshotId = nextIndex;
|
|
1708
|
-
result.screenshotPath = screenshotPath;
|
|
1709
|
-
if (info && info.box) {
|
|
1710
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1711
|
-
}
|
|
1712
1731
|
}
|
|
1713
1732
|
else if (options && options.screenshot) {
|
|
1714
1733
|
result.screenshotPath = options.screenshotPath;
|
|
1715
1734
|
try {
|
|
1716
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
1735
|
+
await this.takeScreenshot(options.screenshotPath, focusedElement);
|
|
1717
1736
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1718
1737
|
// // save the buffer to the screenshot path asynchrously
|
|
1719
1738
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -1726,12 +1745,12 @@ class StableBrowser {
|
|
|
1726
1745
|
this.logger.info("unable to take screenshot, ignored");
|
|
1727
1746
|
}
|
|
1728
1747
|
if (info && info.box) {
|
|
1729
|
-
await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1748
|
+
// await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1730
1749
|
}
|
|
1731
1750
|
}
|
|
1732
1751
|
return result;
|
|
1733
1752
|
}
|
|
1734
|
-
async takeScreenshot(screenshotPath) {
|
|
1753
|
+
async takeScreenshot(screenshotPath, focusedElement = null) {
|
|
1735
1754
|
const playContext = this.context.playContext;
|
|
1736
1755
|
// Using CDP to capture the screenshot
|
|
1737
1756
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -1743,6 +1762,15 @@ class StableBrowser {
|
|
|
1743
1762
|
document.documentElement.clientWidth,
|
|
1744
1763
|
])));
|
|
1745
1764
|
let screenshotBuffer = null;
|
|
1765
|
+
// if (focusedElement) {
|
|
1766
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1767
|
+
// await this._unhighlightElements(focusedElement);
|
|
1768
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1769
|
+
// console.log(`Unhighlighted previous element`);
|
|
1770
|
+
// }
|
|
1771
|
+
if (focusedElement) {
|
|
1772
|
+
await this._highlightElements(focusedElement);
|
|
1773
|
+
}
|
|
1746
1774
|
if (this.context.browserName === "chromium") {
|
|
1747
1775
|
const client = await playContext.newCDPSession(this.page);
|
|
1748
1776
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1764,6 +1792,10 @@ class StableBrowser {
|
|
|
1764
1792
|
else {
|
|
1765
1793
|
screenshotBuffer = await this.page.screenshot();
|
|
1766
1794
|
}
|
|
1795
|
+
if (focusedElement) {
|
|
1796
|
+
// console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1797
|
+
await this._unhighlightElements(focusedElement);
|
|
1798
|
+
}
|
|
1767
1799
|
let image = await Jimp.read(screenshotBuffer);
|
|
1768
1800
|
// Get the image dimensions
|
|
1769
1801
|
const { width, height } = image.bitmap;
|
|
@@ -1790,7 +1822,7 @@ class StableBrowser {
|
|
|
1790
1822
|
};
|
|
1791
1823
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1792
1824
|
try {
|
|
1793
|
-
await _preCommand(state, this);
|
|
1825
|
+
await _preCommand(state, this, world);
|
|
1794
1826
|
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1795
1827
|
return state.info;
|
|
1796
1828
|
}
|
|
@@ -1816,7 +1848,7 @@ class StableBrowser {
|
|
|
1816
1848
|
};
|
|
1817
1849
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1818
1850
|
try {
|
|
1819
|
-
await _preCommand(state, this);
|
|
1851
|
+
await _preCommand(state, this, world);
|
|
1820
1852
|
switch (attribute) {
|
|
1821
1853
|
case "inner_text":
|
|
1822
1854
|
state.value = await state.element.innerText();
|
|
@@ -1834,6 +1866,70 @@ class StableBrowser {
|
|
|
1834
1866
|
state.info.value = state.value;
|
|
1835
1867
|
this.setTestData({ [variable]: state.value }, world);
|
|
1836
1868
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1869
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1870
|
+
return state.info;
|
|
1871
|
+
}
|
|
1872
|
+
catch (e) {
|
|
1873
|
+
await _commandError(state, e, this);
|
|
1874
|
+
}
|
|
1875
|
+
finally {
|
|
1876
|
+
_commandFinally(state, this);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1880
|
+
const state = {
|
|
1881
|
+
selectors,
|
|
1882
|
+
_params,
|
|
1883
|
+
attribute,
|
|
1884
|
+
value,
|
|
1885
|
+
options,
|
|
1886
|
+
world,
|
|
1887
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1888
|
+
highlight: true,
|
|
1889
|
+
screenshot: true,
|
|
1890
|
+
text: `Verify element attribute`,
|
|
1891
|
+
operation: "verifyAttribute",
|
|
1892
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1893
|
+
allowDisabled: true,
|
|
1894
|
+
};
|
|
1895
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1896
|
+
let val;
|
|
1897
|
+
try {
|
|
1898
|
+
await _preCommand(state, this, world);
|
|
1899
|
+
switch (attribute) {
|
|
1900
|
+
case "innerText":
|
|
1901
|
+
val = String(await state.element.innerText());
|
|
1902
|
+
break;
|
|
1903
|
+
case "value":
|
|
1904
|
+
val = String(await state.element.inputValue());
|
|
1905
|
+
break;
|
|
1906
|
+
case "checked":
|
|
1907
|
+
val = String(await state.element.isChecked());
|
|
1908
|
+
break;
|
|
1909
|
+
case "disabled":
|
|
1910
|
+
val = String(await state.element.isDisabled());
|
|
1911
|
+
break;
|
|
1912
|
+
case "readOnly":
|
|
1913
|
+
const isEditable = await state.element.isEditable();
|
|
1914
|
+
val = String(!isEditable);
|
|
1915
|
+
break;
|
|
1916
|
+
default:
|
|
1917
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
state.info.expectedValue = val;
|
|
1921
|
+
let regex;
|
|
1922
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1923
|
+
const patternBody = value.slice(1, -1);
|
|
1924
|
+
regex = new RegExp(patternBody, "g");
|
|
1925
|
+
}
|
|
1926
|
+
else {
|
|
1927
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1928
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1929
|
+
}
|
|
1930
|
+
if (!val.match(regex)) {
|
|
1931
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1932
|
+
}
|
|
1837
1933
|
return state.info;
|
|
1838
1934
|
}
|
|
1839
1935
|
catch (e) {
|
|
@@ -1861,7 +1957,7 @@ class StableBrowser {
|
|
|
1861
1957
|
if (options && options.timeout) {
|
|
1862
1958
|
timeout = options.timeout;
|
|
1863
1959
|
}
|
|
1864
|
-
const serviceUrl =
|
|
1960
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1865
1961
|
const request = {
|
|
1866
1962
|
method: "POST",
|
|
1867
1963
|
url: serviceUrl,
|
|
@@ -1932,27 +2028,32 @@ class StableBrowser {
|
|
|
1932
2028
|
async _highlightElements(scope, css) {
|
|
1933
2029
|
try {
|
|
1934
2030
|
if (!scope) {
|
|
2031
|
+
// console.log(`Scope is not defined`);
|
|
1935
2032
|
return;
|
|
1936
2033
|
}
|
|
1937
2034
|
if (!css) {
|
|
1938
2035
|
scope
|
|
1939
2036
|
.evaluate((node) => {
|
|
1940
2037
|
if (node && node.style) {
|
|
1941
|
-
let
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
}
|
|
2038
|
+
let originalOutline = node.style.outline;
|
|
2039
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2040
|
+
node.__previousOutline = originalOutline;
|
|
2041
|
+
node.style.outline = "2px solid red";
|
|
2042
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2043
|
+
// if (window) {
|
|
2044
|
+
// window.addEventListener("beforeunload", function (e) {
|
|
2045
|
+
// node.style.outline = originalBorder;
|
|
2046
|
+
// });
|
|
2047
|
+
// }
|
|
2048
|
+
// setTimeout(function () {
|
|
2049
|
+
// node.style.outline = originalBorder;
|
|
2050
|
+
//}, 2000);
|
|
1951
2051
|
}
|
|
1952
2052
|
})
|
|
1953
2053
|
.then(() => { })
|
|
1954
2054
|
.catch((e) => {
|
|
1955
2055
|
// ignore
|
|
2056
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1956
2057
|
});
|
|
1957
2058
|
}
|
|
1958
2059
|
else {
|
|
@@ -1968,24 +2069,26 @@ class StableBrowser {
|
|
|
1968
2069
|
if (!element.style) {
|
|
1969
2070
|
return;
|
|
1970
2071
|
}
|
|
1971
|
-
|
|
2072
|
+
let originalOutline = element.style.outline;
|
|
2073
|
+
element.__previousOutline = originalOutline;
|
|
1972
2074
|
// Set the new border to be red and 2px solid
|
|
1973
|
-
element.style.
|
|
1974
|
-
if (window) {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
}
|
|
2075
|
+
element.style.outline = "2px solid red";
|
|
2076
|
+
// if (window) {
|
|
2077
|
+
// window.addEventListener("beforeunload", function (e) {
|
|
2078
|
+
// element.style.outline = originalBorder;
|
|
2079
|
+
// });
|
|
2080
|
+
// }
|
|
1979
2081
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1980
|
-
setTimeout(function () {
|
|
1981
|
-
|
|
1982
|
-
}, 2000);
|
|
2082
|
+
// setTimeout(function () {
|
|
2083
|
+
// element.style.outline = originalBorder;
|
|
2084
|
+
// }, 2000);
|
|
1983
2085
|
}
|
|
1984
2086
|
return;
|
|
1985
2087
|
}, [css])
|
|
1986
2088
|
.then(() => { })
|
|
1987
2089
|
.catch((e) => {
|
|
1988
2090
|
// ignore
|
|
2091
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
1989
2092
|
});
|
|
1990
2093
|
}
|
|
1991
2094
|
}
|
|
@@ -1993,6 +2096,58 @@ class StableBrowser {
|
|
|
1993
2096
|
console.debug(error);
|
|
1994
2097
|
}
|
|
1995
2098
|
}
|
|
2099
|
+
async _unhighlightElements(scope, css) {
|
|
2100
|
+
try {
|
|
2101
|
+
if (!scope) {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (!css) {
|
|
2105
|
+
scope
|
|
2106
|
+
.evaluate((node) => {
|
|
2107
|
+
if (node && node.style) {
|
|
2108
|
+
if (!node.__previousOutline) {
|
|
2109
|
+
node.style.outline = "";
|
|
2110
|
+
}
|
|
2111
|
+
else {
|
|
2112
|
+
node.style.outline = node.__previousOutline;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
})
|
|
2116
|
+
.then(() => { })
|
|
2117
|
+
.catch((e) => {
|
|
2118
|
+
// console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
else {
|
|
2122
|
+
scope
|
|
2123
|
+
.evaluate(([css]) => {
|
|
2124
|
+
if (!css) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
let elements = Array.from(document.querySelectorAll(css));
|
|
2128
|
+
for (i = 0; i < elements.length; i++) {
|
|
2129
|
+
let element = elements[i];
|
|
2130
|
+
if (!element.style) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (!element.__previousOutline) {
|
|
2134
|
+
element.style.outline = "";
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
element.style.outline = element.__previousOutline;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
})
|
|
2141
|
+
.then(() => { })
|
|
2142
|
+
.catch((e) => {
|
|
2143
|
+
// console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
catch (error) {
|
|
2148
|
+
// console.debug(error);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
1996
2151
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
1997
2152
|
const startTime = Date.now();
|
|
1998
2153
|
let error = null;
|
|
@@ -2054,6 +2209,35 @@ class StableBrowser {
|
|
|
2054
2209
|
});
|
|
2055
2210
|
}
|
|
2056
2211
|
}
|
|
2212
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2213
|
+
const frames = this.page.frames();
|
|
2214
|
+
let results = [];
|
|
2215
|
+
let ignoreCase = false;
|
|
2216
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2217
|
+
if (dateAlternatives.date) {
|
|
2218
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2219
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2220
|
+
result.frame = frames[i];
|
|
2221
|
+
results.push(result);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
else if (numberAlternatives.number) {
|
|
2225
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2226
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2227
|
+
result.frame = frames[i];
|
|
2228
|
+
results.push(result);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
else {
|
|
2232
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2233
|
+
result.frame = frames[i];
|
|
2234
|
+
results.push(result);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
state.info.results = results;
|
|
2238
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2239
|
+
return resultWithElementsFound;
|
|
2240
|
+
}
|
|
2057
2241
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2058
2242
|
text = unEscapeString(text);
|
|
2059
2243
|
const state = {
|
|
@@ -2063,7 +2247,7 @@ class StableBrowser {
|
|
|
2063
2247
|
locate: false,
|
|
2064
2248
|
scroll: false,
|
|
2065
2249
|
highlight: false,
|
|
2066
|
-
type: Types.
|
|
2250
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2067
2251
|
text: `Verify text exists in page`,
|
|
2068
2252
|
operation: "verifyTextExistInPage",
|
|
2069
2253
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2078,34 +2262,10 @@ class StableBrowser {
|
|
|
2078
2262
|
let dateAlternatives = findDateAlternatives(text);
|
|
2079
2263
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2080
2264
|
try {
|
|
2081
|
-
await _preCommand(state, this);
|
|
2265
|
+
await _preCommand(state, this, world);
|
|
2082
2266
|
state.info.text = text;
|
|
2083
2267
|
while (true) {
|
|
2084
|
-
const
|
|
2085
|
-
let results = [];
|
|
2086
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2087
|
-
if (dateAlternatives.date) {
|
|
2088
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2089
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2090
|
-
result.frame = frames[i];
|
|
2091
|
-
results.push(result);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
else if (numberAlternatives.number) {
|
|
2095
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2096
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2097
|
-
result.frame = frames[i];
|
|
2098
|
-
results.push(result);
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
else {
|
|
2102
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2103
|
-
result.frame = frames[i];
|
|
2104
|
-
results.push(result);
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
state.info.results = results;
|
|
2108
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2268
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2109
2269
|
if (resultWithElementsFound.length === 0) {
|
|
2110
2270
|
if (Date.now() - state.startTime > timeout) {
|
|
2111
2271
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2115,15 +2275,32 @@ class StableBrowser {
|
|
|
2115
2275
|
}
|
|
2116
2276
|
if (resultWithElementsFound[0].randomToken) {
|
|
2117
2277
|
const frame = resultWithElementsFound[0].frame;
|
|
2118
|
-
const dataAttribute = `[data-blinq-id
|
|
2119
|
-
|
|
2120
|
-
|
|
2278
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2279
|
+
if (world && world.screenshot && !world.screenshotPath) {
|
|
2280
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2281
|
+
this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2282
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2283
|
+
this._unhighlightElements(frame, dataAttribute)
|
|
2284
|
+
.then(async () => {
|
|
2285
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2286
|
+
})
|
|
2287
|
+
.catch((e) => { }
|
|
2288
|
+
// console.error(e)
|
|
2289
|
+
);
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2293
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2294
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2121
2295
|
if (element) {
|
|
2122
2296
|
await this.scrollIfNeeded(element, state.info);
|
|
2123
2297
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2298
|
+
await _screenshot(state, this, element);
|
|
2124
2299
|
}
|
|
2125
2300
|
}
|
|
2126
|
-
|
|
2301
|
+
else {
|
|
2302
|
+
await _screenshot(state, this);
|
|
2303
|
+
}
|
|
2127
2304
|
return state.info;
|
|
2128
2305
|
}
|
|
2129
2306
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
@@ -2159,34 +2336,10 @@ class StableBrowser {
|
|
|
2159
2336
|
let dateAlternatives = findDateAlternatives(text);
|
|
2160
2337
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2161
2338
|
try {
|
|
2162
|
-
await _preCommand(state, this);
|
|
2339
|
+
await _preCommand(state, this, world);
|
|
2163
2340
|
state.info.text = text;
|
|
2164
2341
|
while (true) {
|
|
2165
|
-
const
|
|
2166
|
-
let results = [];
|
|
2167
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2168
|
-
if (dateAlternatives.date) {
|
|
2169
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2170
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2171
|
-
result.frame = frames[i];
|
|
2172
|
-
results.push(result);
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
else if (numberAlternatives.number) {
|
|
2176
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2177
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2178
|
-
result.frame = frames[i];
|
|
2179
|
-
results.push(result);
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
else {
|
|
2183
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2184
|
-
result.frame = frames[i];
|
|
2185
|
-
results.push(result);
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
state.info.results = results;
|
|
2189
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2342
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2190
2343
|
if (resultWithElementsFound.length === 0) {
|
|
2191
2344
|
await _screenshot(state, this);
|
|
2192
2345
|
return state.info;
|
|
@@ -2204,15 +2357,103 @@ class StableBrowser {
|
|
|
2204
2357
|
_commandFinally(state, this);
|
|
2205
2358
|
}
|
|
2206
2359
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2360
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2361
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2362
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2363
|
+
const state = {
|
|
2364
|
+
text_search: textToVerify,
|
|
2365
|
+
options,
|
|
2366
|
+
world,
|
|
2367
|
+
locate: false,
|
|
2368
|
+
scroll: false,
|
|
2369
|
+
highlight: false,
|
|
2370
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2371
|
+
text: `Verify text with relation to another text`,
|
|
2372
|
+
operation: "verify_text_with_relation",
|
|
2373
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2374
|
+
};
|
|
2375
|
+
const timeout = this._getLoadTimeout(options);
|
|
2376
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2377
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2378
|
+
if (newValue !== textAnchor) {
|
|
2379
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2380
|
+
textAnchor = newValue;
|
|
2381
|
+
}
|
|
2382
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2383
|
+
if (newValue !== textToVerify) {
|
|
2384
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2385
|
+
textToVerify = newValue;
|
|
2386
|
+
}
|
|
2387
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2388
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2389
|
+
let foundAncore = false;
|
|
2390
|
+
try {
|
|
2391
|
+
await _preCommand(state, this, world);
|
|
2392
|
+
state.info.text = textToVerify;
|
|
2393
|
+
while (true) {
|
|
2394
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2395
|
+
if (resultWithElementsFound.length === 0) {
|
|
2396
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2397
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2398
|
+
}
|
|
2399
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2403
|
+
foundAncore = true;
|
|
2404
|
+
const result = resultWithElementsFound[i];
|
|
2405
|
+
const token = result.randomToken;
|
|
2406
|
+
const frame = result.frame;
|
|
2407
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2408
|
+
const climbArray1 = [];
|
|
2409
|
+
for (let i = 0; i < climb; i++) {
|
|
2410
|
+
climbArray1.push("..");
|
|
2411
|
+
}
|
|
2412
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2413
|
+
css = css + " >> " + climbXpath;
|
|
2414
|
+
const count = await frame.locator(css).count();
|
|
2415
|
+
for (let j = 0; j < count; j++) {
|
|
2416
|
+
const continer = await frame.locator(css).nth(j);
|
|
2417
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2418
|
+
if (result.elementCount > 0) {
|
|
2419
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2420
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2421
|
+
if (world && world.screenshot && !world.screenshotPath) {
|
|
2422
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2423
|
+
this._highlightElements(frame, dataAttribute)
|
|
2424
|
+
.then(async () => {
|
|
2425
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2426
|
+
this._unhighlightElements(frame, dataAttribute).then(() => { }
|
|
2427
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2428
|
+
);
|
|
2429
|
+
})
|
|
2430
|
+
.catch(e);
|
|
2431
|
+
}
|
|
2432
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2433
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2434
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2435
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2436
|
+
if (element) {
|
|
2437
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2438
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2439
|
+
await _screenshot(state, this, element);
|
|
2440
|
+
}
|
|
2441
|
+
else {
|
|
2442
|
+
await _screenshot(state, this);
|
|
2443
|
+
}
|
|
2444
|
+
return state.info;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2211
2450
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2451
|
+
catch (e) {
|
|
2452
|
+
await _commandError(state, e, this);
|
|
2453
|
+
}
|
|
2454
|
+
finally {
|
|
2455
|
+
_commandFinally(state, this);
|
|
2214
2456
|
}
|
|
2215
|
-
return serviceUrl;
|
|
2216
2457
|
}
|
|
2217
2458
|
async visualVerification(text, options = {}, world = null) {
|
|
2218
2459
|
const startTime = Date.now();
|
|
@@ -2228,7 +2469,7 @@ class StableBrowser {
|
|
|
2228
2469
|
throw new Error("TOKEN is not set");
|
|
2229
2470
|
}
|
|
2230
2471
|
try {
|
|
2231
|
-
let serviceUrl =
|
|
2472
|
+
let serviceUrl = _getServerUrl();
|
|
2232
2473
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2233
2474
|
info.screenshotPath = screenshotPath;
|
|
2234
2475
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2317,18 +2558,19 @@ class StableBrowser {
|
|
|
2317
2558
|
let screenshotPath = null;
|
|
2318
2559
|
const info = {};
|
|
2319
2560
|
info.log = "";
|
|
2561
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2320
2562
|
info.operation = "getTableData";
|
|
2321
2563
|
info.selectors = selectors;
|
|
2322
2564
|
try {
|
|
2323
2565
|
let table = await this._locate(selectors, info, _params);
|
|
2324
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2566
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
|
|
2325
2567
|
const tableData = await getTableData(this.page, table);
|
|
2326
2568
|
return tableData;
|
|
2327
2569
|
}
|
|
2328
2570
|
catch (e) {
|
|
2329
2571
|
this.logger.error("getTableData failed " + info.log);
|
|
2330
2572
|
this.logger.error(e);
|
|
2331
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2573
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info, table));
|
|
2332
2574
|
info.screenshotPath = screenshotPath;
|
|
2333
2575
|
Object.assign(e, { info: info });
|
|
2334
2576
|
error = e;
|
|
@@ -2392,7 +2634,7 @@ class StableBrowser {
|
|
|
2392
2634
|
info.operation = "analyzeTable";
|
|
2393
2635
|
info.selectors = selectors;
|
|
2394
2636
|
info.query = query;
|
|
2395
|
-
query =
|
|
2637
|
+
query = _fixUsingParams(query, _params);
|
|
2396
2638
|
info.query_fixed = query;
|
|
2397
2639
|
info.operator = operator;
|
|
2398
2640
|
info.value = value;
|
|
@@ -2609,7 +2851,7 @@ class StableBrowser {
|
|
|
2609
2851
|
throwError: false,
|
|
2610
2852
|
};
|
|
2611
2853
|
try {
|
|
2612
|
-
await _preCommand(state, this);
|
|
2854
|
+
await _preCommand(state, this, world);
|
|
2613
2855
|
await this.page.close();
|
|
2614
2856
|
}
|
|
2615
2857
|
catch (e) {
|
|
@@ -2620,6 +2862,11 @@ class StableBrowser {
|
|
|
2620
2862
|
_commandFinally(state, this);
|
|
2621
2863
|
}
|
|
2622
2864
|
}
|
|
2865
|
+
saveTestDataAsGlobal(options, world) {
|
|
2866
|
+
const dataFile = this._getDataFile(world);
|
|
2867
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2868
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2869
|
+
}
|
|
2623
2870
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2624
2871
|
const startTime = Date.now();
|
|
2625
2872
|
let error = null;
|
|
@@ -2717,19 +2964,34 @@ class StableBrowser {
|
|
|
2717
2964
|
}
|
|
2718
2965
|
}
|
|
2719
2966
|
async beforeStep(world, step) {
|
|
2720
|
-
this.stepName = step.pickleStep.text;
|
|
2721
|
-
this.logger.info("step: " + this.stepName);
|
|
2722
2967
|
if (this.stepIndex === undefined) {
|
|
2723
2968
|
this.stepIndex = 0;
|
|
2724
2969
|
}
|
|
2725
2970
|
else {
|
|
2726
2971
|
this.stepIndex++;
|
|
2727
2972
|
}
|
|
2973
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2974
|
+
this.stepName = step.pickleStep.text;
|
|
2975
|
+
this.logger.info("step: " + this.stepName);
|
|
2976
|
+
}
|
|
2977
|
+
else if (step && step.text) {
|
|
2978
|
+
this.stepName = step.text;
|
|
2979
|
+
}
|
|
2980
|
+
else {
|
|
2981
|
+
this.stepName = "step " + this.stepIndex;
|
|
2982
|
+
}
|
|
2728
2983
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2729
2984
|
if (this.context.browserObject.context) {
|
|
2730
2985
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2731
2986
|
}
|
|
2732
2987
|
}
|
|
2988
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2989
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2990
|
+
// check if @global_test_data tag is present
|
|
2991
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2992
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2733
2995
|
}
|
|
2734
2996
|
async afterStep(world, step) {
|
|
2735
2997
|
this.stepName = null;
|
|
@@ -2747,156 +3009,5 @@ function createTimedPromise(promise, label) {
|
|
|
2747
3009
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2748
3010
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2749
3011
|
}
|
|
2750
|
-
const KEYBOARD_EVENTS = [
|
|
2751
|
-
"ALT",
|
|
2752
|
-
"AltGraph",
|
|
2753
|
-
"CapsLock",
|
|
2754
|
-
"Control",
|
|
2755
|
-
"Fn",
|
|
2756
|
-
"FnLock",
|
|
2757
|
-
"Hyper",
|
|
2758
|
-
"Meta",
|
|
2759
|
-
"NumLock",
|
|
2760
|
-
"ScrollLock",
|
|
2761
|
-
"Shift",
|
|
2762
|
-
"Super",
|
|
2763
|
-
"Symbol",
|
|
2764
|
-
"SymbolLock",
|
|
2765
|
-
"Enter",
|
|
2766
|
-
"Tab",
|
|
2767
|
-
"ArrowDown",
|
|
2768
|
-
"ArrowLeft",
|
|
2769
|
-
"ArrowRight",
|
|
2770
|
-
"ArrowUp",
|
|
2771
|
-
"End",
|
|
2772
|
-
"Home",
|
|
2773
|
-
"PageDown",
|
|
2774
|
-
"PageUp",
|
|
2775
|
-
"Backspace",
|
|
2776
|
-
"Clear",
|
|
2777
|
-
"Copy",
|
|
2778
|
-
"CrSel",
|
|
2779
|
-
"Cut",
|
|
2780
|
-
"Delete",
|
|
2781
|
-
"EraseEof",
|
|
2782
|
-
"ExSel",
|
|
2783
|
-
"Insert",
|
|
2784
|
-
"Paste",
|
|
2785
|
-
"Redo",
|
|
2786
|
-
"Undo",
|
|
2787
|
-
"Accept",
|
|
2788
|
-
"Again",
|
|
2789
|
-
"Attn",
|
|
2790
|
-
"Cancel",
|
|
2791
|
-
"ContextMenu",
|
|
2792
|
-
"Escape",
|
|
2793
|
-
"Execute",
|
|
2794
|
-
"Find",
|
|
2795
|
-
"Finish",
|
|
2796
|
-
"Help",
|
|
2797
|
-
"Pause",
|
|
2798
|
-
"Play",
|
|
2799
|
-
"Props",
|
|
2800
|
-
"Select",
|
|
2801
|
-
"ZoomIn",
|
|
2802
|
-
"ZoomOut",
|
|
2803
|
-
"BrightnessDown",
|
|
2804
|
-
"BrightnessUp",
|
|
2805
|
-
"Eject",
|
|
2806
|
-
"LogOff",
|
|
2807
|
-
"Power",
|
|
2808
|
-
"PowerOff",
|
|
2809
|
-
"PrintScreen",
|
|
2810
|
-
"Hibernate",
|
|
2811
|
-
"Standby",
|
|
2812
|
-
"WakeUp",
|
|
2813
|
-
"AllCandidates",
|
|
2814
|
-
"Alphanumeric",
|
|
2815
|
-
"CodeInput",
|
|
2816
|
-
"Compose",
|
|
2817
|
-
"Convert",
|
|
2818
|
-
"Dead",
|
|
2819
|
-
"FinalMode",
|
|
2820
|
-
"GroupFirst",
|
|
2821
|
-
"GroupLast",
|
|
2822
|
-
"GroupNext",
|
|
2823
|
-
"GroupPrevious",
|
|
2824
|
-
"ModeChange",
|
|
2825
|
-
"NextCandidate",
|
|
2826
|
-
"NonConvert",
|
|
2827
|
-
"PreviousCandidate",
|
|
2828
|
-
"Process",
|
|
2829
|
-
"SingleCandidate",
|
|
2830
|
-
"HangulMode",
|
|
2831
|
-
"HanjaMode",
|
|
2832
|
-
"JunjaMode",
|
|
2833
|
-
"Eisu",
|
|
2834
|
-
"Hankaku",
|
|
2835
|
-
"Hiragana",
|
|
2836
|
-
"HiraganaKatakana",
|
|
2837
|
-
"KanaMode",
|
|
2838
|
-
"KanjiMode",
|
|
2839
|
-
"Katakana",
|
|
2840
|
-
"Romaji",
|
|
2841
|
-
"Zenkaku",
|
|
2842
|
-
"ZenkakuHanaku",
|
|
2843
|
-
"F1",
|
|
2844
|
-
"F2",
|
|
2845
|
-
"F3",
|
|
2846
|
-
"F4",
|
|
2847
|
-
"F5",
|
|
2848
|
-
"F6",
|
|
2849
|
-
"F7",
|
|
2850
|
-
"F8",
|
|
2851
|
-
"F9",
|
|
2852
|
-
"F10",
|
|
2853
|
-
"F11",
|
|
2854
|
-
"F12",
|
|
2855
|
-
"Soft1",
|
|
2856
|
-
"Soft2",
|
|
2857
|
-
"Soft3",
|
|
2858
|
-
"Soft4",
|
|
2859
|
-
"ChannelDown",
|
|
2860
|
-
"ChannelUp",
|
|
2861
|
-
"Close",
|
|
2862
|
-
"MailForward",
|
|
2863
|
-
"MailReply",
|
|
2864
|
-
"MailSend",
|
|
2865
|
-
"MediaFastForward",
|
|
2866
|
-
"MediaPause",
|
|
2867
|
-
"MediaPlay",
|
|
2868
|
-
"MediaPlayPause",
|
|
2869
|
-
"MediaRecord",
|
|
2870
|
-
"MediaRewind",
|
|
2871
|
-
"MediaStop",
|
|
2872
|
-
"MediaTrackNext",
|
|
2873
|
-
"MediaTrackPrevious",
|
|
2874
|
-
"AudioBalanceLeft",
|
|
2875
|
-
"AudioBalanceRight",
|
|
2876
|
-
"AudioBassBoostDown",
|
|
2877
|
-
"AudioBassBoostToggle",
|
|
2878
|
-
"AudioBassBoostUp",
|
|
2879
|
-
"AudioFaderFront",
|
|
2880
|
-
"AudioFaderRear",
|
|
2881
|
-
"AudioSurroundModeNext",
|
|
2882
|
-
"AudioTrebleDown",
|
|
2883
|
-
"AudioTrebleUp",
|
|
2884
|
-
"AudioVolumeDown",
|
|
2885
|
-
"AudioVolumeMute",
|
|
2886
|
-
"AudioVolumeUp",
|
|
2887
|
-
"MicrophoneToggle",
|
|
2888
|
-
"MicrophoneVolumeDown",
|
|
2889
|
-
"MicrophoneVolumeMute",
|
|
2890
|
-
"MicrophoneVolumeUp",
|
|
2891
|
-
"TV",
|
|
2892
|
-
"TV3DMode",
|
|
2893
|
-
"TVAntennaCable",
|
|
2894
|
-
"TVAudioDescription",
|
|
2895
|
-
];
|
|
2896
|
-
function unEscapeString(str) {
|
|
2897
|
-
const placeholder = "__NEWLINE__";
|
|
2898
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2899
|
-
return str;
|
|
2900
|
-
}
|
|
2901
3012
|
export { StableBrowser };
|
|
2902
3013
|
//# sourceMappingURL=stable_browser.js.map
|