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