automation_model 1.0.536-dev → 1.0.536-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.
@@ -10,7 +10,7 @@ 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";
@@ -19,7 +19,9 @@ 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
- const Types = {
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",
@@ -46,8 +50,13 @@ const Types = {
46
50
  LOAD_DATA: "load_data",
47
51
  SET_INPUT: "set_input",
48
52
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
53
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
54
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
49
55
  };
50
56
  export const apps = {};
57
+ const formatElementName = (elementName) => {
58
+ return elementName ? JSON.stringify(elementName) : "element";
59
+ };
51
60
  class StableBrowser {
52
61
  browser;
53
62
  page;
@@ -60,7 +69,6 @@ class StableBrowser {
60
69
  configuration = null;
61
70
  appName = "main";
62
71
  tags = null;
63
- isRecording = false;
64
72
  constructor(browser, page, logger = null, context = null, world = null) {
65
73
  this.browser = browser;
66
74
  this.page = page;
@@ -91,35 +99,17 @@ class StableBrowser {
91
99
  catch (e) {
92
100
  this.logger.error("unable to read ai_config.json");
93
101
  }
94
- context.pageLoading = { status: false };
95
- context.pages = [this.page];
96
102
  const logFolder = path.join(this.project_path, "logs", "web");
97
103
  this.world = world;
104
+ context.pages = [this.page];
105
+ context.pageLoading = { status: false };
98
106
  this.registerEventListeners(this.context);
99
107
  registerNetworkEvents(this.world, this, this.context, this.page);
100
108
  registerDownloadEvent(this.page, this.world, this.context);
101
109
  }
102
- async scrollPageToLoadLazyElements() {
103
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
104
- let retry = 0;
105
- while (true) {
106
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
107
- await new Promise((resolve) => setTimeout(resolve, 1000));
108
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
109
- if (newHeight === lastHeight) {
110
- break;
111
- }
112
- lastHeight = newHeight;
113
- retry++;
114
- if (retry > 10) {
115
- break;
116
- }
117
- }
118
- await this.page.evaluate(() => window.scrollTo(0, 0));
119
- }
120
110
  registerEventListeners(context) {
121
111
  this.registerConsoleLogListener(this.page, context);
122
- this.registerRequestListener(this.page, context, this.webLogFile);
112
+ // this.registerRequestListener(this.page, context, this.webLogFile);
123
113
  if (!context.pageLoading) {
124
114
  context.pageLoading = { status: false };
125
115
  }
@@ -164,7 +154,7 @@ class StableBrowser {
164
154
  if (this.appName === appName) {
165
155
  return;
166
156
  }
167
- let newContextCreated = false;
157
+ let navigate = false;
168
158
  if (!apps[appName]) {
169
159
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
170
160
  newContextCreated = true;
@@ -175,32 +165,15 @@ class StableBrowser {
175
165
  };
176
166
  }
177
167
  const tempContext = {};
178
- this._copyContext(this, tempContext);
179
- this._copyContext(apps[appName], this);
168
+ _copyContext(this, tempContext);
169
+ _copyContext(apps[appName], this);
180
170
  apps[this.appName] = tempContext;
181
171
  this.appName = appName;
182
- if (newContextCreated) {
183
- this.registerEventListeners(this.context);
172
+ if (navigate) {
184
173
  await this.goto(this.context.environment.baseUrl);
185
174
  await this.waitForPageLoad();
186
175
  }
187
176
  }
188
- _copyContext(from, to) {
189
- to.browser = from.browser;
190
- to.page = from.page;
191
- to.context = from.context;
192
- }
193
- getWebLogFile(logFolder) {
194
- if (!fs.existsSync(logFolder)) {
195
- fs.mkdirSync(logFolder, { recursive: true });
196
- }
197
- let nextIndex = 1;
198
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
199
- nextIndex++;
200
- }
201
- const fileName = nextIndex + ".json";
202
- return path.join(logFolder, fileName);
203
- }
204
177
  registerConsoleLogListener(page, context) {
205
178
  if (!this.context.webLogger) {
206
179
  this.context.webLogger = [];
@@ -264,55 +237,48 @@ class StableBrowser {
264
237
  // async closeUnexpectedPopups() {
265
238
  // await closeUnexpectedPopups(this.page);
266
239
  // }
267
- async goto(url) {
240
+ async goto(url, world = null) {
268
241
  if (!url.startsWith("http")) {
269
242
  url = "https://" + url;
270
243
  }
271
- await this.page.goto(url, {
272
- timeout: 60000,
273
- });
274
- }
275
- _fixUsingParams(text, _params) {
276
- if (!_params || typeof text !== "string") {
277
- return text;
244
+ const state = {
245
+ value: url,
246
+ world: world,
247
+ type: Types.NAVIGATE,
248
+ text: `Navigate Page to: ${url}`,
249
+ operation: "goto",
250
+ log: "***** navigate page to " + url + " *****\n",
251
+ info: {},
252
+ locate: false,
253
+ scroll: false,
254
+ screenshot: false,
255
+ highlight: false,
256
+ };
257
+ try {
258
+ await _preCommand(state, this);
259
+ await this.page.goto(url, {
260
+ timeout: 60000,
261
+ });
262
+ await _screenshot(state, this);
278
263
  }
279
- for (let key in _params) {
280
- let regValue = key;
281
- if (key.startsWith("_")) {
282
- // remove the _ prefix
283
- regValue = key.substring(1);
284
- }
285
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
264
+ catch (error) {
265
+ console.error("Error on goto", error);
266
+ _commandError(state, error, this);
286
267
  }
287
- return text;
288
- }
289
- _fixLocatorUsingParams(locator, _params) {
290
- // check if not null
291
- if (!locator) {
292
- return locator;
268
+ finally {
269
+ _commandFinally(state, this);
293
270
  }
294
- // clone the locator
295
- locator = JSON.parse(JSON.stringify(locator));
296
- this.scanAndManipulate(locator, _params);
297
- return locator;
298
271
  }
299
- _isObject(value) {
300
- return value && typeof value === "object" && value.constructor === Object;
301
- }
302
- scanAndManipulate(currentObj, _params) {
303
- for (const key in currentObj) {
304
- if (typeof currentObj[key] === "string") {
305
- // Perform string manipulation
306
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
307
- }
308
- else if (this._isObject(currentObj[key])) {
309
- // Recursively scan nested objects
310
- this.scanAndManipulate(currentObj[key], _params);
272
+ async _getLocator(locator, scope, _params) {
273
+ locator = _fixLocatorUsingParams(locator, _params);
274
+ // locator = await this._replaceWithLocalData(locator);
275
+ for (let key in locator) {
276
+ if (typeof locator[key] !== "string")
277
+ continue;
278
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
279
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
311
280
  }
312
281
  }
313
- }
314
- _getLocator(locator, scope, _params) {
315
- locator = this._fixLocatorUsingParams(locator, _params);
316
282
  let locatorReturn;
317
283
  if (locator.role) {
318
284
  if (locator.role[1].nameReg) {
@@ -320,7 +286,7 @@ class StableBrowser {
320
286
  delete locator.role[1].nameReg;
321
287
  }
322
288
  // if (locator.role[1].name) {
323
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
289
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
324
290
  // }
325
291
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
326
292
  }
@@ -363,140 +329,54 @@ class StableBrowser {
363
329
  if (css && css.locator) {
364
330
  css = css.locator;
365
331
  }
366
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
332
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
367
333
  if (result.elementCount === 0) {
368
334
  return;
369
335
  }
370
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
336
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
371
337
  // css climb to parent element
372
338
  const climbArray = [];
373
339
  for (let i = 0; i < climb; i++) {
374
340
  climbArray.push("..");
375
341
  }
376
342
  let climbXpath = "xpath=" + climbArray.join("/");
377
- return textElementCss + " >> " + climbXpath + " >> " + css;
378
- }
379
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
380
- //const stringifyText = JSON.stringify(text);
381
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
382
- function isParent(parent, child) {
383
- let currentNode = child.parentNode;
384
- while (currentNode !== null) {
385
- if (currentNode === parent) {
386
- return true;
343
+ let resultCss = textElementCss + " >> " + climbXpath;
344
+ if (css) {
345
+ resultCss = resultCss + " >> " + css;
346
+ }
347
+ return resultCss;
348
+ }
349
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
350
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
351
+ const locator = scope.locator(query);
352
+ const count = await locator.count();
353
+ if (!tag1) {
354
+ tag1 = "*";
355
+ }
356
+ const randomToken = Math.random().toString(36).substring(7);
357
+ let tagCount = 0;
358
+ for (let i = 0; i < count; i++) {
359
+ const element = locator.nth(i);
360
+ // check if the tag matches
361
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
362
+ if (!tag.startsWith("*")) {
363
+ if (el.tagName.toLowerCase() !== tag) {
364
+ return false;
387
365
  }
388
- currentNode = currentNode.parentNode;
389
- }
390
- return false;
391
- }
392
- document.isParent = isParent;
393
- function getRegex(str) {
394
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
395
- if (!match) {
396
- return null;
397
- }
398
- let [_, pattern, flags] = match;
399
- return new RegExp(pattern, flags);
400
- }
401
- document.getRegex = getRegex;
402
- function collectAllShadowDomElements(element, result = []) {
403
- // Check and add the element if it has a shadow root
404
- if (element.shadowRoot) {
405
- result.push(element);
406
- // Also search within the shadow root
407
- document.collectAllShadowDomElements(element.shadowRoot, result);
408
- }
409
- // Iterate over child nodes
410
- element.childNodes.forEach((child) => {
411
- // Recursively call the function for each child node
412
- document.collectAllShadowDomElements(child, result);
413
- });
414
- return result;
415
- }
416
- document.collectAllShadowDomElements = collectAllShadowDomElements;
417
- if (!tag) {
418
- tag = "*:not(script, style, head)";
419
- }
420
- let regexpSearch = document.getRegex(text);
421
- if (regexpSearch) {
422
- regex = true;
423
- }
424
- let elements = Array.from(document.querySelectorAll(tag));
425
- let shadowHosts = [];
426
- document.collectAllShadowDomElements(document, shadowHosts);
427
- for (let i = 0; i < shadowHosts.length; i++) {
428
- let shadowElement = shadowHosts[i].shadowRoot;
429
- if (!shadowElement) {
430
- console.log("shadowElement is null, for host " + shadowHosts[i]);
431
- continue;
432
- }
433
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
434
- elements = elements.concat(shadowElements);
435
- }
436
- let randomToken = null;
437
- const foundElements = [];
438
- if (regex) {
439
- if (!regexpSearch) {
440
- regexpSearch = new RegExp(text, "im");
441
366
  }
442
- for (let i = 0; i < elements.length; i++) {
443
- const element = elements[i];
444
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
445
- (element.value && regexpSearch.test(element.value))) {
446
- foundElements.push(element);
447
- }
448
- }
449
- }
450
- else {
451
- text = text.trim();
452
- for (let i = 0; i < elements.length; i++) {
453
- const element = elements[i];
454
- if (partial) {
455
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
456
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
457
- foundElements.push(element);
458
- }
459
- }
460
- else {
461
- if ((element.innerText && element.innerText.trim() === text) ||
462
- (element.value && element.value === text)) {
463
- foundElements.push(element);
464
- }
465
- }
466
- }
467
- }
468
- let noChildElements = [];
469
- for (let i = 0; i < foundElements.length; i++) {
470
- let element = foundElements[i];
471
- let hasChild = false;
472
- for (let j = 0; j < foundElements.length; j++) {
473
- if (i === j) {
474
- continue;
475
- }
476
- if (isParent(element, foundElements[j])) {
477
- hasChild = true;
478
- break;
479
- }
480
- }
481
- if (!hasChild) {
482
- noChildElements.push(element);
483
- }
484
- }
485
- let elementCount = 0;
486
- if (noChildElements.length > 0) {
487
- for (let i = 0; i < noChildElements.length; i++) {
488
- if (randomToken === null) {
489
- randomToken = Math.random().toString(36).substring(7);
490
- }
491
- let element = noChildElements[i];
492
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
493
- elementCount++;
367
+ if (!el.setAttribute) {
368
+ el = el.parentElement;
494
369
  }
370
+ el.setAttribute("data-blinq-id-" + randomToken, "");
371
+ return true;
372
+ }, [tag1, randomToken]))) {
373
+ continue;
495
374
  }
496
- return { elementCount: elementCount, randomToken: randomToken };
497
- }, [text1, tag1, regex1, partial1]);
375
+ tagCount++;
376
+ }
377
+ return { elementCount: tagCount, randomToken };
498
378
  }
499
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
379
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
500
380
  if (!info) {
501
381
  info = {};
502
382
  }
@@ -505,10 +385,13 @@ class StableBrowser {
505
385
  }
506
386
  if (!info.log) {
507
387
  info.log = "";
388
+ info.locatorLog = new LocatorLog(selectorHierarchy);
508
389
  }
509
390
  let locatorSearch = selectorHierarchy[index];
391
+ let originalLocatorSearch = "";
510
392
  try {
511
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
393
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
394
+ locatorSearch = JSON.parse(originalLocatorSearch);
512
395
  }
513
396
  catch (e) {
514
397
  console.error(e);
@@ -516,30 +399,31 @@ class StableBrowser {
516
399
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
517
400
  let locator = null;
518
401
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
519
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
402
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
403
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
520
404
  if (!locatorString) {
521
405
  info.failCause.textNotFound = true;
522
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
406
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
523
407
  return;
524
408
  }
525
- locator = this._getLocator({ css: locatorString }, scope, _params);
409
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
526
410
  }
527
411
  else if (locatorSearch.text) {
528
- let text = this._fixUsingParams(locatorSearch.text, _params);
529
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
412
+ let text = _fixUsingParams(locatorSearch.text, _params);
413
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
530
414
  if (result.elementCount === 0) {
531
415
  info.failCause.textNotFound = true;
532
- info.failCause.lastError = "failed to locate element by text: " + text;
416
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
533
417
  return;
534
418
  }
535
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
419
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
536
420
  if (locatorSearch.childCss) {
537
421
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
538
422
  }
539
- locator = this._getLocator(locatorSearch, scope, _params);
423
+ locator = await this._getLocator(locatorSearch, scope, _params);
540
424
  }
541
425
  else {
542
- locator = this._getLocator(locatorSearch, scope, _params);
426
+ locator = await this._getLocator(locatorSearch, scope, _params);
543
427
  }
544
428
  // let cssHref = false;
545
429
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -552,18 +436,27 @@ class StableBrowser {
552
436
  //info.log += "total elements found " + count + "\n";
553
437
  //let visibleCount = 0;
554
438
  let visibleLocator = null;
555
- if (locatorSearch.index && locatorSearch.index < count) {
439
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
556
440
  foundLocators.push(locator.nth(locatorSearch.index));
441
+ if (info.locatorLog) {
442
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
443
+ }
557
444
  return;
558
445
  }
446
+ if (info.locatorLog && count === 0) {
447
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
448
+ }
559
449
  for (let j = 0; j < count; j++) {
560
450
  let visible = await locator.nth(j).isVisible();
561
451
  const enabled = await locator.nth(j).isEnabled();
562
452
  if (!visibleOnly) {
563
453
  visible = true;
564
454
  }
565
- if (visible && enabled) {
455
+ if (visible && (allowDisabled || enabled)) {
566
456
  foundLocators.push(locator.nth(j));
457
+ if (info.locatorLog) {
458
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
459
+ }
567
460
  }
568
461
  else {
569
462
  info.failCause.visible = visible;
@@ -571,8 +464,16 @@ class StableBrowser {
571
464
  if (!info.printMessages) {
572
465
  info.printMessages = {};
573
466
  }
467
+ if (info.locatorLog && !visible) {
468
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
469
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
470
+ }
471
+ if (info.locatorLog && !enabled) {
472
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
473
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
474
+ }
574
475
  if (!info.printMessages[j.toString()]) {
575
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
476
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
576
477
  info.printMessages[j.toString()] = true;
577
478
  }
578
479
  }
@@ -588,12 +489,12 @@ class StableBrowser {
588
489
  if (!info) {
589
490
  info = {};
590
491
  }
591
- info.log += "scan for popup handlers" + "\n";
492
+ //info.log += "scan for popup handlers" + "\n";
592
493
  const handlerGroup = [];
593
494
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
594
495
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
595
496
  }
596
- const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
497
+ const scopes = [this.page, ...this.page.frames()];
597
498
  let result = null;
598
499
  let scope = null;
599
500
  for (let i = 0; i < scopes.length; i++) {
@@ -636,7 +537,7 @@ class StableBrowser {
636
537
  }
637
538
  return { rerun: false };
638
539
  }
639
- async _locate(selectors, info, _params, timeout) {
540
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
640
541
  if (!timeout) {
641
542
  timeout = 30000;
642
543
  }
@@ -646,9 +547,18 @@ class StableBrowser {
646
547
  let selector = selectors.locators[j];
647
548
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
648
549
  }
649
- let element = await this._locate_internal(selectors, info, _params, timeout);
550
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
650
551
  if (!element.rerun) {
651
- return element;
552
+ const randomToken = Math.random().toString(36).substring(7);
553
+ element.evaluate((el, randomToken) => {
554
+ el.setAttribute("data-blinq-id-" + randomToken, "");
555
+ }, randomToken);
556
+ if (element._frame) {
557
+ return element;
558
+ }
559
+ const scope = element.page();
560
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
561
+ return newSelector;
652
562
  }
653
563
  }
654
564
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -659,6 +569,7 @@ class StableBrowser {
659
569
  info.failCause = {};
660
570
  info.log = "";
661
571
  }
572
+ let startTime = Date.now();
662
573
  let scope = this.page;
663
574
  if (selectors.frame) {
664
575
  return selectors.frame;
@@ -689,9 +600,11 @@ class StableBrowser {
689
600
  }
690
601
  return framescope;
691
602
  };
603
+ let fLocator = null;
692
604
  while (true) {
693
605
  let frameFound = false;
694
606
  if (selectors.nestFrmLoc) {
607
+ fLocator = selectors.nestFrmLoc;
695
608
  scope = await findFrame(selectors.nestFrmLoc, scope);
696
609
  frameFound = true;
697
610
  break;
@@ -700,6 +613,7 @@ class StableBrowser {
700
613
  for (let i = 0; i < selectors.frameLocators.length; i++) {
701
614
  let frameLocator = selectors.frameLocators[i];
702
615
  if (frameLocator.css) {
616
+ fLocator = frameLocator.css;
703
617
  scope = scope.frameLocator(frameLocator.css);
704
618
  frameFound = true;
705
619
  break;
@@ -707,18 +621,25 @@ class StableBrowser {
707
621
  }
708
622
  }
709
623
  if (!frameFound && selectors.iframe_src) {
624
+ fLocator = selectors.iframe_src;
710
625
  scope = this.page.frame({ url: selectors.iframe_src });
711
626
  }
712
627
  if (!scope) {
713
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
714
- if (performance.now() - startTime > timeout) {
628
+ if (info && info.locatorLog) {
629
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
630
+ }
631
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
632
+ if (Date.now() - startTime > timeout) {
715
633
  info.failCause.iframeNotFound = true;
716
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
634
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
717
635
  throw new Error("unable to locate iframe " + selectors.iframe_src);
718
636
  }
719
637
  await new Promise((resolve) => setTimeout(resolve, 1000));
720
638
  }
721
639
  else {
640
+ if (info && info.locatorLog) {
641
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
642
+ }
722
643
  break;
723
644
  }
724
645
  }
@@ -735,15 +656,16 @@ class StableBrowser {
735
656
  return bodyContent;
736
657
  });
737
658
  }
738
- async _locate_internal(selectors, info, _params, timeout = 30000) {
659
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
739
660
  if (!info) {
740
661
  info = {};
741
662
  info.failCause = {};
742
663
  info.log = "";
664
+ info.locatorLog = new LocatorLog(selectors);
743
665
  }
744
666
  let highPriorityTimeout = 5000;
745
667
  let visibleOnlyTimeout = 6000;
746
- let startTime = performance.now();
668
+ let startTime = Date.now();
747
669
  let locatorsCount = 0;
748
670
  let lazy_scroll = false;
749
671
  //let arrayMode = Array.isArray(selectors);
@@ -783,17 +705,17 @@ class StableBrowser {
783
705
  }
784
706
  // info.log += "scanning locators in priority 1" + "\n";
785
707
  let onlyPriority3 = selectorsLocators[0].priority === 3;
786
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
708
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
787
709
  if (result.foundElements.length === 0) {
788
710
  // info.log += "scanning locators in priority 2" + "\n";
789
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
711
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
790
712
  }
791
713
  if (result.foundElements.length === 0 && onlyPriority3) {
792
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
714
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
793
715
  }
794
716
  else {
795
717
  if (result.foundElements.length === 0 && !highPriorityOnly) {
796
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
718
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
797
719
  }
798
720
  }
799
721
  let foundElements = result.foundElements;
@@ -834,30 +756,38 @@ class StableBrowser {
834
756
  return maxCountElement.locator;
835
757
  }
836
758
  }
837
- if (performance.now() - startTime > timeout) {
759
+ if (Date.now() - startTime > timeout) {
838
760
  break;
839
761
  }
840
- if (performance.now() - startTime > highPriorityTimeout) {
841
- info.log += "high priority timeout, will try all elements" + "\n";
762
+ if (Date.now() - startTime > highPriorityTimeout) {
763
+ //info.log += "high priority timeout, will try all elements" + "\n";
842
764
  highPriorityOnly = false;
843
765
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
844
766
  lazy_scroll = true;
845
- await this.scrollPageToLoadLazyElements();
767
+ await scrollPageToLoadLazyElements(this.page);
846
768
  }
847
769
  }
848
- if (performance.now() - startTime > visibleOnlyTimeout) {
849
- info.log += "visible only timeout, will try all elements" + "\n";
770
+ if (Date.now() - startTime > visibleOnlyTimeout) {
771
+ //info.log += "visible only timeout, will try all elements" + "\n";
850
772
  visibleOnly = false;
851
773
  }
852
774
  await new Promise((resolve) => setTimeout(resolve, 1000));
853
775
  }
854
776
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
855
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
777
+ // if (info.locatorLog) {
778
+ // const lines = info.locatorLog.toString().split("\n");
779
+ // for (let line of lines) {
780
+ // this.logger.debug(line);
781
+ // }
782
+ // }
783
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
856
784
  info.failCause.locatorNotFound = true;
857
- info.failCause.lastError = "failed to locate unique element";
785
+ if (!info?.failCause?.lastError) {
786
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
787
+ }
858
788
  throw new Error("failed to locate first element no elements found, " + info.log);
859
789
  }
860
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
790
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
861
791
  let foundElements = [];
862
792
  const result = {
863
793
  foundElements: foundElements,
@@ -865,14 +795,15 @@ class StableBrowser {
865
795
  for (let i = 0; i < locatorsGroup.length; i++) {
866
796
  let foundLocators = [];
867
797
  try {
868
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
798
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
869
799
  }
870
800
  catch (e) {
871
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
872
- this.logger.debug(e);
801
+ // this call can fail it the browser is navigating
802
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
803
+ // this.logger.debug(e);
873
804
  foundLocators = [];
874
805
  try {
875
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
806
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
876
807
  }
877
808
  catch (e) {
878
809
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -888,11 +819,27 @@ class StableBrowser {
888
819
  }
889
820
  if (foundLocators.length > 1) {
890
821
  info.failCause.foundMultiple = true;
822
+ if (info.locatorLog) {
823
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
824
+ }
891
825
  }
892
826
  }
893
827
  return result;
894
828
  }
895
829
  async simpleClick(elementDescription, _params, options = {}, world = null) {
830
+ const state = {
831
+ locate: false,
832
+ scroll: false,
833
+ highlight: false,
834
+ _params,
835
+ options,
836
+ world,
837
+ type: Types.CLICK,
838
+ text: "Click element",
839
+ operation: "simpleClick",
840
+ log: "***** click on " + elementDescription + " *****\n",
841
+ };
842
+ _preCommand(state, this);
896
843
  const startTime = Date.now();
897
844
  let timeout = 30000;
898
845
  if (options && options.timeout) {
@@ -917,13 +864,31 @@ class StableBrowser {
917
864
  catch (e) {
918
865
  if (performance.now() - startTime > timeout) {
919
866
  // throw e;
920
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
867
+ try {
868
+ await _commandError(state, "timeout looking for " + elementDescription, this);
869
+ }
870
+ finally {
871
+ _commandFinally(state, this);
872
+ }
921
873
  }
922
874
  }
923
875
  await new Promise((resolve) => setTimeout(resolve, 3000));
924
876
  }
925
877
  }
926
878
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
879
+ const state = {
880
+ locate: false,
881
+ scroll: false,
882
+ highlight: false,
883
+ _params,
884
+ options,
885
+ world,
886
+ type: Types.FILL,
887
+ text: "Fill element",
888
+ operation: "simpleClickType",
889
+ log: "***** click type on " + elementDescription + " *****\n",
890
+ };
891
+ _preCommand(state, this);
927
892
  const startTime = Date.now();
928
893
  let timeout = 30000;
929
894
  if (options && options.timeout) {
@@ -948,7 +913,12 @@ class StableBrowser {
948
913
  catch (e) {
949
914
  if (performance.now() - startTime > timeout) {
950
915
  // throw e;
951
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
916
+ try {
917
+ await _commandError(state, "timeout looking for " + elementDescription, this);
918
+ }
919
+ finally {
920
+ _commandFinally(state, this);
921
+ }
952
922
  }
953
923
  }
954
924
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -967,9 +937,9 @@ class StableBrowser {
967
937
  };
968
938
  try {
969
939
  await _preCommand(state, this);
970
- if (state.options && state.options.context) {
971
- state.selectors.locators[0].text = state.options.context;
972
- }
940
+ // if (state.options && state.options.context) {
941
+ // state.selectors.locators[0].text = state.options.context;
942
+ // }
973
943
  try {
974
944
  await state.element.click();
975
945
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1007,9 +977,15 @@ class StableBrowser {
1007
977
  // let element = await this._locate(selectors, info, _params);
1008
978
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1009
979
  try {
1010
- // await this._highlightElements(element);
980
+ // if (world && world.screenshot && !world.screenshotPath) {
981
+ // console.log(`Highlighting while running from recorder`);
982
+ await this._highlightElements(element);
1011
983
  await state.element.setChecked(checked);
1012
984
  await new Promise((resolve) => setTimeout(resolve, 1000));
985
+ // await this._unHighlightElements(element);
986
+ // }
987
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
988
+ // await this._unHighlightElements(element);
1013
989
  }
1014
990
  catch (e) {
1015
991
  if (e.message && e.message.includes("did not change its state")) {
@@ -1048,6 +1024,7 @@ class StableBrowser {
1048
1024
  await _preCommand(state, this);
1049
1025
  try {
1050
1026
  await state.element.hover();
1027
+ // await _screenshot(state, this);
1051
1028
  await new Promise((resolve) => setTimeout(resolve, 1000));
1052
1029
  }
1053
1030
  catch (e) {
@@ -1055,6 +1032,7 @@ class StableBrowser {
1055
1032
  state.info.log += "hover failed, will try again" + "\n";
1056
1033
  state.element = await this._locate(selectors, state.info, _params);
1057
1034
  await state.element.hover({ timeout: 10000 });
1035
+ // await _screenshot(state, this);
1058
1036
  await new Promise((resolve) => setTimeout(resolve, 1000));
1059
1037
  }
1060
1038
  await _screenshot(state, this);
@@ -1329,7 +1307,12 @@ class StableBrowser {
1329
1307
  await this.waitForPageLoad();
1330
1308
  }
1331
1309
  else if (enter === false) {
1332
- await state.element.dispatchEvent("change");
1310
+ try {
1311
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1312
+ }
1313
+ catch (e) {
1314
+ // ignore
1315
+ }
1333
1316
  //await this.page.keyboard.press("Tab");
1334
1317
  }
1335
1318
  else {
@@ -1386,6 +1369,7 @@ class StableBrowser {
1386
1369
  let screenshotPath = null;
1387
1370
  if (!info.log) {
1388
1371
  info.log = "";
1372
+ info.locatorLog = new LocatorLog(selectors);
1389
1373
  }
1390
1374
  info.operation = "getText";
1391
1375
  info.selectors = selectors;
@@ -1408,6 +1392,18 @@ class StableBrowser {
1408
1392
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1409
1393
  try {
1410
1394
  await this._highlightElements(element);
1395
+ // if (world && world.screenshot && !world.screenshotPath) {
1396
+ // // console.log(`Highlighting for get text while running from recorder`);
1397
+ // this._highlightElements(element)
1398
+ // .then(async () => {
1399
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1400
+ // this._unhighlightElements(element).then(
1401
+ // () => {}
1402
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1403
+ // );
1404
+ // })
1405
+ // .catch(e);
1406
+ // }
1411
1407
  const elementText = await element.innerText();
1412
1408
  return {
1413
1409
  text: elementText,
@@ -1419,7 +1415,7 @@ class StableBrowser {
1419
1415
  }
1420
1416
  catch (e) {
1421
1417
  //await this.closeUnexpectedPopups();
1422
- this.logger.info("no innerText will use textContent");
1418
+ this.logger.info("no innerText, will use textContent");
1423
1419
  const elementText = await element.textContent();
1424
1420
  return { text: elementText, screenshotId, screenshotPath, value: value };
1425
1421
  }
@@ -1545,22 +1541,6 @@ class StableBrowser {
1545
1541
  _commandFinally(state, this);
1546
1542
  }
1547
1543
  }
1548
- _getDataFile(world = null) {
1549
- let dataFile = null;
1550
- if (world && world.reportFolder) {
1551
- dataFile = path.join(world.reportFolder, "data.json");
1552
- }
1553
- else if (this.reportFolder) {
1554
- dataFile = path.join(this.reportFolder, "data.json");
1555
- }
1556
- else if (this.context && this.context.reportFolder) {
1557
- dataFile = path.join(this.context.reportFolder, "data.json");
1558
- }
1559
- else {
1560
- dataFile = "data.json";
1561
- }
1562
- return dataFile;
1563
- }
1564
1544
  async waitForUserInput(message, world = null) {
1565
1545
  if (!message) {
1566
1546
  message = "# Wait for user input. Press any key to continue";
@@ -1589,7 +1569,7 @@ class StableBrowser {
1589
1569
  return;
1590
1570
  }
1591
1571
  // if data file exists, load it
1592
- const dataFile = this._getDataFile(world);
1572
+ const dataFile = _getDataFile(world, this.context, this);
1593
1573
  let data = this.getTestData(world);
1594
1574
  // merge the testData with the existing data
1595
1575
  Object.assign(data, testData);
@@ -1692,7 +1672,7 @@ class StableBrowser {
1692
1672
  }
1693
1673
  }
1694
1674
  getTestData(world = null) {
1695
- const dataFile = this._getDataFile(world);
1675
+ const dataFile = _getDataFile(world, this.context, this);
1696
1676
  let data = {};
1697
1677
  if (fs.existsSync(dataFile)) {
1698
1678
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1779,6 +1759,15 @@ class StableBrowser {
1779
1759
  document.documentElement.clientWidth,
1780
1760
  ])));
1781
1761
  let screenshotBuffer = null;
1762
+ // if (focusedElement) {
1763
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1764
+ // await this._unhighlightElements(focusedElement);
1765
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1766
+ // console.log(`Unhighlighted previous element`);
1767
+ // }
1768
+ // if (focusedElement) {
1769
+ // await this._highlightElements(focusedElement);
1770
+ // }
1782
1771
  if (this.context.browserName === "chromium") {
1783
1772
  const client = await playContext.newCDPSession(this.page);
1784
1773
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1800,6 +1789,10 @@ class StableBrowser {
1800
1789
  else {
1801
1790
  screenshotBuffer = await this.page.screenshot();
1802
1791
  }
1792
+ // if (focusedElement) {
1793
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1794
+ // await this._unhighlightElements(focusedElement);
1795
+ // }
1803
1796
  let image = await Jimp.read(screenshotBuffer);
1804
1797
  // Get the image dimensions
1805
1798
  const { width, height } = image.bitmap;
@@ -1812,6 +1805,7 @@ class StableBrowser {
1812
1805
  else {
1813
1806
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1814
1807
  }
1808
+ return screenshotBuffer;
1815
1809
  }
1816
1810
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1817
1811
  const state = {
@@ -1849,6 +1843,7 @@ class StableBrowser {
1849
1843
  text: `Extract attribute from element`,
1850
1844
  operation: "extractAttribute",
1851
1845
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1846
+ allowDisabled: true,
1852
1847
  };
1853
1848
  await new Promise((resolve) => setTimeout(resolve, 2000));
1854
1849
  try {
@@ -1870,6 +1865,76 @@ class StableBrowser {
1870
1865
  state.info.value = state.value;
1871
1866
  this.setTestData({ [variable]: state.value }, world);
1872
1867
  this.logger.info("set test data: " + variable + "=" + state.value);
1868
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1869
+ return state.info;
1870
+ }
1871
+ catch (e) {
1872
+ await _commandError(state, e, this);
1873
+ }
1874
+ finally {
1875
+ _commandFinally(state, this);
1876
+ }
1877
+ }
1878
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1879
+ const state = {
1880
+ selectors,
1881
+ _params,
1882
+ attribute,
1883
+ value,
1884
+ options,
1885
+ world,
1886
+ type: Types.VERIFY_ATTRIBUTE,
1887
+ highlight: true,
1888
+ screenshot: true,
1889
+ text: `Verify element attribute`,
1890
+ operation: "verifyAttribute",
1891
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1892
+ allowDisabled: true,
1893
+ };
1894
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1895
+ let val;
1896
+ let expectedValue;
1897
+ try {
1898
+ await _preCommand(state, this);
1899
+ expectedValue = state.value;
1900
+ state.info.expectedValue = expectedValue;
1901
+ switch (attribute) {
1902
+ case "innerText":
1903
+ val = String(await state.element.innerText());
1904
+ break;
1905
+ case "value":
1906
+ val = String(await state.element.inputValue());
1907
+ break;
1908
+ case "checked":
1909
+ val = String(await state.element.isChecked());
1910
+ break;
1911
+ case "disabled":
1912
+ val = String(await state.element.isDisabled());
1913
+ break;
1914
+ case "readOnly":
1915
+ const isEditable = await state.element.isEditable();
1916
+ val = String(!isEditable);
1917
+ break;
1918
+ default:
1919
+ val = String(await state.element.getAttribute(attribute));
1920
+ break;
1921
+ }
1922
+ state.info.value = val;
1923
+ let regex;
1924
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1925
+ const patternBody = expectedValue.slice(1, -1);
1926
+ regex = new RegExp(patternBody, "g");
1927
+ }
1928
+ else {
1929
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1930
+ regex = new RegExp(escapedPattern, "g");
1931
+ }
1932
+ if (!val.match(regex)) {
1933
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1934
+ state.info.failCause.assertionFailed = true;
1935
+ state.info.failCause.lastError = errorMessage;
1936
+ throw new Error(errorMessage);
1937
+ }
1873
1938
  return state.info;
1874
1939
  }
1875
1940
  catch (e) {
@@ -1897,7 +1962,7 @@ class StableBrowser {
1897
1962
  if (options && options.timeout) {
1898
1963
  timeout = options.timeout;
1899
1964
  }
1900
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1965
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1901
1966
  const request = {
1902
1967
  method: "POST",
1903
1968
  url: serviceUrl,
@@ -1968,27 +2033,32 @@ class StableBrowser {
1968
2033
  async _highlightElements(scope, css) {
1969
2034
  try {
1970
2035
  if (!scope) {
2036
+ // console.log(`Scope is not defined`);
1971
2037
  return;
1972
2038
  }
1973
2039
  if (!css) {
1974
2040
  scope
1975
2041
  .evaluate((node) => {
1976
2042
  if (node && node.style) {
1977
- let originalBorder = node.style.border;
1978
- node.style.border = "2px solid red";
2043
+ let originalOutline = node.style.outline;
2044
+ // console.log(`Original outline was: ${originalOutline}`);
2045
+ // node.__previousOutline = originalOutline;
2046
+ node.style.outline = "2px solid red";
2047
+ // console.log(`New outline is: ${node.style.outline}`);
1979
2048
  if (window) {
1980
2049
  window.addEventListener("beforeunload", function (e) {
1981
- node.style.border = originalBorder;
2050
+ node.style.outline = originalOutline;
1982
2051
  });
1983
2052
  }
1984
2053
  setTimeout(function () {
1985
- node.style.border = originalBorder;
2054
+ node.style.outline = originalOutline;
1986
2055
  }, 2000);
1987
2056
  }
1988
2057
  })
1989
2058
  .then(() => { })
1990
2059
  .catch((e) => {
1991
2060
  // ignore
2061
+ // console.error(`Could not highlight node : ${e}`);
1992
2062
  });
1993
2063
  }
1994
2064
  else {
@@ -2004,17 +2074,18 @@ class StableBrowser {
2004
2074
  if (!element.style) {
2005
2075
  return;
2006
2076
  }
2007
- var originalBorder = element.style.border;
2077
+ let originalOutline = element.style.outline;
2078
+ element.__previousOutline = originalOutline;
2008
2079
  // Set the new border to be red and 2px solid
2009
- element.style.border = "2px solid red";
2080
+ element.style.outline = "2px solid red";
2010
2081
  if (window) {
2011
2082
  window.addEventListener("beforeunload", function (e) {
2012
- element.style.border = originalBorder;
2083
+ element.style.outline = originalOutline;
2013
2084
  });
2014
2085
  }
2015
2086
  // Set a timeout to revert to the original border after 2 seconds
2016
2087
  setTimeout(function () {
2017
- element.style.border = originalBorder;
2088
+ element.style.outline = originalOutline;
2018
2089
  }, 2000);
2019
2090
  }
2020
2091
  return;
@@ -2022,6 +2093,7 @@ class StableBrowser {
2022
2093
  .then(() => { })
2023
2094
  .catch((e) => {
2024
2095
  // ignore
2096
+ // console.error(`Could not highlight css: ${e}`);
2025
2097
  });
2026
2098
  }
2027
2099
  }
@@ -2029,6 +2101,54 @@ class StableBrowser {
2029
2101
  console.debug(error);
2030
2102
  }
2031
2103
  }
2104
+ // async _unhighlightElements(scope, css) {
2105
+ // try {
2106
+ // if (!scope) {
2107
+ // return;
2108
+ // }
2109
+ // if (!css) {
2110
+ // scope
2111
+ // .evaluate((node) => {
2112
+ // if (node && node.style) {
2113
+ // if (!node.__previousOutline) {
2114
+ // node.style.outline = "";
2115
+ // } else {
2116
+ // node.style.outline = node.__previousOutline;
2117
+ // }
2118
+ // }
2119
+ // })
2120
+ // .then(() => {})
2121
+ // .catch((e) => {
2122
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2123
+ // });
2124
+ // } else {
2125
+ // scope
2126
+ // .evaluate(([css]) => {
2127
+ // if (!css) {
2128
+ // return;
2129
+ // }
2130
+ // let elements = Array.from(document.querySelectorAll(css));
2131
+ // for (i = 0; i < elements.length; i++) {
2132
+ // let element = elements[i];
2133
+ // if (!element.style) {
2134
+ // return;
2135
+ // }
2136
+ // if (!element.__previousOutline) {
2137
+ // element.style.outline = "";
2138
+ // } else {
2139
+ // element.style.outline = element.__previousOutline;
2140
+ // }
2141
+ // }
2142
+ // })
2143
+ // .then(() => {})
2144
+ // .catch((e) => {
2145
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2146
+ // });
2147
+ // }
2148
+ // } catch (error) {
2149
+ // // console.debug(error);
2150
+ // }
2151
+ // }
2032
2152
  async verifyPagePath(pathPart, options = {}, world = null) {
2033
2153
  const startTime = Date.now();
2034
2154
  let error = null;
@@ -2090,6 +2210,35 @@ class StableBrowser {
2090
2210
  });
2091
2211
  }
2092
2212
  }
2213
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2214
+ const frames = this.page.frames();
2215
+ let results = [];
2216
+ let ignoreCase = false;
2217
+ for (let i = 0; i < frames.length; i++) {
2218
+ if (dateAlternatives.date) {
2219
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2220
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2221
+ result.frame = frames[i];
2222
+ results.push(result);
2223
+ }
2224
+ }
2225
+ else if (numberAlternatives.number) {
2226
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2227
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2228
+ result.frame = frames[i];
2229
+ results.push(result);
2230
+ }
2231
+ }
2232
+ else {
2233
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2234
+ result.frame = frames[i];
2235
+ results.push(result);
2236
+ }
2237
+ }
2238
+ state.info.results = results;
2239
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2240
+ return resultWithElementsFound;
2241
+ }
2093
2242
  async verifyTextExistInPage(text, options = {}, world = null) {
2094
2243
  text = unEscapeString(text);
2095
2244
  const state = {
@@ -2099,7 +2248,7 @@ class StableBrowser {
2099
2248
  locate: false,
2100
2249
  scroll: false,
2101
2250
  highlight: false,
2102
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2251
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2103
2252
  text: `Verify text exists in page`,
2104
2253
  operation: "verifyTextExistInPage",
2105
2254
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2117,31 +2266,15 @@ class StableBrowser {
2117
2266
  await _preCommand(state, this);
2118
2267
  state.info.text = text;
2119
2268
  while (true) {
2120
- const frames = this.page.frames();
2121
- let results = [];
2122
- for (let i = 0; i < frames.length; i++) {
2123
- if (dateAlternatives.date) {
2124
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2125
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2126
- result.frame = frames[i];
2127
- results.push(result);
2128
- }
2129
- }
2130
- else if (numberAlternatives.number) {
2131
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2132
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2133
- result.frame = frames[i];
2134
- results.push(result);
2135
- }
2136
- }
2137
- else {
2138
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2139
- result.frame = frames[i];
2140
- results.push(result);
2141
- }
2269
+ let resultWithElementsFound = {
2270
+ length: 0,
2271
+ };
2272
+ try {
2273
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2274
+ }
2275
+ catch (error) {
2276
+ // ignore
2142
2277
  }
2143
- state.info.results = results;
2144
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2145
2278
  if (resultWithElementsFound.length === 0) {
2146
2279
  if (Date.now() - state.startTime > timeout) {
2147
2280
  throw new Error(`Text ${text} not found in page`);
@@ -2149,18 +2282,40 @@ class StableBrowser {
2149
2282
  await new Promise((resolve) => setTimeout(resolve, 1000));
2150
2283
  continue;
2151
2284
  }
2152
- if (resultWithElementsFound[0].randomToken) {
2153
- const frame = resultWithElementsFound[0].frame;
2154
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2155
- await this._highlightElements(frame, dataAttribute);
2156
- const element = await frame.$(dataAttribute);
2157
- if (element) {
2158
- await this.scrollIfNeeded(element, state.info);
2159
- await element.dispatchEvent("bvt_verify_page_contains_text");
2285
+ try {
2286
+ if (resultWithElementsFound[0].randomToken) {
2287
+ const frame = resultWithElementsFound[0].frame;
2288
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2289
+ await this._highlightElements(frame, dataAttribute);
2290
+ // if (world && world.screenshot && !world.screenshotPath) {
2291
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2292
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2293
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2294
+ // this._unhighlightElements(frame, dataAttribute)
2295
+ // .then(async () => {
2296
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2297
+ // })
2298
+ // .catch(
2299
+ // (e) => {}
2300
+ // console.error(e)
2301
+ // );
2302
+ // });
2303
+ // }
2304
+ const element = await frame.locator(dataAttribute).first();
2305
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2306
+ // await this._unhighlightElements(frame, dataAttribute);
2307
+ if (element) {
2308
+ await this.scrollIfNeeded(element, state.info);
2309
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2310
+ // await _screenshot(state, this, element);
2311
+ }
2160
2312
  }
2313
+ await _screenshot(state, this);
2314
+ return state.info;
2315
+ }
2316
+ catch (error) {
2317
+ console.error(error);
2161
2318
  }
2162
- await _screenshot(state, this);
2163
- return state.info;
2164
2319
  }
2165
2320
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2166
2321
  }
@@ -2197,32 +2352,16 @@ class StableBrowser {
2197
2352
  try {
2198
2353
  await _preCommand(state, this);
2199
2354
  state.info.text = text;
2355
+ let resultWithElementsFound = {
2356
+ length: null, // initial cannot be 0
2357
+ };
2200
2358
  while (true) {
2201
- const frames = this.page.frames();
2202
- let results = [];
2203
- for (let i = 0; i < frames.length; i++) {
2204
- if (dateAlternatives.date) {
2205
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2206
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2207
- result.frame = frames[i];
2208
- results.push(result);
2209
- }
2210
- }
2211
- else if (numberAlternatives.number) {
2212
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2213
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2214
- result.frame = frames[i];
2215
- results.push(result);
2216
- }
2217
- }
2218
- else {
2219
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2220
- result.frame = frames[i];
2221
- results.push(result);
2222
- }
2359
+ try {
2360
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2361
+ }
2362
+ catch (error) {
2363
+ // ignore
2223
2364
  }
2224
- state.info.results = results;
2225
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2226
2365
  if (resultWithElementsFound.length === 0) {
2227
2366
  await _screenshot(state, this);
2228
2367
  return state.info;
@@ -2240,15 +2379,115 @@ class StableBrowser {
2240
2379
  _commandFinally(state, this);
2241
2380
  }
2242
2381
  }
2243
- _getServerUrl() {
2244
- let serviceUrl = "https://api.blinq.io";
2245
- if (process.env.NODE_ENV_BLINQ === "dev") {
2246
- serviceUrl = "https://dev.api.blinq.io";
2382
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2383
+ textAnchor = unEscapeString(textAnchor);
2384
+ textToVerify = unEscapeString(textToVerify);
2385
+ const state = {
2386
+ text_search: textToVerify,
2387
+ options,
2388
+ world,
2389
+ locate: false,
2390
+ scroll: false,
2391
+ highlight: false,
2392
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2393
+ text: `Verify text with relation to another text`,
2394
+ operation: "verify_text_with_relation",
2395
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2396
+ };
2397
+ const timeout = this._getLoadTimeout(options);
2398
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2399
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2400
+ if (newValue !== textAnchor) {
2401
+ this.logger.info(textAnchor + "=" + newValue);
2402
+ textAnchor = newValue;
2403
+ }
2404
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2405
+ if (newValue !== textToVerify) {
2406
+ this.logger.info(textToVerify + "=" + newValue);
2407
+ textToVerify = newValue;
2408
+ }
2409
+ let dateAlternatives = findDateAlternatives(textToVerify);
2410
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2411
+ let foundAncore = false;
2412
+ try {
2413
+ await _preCommand(state, this);
2414
+ state.info.text = textToVerify;
2415
+ let resultWithElementsFound = {
2416
+ length: 0,
2417
+ };
2418
+ while (true) {
2419
+ try {
2420
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2421
+ }
2422
+ catch (error) {
2423
+ // ignore
2424
+ }
2425
+ if (resultWithElementsFound.length === 0) {
2426
+ if (Date.now() - state.startTime > timeout) {
2427
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2428
+ }
2429
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2430
+ continue;
2431
+ }
2432
+ try {
2433
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2434
+ foundAncore = true;
2435
+ const result = resultWithElementsFound[i];
2436
+ const token = result.randomToken;
2437
+ const frame = result.frame;
2438
+ let css = `[data-blinq-id-${token}]`;
2439
+ const climbArray1 = [];
2440
+ for (let i = 0; i < climb; i++) {
2441
+ climbArray1.push("..");
2442
+ }
2443
+ let climbXpath = "xpath=" + climbArray1.join("/");
2444
+ css = css + " >> " + climbXpath;
2445
+ const count = await frame.locator(css).count();
2446
+ for (let j = 0; j < count; j++) {
2447
+ const continer = await frame.locator(css).nth(j);
2448
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2449
+ if (result.elementCount > 0) {
2450
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2451
+ await this._highlightElements(frame, dataAttribute);
2452
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2453
+ // if (world && world.screenshot && !world.screenshotPath) {
2454
+ // console.log(`Highlighting for vtrt while running from recorder`);
2455
+ // this._highlightElements(frame, dataAttribute)
2456
+ // .then(async () => {
2457
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2458
+ // this._unhighlightElements(frame, dataAttribute).then(
2459
+ // () => {}
2460
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2461
+ // );
2462
+ // })
2463
+ // .catch(e);
2464
+ // }
2465
+ //await this._highlightElements(frame, cssAnchor);
2466
+ const element = await frame.locator(dataAttribute).first();
2467
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2468
+ // await this._unhighlightElements(frame, dataAttribute);
2469
+ if (element) {
2470
+ await this.scrollIfNeeded(element, state.info);
2471
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2472
+ }
2473
+ await _screenshot(state, this);
2474
+ return state.info;
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ catch (error) {
2480
+ console.error(error);
2481
+ }
2482
+ }
2483
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2247
2484
  }
2248
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2249
- serviceUrl = "https://stage.api.blinq.io";
2485
+ catch (e) {
2486
+ await _commandError(state, e, this);
2487
+ }
2488
+ finally {
2489
+ _commandFinally(state, this);
2250
2490
  }
2251
- return serviceUrl;
2252
2491
  }
2253
2492
  async visualVerification(text, options = {}, world = null) {
2254
2493
  const startTime = Date.now();
@@ -2264,14 +2503,17 @@ class StableBrowser {
2264
2503
  throw new Error("TOKEN is not set");
2265
2504
  }
2266
2505
  try {
2267
- let serviceUrl = this._getServerUrl();
2506
+ let serviceUrl = _getServerUrl();
2268
2507
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2269
2508
  info.screenshotPath = screenshotPath;
2270
2509
  const screenshot = await this.takeScreenshot();
2271
- const request = {
2272
- method: "POST",
2510
+ let request = {
2511
+ method: "post",
2512
+ maxBodyLength: Infinity,
2273
2513
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2274
2514
  headers: {
2515
+ "x-bvt-project-id": path.basename(this.project_path),
2516
+ "x-source": "aaa",
2275
2517
  "Content-Type": "application/json",
2276
2518
  Authorization: `Bearer ${process.env.TOKEN}`,
2277
2519
  },
@@ -2280,7 +2522,7 @@ class StableBrowser {
2280
2522
  screenshot: screenshot,
2281
2523
  }),
2282
2524
  };
2283
- let result = await this.context.api.request(request);
2525
+ const result = await axios.request(request);
2284
2526
  if (result.data.status !== true) {
2285
2527
  throw new Error("Visual validation failed");
2286
2528
  }
@@ -2353,6 +2595,7 @@ class StableBrowser {
2353
2595
  let screenshotPath = null;
2354
2596
  const info = {};
2355
2597
  info.log = "";
2598
+ info.locatorLog = new LocatorLog(selectors);
2356
2599
  info.operation = "getTableData";
2357
2600
  info.selectors = selectors;
2358
2601
  try {
@@ -2428,7 +2671,7 @@ class StableBrowser {
2428
2671
  info.operation = "analyzeTable";
2429
2672
  info.selectors = selectors;
2430
2673
  info.query = query;
2431
- query = this._fixUsingParams(query, _params);
2674
+ query = _fixUsingParams(query, _params);
2432
2675
  info.query_fixed = query;
2433
2676
  info.operator = operator;
2434
2677
  info.value = value;
@@ -2573,6 +2816,17 @@ class StableBrowser {
2573
2816
  }
2574
2817
  return timeout;
2575
2818
  }
2819
+ async saveStoreState(path = null, world = null) {
2820
+ const storageState = await this.page.context().storageState();
2821
+ //const testDataFile = _getDataFile(world, this.context, this);
2822
+ if (path) {
2823
+ // save { storageState: storageState } into the path
2824
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2825
+ }
2826
+ else {
2827
+ await this.setTestData({ storageState: storageState }, world);
2828
+ }
2829
+ }
2576
2830
  async waitForPageLoad(options = {}, world = null) {
2577
2831
  let timeout = this._getLoadTimeout(options);
2578
2832
  const promiseArray = [];
@@ -2758,14 +3012,22 @@ class StableBrowser {
2758
3012
  }
2759
3013
  }
2760
3014
  async beforeStep(world, step) {
2761
- this.stepName = step.pickleStep.text;
2762
- this.logger.info("step: " + this.stepName);
2763
3015
  if (this.stepIndex === undefined) {
2764
3016
  this.stepIndex = 0;
2765
3017
  }
2766
3018
  else {
2767
3019
  this.stepIndex++;
2768
3020
  }
3021
+ if (step && step.pickleStep && step.pickleStep.text) {
3022
+ this.stepName = step.pickleStep.text;
3023
+ this.logger.info("step: " + this.stepName);
3024
+ }
3025
+ else if (step && step.text) {
3026
+ this.stepName = step.text;
3027
+ }
3028
+ else {
3029
+ this.stepName = "step " + this.stepIndex;
3030
+ }
2769
3031
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2770
3032
  if (this.context.browserObject.context) {
2771
3033
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2795,156 +3057,5 @@ function createTimedPromise(promise, label) {
2795
3057
  .then((result) => ({ status: "fulfilled", label, result }))
2796
3058
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2797
3059
  }
2798
- const KEYBOARD_EVENTS = [
2799
- "ALT",
2800
- "AltGraph",
2801
- "CapsLock",
2802
- "Control",
2803
- "Fn",
2804
- "FnLock",
2805
- "Hyper",
2806
- "Meta",
2807
- "NumLock",
2808
- "ScrollLock",
2809
- "Shift",
2810
- "Super",
2811
- "Symbol",
2812
- "SymbolLock",
2813
- "Enter",
2814
- "Tab",
2815
- "ArrowDown",
2816
- "ArrowLeft",
2817
- "ArrowRight",
2818
- "ArrowUp",
2819
- "End",
2820
- "Home",
2821
- "PageDown",
2822
- "PageUp",
2823
- "Backspace",
2824
- "Clear",
2825
- "Copy",
2826
- "CrSel",
2827
- "Cut",
2828
- "Delete",
2829
- "EraseEof",
2830
- "ExSel",
2831
- "Insert",
2832
- "Paste",
2833
- "Redo",
2834
- "Undo",
2835
- "Accept",
2836
- "Again",
2837
- "Attn",
2838
- "Cancel",
2839
- "ContextMenu",
2840
- "Escape",
2841
- "Execute",
2842
- "Find",
2843
- "Finish",
2844
- "Help",
2845
- "Pause",
2846
- "Play",
2847
- "Props",
2848
- "Select",
2849
- "ZoomIn",
2850
- "ZoomOut",
2851
- "BrightnessDown",
2852
- "BrightnessUp",
2853
- "Eject",
2854
- "LogOff",
2855
- "Power",
2856
- "PowerOff",
2857
- "PrintScreen",
2858
- "Hibernate",
2859
- "Standby",
2860
- "WakeUp",
2861
- "AllCandidates",
2862
- "Alphanumeric",
2863
- "CodeInput",
2864
- "Compose",
2865
- "Convert",
2866
- "Dead",
2867
- "FinalMode",
2868
- "GroupFirst",
2869
- "GroupLast",
2870
- "GroupNext",
2871
- "GroupPrevious",
2872
- "ModeChange",
2873
- "NextCandidate",
2874
- "NonConvert",
2875
- "PreviousCandidate",
2876
- "Process",
2877
- "SingleCandidate",
2878
- "HangulMode",
2879
- "HanjaMode",
2880
- "JunjaMode",
2881
- "Eisu",
2882
- "Hankaku",
2883
- "Hiragana",
2884
- "HiraganaKatakana",
2885
- "KanaMode",
2886
- "KanjiMode",
2887
- "Katakana",
2888
- "Romaji",
2889
- "Zenkaku",
2890
- "ZenkakuHanaku",
2891
- "F1",
2892
- "F2",
2893
- "F3",
2894
- "F4",
2895
- "F5",
2896
- "F6",
2897
- "F7",
2898
- "F8",
2899
- "F9",
2900
- "F10",
2901
- "F11",
2902
- "F12",
2903
- "Soft1",
2904
- "Soft2",
2905
- "Soft3",
2906
- "Soft4",
2907
- "ChannelDown",
2908
- "ChannelUp",
2909
- "Close",
2910
- "MailForward",
2911
- "MailReply",
2912
- "MailSend",
2913
- "MediaFastForward",
2914
- "MediaPause",
2915
- "MediaPlay",
2916
- "MediaPlayPause",
2917
- "MediaRecord",
2918
- "MediaRewind",
2919
- "MediaStop",
2920
- "MediaTrackNext",
2921
- "MediaTrackPrevious",
2922
- "AudioBalanceLeft",
2923
- "AudioBalanceRight",
2924
- "AudioBassBoostDown",
2925
- "AudioBassBoostToggle",
2926
- "AudioBassBoostUp",
2927
- "AudioFaderFront",
2928
- "AudioFaderRear",
2929
- "AudioSurroundModeNext",
2930
- "AudioTrebleDown",
2931
- "AudioTrebleUp",
2932
- "AudioVolumeDown",
2933
- "AudioVolumeMute",
2934
- "AudioVolumeUp",
2935
- "MicrophoneToggle",
2936
- "MicrophoneVolumeDown",
2937
- "MicrophoneVolumeMute",
2938
- "MicrophoneVolumeUp",
2939
- "TV",
2940
- "TV3DMode",
2941
- "TVAntennaCable",
2942
- "TVAudioDescription",
2943
- ];
2944
- function unEscapeString(str) {
2945
- const placeholder = "__NEWLINE__";
2946
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2947
- return str;
2948
- }
2949
3060
  export { StableBrowser };
2950
3061
  //# sourceMappingURL=stable_browser.js.map