automation_model 1.0.486-dev → 1.0.486-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.
Files changed (50) hide show
  1. package/lib/api.d.ts +2 -1
  2. package/lib/api.js +95 -98
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +2 -1
  5. package/lib/auto_page.js +39 -17
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +6 -3
  8. package/lib/browser_manager.js +110 -16
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +58 -6
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/error-messages.d.ts +6 -0
  14. package/lib/error-messages.js +188 -0
  15. package/lib/error-messages.js.map +1 -0
  16. package/lib/generation_scripts.d.ts +4 -0
  17. package/lib/generation_scripts.js +2 -0
  18. package/lib/generation_scripts.js.map +1 -0
  19. package/lib/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/index.js.map +1 -1
  22. package/lib/init_browser.d.ts +2 -1
  23. package/lib/init_browser.js +31 -4
  24. package/lib/init_browser.js.map +1 -1
  25. package/lib/locate_element.js +15 -13
  26. package/lib/locate_element.js.map +1 -1
  27. package/lib/locator.d.ts +36 -0
  28. package/lib/locator.js +165 -0
  29. package/lib/locator.js.map +1 -1
  30. package/lib/locator_log.d.ts +26 -0
  31. package/lib/locator_log.js +69 -0
  32. package/lib/locator_log.js.map +1 -0
  33. package/lib/network.d.ts +3 -0
  34. package/lib/network.js +155 -0
  35. package/lib/network.js.map +1 -0
  36. package/lib/scripts/find_text.js +126 -0
  37. package/lib/stable_browser.d.ts +48 -16
  38. package/lib/stable_browser.js +579 -574
  39. package/lib/stable_browser.js.map +1 -1
  40. package/lib/table.d.ts +13 -0
  41. package/lib/table.js +187 -0
  42. package/lib/table.js.map +1 -0
  43. package/lib/test_context.d.ts +1 -0
  44. package/lib/test_context.js +1 -0
  45. package/lib/test_context.js.map +1 -1
  46. package/lib/utils.d.ts +13 -2
  47. package/lib/utils.js +238 -5
  48. package/lib/utils.js.map +1 -1
  49. package/package.json +7 -5
  50. /package/lib/{axe → scripts}/axe.mini.js +0 -0
@@ -10,14 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
20
- const Types = {
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
21
24
  CLICK: "click_element",
22
25
  NAVIGATE: "navigate",
23
26
  FILL: "fill_element",
@@ -28,6 +31,8 @@ const Types = {
28
31
  GET_PAGE_STATUS: "get_page_status",
29
32
  CLICK_ROW_ACTION: "click_row_action",
30
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
31
36
  ANALYZE_TABLE: "analyze_table",
32
37
  SELECT: "select_combobox",
33
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -43,6 +48,9 @@ const Types = {
43
48
  VERIFY_VISUAL: "verify_visual",
44
49
  LOAD_DATA: "load_data",
45
50
  SET_INPUT: "set_input",
51
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
46
54
  };
47
55
  export const apps = {};
48
56
  class StableBrowser {
@@ -56,6 +64,7 @@ class StableBrowser {
56
64
  networkLogger = null;
57
65
  configuration = null;
58
66
  appName = "main";
67
+ tags = null;
59
68
  constructor(browser, page, logger = null, context = null, world = null) {
60
69
  this.browser = browser;
61
70
  this.page = page;
@@ -86,23 +95,32 @@ class StableBrowser {
86
95
  catch (e) {
87
96
  this.logger.error("unable to read ai_config.json");
88
97
  }
89
- context.pageLoading = { status: false };
90
- context.pages = [this.page];
91
98
  const logFolder = path.join(this.project_path, "logs", "web");
92
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
93
102
  this.registerEventListeners(this.context);
103
+ registerNetworkEvents(this.world, this, this.context, this.page);
104
+ registerDownloadEvent(this.page, this.world, this.context);
94
105
  }
95
106
  registerEventListeners(context) {
96
107
  this.registerConsoleLogListener(this.page, context);
97
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
98
109
  if (!context.pageLoading) {
99
110
  context.pageLoading = { status: false };
100
111
  }
101
112
  context.playContext.on("page", async function (page) {
113
+ if (this.configuration && this.configuration.closePopups === true) {
114
+ console.log("close unexpected popups");
115
+ await page.close();
116
+ return;
117
+ }
102
118
  context.pageLoading.status = true;
103
119
  this.page = page;
104
120
  context.page = page;
105
121
  context.pages.push(page);
122
+ registerNetworkEvents(this.world, this, context, this.page);
123
+ registerDownloadEvent(this.page, this.world, context);
106
124
  page.on("close", async () => {
107
125
  if (this.context && this.context.pages && this.context.pages.length > 1) {
108
126
  this.context.pages.pop();
@@ -132,9 +150,9 @@ class StableBrowser {
132
150
  if (this.appName === appName) {
133
151
  return;
134
152
  }
135
- let newContextCreated = false;
153
+ let navigate = false;
136
154
  if (!apps[appName]) {
137
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
155
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
138
156
  newContextCreated = true;
139
157
  apps[appName] = {
140
158
  context: newContext,
@@ -143,32 +161,15 @@ class StableBrowser {
143
161
  };
144
162
  }
145
163
  const tempContext = {};
146
- this._copyContext(this, tempContext);
147
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
148
166
  apps[this.appName] = tempContext;
149
167
  this.appName = appName;
150
- if (newContextCreated) {
151
- this.registerEventListeners(this.context);
168
+ if (navigate) {
152
169
  await this.goto(this.context.environment.baseUrl);
153
170
  await this.waitForPageLoad();
154
171
  }
155
172
  }
156
- _copyContext(from, to) {
157
- to.browser = from.browser;
158
- to.page = from.page;
159
- to.context = from.context;
160
- }
161
- getWebLogFile(logFolder) {
162
- if (!fs.existsSync(logFolder)) {
163
- fs.mkdirSync(logFolder, { recursive: true });
164
- }
165
- let nextIndex = 1;
166
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
167
- nextIndex++;
168
- }
169
- const fileName = nextIndex + ".json";
170
- return path.join(logFolder, fileName);
171
- }
172
173
  registerConsoleLogListener(page, context) {
173
174
  if (!this.context.webLogger) {
174
175
  this.context.webLogger = [];
@@ -218,7 +219,7 @@ class StableBrowser {
218
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
219
220
  }
220
221
  catch (error) {
221
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
222
223
  context.networkLogger.push({
223
224
  error: "not able to listen",
224
225
  message: error.message,
@@ -240,47 +241,8 @@ class StableBrowser {
240
241
  timeout: 60000,
241
242
  });
242
243
  }
243
- _fixUsingParams(text, _params) {
244
- if (!_params || typeof text !== "string") {
245
- return text;
246
- }
247
- for (let key in _params) {
248
- let regValue = key;
249
- if (key.startsWith("_")) {
250
- // remove the _ prefix
251
- regValue = key.substring(1);
252
- }
253
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
254
- }
255
- return text;
256
- }
257
- _fixLocatorUsingParams(locator, _params) {
258
- // check if not null
259
- if (!locator) {
260
- return locator;
261
- }
262
- // clone the locator
263
- locator = JSON.parse(JSON.stringify(locator));
264
- this.scanAndManipulate(locator, _params);
265
- return locator;
266
- }
267
- _isObject(value) {
268
- return value && typeof value === "object" && value.constructor === Object;
269
- }
270
- scanAndManipulate(currentObj, _params) {
271
- for (const key in currentObj) {
272
- if (typeof currentObj[key] === "string") {
273
- // Perform string manipulation
274
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
275
- }
276
- else if (this._isObject(currentObj[key])) {
277
- // Recursively scan nested objects
278
- this.scanAndManipulate(currentObj[key], _params);
279
- }
280
- }
281
- }
282
244
  _getLocator(locator, scope, _params) {
283
- locator = this._fixLocatorUsingParams(locator, _params);
245
+ locator = _fixLocatorUsingParams(locator, _params);
284
246
  let locatorReturn;
285
247
  if (locator.role) {
286
248
  if (locator.role[1].nameReg) {
@@ -288,7 +250,7 @@ class StableBrowser {
288
250
  delete locator.role[1].nameReg;
289
251
  }
290
252
  // if (locator.role[1].name) {
291
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
253
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
292
254
  // }
293
255
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
294
256
  }
@@ -331,7 +293,7 @@ class StableBrowser {
331
293
  if (css && css.locator) {
332
294
  css = css.locator;
333
295
  }
334
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
296
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
335
297
  if (result.elementCount === 0) {
336
298
  return;
337
299
  }
@@ -347,116 +309,28 @@ class StableBrowser {
347
309
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
348
310
  //const stringifyText = JSON.stringify(text);
349
311
  return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
350
- function isParent(parent, child) {
351
- let currentNode = child.parentNode;
352
- while (currentNode !== null) {
353
- if (currentNode === parent) {
354
- return true;
355
- }
356
- currentNode = currentNode.parentNode;
357
- }
358
- return false;
359
- }
360
- document.isParent = isParent;
361
- function getRegex(str) {
362
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
363
- if (!match) {
364
- return null;
365
- }
366
- let [_, pattern, flags] = match;
367
- return new RegExp(pattern, flags);
368
- }
369
- document.getRegex = getRegex;
370
- function collectAllShadowDomElements(element, result = []) {
371
- // Check and add the element if it has a shadow root
372
- if (element.shadowRoot) {
373
- result.push(element);
374
- // Also search within the shadow root
375
- document.collectAllShadowDomElements(element.shadowRoot, result);
376
- }
377
- // Iterate over child nodes
378
- element.childNodes.forEach((child) => {
379
- // Recursively call the function for each child node
380
- document.collectAllShadowDomElements(child, result);
381
- });
382
- return result;
383
- }
384
- document.collectAllShadowDomElements = collectAllShadowDomElements;
385
- if (!tag) {
386
- tag = "*";
387
- }
388
- let regexpSearch = document.getRegex(text);
389
- if (regexpSearch) {
390
- regex = true;
391
- }
392
- let elements = Array.from(document.querySelectorAll(tag));
393
- let shadowHosts = [];
394
- document.collectAllShadowDomElements(document, shadowHosts);
395
- for (let i = 0; i < shadowHosts.length; i++) {
396
- let shadowElement = shadowHosts[i].shadowRoot;
397
- if (!shadowElement) {
398
- console.log("shadowElement is null, for host " + shadowHosts[i]);
399
- continue;
400
- }
401
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
402
- elements = elements.concat(shadowElements);
403
- }
404
- let randomToken = null;
405
- const foundElements = [];
312
+ const options = {
313
+ innerText: true,
314
+ };
406
315
  if (regex) {
407
- if (!regexpSearch) {
408
- regexpSearch = new RegExp(text, "im");
409
- }
410
- for (let i = 0; i < elements.length; i++) {
411
- const element = elements[i];
412
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
413
- (element.value && regexpSearch.test(element.value))) {
414
- foundElements.push(element);
415
- }
416
- }
316
+ options.singleRegex = true;
417
317
  }
418
- else {
419
- text = text.trim();
420
- for (let i = 0; i < elements.length; i++) {
421
- const element = elements[i];
422
- if (partial) {
423
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
424
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
425
- foundElements.push(element);
426
- }
427
- }
428
- else {
429
- if ((element.innerText && element.innerText.trim() === text) ||
430
- (element.value && element.value === text)) {
431
- foundElements.push(element);
432
- }
433
- }
434
- }
318
+ if (tag) {
319
+ options.tag = tag;
435
320
  }
436
- let noChildElements = [];
437
- for (let i = 0; i < foundElements.length; i++) {
438
- let element = foundElements[i];
439
- let hasChild = false;
440
- for (let j = 0; j < foundElements.length; j++) {
441
- if (i === j) {
442
- continue;
443
- }
444
- if (isParent(element, foundElements[j])) {
445
- hasChild = true;
446
- break;
447
- }
448
- }
449
- if (!hasChild) {
450
- noChildElements.push(element);
451
- }
321
+ if (!(partial === true)) {
322
+ options.exactMatch = true;
452
323
  }
324
+ const elements = window.findMatchingElements(text, options);
325
+ let randomToken = null;
326
+ const foundElements = [];
453
327
  let elementCount = 0;
454
- if (noChildElements.length > 0) {
455
- for (let i = 0; i < noChildElements.length; i++) {
328
+ if (elements.length > 0) {
329
+ for (let i = 0; i < elements.length; i++) {
456
330
  if (randomToken === null) {
457
331
  randomToken = Math.random().toString(36).substring(7);
458
332
  }
459
- let element = noChildElements[i];
333
+ let element = elements[i];
460
334
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
461
335
  elementCount++;
462
336
  }
@@ -465,9 +339,21 @@ class StableBrowser {
465
339
  }, [text1, tag1, regex1, partial1]);
466
340
  }
467
341
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
342
+ if (!info) {
343
+ info = {};
344
+ }
345
+ if (!info.failCause) {
346
+ info.failCause = {};
347
+ }
348
+ if (!info.log) {
349
+ info.log = "";
350
+ info.locatorLog = new LocatorLog(selectorHierarchy);
351
+ }
468
352
  let locatorSearch = selectorHierarchy[index];
353
+ let originalLocatorSearch = "";
469
354
  try {
470
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
355
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
356
+ locatorSearch = JSON.parse(originalLocatorSearch);
471
357
  }
472
358
  catch (e) {
473
359
  console.error(e);
@@ -477,13 +363,18 @@ class StableBrowser {
477
363
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
478
364
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
479
365
  if (!locatorString) {
366
+ info.failCause.textNotFound = true;
367
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
480
368
  return;
481
369
  }
482
370
  locator = this._getLocator({ css: locatorString }, scope, _params);
483
371
  }
484
372
  else if (locatorSearch.text) {
485
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
373
+ let text = _fixUsingParams(locatorSearch.text, _params);
374
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
486
375
  if (result.elementCount === 0) {
376
+ info.failCause.textNotFound = true;
377
+ info.failCause.lastError = "failed to locate element by text: " + text;
487
378
  return;
488
379
  }
489
380
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -500,13 +391,22 @@ class StableBrowser {
500
391
  // cssHref = true;
501
392
  // }
502
393
  let count = await locator.count();
394
+ if (count > 0 && !info.failCause.count) {
395
+ info.failCause.count = count;
396
+ }
503
397
  //info.log += "total elements found " + count + "\n";
504
398
  //let visibleCount = 0;
505
399
  let visibleLocator = null;
506
- if (locatorSearch.index && locatorSearch.index < count) {
400
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
507
401
  foundLocators.push(locator.nth(locatorSearch.index));
402
+ if (info.locatorLog) {
403
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
404
+ }
508
405
  return;
509
406
  }
407
+ if (info.locatorLog && count === 0) {
408
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
409
+ }
510
410
  for (let j = 0; j < count; j++) {
511
411
  let visible = await locator.nth(j).isVisible();
512
412
  const enabled = await locator.nth(j).isEnabled();
@@ -515,24 +415,40 @@ class StableBrowser {
515
415
  }
516
416
  if (visible && enabled) {
517
417
  foundLocators.push(locator.nth(j));
418
+ if (info.locatorLog) {
419
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
420
+ }
518
421
  }
519
422
  else {
423
+ info.failCause.visible = visible;
424
+ info.failCause.enabled = enabled;
520
425
  if (!info.printMessages) {
521
426
  info.printMessages = {};
522
427
  }
428
+ if (info.locatorLog && !visible) {
429
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
430
+ }
431
+ if (info.locatorLog && !enabled) {
432
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
433
+ }
523
434
  if (!info.printMessages[j.toString()]) {
524
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
435
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
525
436
  info.printMessages[j.toString()] = true;
526
437
  }
527
438
  }
528
439
  }
529
440
  }
530
441
  async closeUnexpectedPopups(info, _params) {
442
+ if (!info) {
443
+ info = {};
444
+ info.failCause = {};
445
+ info.log = "";
446
+ }
531
447
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
532
448
  if (!info) {
533
449
  info = {};
534
450
  }
535
- info.log += "scan for popup handlers" + "\n";
451
+ //info.log += "scan for popup handlers" + "\n";
536
452
  const handlerGroup = [];
537
453
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
538
454
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -559,9 +475,21 @@ class StableBrowser {
559
475
  }
560
476
  if (result.foundElements.length > 0) {
561
477
  let dialogCloseLocator = result.foundElements[0].locator;
562
- await dialogCloseLocator.click();
563
- // wait for the dialog to close
564
- await dialogCloseLocator.waitFor({ state: "hidden" });
478
+ try {
479
+ await scope?.evaluate(() => {
480
+ window.__isClosingPopups = true;
481
+ });
482
+ await dialogCloseLocator.click();
483
+ // wait for the dialog to close
484
+ await dialogCloseLocator.waitFor({ state: "hidden" });
485
+ }
486
+ catch (e) {
487
+ }
488
+ finally {
489
+ await scope?.evaluate(() => {
490
+ window.__isClosingPopups = false;
491
+ });
492
+ }
565
493
  return { rerun: true };
566
494
  }
567
495
  }
@@ -585,7 +513,13 @@ class StableBrowser {
585
513
  }
586
514
  throw new Error("unable to locate element " + JSON.stringify(selectors));
587
515
  }
588
- async _findFrameScope(selectors, timeout = 30000) {
516
+ async _findFrameScope(selectors, timeout = 30000, info) {
517
+ if (!info) {
518
+ info = {};
519
+ info.failCause = {};
520
+ info.log = "";
521
+ }
522
+ let startTime = Date.now();
589
523
  let scope = this.page;
590
524
  if (selectors.frame) {
591
525
  return selectors.frame;
@@ -616,9 +550,11 @@ class StableBrowser {
616
550
  }
617
551
  return framescope;
618
552
  };
553
+ let fLocator = null;
619
554
  while (true) {
620
555
  let frameFound = false;
621
556
  if (selectors.nestFrmLoc) {
557
+ fLocator = selectors.nestFrmLoc;
622
558
  scope = await findFrame(selectors.nestFrmLoc, scope);
623
559
  frameFound = true;
624
560
  break;
@@ -627,6 +563,7 @@ class StableBrowser {
627
563
  for (let i = 0; i < selectors.frameLocators.length; i++) {
628
564
  let frameLocator = selectors.frameLocators[i];
629
565
  if (frameLocator.css) {
566
+ fLocator = frameLocator.css;
630
567
  scope = scope.frameLocator(frameLocator.css);
631
568
  frameFound = true;
632
569
  break;
@@ -634,16 +571,25 @@ class StableBrowser {
634
571
  }
635
572
  }
636
573
  if (!frameFound && selectors.iframe_src) {
574
+ fLocator = selectors.iframe_src;
637
575
  scope = this.page.frame({ url: selectors.iframe_src });
638
576
  }
639
577
  if (!scope) {
640
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
641
- if (performance.now() - startTime > timeout) {
578
+ if (info && info.locatorLog) {
579
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
580
+ }
581
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
582
+ if (Date.now() - startTime > timeout) {
583
+ info.failCause.iframeNotFound = true;
584
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
642
585
  throw new Error("unable to locate iframe " + selectors.iframe_src);
643
586
  }
644
587
  await new Promise((resolve) => setTimeout(resolve, 1000));
645
588
  }
646
589
  else {
590
+ if (info && info.locatorLog) {
591
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
592
+ }
647
593
  break;
648
594
  }
649
595
  }
@@ -653,20 +599,27 @@ class StableBrowser {
653
599
  }
654
600
  return scope;
655
601
  }
656
- async _getDocumentBody(selectors, timeout = 30000) {
657
- let scope = await this._findFrameScope(selectors, timeout);
602
+ async _getDocumentBody(selectors, timeout = 30000, info) {
603
+ let scope = await this._findFrameScope(selectors, timeout, info);
658
604
  return scope.evaluate(() => {
659
605
  var bodyContent = document.body.innerHTML;
660
606
  return bodyContent;
661
607
  });
662
608
  }
663
609
  async _locate_internal(selectors, info, _params, timeout = 30000) {
610
+ if (!info) {
611
+ info = {};
612
+ info.failCause = {};
613
+ info.log = "";
614
+ info.locatorLog = new LocatorLog(selectors);
615
+ }
664
616
  let highPriorityTimeout = 5000;
665
617
  let visibleOnlyTimeout = 6000;
666
- let startTime = performance.now();
618
+ let startTime = Date.now();
667
619
  let locatorsCount = 0;
620
+ let lazy_scroll = false;
668
621
  //let arrayMode = Array.isArray(selectors);
669
- let scope = await this._findFrameScope(selectors, timeout);
622
+ let scope = await this._findFrameScope(selectors, timeout, info);
670
623
  let selectorsLocators = null;
671
624
  selectorsLocators = selectors.locators;
672
625
  // group selectors by priority
@@ -753,21 +706,33 @@ class StableBrowser {
753
706
  return maxCountElement.locator;
754
707
  }
755
708
  }
756
- if (performance.now() - startTime > timeout) {
709
+ if (Date.now() - startTime > timeout) {
757
710
  break;
758
711
  }
759
- if (performance.now() - startTime > highPriorityTimeout) {
712
+ if (Date.now() - startTime > highPriorityTimeout) {
760
713
  info.log += "high priority timeout, will try all elements" + "\n";
761
714
  highPriorityOnly = false;
715
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
716
+ lazy_scroll = true;
717
+ await scrollPageToLoadLazyElements(this.page);
718
+ }
762
719
  }
763
- if (performance.now() - startTime > visibleOnlyTimeout) {
720
+ if (Date.now() - startTime > visibleOnlyTimeout) {
764
721
  info.log += "visible only timeout, will try all elements" + "\n";
765
722
  visibleOnly = false;
766
723
  }
767
724
  await new Promise((resolve) => setTimeout(resolve, 1000));
768
725
  }
769
726
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
770
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
727
+ // if (info.locatorLog) {
728
+ // const lines = info.locatorLog.toString().split("\n");
729
+ // for (let line of lines) {
730
+ // this.logger.debug(line);
731
+ // }
732
+ // }
733
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
734
+ info.failCause.locatorNotFound = true;
735
+ info.failCause.lastError = "failed to locate unique element";
771
736
  throw new Error("failed to locate first element no elements found, " + info.log);
772
737
  }
773
738
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -781,8 +746,9 @@ class StableBrowser {
781
746
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
782
747
  }
783
748
  catch (e) {
784
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
785
- this.logger.debug(e);
749
+ // this call can fail it the browser is navigating
750
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
751
+ // this.logger.debug(e);
786
752
  foundLocators = [];
787
753
  try {
788
754
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -799,10 +765,29 @@ class StableBrowser {
799
765
  });
800
766
  result.locatorIndex = i;
801
767
  }
768
+ if (foundLocators.length > 1) {
769
+ info.failCause.foundMultiple = true;
770
+ if (info.locatorLog) {
771
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
772
+ }
773
+ }
802
774
  }
803
775
  return result;
804
776
  }
805
777
  async simpleClick(elementDescription, _params, options = {}, world = null) {
778
+ const state = {
779
+ locate: false,
780
+ scroll: false,
781
+ highlight: false,
782
+ _params,
783
+ options,
784
+ world,
785
+ type: Types.CLICK,
786
+ text: "Click element",
787
+ operation: "simpleClick",
788
+ log: "***** click on " + elementDescription + " *****\n",
789
+ };
790
+ _preCommand(state, this);
806
791
  const startTime = Date.now();
807
792
  let timeout = 30000;
808
793
  if (options && options.timeout) {
@@ -826,13 +811,32 @@ class StableBrowser {
826
811
  }
827
812
  catch (e) {
828
813
  if (performance.now() - startTime > timeout) {
829
- throw e;
814
+ // throw e;
815
+ try {
816
+ await _commandError(state, "timeout looking for " + elementDescription, this);
817
+ }
818
+ finally {
819
+ _commandFinally(state, this);
820
+ }
830
821
  }
831
822
  }
832
823
  await new Promise((resolve) => setTimeout(resolve, 3000));
833
824
  }
834
825
  }
835
826
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
827
+ const state = {
828
+ locate: false,
829
+ scroll: false,
830
+ highlight: false,
831
+ _params,
832
+ options,
833
+ world,
834
+ type: Types.FILL,
835
+ text: "Fill element",
836
+ operation: "simpleClickType",
837
+ log: "***** click type on " + elementDescription + " *****\n",
838
+ };
839
+ _preCommand(state, this);
836
840
  const startTime = Date.now();
837
841
  let timeout = 30000;
838
842
  if (options && options.timeout) {
@@ -856,7 +860,13 @@ class StableBrowser {
856
860
  }
857
861
  catch (e) {
858
862
  if (performance.now() - startTime > timeout) {
859
- throw e;
863
+ // throw e;
864
+ try {
865
+ await _commandError(state, "timeout looking for " + elementDescription, this);
866
+ }
867
+ finally {
868
+ _commandFinally(state, this);
869
+ }
860
870
  }
861
871
  }
862
872
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -880,13 +890,13 @@ class StableBrowser {
880
890
  }
881
891
  try {
882
892
  await state.element.click();
883
- await new Promise((resolve) => setTimeout(resolve, 1000));
893
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
884
894
  }
885
895
  catch (e) {
886
896
  // await this.closeUnexpectedPopups();
887
897
  state.element = await this._locate(selectors, state.info, _params);
888
898
  await state.element.dispatchEvent("click");
889
- await new Promise((resolve) => setTimeout(resolve, 1000));
899
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
890
900
  }
891
901
  await this.waitForPageLoad();
892
902
  return state.info;
@@ -965,7 +975,6 @@ class StableBrowser {
965
975
  await state.element.hover({ timeout: 10000 });
966
976
  await new Promise((resolve) => setTimeout(resolve, 1000));
967
977
  }
968
- await _screenshot(state, this);
969
978
  await this.waitForPageLoad();
970
979
  return state.info;
971
980
  }
@@ -1093,33 +1102,30 @@ class StableBrowser {
1093
1102
  }
1094
1103
  }
1095
1104
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1096
- _validateSelectors(selectors);
1097
- const startTime = Date.now();
1098
- let error = null;
1099
- let screenshotId = null;
1100
- let screenshotPath = null;
1101
- const info = {};
1102
- info.log = "";
1103
- info.operation = Types.SET_DATE_TIME;
1104
- info.selectors = selectors;
1105
- info.value = value;
1105
+ const state = {
1106
+ selectors,
1107
+ _params,
1108
+ value: await this._replaceWithLocalData(value, this),
1109
+ options,
1110
+ world,
1111
+ type: Types.SET_DATE_TIME,
1112
+ text: `Set date time value: ${value}`,
1113
+ operation: "setDateTime",
1114
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1115
+ throwError: false,
1116
+ };
1106
1117
  try {
1107
- value = await this._replaceWithLocalData(value, this);
1108
- let element = await this._locate(selectors, info, _params);
1109
- //insert red border around the element
1110
- await this.scrollIfNeeded(element, info);
1111
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1112
- await this._highlightElements(element);
1118
+ await _preCommand(state, this);
1113
1119
  try {
1114
- await element.click();
1120
+ await state.element.click();
1115
1121
  await new Promise((resolve) => setTimeout(resolve, 500));
1116
1122
  if (format) {
1117
- value = dayjs(value).format(format);
1118
- await element.fill(value);
1123
+ state.value = dayjs(state.value).format(format);
1124
+ await state.element.fill(state.value);
1119
1125
  }
1120
1126
  else {
1121
- const dateTimeValue = await getDateTimeValue({ value, element });
1122
- await element.evaluateHandle((el, dateTimeValue) => {
1127
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1128
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1123
1129
  el.value = ""; // clear input
1124
1130
  el.value = dateTimeValue;
1125
1131
  }, dateTimeValue);
@@ -1132,20 +1138,19 @@ class StableBrowser {
1132
1138
  }
1133
1139
  catch (err) {
1134
1140
  //await this.closeUnexpectedPopups();
1135
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1141
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1136
1142
  this.logger.info("Trying again");
1137
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1138
- info.screenshotPath = screenshotPath;
1139
- Object.assign(err, { info: info });
1143
+ await _screenshot(state, this);
1144
+ Object.assign(err, { info: state.info });
1140
1145
  await element.click();
1141
1146
  await new Promise((resolve) => setTimeout(resolve, 500));
1142
1147
  if (format) {
1143
- value = dayjs(value).format(format);
1144
- await element.fill(value);
1148
+ state.value = dayjs(state.value).format(format);
1149
+ await state.element.fill(state.value);
1145
1150
  }
1146
1151
  else {
1147
- const dateTimeValue = await getDateTimeValue({ value, element });
1148
- await element.evaluateHandle((el, dateTimeValue) => {
1152
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1153
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1149
1154
  el.value = ""; // clear input
1150
1155
  el.value = dateTimeValue;
1151
1156
  }, dateTimeValue);
@@ -1158,31 +1163,10 @@ class StableBrowser {
1158
1163
  }
1159
1164
  }
1160
1165
  catch (e) {
1161
- error = e;
1162
- throw e;
1166
+ await _commandError(state, e, this);
1163
1167
  }
1164
1168
  finally {
1165
- const endTime = Date.now();
1166
- this._reportToWorld(world, {
1167
- element_name: selectors.element_name,
1168
- type: Types.SET_DATE_TIME,
1169
- screenshotId,
1170
- value: value,
1171
- text: `setDateTime input with value: ${value}`,
1172
- result: error
1173
- ? {
1174
- status: "FAILED",
1175
- startTime,
1176
- endTime,
1177
- message: error === null || error === void 0 ? void 0 : error.message,
1178
- }
1179
- : {
1180
- status: "PASSED",
1181
- startTime,
1182
- endTime,
1183
- },
1184
- info: info,
1185
- });
1169
+ _commandFinally(state, this);
1186
1170
  }
1187
1171
  }
1188
1172
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1319,6 +1303,7 @@ class StableBrowser {
1319
1303
  let screenshotPath = null;
1320
1304
  if (!info.log) {
1321
1305
  info.log = "";
1306
+ info.locatorLog = new LocatorLog(selectors);
1322
1307
  }
1323
1308
  info.operation = "getText";
1324
1309
  info.selectors = selectors;
@@ -1657,11 +1642,9 @@ class StableBrowser {
1657
1642
  if (!fs.existsSync(world.screenshotPath)) {
1658
1643
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1659
1644
  }
1660
- let nextIndex = 1;
1661
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1662
- nextIndex++;
1663
- }
1664
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1645
+ // to make sure the path doesn't start with -
1646
+ const uuidStr = "id_" + randomUUID();
1647
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1665
1648
  try {
1666
1649
  await this.takeScreenshot(screenshotPath);
1667
1650
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1675,7 +1658,7 @@ class StableBrowser {
1675
1658
  catch (e) {
1676
1659
  this.logger.info("unable to take screenshot, ignored");
1677
1660
  }
1678
- result.screenshotId = nextIndex;
1661
+ result.screenshotId = uuidStr;
1679
1662
  result.screenshotPath = screenshotPath;
1680
1663
  if (info && info.box) {
1681
1664
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1773,74 +1756,101 @@ class StableBrowser {
1773
1756
  }
1774
1757
  }
1775
1758
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1776
- _validateSelectors(selectors);
1777
- const startTime = Date.now();
1778
- let error = null;
1779
- let screenshotId = null;
1780
- let screenshotPath = null;
1759
+ const state = {
1760
+ selectors,
1761
+ _params,
1762
+ attribute,
1763
+ variable,
1764
+ options,
1765
+ world,
1766
+ type: Types.EXTRACT,
1767
+ text: `Extract attribute from element`,
1768
+ operation: "extractAttribute",
1769
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1770
+ };
1781
1771
  await new Promise((resolve) => setTimeout(resolve, 2000));
1782
- const info = {};
1783
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1784
- info.operation = "extract";
1785
- info.selectors = selectors;
1786
1772
  try {
1787
- const element = await this._locate(selectors, info, _params);
1788
- await this._highlightElements(element);
1789
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1773
+ await _preCommand(state, this);
1790
1774
  switch (attribute) {
1791
1775
  case "inner_text":
1792
- info.value = await element.innerText();
1776
+ state.value = await state.element.innerText();
1793
1777
  break;
1794
1778
  case "href":
1795
- info.value = await element.getAttribute("href");
1779
+ state.value = await state.element.getAttribute("href");
1780
+ break;
1781
+ case "value":
1782
+ state.value = await state.element.inputValue();
1783
+ break;
1784
+ default:
1785
+ state.value = await state.element.getAttribute(attribute);
1786
+ break;
1787
+ }
1788
+ state.info.value = state.value;
1789
+ this.setTestData({ [variable]: state.value }, world);
1790
+ this.logger.info("set test data: " + variable + "=" + state.value);
1791
+ return state.info;
1792
+ }
1793
+ catch (e) {
1794
+ await _commandError(state, e, this);
1795
+ }
1796
+ finally {
1797
+ _commandFinally(state, this);
1798
+ }
1799
+ }
1800
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1801
+ const state = {
1802
+ selectors,
1803
+ _params,
1804
+ attribute,
1805
+ value,
1806
+ options,
1807
+ world,
1808
+ type: Types.VERIFY_ATTRIBUTE,
1809
+ text: `Verify element attribute`,
1810
+ operation: "verifyAttribute",
1811
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1812
+ };
1813
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1814
+ let val;
1815
+ try {
1816
+ await _preCommand(state, this);
1817
+ switch (attribute) {
1818
+ case "innerText":
1819
+ val = String(await state.element.innerText());
1796
1820
  break;
1797
1821
  case "value":
1798
- info.value = await element.inputValue();
1822
+ val = String(await state.element.inputValue());
1823
+ break;
1824
+ case "checked":
1825
+ val = String(await state.element.isChecked());
1826
+ break;
1827
+ case "disabled":
1828
+ val = String(await state.element.isDisabled());
1799
1829
  break;
1800
1830
  default:
1801
- info.value = await element.getAttribute(attribute);
1831
+ val = String(await state.element.getAttribute(attribute));
1802
1832
  break;
1803
1833
  }
1804
- this[variable] = info.value;
1805
- if (world) {
1806
- world[variable] = info.value;
1834
+ let regex;
1835
+ if (value.startsWith("/") && value.endsWith("/")) {
1836
+ const patternBody = value.slice(1, -1);
1837
+ regex = new RegExp(patternBody, "g");
1807
1838
  }
1808
- this.setTestData({ [variable]: info.value }, world);
1809
- this.logger.info("set test data: " + variable + "=" + info.value);
1810
- return info;
1839
+ else {
1840
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1841
+ regex = new RegExp(escapedPattern, "g");
1842
+ }
1843
+ if (!val.match(regex)) {
1844
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1845
+ }
1846
+ state.info.value = val;
1847
+ return state.info;
1811
1848
  }
1812
1849
  catch (e) {
1813
- //await this.closeUnexpectedPopups();
1814
- this.logger.error("extract failed " + info.log);
1815
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1816
- info.screenshotPath = screenshotPath;
1817
- Object.assign(e, { info: info });
1818
- error = e;
1819
- throw e;
1850
+ await _commandError(state, e, this);
1820
1851
  }
1821
1852
  finally {
1822
- const endTime = Date.now();
1823
- this._reportToWorld(world, {
1824
- element_name: selectors.element_name,
1825
- type: Types.EXTRACT_ATTRIBUTE,
1826
- variable: variable,
1827
- value: info.value,
1828
- text: "Extract attribute from element",
1829
- screenshotId,
1830
- result: error
1831
- ? {
1832
- status: "FAILED",
1833
- startTime,
1834
- endTime,
1835
- message: error?.message,
1836
- }
1837
- : {
1838
- status: "PASSED",
1839
- startTime,
1840
- endTime,
1841
- },
1842
- info: info,
1843
- });
1853
+ _commandFinally(state, this);
1844
1854
  }
1845
1855
  }
1846
1856
  async extractEmailData(emailAddress, options, world) {
@@ -1861,7 +1871,7 @@ class StableBrowser {
1861
1871
  if (options && options.timeout) {
1862
1872
  timeout = options.timeout;
1863
1873
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1874
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1875
  const request = {
1866
1876
  method: "POST",
1867
1877
  url: serviceUrl,
@@ -1917,7 +1927,8 @@ class StableBrowser {
1917
1927
  catch (e) {
1918
1928
  errorCount++;
1919
1929
  if (errorCount > 3) {
1920
- throw e;
1930
+ // throw e;
1931
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1921
1932
  }
1922
1933
  // ignore
1923
1934
  }
@@ -2028,11 +2039,12 @@ class StableBrowser {
2028
2039
  info.screenshotPath = screenshotPath;
2029
2040
  Object.assign(e, { info: info });
2030
2041
  error = e;
2031
- throw e;
2042
+ // throw e;
2043
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2032
2044
  }
2033
2045
  finally {
2034
2046
  const endTime = Date.now();
2035
- this._reportToWorld(world, {
2047
+ _reportToWorld(world, {
2036
2048
  type: Types.VERIFY_PAGE_PATH,
2037
2049
  text: "Verify page path",
2038
2050
  screenshotId,
@@ -2052,54 +2064,64 @@ class StableBrowser {
2052
2064
  });
2053
2065
  }
2054
2066
  }
2067
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2068
+ const frames = this.page.frames();
2069
+ let results = [];
2070
+ for (let i = 0; i < frames.length; i++) {
2071
+ if (dateAlternatives.date) {
2072
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2073
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2074
+ result.frame = frames[i];
2075
+ results.push(result);
2076
+ }
2077
+ }
2078
+ else if (numberAlternatives.number) {
2079
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2080
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2081
+ result.frame = frames[i];
2082
+ results.push(result);
2083
+ }
2084
+ }
2085
+ else {
2086
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2087
+ result.frame = frames[i];
2088
+ results.push(result);
2089
+ }
2090
+ }
2091
+ state.info.results = results;
2092
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2093
+ return resultWithElementsFound;
2094
+ }
2055
2095
  async verifyTextExistInPage(text, options = {}, world = null) {
2056
2096
  text = unEscapeString(text);
2057
- const startTime = Date.now();
2097
+ const state = {
2098
+ text_search: text,
2099
+ options,
2100
+ world,
2101
+ locate: false,
2102
+ scroll: false,
2103
+ highlight: false,
2104
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2105
+ text: `Verify text exists in page`,
2106
+ operation: "verifyTextExistInPage",
2107
+ log: "***** verify text " + text + " exists in page *****\n",
2108
+ };
2058
2109
  const timeout = this._getLoadTimeout(options);
2059
- let error = null;
2060
- let screenshotId = null;
2061
- let screenshotPath = null;
2062
2110
  await new Promise((resolve) => setTimeout(resolve, 2000));
2063
- const info = {};
2064
- info.log = "***** verify text " + text + " exists in page *****\n";
2065
- info.operation = "verifyTextExistInPage";
2066
2111
  const newValue = await this._replaceWithLocalData(text, world);
2067
2112
  if (newValue !== text) {
2068
2113
  this.logger.info(text + "=" + newValue);
2069
2114
  text = newValue;
2070
2115
  }
2071
- info.text = text;
2072
2116
  let dateAlternatives = findDateAlternatives(text);
2073
2117
  let numberAlternatives = findNumberAlternatives(text);
2074
2118
  try {
2119
+ await _preCommand(state, this);
2120
+ state.info.text = text;
2075
2121
  while (true) {
2076
- const frames = this.page.frames();
2077
- let results = [];
2078
- for (let i = 0; i < frames.length; i++) {
2079
- if (dateAlternatives.date) {
2080
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2081
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2082
- result.frame = frames[i];
2083
- results.push(result);
2084
- }
2085
- }
2086
- else if (numberAlternatives.number) {
2087
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2088
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2089
- result.frame = frames[i];
2090
- results.push(result);
2091
- }
2092
- }
2093
- else {
2094
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2095
- result.frame = frames[i];
2096
- results.push(result);
2097
- }
2098
- }
2099
- info.results = results;
2100
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2122
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2101
2123
  if (resultWithElementsFound.length === 0) {
2102
- if (Date.now() - startTime > timeout) {
2124
+ if (Date.now() - state.startTime > timeout) {
2103
2125
  throw new Error(`Text ${text} not found in page`);
2104
2126
  }
2105
2127
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2111,55 +2133,163 @@ class StableBrowser {
2111
2133
  await this._highlightElements(frame, dataAttribute);
2112
2134
  const element = await frame.$(dataAttribute);
2113
2135
  if (element) {
2114
- await this.scrollIfNeeded(element, info);
2136
+ await this.scrollIfNeeded(element, state.info);
2115
2137
  await element.dispatchEvent("bvt_verify_page_contains_text");
2116
2138
  }
2117
2139
  }
2118
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2119
- return info;
2140
+ await _screenshot(state, this);
2141
+ return state.info;
2120
2142
  }
2121
2143
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2122
2144
  }
2123
2145
  catch (e) {
2124
- //await this.closeUnexpectedPopups();
2125
- this.logger.error("verify text exist in page failed " + info.log);
2126
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2127
- info.screenshotPath = screenshotPath;
2128
- Object.assign(e, { info: info });
2129
- error = e;
2130
- throw e;
2146
+ await _commandError(state, e, this);
2131
2147
  }
2132
2148
  finally {
2133
- const endTime = Date.now();
2134
- this._reportToWorld(world, {
2135
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2136
- text: "Verify text exists in page",
2137
- screenshotId,
2138
- result: error
2139
- ? {
2140
- status: "FAILED",
2141
- startTime,
2142
- endTime,
2143
- message: error?.message,
2144
- }
2145
- : {
2146
- status: "PASSED",
2147
- startTime,
2148
- endTime,
2149
- },
2150
- info: info,
2151
- });
2149
+ _commandFinally(state, this);
2150
+ }
2151
+ }
2152
+ async waitForTextToDisappear(text, options = {}, world = null) {
2153
+ text = unEscapeString(text);
2154
+ const state = {
2155
+ text_search: text,
2156
+ options,
2157
+ world,
2158
+ locate: false,
2159
+ scroll: false,
2160
+ highlight: false,
2161
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2162
+ text: `Verify text does not exist in page`,
2163
+ operation: "verifyTextNotExistInPage",
2164
+ log: "***** verify text " + text + " does not exist in page *****\n",
2165
+ };
2166
+ const timeout = this._getLoadTimeout(options);
2167
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2168
+ const newValue = await this._replaceWithLocalData(text, world);
2169
+ if (newValue !== text) {
2170
+ this.logger.info(text + "=" + newValue);
2171
+ text = newValue;
2172
+ }
2173
+ let dateAlternatives = findDateAlternatives(text);
2174
+ let numberAlternatives = findNumberAlternatives(text);
2175
+ try {
2176
+ await _preCommand(state, this);
2177
+ state.info.text = text;
2178
+ while (true) {
2179
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2180
+ if (resultWithElementsFound.length === 0) {
2181
+ await _screenshot(state, this);
2182
+ return state.info;
2183
+ }
2184
+ if (Date.now() - state.startTime > timeout) {
2185
+ throw new Error(`Text ${text} found in page`);
2186
+ }
2187
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2188
+ }
2189
+ }
2190
+ catch (e) {
2191
+ await _commandError(state, e, this);
2192
+ }
2193
+ finally {
2194
+ _commandFinally(state, this);
2152
2195
  }
2153
2196
  }
2154
- _getServerUrl() {
2155
- let serviceUrl = "https://api.blinq.io";
2156
- if (process.env.NODE_ENV_BLINQ === "dev") {
2157
- serviceUrl = "https://dev.api.blinq.io";
2197
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2198
+ textAnchor = unEscapeString(textAnchor);
2199
+ textToVerify = unEscapeString(textToVerify);
2200
+ const state = {
2201
+ text_search: textToVerify,
2202
+ options,
2203
+ world,
2204
+ locate: false,
2205
+ scroll: false,
2206
+ highlight: false,
2207
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2208
+ text: `Verify text with relation to another text`,
2209
+ operation: "verify_text_with_relation",
2210
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2211
+ };
2212
+ const timeout = this._getLoadTimeout(options);
2213
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2214
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2215
+ if (newValue !== textAnchor) {
2216
+ this.logger.info(textAnchor + "=" + newValue);
2217
+ textAnchor = newValue;
2218
+ }
2219
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2220
+ if (newValue !== textToVerify) {
2221
+ this.logger.info(textToVerify + "=" + newValue);
2222
+ textToVerify = newValue;
2223
+ }
2224
+ let dateAlternatives = findDateAlternatives(textToVerify);
2225
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2226
+ let foundAncore = false;
2227
+ try {
2228
+ await _preCommand(state, this);
2229
+ state.info.text = textToVerify;
2230
+ while (true) {
2231
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2232
+ if (resultWithElementsFound.length === 0) {
2233
+ if (Date.now() - state.startTime > timeout) {
2234
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2235
+ }
2236
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2237
+ continue;
2238
+ }
2239
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2240
+ foundAncore = true;
2241
+ const result = resultWithElementsFound[i];
2242
+ const token = result.randomToken;
2243
+ const frame = result.frame;
2244
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2245
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2246
+ const elements = Array.from(document.querySelectorAll(css));
2247
+ for (let i = 0; i < elements.length; i++) {
2248
+ const element = elements[i];
2249
+ let climbParent = element;
2250
+ for (let j = 0; j < climb; j++) {
2251
+ climbParent = climbParent.parentElement;
2252
+ if (!climbParent) {
2253
+ break;
2254
+ }
2255
+ }
2256
+ if (!climbParent) {
2257
+ continue;
2258
+ }
2259
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2260
+ if (foundElements.length > 0) {
2261
+ // set the container element attribute
2262
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2263
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2264
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2265
+ return { found: true };
2266
+ }
2267
+ }
2268
+ return { found: false };
2269
+ }, [css, climb, textToVerify, result.randomToken]);
2270
+ if (findResult.found === true) {
2271
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2272
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2273
+ await this._highlightElements(frame, dataAttribute);
2274
+ await this._highlightElements(frame, cssAnchor);
2275
+ const element = await frame.$(dataAttribute);
2276
+ if (element) {
2277
+ await this.scrollIfNeeded(element, state.info);
2278
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2279
+ }
2280
+ await _screenshot(state, this);
2281
+ return state.info;
2282
+ }
2283
+ }
2284
+ }
2285
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2158
2286
  }
2159
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2160
- serviceUrl = "https://stage.api.blinq.io";
2287
+ catch (e) {
2288
+ await _commandError(state, e, this);
2289
+ }
2290
+ finally {
2291
+ _commandFinally(state, this);
2161
2292
  }
2162
- return serviceUrl;
2163
2293
  }
2164
2294
  async visualVerification(text, options = {}, world = null) {
2165
2295
  const startTime = Date.now();
@@ -2175,7 +2305,7 @@ class StableBrowser {
2175
2305
  throw new Error("TOKEN is not set");
2176
2306
  }
2177
2307
  try {
2178
- let serviceUrl = this._getServerUrl();
2308
+ let serviceUrl = _getServerUrl();
2179
2309
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2180
2310
  info.screenshotPath = screenshotPath;
2181
2311
  const screenshot = await this.takeScreenshot();
@@ -2211,11 +2341,12 @@ class StableBrowser {
2211
2341
  info.screenshotPath = screenshotPath;
2212
2342
  Object.assign(e, { info: info });
2213
2343
  error = e;
2214
- throw e;
2344
+ // throw e;
2345
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2215
2346
  }
2216
2347
  finally {
2217
2348
  const endTime = Date.now();
2218
- this._reportToWorld(world, {
2349
+ _reportToWorld(world, {
2219
2350
  type: Types.VERIFY_VISUAL,
2220
2351
  text: "Visual verification",
2221
2352
  screenshotId,
@@ -2263,6 +2394,7 @@ class StableBrowser {
2263
2394
  let screenshotPath = null;
2264
2395
  const info = {};
2265
2396
  info.log = "";
2397
+ info.locatorLog = new LocatorLog(selectors);
2266
2398
  info.operation = "getTableData";
2267
2399
  info.selectors = selectors;
2268
2400
  try {
@@ -2278,11 +2410,12 @@ class StableBrowser {
2278
2410
  info.screenshotPath = screenshotPath;
2279
2411
  Object.assign(e, { info: info });
2280
2412
  error = e;
2281
- throw e;
2413
+ // throw e;
2414
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2282
2415
  }
2283
2416
  finally {
2284
2417
  const endTime = Date.now();
2285
- this._reportToWorld(world, {
2418
+ _reportToWorld(world, {
2286
2419
  element_name: selectors.element_name,
2287
2420
  type: Types.GET_TABLE_DATA,
2288
2421
  text: "Get table data",
@@ -2337,7 +2470,7 @@ class StableBrowser {
2337
2470
  info.operation = "analyzeTable";
2338
2471
  info.selectors = selectors;
2339
2472
  info.query = query;
2340
- query = this._fixUsingParams(query, _params);
2473
+ query = _fixUsingParams(query, _params);
2341
2474
  info.query_fixed = query;
2342
2475
  info.operator = operator;
2343
2476
  info.value = value;
@@ -2443,11 +2576,12 @@ class StableBrowser {
2443
2576
  info.screenshotPath = screenshotPath;
2444
2577
  Object.assign(e, { info: info });
2445
2578
  error = e;
2446
- throw e;
2579
+ // throw e;
2580
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2447
2581
  }
2448
2582
  finally {
2449
2583
  const endTime = Date.now();
2450
- this._reportToWorld(world, {
2584
+ _reportToWorld(world, {
2451
2585
  element_name: selectors.element_name,
2452
2586
  type: Types.ANALYZE_TABLE,
2453
2587
  text: "Analyze table",
@@ -2520,7 +2654,7 @@ class StableBrowser {
2520
2654
  await new Promise((resolve) => setTimeout(resolve, 2000));
2521
2655
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2522
2656
  const endTime = Date.now();
2523
- this._reportToWorld(world, {
2657
+ _reportToWorld(world, {
2524
2658
  type: Types.GET_PAGE_STATUS,
2525
2659
  text: "Wait for page load",
2526
2660
  screenshotId,
@@ -2540,41 +2674,35 @@ class StableBrowser {
2540
2674
  }
2541
2675
  }
2542
2676
  async closePage(options = {}, world = null) {
2543
- const startTime = Date.now();
2544
- let error = null;
2545
- let screenshotId = null;
2546
- let screenshotPath = null;
2547
- const info = {};
2677
+ const state = {
2678
+ options,
2679
+ world,
2680
+ locate: false,
2681
+ scroll: false,
2682
+ highlight: false,
2683
+ type: Types.CLOSE_PAGE,
2684
+ text: `Close page`,
2685
+ operation: "closePage",
2686
+ log: "***** close page *****\n",
2687
+ throwError: false,
2688
+ };
2548
2689
  try {
2690
+ await _preCommand(state, this);
2549
2691
  await this.page.close();
2550
2692
  }
2551
2693
  catch (e) {
2552
2694
  console.log(".");
2695
+ await _commandError(state, e, this);
2553
2696
  }
2554
2697
  finally {
2555
- await new Promise((resolve) => setTimeout(resolve, 2000));
2556
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2557
- const endTime = Date.now();
2558
- this._reportToWorld(world, {
2559
- type: Types.CLOSE_PAGE,
2560
- text: "close page",
2561
- screenshotId,
2562
- result: error
2563
- ? {
2564
- status: "FAILED",
2565
- startTime,
2566
- endTime,
2567
- message: error?.message,
2568
- }
2569
- : {
2570
- status: "PASSED",
2571
- startTime,
2572
- endTime,
2573
- },
2574
- info: info,
2575
- });
2698
+ _commandFinally(state, this);
2576
2699
  }
2577
2700
  }
2701
+ saveTestDataAsGlobal(options, world) {
2702
+ const dataFile = this._getDataFile(world);
2703
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2704
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2705
+ }
2578
2706
  async setViewportSize(width, hight, options = {}, world = null) {
2579
2707
  const startTime = Date.now();
2580
2708
  let error = null;
@@ -2592,12 +2720,13 @@ class StableBrowser {
2592
2720
  }
2593
2721
  catch (e) {
2594
2722
  console.log(".");
2723
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2595
2724
  }
2596
2725
  finally {
2597
2726
  await new Promise((resolve) => setTimeout(resolve, 2000));
2598
2727
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2599
2728
  const endTime = Date.now();
2600
- this._reportToWorld(world, {
2729
+ _reportToWorld(world, {
2601
2730
  type: Types.SET_VIEWPORT,
2602
2731
  text: "set viewport size to " + width + "x" + hight,
2603
2732
  screenshotId,
@@ -2628,12 +2757,13 @@ class StableBrowser {
2628
2757
  }
2629
2758
  catch (e) {
2630
2759
  console.log(".");
2760
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2631
2761
  }
2632
2762
  finally {
2633
2763
  await new Promise((resolve) => setTimeout(resolve, 2000));
2634
2764
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2635
2765
  const endTime = Date.now();
2636
- this._reportToWorld(world, {
2766
+ _reportToWorld(world, {
2637
2767
  type: Types.GET_PAGE_STATUS,
2638
2768
  text: "page relaod",
2639
2769
  screenshotId,
@@ -2669,11 +2799,37 @@ class StableBrowser {
2669
2799
  console.log("#-#");
2670
2800
  }
2671
2801
  }
2672
- _reportToWorld(world, properties) {
2673
- if (!world || !world.attach) {
2674
- return;
2802
+ async beforeStep(world, step) {
2803
+ this.stepName = step.pickleStep.text;
2804
+ this.logger.info("step: " + this.stepName);
2805
+ if (this.stepIndex === undefined) {
2806
+ this.stepIndex = 0;
2807
+ }
2808
+ else {
2809
+ this.stepIndex++;
2810
+ }
2811
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2812
+ if (this.context.browserObject.context) {
2813
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2814
+ }
2815
+ }
2816
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2817
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2818
+ // check if @global_test_data tag is present
2819
+ if (this.tags.includes("@global_test_data")) {
2820
+ this.saveTestDataAsGlobal({}, world);
2821
+ }
2822
+ }
2823
+ }
2824
+ async afterStep(world, step) {
2825
+ this.stepName = null;
2826
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2827
+ if (this.context.browserObject.context) {
2828
+ await this.context.browserObject.context.tracing.stopChunk({
2829
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2830
+ });
2831
+ }
2675
2832
  }
2676
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2677
2833
  }
2678
2834
  }
2679
2835
  function createTimedPromise(promise, label) {
@@ -2681,156 +2837,5 @@ function createTimedPromise(promise, label) {
2681
2837
  .then((result) => ({ status: "fulfilled", label, result }))
2682
2838
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2683
2839
  }
2684
- const KEYBOARD_EVENTS = [
2685
- "ALT",
2686
- "AltGraph",
2687
- "CapsLock",
2688
- "Control",
2689
- "Fn",
2690
- "FnLock",
2691
- "Hyper",
2692
- "Meta",
2693
- "NumLock",
2694
- "ScrollLock",
2695
- "Shift",
2696
- "Super",
2697
- "Symbol",
2698
- "SymbolLock",
2699
- "Enter",
2700
- "Tab",
2701
- "ArrowDown",
2702
- "ArrowLeft",
2703
- "ArrowRight",
2704
- "ArrowUp",
2705
- "End",
2706
- "Home",
2707
- "PageDown",
2708
- "PageUp",
2709
- "Backspace",
2710
- "Clear",
2711
- "Copy",
2712
- "CrSel",
2713
- "Cut",
2714
- "Delete",
2715
- "EraseEof",
2716
- "ExSel",
2717
- "Insert",
2718
- "Paste",
2719
- "Redo",
2720
- "Undo",
2721
- "Accept",
2722
- "Again",
2723
- "Attn",
2724
- "Cancel",
2725
- "ContextMenu",
2726
- "Escape",
2727
- "Execute",
2728
- "Find",
2729
- "Finish",
2730
- "Help",
2731
- "Pause",
2732
- "Play",
2733
- "Props",
2734
- "Select",
2735
- "ZoomIn",
2736
- "ZoomOut",
2737
- "BrightnessDown",
2738
- "BrightnessUp",
2739
- "Eject",
2740
- "LogOff",
2741
- "Power",
2742
- "PowerOff",
2743
- "PrintScreen",
2744
- "Hibernate",
2745
- "Standby",
2746
- "WakeUp",
2747
- "AllCandidates",
2748
- "Alphanumeric",
2749
- "CodeInput",
2750
- "Compose",
2751
- "Convert",
2752
- "Dead",
2753
- "FinalMode",
2754
- "GroupFirst",
2755
- "GroupLast",
2756
- "GroupNext",
2757
- "GroupPrevious",
2758
- "ModeChange",
2759
- "NextCandidate",
2760
- "NonConvert",
2761
- "PreviousCandidate",
2762
- "Process",
2763
- "SingleCandidate",
2764
- "HangulMode",
2765
- "HanjaMode",
2766
- "JunjaMode",
2767
- "Eisu",
2768
- "Hankaku",
2769
- "Hiragana",
2770
- "HiraganaKatakana",
2771
- "KanaMode",
2772
- "KanjiMode",
2773
- "Katakana",
2774
- "Romaji",
2775
- "Zenkaku",
2776
- "ZenkakuHanaku",
2777
- "F1",
2778
- "F2",
2779
- "F3",
2780
- "F4",
2781
- "F5",
2782
- "F6",
2783
- "F7",
2784
- "F8",
2785
- "F9",
2786
- "F10",
2787
- "F11",
2788
- "F12",
2789
- "Soft1",
2790
- "Soft2",
2791
- "Soft3",
2792
- "Soft4",
2793
- "ChannelDown",
2794
- "ChannelUp",
2795
- "Close",
2796
- "MailForward",
2797
- "MailReply",
2798
- "MailSend",
2799
- "MediaFastForward",
2800
- "MediaPause",
2801
- "MediaPlay",
2802
- "MediaPlayPause",
2803
- "MediaRecord",
2804
- "MediaRewind",
2805
- "MediaStop",
2806
- "MediaTrackNext",
2807
- "MediaTrackPrevious",
2808
- "AudioBalanceLeft",
2809
- "AudioBalanceRight",
2810
- "AudioBassBoostDown",
2811
- "AudioBassBoostToggle",
2812
- "AudioBassBoostUp",
2813
- "AudioFaderFront",
2814
- "AudioFaderRear",
2815
- "AudioSurroundModeNext",
2816
- "AudioTrebleDown",
2817
- "AudioTrebleUp",
2818
- "AudioVolumeDown",
2819
- "AudioVolumeMute",
2820
- "AudioVolumeUp",
2821
- "MicrophoneToggle",
2822
- "MicrophoneVolumeDown",
2823
- "MicrophoneVolumeMute",
2824
- "MicrophoneVolumeUp",
2825
- "TV",
2826
- "TV3DMode",
2827
- "TVAntennaCable",
2828
- "TVAudioDescription",
2829
- ];
2830
- function unEscapeString(str) {
2831
- const placeholder = "__NEWLINE__";
2832
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2833
- return str;
2834
- }
2835
2840
  export { StableBrowser };
2836
2841
  //# sourceMappingURL=stable_browser.js.map