automation_model 1.0.485-dev → 1.0.485-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 +59 -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 +584 -578
  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 -1
  47. package/lib/utils.js +252 -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 { 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,50 +1163,30 @@ 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) {
1173
+ _value = unEscapeString(_value);
1174
+ const newValue = await this._replaceWithLocalData(_value, world);
1189
1175
  const state = {
1190
1176
  selectors,
1191
1177
  _params,
1192
- value: unEscapeString(_value),
1178
+ value: newValue,
1179
+ originalValue: _value,
1193
1180
  options,
1194
1181
  world,
1195
1182
  type: Types.FILL,
1196
1183
  text: `Click type input with value: ${_value}`,
1197
1184
  operation: "clickType",
1198
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1185
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1199
1186
  };
1200
- const newValue = await this._replaceWithLocalData(state.value, world);
1201
1187
  if (newValue !== _value) {
1202
1188
  //this.logger.info(_value + "=" + newValue);
1203
1189
  _value = newValue;
1204
- state.value = newValue;
1205
1190
  }
1206
1191
  try {
1207
1192
  await _preCommand(state, this);
@@ -1318,6 +1303,7 @@ class StableBrowser {
1318
1303
  let screenshotPath = null;
1319
1304
  if (!info.log) {
1320
1305
  info.log = "";
1306
+ info.locatorLog = new LocatorLog(selectors);
1321
1307
  }
1322
1308
  info.operation = "getText";
1323
1309
  info.selectors = selectors;
@@ -1656,11 +1642,9 @@ class StableBrowser {
1656
1642
  if (!fs.existsSync(world.screenshotPath)) {
1657
1643
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1658
1644
  }
1659
- let nextIndex = 1;
1660
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1661
- nextIndex++;
1662
- }
1663
- 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");
1664
1648
  try {
1665
1649
  await this.takeScreenshot(screenshotPath);
1666
1650
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1674,7 +1658,7 @@ class StableBrowser {
1674
1658
  catch (e) {
1675
1659
  this.logger.info("unable to take screenshot, ignored");
1676
1660
  }
1677
- result.screenshotId = nextIndex;
1661
+ result.screenshotId = uuidStr;
1678
1662
  result.screenshotPath = screenshotPath;
1679
1663
  if (info && info.box) {
1680
1664
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1772,74 +1756,101 @@ class StableBrowser {
1772
1756
  }
1773
1757
  }
1774
1758
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1775
- _validateSelectors(selectors);
1776
- const startTime = Date.now();
1777
- let error = null;
1778
- let screenshotId = null;
1779
- 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
+ };
1780
1771
  await new Promise((resolve) => setTimeout(resolve, 2000));
1781
- const info = {};
1782
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1783
- info.operation = "extract";
1784
- info.selectors = selectors;
1785
1772
  try {
1786
- const element = await this._locate(selectors, info, _params);
1787
- await this._highlightElements(element);
1788
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1773
+ await _preCommand(state, this);
1789
1774
  switch (attribute) {
1790
1775
  case "inner_text":
1791
- info.value = await element.innerText();
1776
+ state.value = await state.element.innerText();
1792
1777
  break;
1793
1778
  case "href":
1794
- 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());
1795
1820
  break;
1796
1821
  case "value":
1797
- 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());
1798
1829
  break;
1799
1830
  default:
1800
- info.value = await element.getAttribute(attribute);
1831
+ val = String(await state.element.getAttribute(attribute));
1801
1832
  break;
1802
1833
  }
1803
- this[variable] = info.value;
1804
- if (world) {
1805
- 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");
1806
1838
  }
1807
- this.setTestData({ [variable]: info.value }, world);
1808
- this.logger.info("set test data: " + variable + "=" + info.value);
1809
- 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;
1810
1848
  }
1811
1849
  catch (e) {
1812
- //await this.closeUnexpectedPopups();
1813
- this.logger.error("extract failed " + info.log);
1814
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1815
- info.screenshotPath = screenshotPath;
1816
- Object.assign(e, { info: info });
1817
- error = e;
1818
- throw e;
1850
+ await _commandError(state, e, this);
1819
1851
  }
1820
1852
  finally {
1821
- const endTime = Date.now();
1822
- this._reportToWorld(world, {
1823
- element_name: selectors.element_name,
1824
- type: Types.EXTRACT_ATTRIBUTE,
1825
- variable: variable,
1826
- value: info.value,
1827
- text: "Extract attribute from element",
1828
- screenshotId,
1829
- result: error
1830
- ? {
1831
- status: "FAILED",
1832
- startTime,
1833
- endTime,
1834
- message: error?.message,
1835
- }
1836
- : {
1837
- status: "PASSED",
1838
- startTime,
1839
- endTime,
1840
- },
1841
- info: info,
1842
- });
1853
+ _commandFinally(state, this);
1843
1854
  }
1844
1855
  }
1845
1856
  async extractEmailData(emailAddress, options, world) {
@@ -1860,7 +1871,7 @@ class StableBrowser {
1860
1871
  if (options && options.timeout) {
1861
1872
  timeout = options.timeout;
1862
1873
  }
1863
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1874
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1864
1875
  const request = {
1865
1876
  method: "POST",
1866
1877
  url: serviceUrl,
@@ -1916,7 +1927,8 @@ class StableBrowser {
1916
1927
  catch (e) {
1917
1928
  errorCount++;
1918
1929
  if (errorCount > 3) {
1919
- throw e;
1930
+ // throw e;
1931
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1920
1932
  }
1921
1933
  // ignore
1922
1934
  }
@@ -2027,11 +2039,12 @@ class StableBrowser {
2027
2039
  info.screenshotPath = screenshotPath;
2028
2040
  Object.assign(e, { info: info });
2029
2041
  error = e;
2030
- throw e;
2042
+ // throw e;
2043
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2031
2044
  }
2032
2045
  finally {
2033
2046
  const endTime = Date.now();
2034
- this._reportToWorld(world, {
2047
+ _reportToWorld(world, {
2035
2048
  type: Types.VERIFY_PAGE_PATH,
2036
2049
  text: "Verify page path",
2037
2050
  screenshotId,
@@ -2051,54 +2064,64 @@ class StableBrowser {
2051
2064
  });
2052
2065
  }
2053
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
+ }
2054
2095
  async verifyTextExistInPage(text, options = {}, world = null) {
2055
2096
  text = unEscapeString(text);
2056
- 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
+ };
2057
2109
  const timeout = this._getLoadTimeout(options);
2058
- let error = null;
2059
- let screenshotId = null;
2060
- let screenshotPath = null;
2061
2110
  await new Promise((resolve) => setTimeout(resolve, 2000));
2062
- const info = {};
2063
- info.log = "***** verify text " + text + " exists in page *****\n";
2064
- info.operation = "verifyTextExistInPage";
2065
2111
  const newValue = await this._replaceWithLocalData(text, world);
2066
2112
  if (newValue !== text) {
2067
2113
  this.logger.info(text + "=" + newValue);
2068
2114
  text = newValue;
2069
2115
  }
2070
- info.text = text;
2071
2116
  let dateAlternatives = findDateAlternatives(text);
2072
2117
  let numberAlternatives = findNumberAlternatives(text);
2073
2118
  try {
2119
+ await _preCommand(state, this);
2120
+ state.info.text = text;
2074
2121
  while (true) {
2075
- const frames = this.page.frames();
2076
- let results = [];
2077
- for (let i = 0; i < frames.length; i++) {
2078
- if (dateAlternatives.date) {
2079
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2080
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2081
- result.frame = frames[i];
2082
- results.push(result);
2083
- }
2084
- }
2085
- else if (numberAlternatives.number) {
2086
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2087
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2088
- result.frame = frames[i];
2089
- results.push(result);
2090
- }
2091
- }
2092
- else {
2093
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2094
- result.frame = frames[i];
2095
- results.push(result);
2096
- }
2097
- }
2098
- info.results = results;
2099
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2122
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2100
2123
  if (resultWithElementsFound.length === 0) {
2101
- if (Date.now() - startTime > timeout) {
2124
+ if (Date.now() - state.startTime > timeout) {
2102
2125
  throw new Error(`Text ${text} not found in page`);
2103
2126
  }
2104
2127
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2110,55 +2133,163 @@ class StableBrowser {
2110
2133
  await this._highlightElements(frame, dataAttribute);
2111
2134
  const element = await frame.$(dataAttribute);
2112
2135
  if (element) {
2113
- await this.scrollIfNeeded(element, info);
2136
+ await this.scrollIfNeeded(element, state.info);
2114
2137
  await element.dispatchEvent("bvt_verify_page_contains_text");
2115
2138
  }
2116
2139
  }
2117
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2118
- return info;
2140
+ await _screenshot(state, this);
2141
+ return state.info;
2119
2142
  }
2120
2143
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2121
2144
  }
2122
2145
  catch (e) {
2123
- //await this.closeUnexpectedPopups();
2124
- this.logger.error("verify text exist in page failed " + info.log);
2125
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2126
- info.screenshotPath = screenshotPath;
2127
- Object.assign(e, { info: info });
2128
- error = e;
2129
- throw e;
2146
+ await _commandError(state, e, this);
2130
2147
  }
2131
2148
  finally {
2132
- const endTime = Date.now();
2133
- this._reportToWorld(world, {
2134
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2135
- text: "Verify text exists in page",
2136
- screenshotId,
2137
- result: error
2138
- ? {
2139
- status: "FAILED",
2140
- startTime,
2141
- endTime,
2142
- message: error?.message,
2143
- }
2144
- : {
2145
- status: "PASSED",
2146
- startTime,
2147
- endTime,
2148
- },
2149
- info: info,
2150
- });
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);
2151
2195
  }
2152
2196
  }
2153
- _getServerUrl() {
2154
- let serviceUrl = "https://api.blinq.io";
2155
- if (process.env.NODE_ENV_BLINQ === "dev") {
2156
- 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 });
2157
2286
  }
2158
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2159
- serviceUrl = "https://stage.api.blinq.io";
2287
+ catch (e) {
2288
+ await _commandError(state, e, this);
2289
+ }
2290
+ finally {
2291
+ _commandFinally(state, this);
2160
2292
  }
2161
- return serviceUrl;
2162
2293
  }
2163
2294
  async visualVerification(text, options = {}, world = null) {
2164
2295
  const startTime = Date.now();
@@ -2174,7 +2305,7 @@ class StableBrowser {
2174
2305
  throw new Error("TOKEN is not set");
2175
2306
  }
2176
2307
  try {
2177
- let serviceUrl = this._getServerUrl();
2308
+ let serviceUrl = _getServerUrl();
2178
2309
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2179
2310
  info.screenshotPath = screenshotPath;
2180
2311
  const screenshot = await this.takeScreenshot();
@@ -2210,11 +2341,12 @@ class StableBrowser {
2210
2341
  info.screenshotPath = screenshotPath;
2211
2342
  Object.assign(e, { info: info });
2212
2343
  error = e;
2213
- throw e;
2344
+ // throw e;
2345
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2214
2346
  }
2215
2347
  finally {
2216
2348
  const endTime = Date.now();
2217
- this._reportToWorld(world, {
2349
+ _reportToWorld(world, {
2218
2350
  type: Types.VERIFY_VISUAL,
2219
2351
  text: "Visual verification",
2220
2352
  screenshotId,
@@ -2262,6 +2394,7 @@ class StableBrowser {
2262
2394
  let screenshotPath = null;
2263
2395
  const info = {};
2264
2396
  info.log = "";
2397
+ info.locatorLog = new LocatorLog(selectors);
2265
2398
  info.operation = "getTableData";
2266
2399
  info.selectors = selectors;
2267
2400
  try {
@@ -2277,11 +2410,12 @@ class StableBrowser {
2277
2410
  info.screenshotPath = screenshotPath;
2278
2411
  Object.assign(e, { info: info });
2279
2412
  error = e;
2280
- throw e;
2413
+ // throw e;
2414
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2281
2415
  }
2282
2416
  finally {
2283
2417
  const endTime = Date.now();
2284
- this._reportToWorld(world, {
2418
+ _reportToWorld(world, {
2285
2419
  element_name: selectors.element_name,
2286
2420
  type: Types.GET_TABLE_DATA,
2287
2421
  text: "Get table data",
@@ -2336,7 +2470,7 @@ class StableBrowser {
2336
2470
  info.operation = "analyzeTable";
2337
2471
  info.selectors = selectors;
2338
2472
  info.query = query;
2339
- query = this._fixUsingParams(query, _params);
2473
+ query = _fixUsingParams(query, _params);
2340
2474
  info.query_fixed = query;
2341
2475
  info.operator = operator;
2342
2476
  info.value = value;
@@ -2442,11 +2576,12 @@ class StableBrowser {
2442
2576
  info.screenshotPath = screenshotPath;
2443
2577
  Object.assign(e, { info: info });
2444
2578
  error = e;
2445
- throw e;
2579
+ // throw e;
2580
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2446
2581
  }
2447
2582
  finally {
2448
2583
  const endTime = Date.now();
2449
- this._reportToWorld(world, {
2584
+ _reportToWorld(world, {
2450
2585
  element_name: selectors.element_name,
2451
2586
  type: Types.ANALYZE_TABLE,
2452
2587
  text: "Analyze table",
@@ -2519,7 +2654,7 @@ class StableBrowser {
2519
2654
  await new Promise((resolve) => setTimeout(resolve, 2000));
2520
2655
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2521
2656
  const endTime = Date.now();
2522
- this._reportToWorld(world, {
2657
+ _reportToWorld(world, {
2523
2658
  type: Types.GET_PAGE_STATUS,
2524
2659
  text: "Wait for page load",
2525
2660
  screenshotId,
@@ -2539,41 +2674,35 @@ class StableBrowser {
2539
2674
  }
2540
2675
  }
2541
2676
  async closePage(options = {}, world = null) {
2542
- const startTime = Date.now();
2543
- let error = null;
2544
- let screenshotId = null;
2545
- let screenshotPath = null;
2546
- 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
+ };
2547
2689
  try {
2690
+ await _preCommand(state, this);
2548
2691
  await this.page.close();
2549
2692
  }
2550
2693
  catch (e) {
2551
2694
  console.log(".");
2695
+ await _commandError(state, e, this);
2552
2696
  }
2553
2697
  finally {
2554
- await new Promise((resolve) => setTimeout(resolve, 2000));
2555
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2556
- const endTime = Date.now();
2557
- this._reportToWorld(world, {
2558
- type: Types.CLOSE_PAGE,
2559
- text: "close page",
2560
- screenshotId,
2561
- result: error
2562
- ? {
2563
- status: "FAILED",
2564
- startTime,
2565
- endTime,
2566
- message: error?.message,
2567
- }
2568
- : {
2569
- status: "PASSED",
2570
- startTime,
2571
- endTime,
2572
- },
2573
- info: info,
2574
- });
2698
+ _commandFinally(state, this);
2575
2699
  }
2576
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
+ }
2577
2706
  async setViewportSize(width, hight, options = {}, world = null) {
2578
2707
  const startTime = Date.now();
2579
2708
  let error = null;
@@ -2591,12 +2720,13 @@ class StableBrowser {
2591
2720
  }
2592
2721
  catch (e) {
2593
2722
  console.log(".");
2723
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2594
2724
  }
2595
2725
  finally {
2596
2726
  await new Promise((resolve) => setTimeout(resolve, 2000));
2597
2727
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2598
2728
  const endTime = Date.now();
2599
- this._reportToWorld(world, {
2729
+ _reportToWorld(world, {
2600
2730
  type: Types.SET_VIEWPORT,
2601
2731
  text: "set viewport size to " + width + "x" + hight,
2602
2732
  screenshotId,
@@ -2627,12 +2757,13 @@ class StableBrowser {
2627
2757
  }
2628
2758
  catch (e) {
2629
2759
  console.log(".");
2760
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2630
2761
  }
2631
2762
  finally {
2632
2763
  await new Promise((resolve) => setTimeout(resolve, 2000));
2633
2764
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2634
2765
  const endTime = Date.now();
2635
- this._reportToWorld(world, {
2766
+ _reportToWorld(world, {
2636
2767
  type: Types.GET_PAGE_STATUS,
2637
2768
  text: "page relaod",
2638
2769
  screenshotId,
@@ -2668,11 +2799,37 @@ class StableBrowser {
2668
2799
  console.log("#-#");
2669
2800
  }
2670
2801
  }
2671
- _reportToWorld(world, properties) {
2672
- if (!world || !world.attach) {
2673
- 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
+ }
2674
2832
  }
2675
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2676
2833
  }
2677
2834
  }
2678
2835
  function createTimedPromise(promise, label) {
@@ -2680,156 +2837,5 @@ function createTimedPromise(promise, label) {
2680
2837
  .then((result) => ({ status: "fulfilled", label, result }))
2681
2838
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2682
2839
  }
2683
- const KEYBOARD_EVENTS = [
2684
- "ALT",
2685
- "AltGraph",
2686
- "CapsLock",
2687
- "Control",
2688
- "Fn",
2689
- "FnLock",
2690
- "Hyper",
2691
- "Meta",
2692
- "NumLock",
2693
- "ScrollLock",
2694
- "Shift",
2695
- "Super",
2696
- "Symbol",
2697
- "SymbolLock",
2698
- "Enter",
2699
- "Tab",
2700
- "ArrowDown",
2701
- "ArrowLeft",
2702
- "ArrowRight",
2703
- "ArrowUp",
2704
- "End",
2705
- "Home",
2706
- "PageDown",
2707
- "PageUp",
2708
- "Backspace",
2709
- "Clear",
2710
- "Copy",
2711
- "CrSel",
2712
- "Cut",
2713
- "Delete",
2714
- "EraseEof",
2715
- "ExSel",
2716
- "Insert",
2717
- "Paste",
2718
- "Redo",
2719
- "Undo",
2720
- "Accept",
2721
- "Again",
2722
- "Attn",
2723
- "Cancel",
2724
- "ContextMenu",
2725
- "Escape",
2726
- "Execute",
2727
- "Find",
2728
- "Finish",
2729
- "Help",
2730
- "Pause",
2731
- "Play",
2732
- "Props",
2733
- "Select",
2734
- "ZoomIn",
2735
- "ZoomOut",
2736
- "BrightnessDown",
2737
- "BrightnessUp",
2738
- "Eject",
2739
- "LogOff",
2740
- "Power",
2741
- "PowerOff",
2742
- "PrintScreen",
2743
- "Hibernate",
2744
- "Standby",
2745
- "WakeUp",
2746
- "AllCandidates",
2747
- "Alphanumeric",
2748
- "CodeInput",
2749
- "Compose",
2750
- "Convert",
2751
- "Dead",
2752
- "FinalMode",
2753
- "GroupFirst",
2754
- "GroupLast",
2755
- "GroupNext",
2756
- "GroupPrevious",
2757
- "ModeChange",
2758
- "NextCandidate",
2759
- "NonConvert",
2760
- "PreviousCandidate",
2761
- "Process",
2762
- "SingleCandidate",
2763
- "HangulMode",
2764
- "HanjaMode",
2765
- "JunjaMode",
2766
- "Eisu",
2767
- "Hankaku",
2768
- "Hiragana",
2769
- "HiraganaKatakana",
2770
- "KanaMode",
2771
- "KanjiMode",
2772
- "Katakana",
2773
- "Romaji",
2774
- "Zenkaku",
2775
- "ZenkakuHanaku",
2776
- "F1",
2777
- "F2",
2778
- "F3",
2779
- "F4",
2780
- "F5",
2781
- "F6",
2782
- "F7",
2783
- "F8",
2784
- "F9",
2785
- "F10",
2786
- "F11",
2787
- "F12",
2788
- "Soft1",
2789
- "Soft2",
2790
- "Soft3",
2791
- "Soft4",
2792
- "ChannelDown",
2793
- "ChannelUp",
2794
- "Close",
2795
- "MailForward",
2796
- "MailReply",
2797
- "MailSend",
2798
- "MediaFastForward",
2799
- "MediaPause",
2800
- "MediaPlay",
2801
- "MediaPlayPause",
2802
- "MediaRecord",
2803
- "MediaRewind",
2804
- "MediaStop",
2805
- "MediaTrackNext",
2806
- "MediaTrackPrevious",
2807
- "AudioBalanceLeft",
2808
- "AudioBalanceRight",
2809
- "AudioBassBoostDown",
2810
- "AudioBassBoostToggle",
2811
- "AudioBassBoostUp",
2812
- "AudioFaderFront",
2813
- "AudioFaderRear",
2814
- "AudioSurroundModeNext",
2815
- "AudioTrebleDown",
2816
- "AudioTrebleUp",
2817
- "AudioVolumeDown",
2818
- "AudioVolumeMute",
2819
- "AudioVolumeUp",
2820
- "MicrophoneToggle",
2821
- "MicrophoneVolumeDown",
2822
- "MicrophoneVolumeMute",
2823
- "MicrophoneVolumeUp",
2824
- "TV",
2825
- "TV3DMode",
2826
- "TVAntennaCable",
2827
- "TVAudioDescription",
2828
- ];
2829
- function unEscapeString(str) {
2830
- const placeholder = "__NEWLINE__";
2831
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2832
- return str;
2833
- }
2834
2840
  export { StableBrowser };
2835
2841
  //# sourceMappingURL=stable_browser.js.map