automation_model 1.0.489-dev → 1.0.489-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 +55 -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 +2 -1
  34. package/lib/network.js +33 -1
  35. package/lib/network.js.map +1 -1
  36. package/lib/scripts/find_text.js +126 -0
  37. package/lib/stable_browser.d.ts +49 -17
  38. package/lib/stable_browser.js +602 -576
  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 +12 -1
  47. package/lib/utils.js +237 -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,15 +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
- import { registerNetworkEvents } from "./network.js";
21
- 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 = {
22
24
  CLICK: "click_element",
23
25
  NAVIGATE: "navigate",
24
26
  FILL: "fill_element",
@@ -29,6 +31,8 @@ const Types = {
29
31
  GET_PAGE_STATUS: "get_page_status",
30
32
  CLICK_ROW_ACTION: "click_row_action",
31
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",
32
36
  ANALYZE_TABLE: "analyze_table",
33
37
  SELECT: "select_combobox",
34
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -44,6 +48,9 @@ const Types = {
44
48
  VERIFY_VISUAL: "verify_visual",
45
49
  LOAD_DATA: "load_data",
46
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",
47
54
  };
48
55
  export const apps = {};
49
56
  class StableBrowser {
@@ -57,6 +64,7 @@ class StableBrowser {
57
64
  networkLogger = null;
58
65
  configuration = null;
59
66
  appName = "main";
67
+ tags = null;
60
68
  constructor(browser, page, logger = null, context = null, world = null) {
61
69
  this.browser = browser;
62
70
  this.page = page;
@@ -87,25 +95,32 @@ class StableBrowser {
87
95
  catch (e) {
88
96
  this.logger.error("unable to read ai_config.json");
89
97
  }
90
- context.pageLoading = { status: false };
91
- context.pages = [this.page];
92
98
  const logFolder = path.join(this.project_path, "logs", "web");
93
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
94
102
  this.registerEventListeners(this.context);
95
103
  registerNetworkEvents(this.world, this, this.context, this.page);
104
+ registerDownloadEvent(this.page, this.world, this.context);
96
105
  }
97
106
  registerEventListeners(context) {
98
107
  this.registerConsoleLogListener(this.page, context);
99
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
100
109
  if (!context.pageLoading) {
101
110
  context.pageLoading = { status: false };
102
111
  }
103
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
+ }
104
118
  context.pageLoading.status = true;
105
119
  this.page = page;
106
120
  context.page = page;
107
121
  context.pages.push(page);
108
122
  registerNetworkEvents(this.world, this, context, this.page);
123
+ registerDownloadEvent(this.page, this.world, context);
109
124
  page.on("close", async () => {
110
125
  if (this.context && this.context.pages && this.context.pages.length > 1) {
111
126
  this.context.pages.pop();
@@ -135,9 +150,9 @@ class StableBrowser {
135
150
  if (this.appName === appName) {
136
151
  return;
137
152
  }
138
- let newContextCreated = false;
153
+ let navigate = false;
139
154
  if (!apps[appName]) {
140
- 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);
141
156
  newContextCreated = true;
142
157
  apps[appName] = {
143
158
  context: newContext,
@@ -146,32 +161,15 @@ class StableBrowser {
146
161
  };
147
162
  }
148
163
  const tempContext = {};
149
- this._copyContext(this, tempContext);
150
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
151
166
  apps[this.appName] = tempContext;
152
167
  this.appName = appName;
153
- if (newContextCreated) {
154
- this.registerEventListeners(this.context);
168
+ if (navigate) {
155
169
  await this.goto(this.context.environment.baseUrl);
156
170
  await this.waitForPageLoad();
157
171
  }
158
172
  }
159
- _copyContext(from, to) {
160
- to.browser = from.browser;
161
- to.page = from.page;
162
- to.context = from.context;
163
- }
164
- getWebLogFile(logFolder) {
165
- if (!fs.existsSync(logFolder)) {
166
- fs.mkdirSync(logFolder, { recursive: true });
167
- }
168
- let nextIndex = 1;
169
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
170
- nextIndex++;
171
- }
172
- const fileName = nextIndex + ".json";
173
- return path.join(logFolder, fileName);
174
- }
175
173
  registerConsoleLogListener(page, context) {
176
174
  if (!this.context.webLogger) {
177
175
  this.context.webLogger = [];
@@ -221,7 +219,7 @@ class StableBrowser {
221
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
222
220
  }
223
221
  catch (error) {
224
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
225
223
  context.networkLogger.push({
226
224
  error: "not able to listen",
227
225
  message: error.message,
@@ -235,55 +233,40 @@ class StableBrowser {
235
233
  // async closeUnexpectedPopups() {
236
234
  // await closeUnexpectedPopups(this.page);
237
235
  // }
238
- async goto(url) {
236
+ async goto(url, world = null) {
239
237
  if (!url.startsWith("http")) {
240
238
  url = "https://" + url;
241
239
  }
242
- await this.page.goto(url, {
243
- timeout: 60000,
244
- });
245
- }
246
- _fixUsingParams(text, _params) {
247
- if (!_params || typeof text !== "string") {
248
- return text;
249
- }
250
- for (let key in _params) {
251
- let regValue = key;
252
- if (key.startsWith("_")) {
253
- // remove the _ prefix
254
- regValue = key.substring(1);
255
- }
256
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
240
+ const state = {
241
+ value: url,
242
+ world: world,
243
+ type: Types.NAVIGATE,
244
+ text: `Navigate Page to: ${url}`,
245
+ operation: "goto",
246
+ log: "***** navigate page to " + url + " *****\n",
247
+ info: {},
248
+ locate: false,
249
+ scroll: false,
250
+ screenshot: false,
251
+ highlight: false,
252
+ };
253
+ try {
254
+ await _preCommand(state, this);
255
+ await this.page.goto(url, {
256
+ timeout: 60000,
257
+ });
258
+ await _screenshot(state, this);
257
259
  }
258
- return text;
259
- }
260
- _fixLocatorUsingParams(locator, _params) {
261
- // check if not null
262
- if (!locator) {
263
- return locator;
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
264
263
  }
265
- // clone the locator
266
- locator = JSON.parse(JSON.stringify(locator));
267
- this.scanAndManipulate(locator, _params);
268
- return locator;
269
- }
270
- _isObject(value) {
271
- return value && typeof value === "object" && value.constructor === Object;
272
- }
273
- scanAndManipulate(currentObj, _params) {
274
- for (const key in currentObj) {
275
- if (typeof currentObj[key] === "string") {
276
- // Perform string manipulation
277
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
278
- }
279
- else if (this._isObject(currentObj[key])) {
280
- // Recursively scan nested objects
281
- this.scanAndManipulate(currentObj[key], _params);
282
- }
264
+ finally {
265
+ _commandFinally(state, this);
283
266
  }
284
267
  }
285
268
  _getLocator(locator, scope, _params) {
286
- locator = this._fixLocatorUsingParams(locator, _params);
269
+ locator = _fixLocatorUsingParams(locator, _params);
287
270
  let locatorReturn;
288
271
  if (locator.role) {
289
272
  if (locator.role[1].nameReg) {
@@ -291,7 +274,7 @@ class StableBrowser {
291
274
  delete locator.role[1].nameReg;
292
275
  }
293
276
  // if (locator.role[1].name) {
294
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
277
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
295
278
  // }
296
279
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
297
280
  }
@@ -334,7 +317,7 @@ class StableBrowser {
334
317
  if (css && css.locator) {
335
318
  css = css.locator;
336
319
  }
337
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
320
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
338
321
  if (result.elementCount === 0) {
339
322
  return;
340
323
  }
@@ -350,116 +333,28 @@ class StableBrowser {
350
333
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
351
334
  //const stringifyText = JSON.stringify(text);
352
335
  return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
353
- function isParent(parent, child) {
354
- let currentNode = child.parentNode;
355
- while (currentNode !== null) {
356
- if (currentNode === parent) {
357
- return true;
358
- }
359
- currentNode = currentNode.parentNode;
360
- }
361
- return false;
362
- }
363
- document.isParent = isParent;
364
- function getRegex(str) {
365
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
366
- if (!match) {
367
- return null;
368
- }
369
- let [_, pattern, flags] = match;
370
- return new RegExp(pattern, flags);
371
- }
372
- document.getRegex = getRegex;
373
- function collectAllShadowDomElements(element, result = []) {
374
- // Check and add the element if it has a shadow root
375
- if (element.shadowRoot) {
376
- result.push(element);
377
- // Also search within the shadow root
378
- document.collectAllShadowDomElements(element.shadowRoot, result);
379
- }
380
- // Iterate over child nodes
381
- element.childNodes.forEach((child) => {
382
- // Recursively call the function for each child node
383
- document.collectAllShadowDomElements(child, result);
384
- });
385
- return result;
386
- }
387
- document.collectAllShadowDomElements = collectAllShadowDomElements;
388
- if (!tag) {
389
- tag = "*";
390
- }
391
- let regexpSearch = document.getRegex(text);
392
- if (regexpSearch) {
393
- regex = true;
394
- }
395
- let elements = Array.from(document.querySelectorAll(tag));
396
- let shadowHosts = [];
397
- document.collectAllShadowDomElements(document, shadowHosts);
398
- for (let i = 0; i < shadowHosts.length; i++) {
399
- let shadowElement = shadowHosts[i].shadowRoot;
400
- if (!shadowElement) {
401
- console.log("shadowElement is null, for host " + shadowHosts[i]);
402
- continue;
403
- }
404
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
405
- elements = elements.concat(shadowElements);
406
- }
407
- let randomToken = null;
408
- const foundElements = [];
336
+ const options = {
337
+ innerText: true,
338
+ };
409
339
  if (regex) {
410
- if (!regexpSearch) {
411
- regexpSearch = new RegExp(text, "im");
412
- }
413
- for (let i = 0; i < elements.length; i++) {
414
- const element = elements[i];
415
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
416
- (element.value && regexpSearch.test(element.value))) {
417
- foundElements.push(element);
418
- }
419
- }
340
+ options.singleRegex = true;
420
341
  }
421
- else {
422
- text = text.trim();
423
- for (let i = 0; i < elements.length; i++) {
424
- const element = elements[i];
425
- if (partial) {
426
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
427
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
428
- foundElements.push(element);
429
- }
430
- }
431
- else {
432
- if ((element.innerText && element.innerText.trim() === text) ||
433
- (element.value && element.value === text)) {
434
- foundElements.push(element);
435
- }
436
- }
437
- }
342
+ if (tag) {
343
+ options.tag = tag;
438
344
  }
439
- let noChildElements = [];
440
- for (let i = 0; i < foundElements.length; i++) {
441
- let element = foundElements[i];
442
- let hasChild = false;
443
- for (let j = 0; j < foundElements.length; j++) {
444
- if (i === j) {
445
- continue;
446
- }
447
- if (isParent(element, foundElements[j])) {
448
- hasChild = true;
449
- break;
450
- }
451
- }
452
- if (!hasChild) {
453
- noChildElements.push(element);
454
- }
345
+ if (!(partial === true)) {
346
+ options.exactMatch = true;
455
347
  }
348
+ const elements = window.findMatchingElements(text, options);
349
+ let randomToken = null;
350
+ const foundElements = [];
456
351
  let elementCount = 0;
457
- if (noChildElements.length > 0) {
458
- for (let i = 0; i < noChildElements.length; i++) {
352
+ if (elements.length > 0) {
353
+ for (let i = 0; i < elements.length; i++) {
459
354
  if (randomToken === null) {
460
355
  randomToken = Math.random().toString(36).substring(7);
461
356
  }
462
- let element = noChildElements[i];
357
+ let element = elements[i];
463
358
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
464
359
  elementCount++;
465
360
  }
@@ -468,9 +363,21 @@ class StableBrowser {
468
363
  }, [text1, tag1, regex1, partial1]);
469
364
  }
470
365
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
366
+ if (!info) {
367
+ info = {};
368
+ }
369
+ if (!info.failCause) {
370
+ info.failCause = {};
371
+ }
372
+ if (!info.log) {
373
+ info.log = "";
374
+ info.locatorLog = new LocatorLog(selectorHierarchy);
375
+ }
471
376
  let locatorSearch = selectorHierarchy[index];
377
+ let originalLocatorSearch = "";
472
378
  try {
473
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
379
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
380
+ locatorSearch = JSON.parse(originalLocatorSearch);
474
381
  }
475
382
  catch (e) {
476
383
  console.error(e);
@@ -480,13 +387,18 @@ class StableBrowser {
480
387
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
481
388
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
482
389
  if (!locatorString) {
390
+ info.failCause.textNotFound = true;
391
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
483
392
  return;
484
393
  }
485
394
  locator = this._getLocator({ css: locatorString }, scope, _params);
486
395
  }
487
396
  else if (locatorSearch.text) {
488
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
397
+ let text = _fixUsingParams(locatorSearch.text, _params);
398
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
489
399
  if (result.elementCount === 0) {
400
+ info.failCause.textNotFound = true;
401
+ info.failCause.lastError = "failed to locate element by text: " + text;
490
402
  return;
491
403
  }
492
404
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -503,13 +415,22 @@ class StableBrowser {
503
415
  // cssHref = true;
504
416
  // }
505
417
  let count = await locator.count();
418
+ if (count > 0 && !info.failCause.count) {
419
+ info.failCause.count = count;
420
+ }
506
421
  //info.log += "total elements found " + count + "\n";
507
422
  //let visibleCount = 0;
508
423
  let visibleLocator = null;
509
- if (locatorSearch.index && locatorSearch.index < count) {
424
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
510
425
  foundLocators.push(locator.nth(locatorSearch.index));
426
+ if (info.locatorLog) {
427
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
428
+ }
511
429
  return;
512
430
  }
431
+ if (info.locatorLog && count === 0) {
432
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
433
+ }
513
434
  for (let j = 0; j < count; j++) {
514
435
  let visible = await locator.nth(j).isVisible();
515
436
  const enabled = await locator.nth(j).isEnabled();
@@ -518,24 +439,40 @@ class StableBrowser {
518
439
  }
519
440
  if (visible && enabled) {
520
441
  foundLocators.push(locator.nth(j));
442
+ if (info.locatorLog) {
443
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
444
+ }
521
445
  }
522
446
  else {
447
+ info.failCause.visible = visible;
448
+ info.failCause.enabled = enabled;
523
449
  if (!info.printMessages) {
524
450
  info.printMessages = {};
525
451
  }
452
+ if (info.locatorLog && !visible) {
453
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
454
+ }
455
+ if (info.locatorLog && !enabled) {
456
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
457
+ }
526
458
  if (!info.printMessages[j.toString()]) {
527
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
459
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
528
460
  info.printMessages[j.toString()] = true;
529
461
  }
530
462
  }
531
463
  }
532
464
  }
533
465
  async closeUnexpectedPopups(info, _params) {
466
+ if (!info) {
467
+ info = {};
468
+ info.failCause = {};
469
+ info.log = "";
470
+ }
534
471
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
535
472
  if (!info) {
536
473
  info = {};
537
474
  }
538
- info.log += "scan for popup handlers" + "\n";
475
+ //info.log += "scan for popup handlers" + "\n";
539
476
  const handlerGroup = [];
540
477
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
541
478
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -562,9 +499,21 @@ class StableBrowser {
562
499
  }
563
500
  if (result.foundElements.length > 0) {
564
501
  let dialogCloseLocator = result.foundElements[0].locator;
565
- await dialogCloseLocator.click();
566
- // wait for the dialog to close
567
- await dialogCloseLocator.waitFor({ state: "hidden" });
502
+ try {
503
+ await scope?.evaluate(() => {
504
+ window.__isClosingPopups = true;
505
+ });
506
+ await dialogCloseLocator.click();
507
+ // wait for the dialog to close
508
+ await dialogCloseLocator.waitFor({ state: "hidden" });
509
+ }
510
+ catch (e) {
511
+ }
512
+ finally {
513
+ await scope?.evaluate(() => {
514
+ window.__isClosingPopups = false;
515
+ });
516
+ }
568
517
  return { rerun: true };
569
518
  }
570
519
  }
@@ -588,7 +537,13 @@ class StableBrowser {
588
537
  }
589
538
  throw new Error("unable to locate element " + JSON.stringify(selectors));
590
539
  }
591
- async _findFrameScope(selectors, timeout = 30000) {
540
+ async _findFrameScope(selectors, timeout = 30000, info) {
541
+ if (!info) {
542
+ info = {};
543
+ info.failCause = {};
544
+ info.log = "";
545
+ }
546
+ let startTime = Date.now();
592
547
  let scope = this.page;
593
548
  if (selectors.frame) {
594
549
  return selectors.frame;
@@ -619,9 +574,11 @@ class StableBrowser {
619
574
  }
620
575
  return framescope;
621
576
  };
577
+ let fLocator = null;
622
578
  while (true) {
623
579
  let frameFound = false;
624
580
  if (selectors.nestFrmLoc) {
581
+ fLocator = selectors.nestFrmLoc;
625
582
  scope = await findFrame(selectors.nestFrmLoc, scope);
626
583
  frameFound = true;
627
584
  break;
@@ -630,6 +587,7 @@ class StableBrowser {
630
587
  for (let i = 0; i < selectors.frameLocators.length; i++) {
631
588
  let frameLocator = selectors.frameLocators[i];
632
589
  if (frameLocator.css) {
590
+ fLocator = frameLocator.css;
633
591
  scope = scope.frameLocator(frameLocator.css);
634
592
  frameFound = true;
635
593
  break;
@@ -637,16 +595,25 @@ class StableBrowser {
637
595
  }
638
596
  }
639
597
  if (!frameFound && selectors.iframe_src) {
598
+ fLocator = selectors.iframe_src;
640
599
  scope = this.page.frame({ url: selectors.iframe_src });
641
600
  }
642
601
  if (!scope) {
643
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
644
- if (performance.now() - startTime > timeout) {
602
+ if (info && info.locatorLog) {
603
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
604
+ }
605
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
606
+ if (Date.now() - startTime > timeout) {
607
+ info.failCause.iframeNotFound = true;
608
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
645
609
  throw new Error("unable to locate iframe " + selectors.iframe_src);
646
610
  }
647
611
  await new Promise((resolve) => setTimeout(resolve, 1000));
648
612
  }
649
613
  else {
614
+ if (info && info.locatorLog) {
615
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
616
+ }
650
617
  break;
651
618
  }
652
619
  }
@@ -656,20 +623,27 @@ class StableBrowser {
656
623
  }
657
624
  return scope;
658
625
  }
659
- async _getDocumentBody(selectors, timeout = 30000) {
660
- let scope = await this._findFrameScope(selectors, timeout);
626
+ async _getDocumentBody(selectors, timeout = 30000, info) {
627
+ let scope = await this._findFrameScope(selectors, timeout, info);
661
628
  return scope.evaluate(() => {
662
629
  var bodyContent = document.body.innerHTML;
663
630
  return bodyContent;
664
631
  });
665
632
  }
666
633
  async _locate_internal(selectors, info, _params, timeout = 30000) {
634
+ if (!info) {
635
+ info = {};
636
+ info.failCause = {};
637
+ info.log = "";
638
+ info.locatorLog = new LocatorLog(selectors);
639
+ }
667
640
  let highPriorityTimeout = 5000;
668
641
  let visibleOnlyTimeout = 6000;
669
- let startTime = performance.now();
642
+ let startTime = Date.now();
670
643
  let locatorsCount = 0;
644
+ let lazy_scroll = false;
671
645
  //let arrayMode = Array.isArray(selectors);
672
- let scope = await this._findFrameScope(selectors, timeout);
646
+ let scope = await this._findFrameScope(selectors, timeout, info);
673
647
  let selectorsLocators = null;
674
648
  selectorsLocators = selectors.locators;
675
649
  // group selectors by priority
@@ -756,21 +730,33 @@ class StableBrowser {
756
730
  return maxCountElement.locator;
757
731
  }
758
732
  }
759
- if (performance.now() - startTime > timeout) {
733
+ if (Date.now() - startTime > timeout) {
760
734
  break;
761
735
  }
762
- if (performance.now() - startTime > highPriorityTimeout) {
736
+ if (Date.now() - startTime > highPriorityTimeout) {
763
737
  info.log += "high priority timeout, will try all elements" + "\n";
764
738
  highPriorityOnly = false;
739
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
740
+ lazy_scroll = true;
741
+ await scrollPageToLoadLazyElements(this.page);
742
+ }
765
743
  }
766
- if (performance.now() - startTime > visibleOnlyTimeout) {
744
+ if (Date.now() - startTime > visibleOnlyTimeout) {
767
745
  info.log += "visible only timeout, will try all elements" + "\n";
768
746
  visibleOnly = false;
769
747
  }
770
748
  await new Promise((resolve) => setTimeout(resolve, 1000));
771
749
  }
772
750
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
773
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
751
+ // if (info.locatorLog) {
752
+ // const lines = info.locatorLog.toString().split("\n");
753
+ // for (let line of lines) {
754
+ // this.logger.debug(line);
755
+ // }
756
+ // }
757
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
758
+ info.failCause.locatorNotFound = true;
759
+ info.failCause.lastError = "failed to locate unique element";
774
760
  throw new Error("failed to locate first element no elements found, " + info.log);
775
761
  }
776
762
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -784,8 +770,9 @@ class StableBrowser {
784
770
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
785
771
  }
786
772
  catch (e) {
787
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
788
- this.logger.debug(e);
773
+ // this call can fail it the browser is navigating
774
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
775
+ // this.logger.debug(e);
789
776
  foundLocators = [];
790
777
  try {
791
778
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -802,10 +789,29 @@ class StableBrowser {
802
789
  });
803
790
  result.locatorIndex = i;
804
791
  }
792
+ if (foundLocators.length > 1) {
793
+ info.failCause.foundMultiple = true;
794
+ if (info.locatorLog) {
795
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
796
+ }
797
+ }
805
798
  }
806
799
  return result;
807
800
  }
808
801
  async simpleClick(elementDescription, _params, options = {}, world = null) {
802
+ const state = {
803
+ locate: false,
804
+ scroll: false,
805
+ highlight: false,
806
+ _params,
807
+ options,
808
+ world,
809
+ type: Types.CLICK,
810
+ text: "Click element",
811
+ operation: "simpleClick",
812
+ log: "***** click on " + elementDescription + " *****\n",
813
+ };
814
+ _preCommand(state, this);
809
815
  const startTime = Date.now();
810
816
  let timeout = 30000;
811
817
  if (options && options.timeout) {
@@ -829,13 +835,32 @@ class StableBrowser {
829
835
  }
830
836
  catch (e) {
831
837
  if (performance.now() - startTime > timeout) {
832
- throw e;
838
+ // throw e;
839
+ try {
840
+ await _commandError(state, "timeout looking for " + elementDescription, this);
841
+ }
842
+ finally {
843
+ _commandFinally(state, this);
844
+ }
833
845
  }
834
846
  }
835
847
  await new Promise((resolve) => setTimeout(resolve, 3000));
836
848
  }
837
849
  }
838
850
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
851
+ const state = {
852
+ locate: false,
853
+ scroll: false,
854
+ highlight: false,
855
+ _params,
856
+ options,
857
+ world,
858
+ type: Types.FILL,
859
+ text: "Fill element",
860
+ operation: "simpleClickType",
861
+ log: "***** click type on " + elementDescription + " *****\n",
862
+ };
863
+ _preCommand(state, this);
839
864
  const startTime = Date.now();
840
865
  let timeout = 30000;
841
866
  if (options && options.timeout) {
@@ -859,7 +884,13 @@ class StableBrowser {
859
884
  }
860
885
  catch (e) {
861
886
  if (performance.now() - startTime > timeout) {
862
- throw e;
887
+ // throw e;
888
+ try {
889
+ await _commandError(state, "timeout looking for " + elementDescription, this);
890
+ }
891
+ finally {
892
+ _commandFinally(state, this);
893
+ }
863
894
  }
864
895
  }
865
896
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -883,13 +914,13 @@ class StableBrowser {
883
914
  }
884
915
  try {
885
916
  await state.element.click();
886
- await new Promise((resolve) => setTimeout(resolve, 1000));
917
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
887
918
  }
888
919
  catch (e) {
889
920
  // await this.closeUnexpectedPopups();
890
921
  state.element = await this._locate(selectors, state.info, _params);
891
922
  await state.element.dispatchEvent("click");
892
- await new Promise((resolve) => setTimeout(resolve, 1000));
923
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
893
924
  }
894
925
  await this.waitForPageLoad();
895
926
  return state.info;
@@ -968,7 +999,6 @@ class StableBrowser {
968
999
  await state.element.hover({ timeout: 10000 });
969
1000
  await new Promise((resolve) => setTimeout(resolve, 1000));
970
1001
  }
971
- await _screenshot(state, this);
972
1002
  await this.waitForPageLoad();
973
1003
  return state.info;
974
1004
  }
@@ -1096,33 +1126,30 @@ class StableBrowser {
1096
1126
  }
1097
1127
  }
1098
1128
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1099
- _validateSelectors(selectors);
1100
- const startTime = Date.now();
1101
- let error = null;
1102
- let screenshotId = null;
1103
- let screenshotPath = null;
1104
- const info = {};
1105
- info.log = "";
1106
- info.operation = Types.SET_DATE_TIME;
1107
- info.selectors = selectors;
1108
- info.value = value;
1129
+ const state = {
1130
+ selectors,
1131
+ _params,
1132
+ value: await this._replaceWithLocalData(value, this),
1133
+ options,
1134
+ world,
1135
+ type: Types.SET_DATE_TIME,
1136
+ text: `Set date time value: ${value}`,
1137
+ operation: "setDateTime",
1138
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1139
+ throwError: false,
1140
+ };
1109
1141
  try {
1110
- value = await this._replaceWithLocalData(value, this);
1111
- let element = await this._locate(selectors, info, _params);
1112
- //insert red border around the element
1113
- await this.scrollIfNeeded(element, info);
1114
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1115
- await this._highlightElements(element);
1142
+ await _preCommand(state, this);
1116
1143
  try {
1117
- await element.click();
1144
+ await state.element.click();
1118
1145
  await new Promise((resolve) => setTimeout(resolve, 500));
1119
1146
  if (format) {
1120
- value = dayjs(value).format(format);
1121
- await element.fill(value);
1147
+ state.value = dayjs(state.value).format(format);
1148
+ await state.element.fill(state.value);
1122
1149
  }
1123
1150
  else {
1124
- const dateTimeValue = await getDateTimeValue({ value, element });
1125
- await element.evaluateHandle((el, dateTimeValue) => {
1151
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1152
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1126
1153
  el.value = ""; // clear input
1127
1154
  el.value = dateTimeValue;
1128
1155
  }, dateTimeValue);
@@ -1135,20 +1162,19 @@ class StableBrowser {
1135
1162
  }
1136
1163
  catch (err) {
1137
1164
  //await this.closeUnexpectedPopups();
1138
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1165
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1139
1166
  this.logger.info("Trying again");
1140
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1141
- info.screenshotPath = screenshotPath;
1142
- Object.assign(err, { info: info });
1167
+ await _screenshot(state, this);
1168
+ Object.assign(err, { info: state.info });
1143
1169
  await element.click();
1144
1170
  await new Promise((resolve) => setTimeout(resolve, 500));
1145
1171
  if (format) {
1146
- value = dayjs(value).format(format);
1147
- await element.fill(value);
1172
+ state.value = dayjs(state.value).format(format);
1173
+ await state.element.fill(state.value);
1148
1174
  }
1149
1175
  else {
1150
- const dateTimeValue = await getDateTimeValue({ value, element });
1151
- await element.evaluateHandle((el, dateTimeValue) => {
1176
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1177
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1152
1178
  el.value = ""; // clear input
1153
1179
  el.value = dateTimeValue;
1154
1180
  }, dateTimeValue);
@@ -1161,31 +1187,10 @@ class StableBrowser {
1161
1187
  }
1162
1188
  }
1163
1189
  catch (e) {
1164
- error = e;
1165
- throw e;
1190
+ await _commandError(state, e, this);
1166
1191
  }
1167
1192
  finally {
1168
- const endTime = Date.now();
1169
- this._reportToWorld(world, {
1170
- element_name: selectors.element_name,
1171
- type: Types.SET_DATE_TIME,
1172
- screenshotId,
1173
- value: value,
1174
- text: `setDateTime input with value: ${value}`,
1175
- result: error
1176
- ? {
1177
- status: "FAILED",
1178
- startTime,
1179
- endTime,
1180
- message: error === null || error === void 0 ? void 0 : error.message,
1181
- }
1182
- : {
1183
- status: "PASSED",
1184
- startTime,
1185
- endTime,
1186
- },
1187
- info: info,
1188
- });
1193
+ _commandFinally(state, this);
1189
1194
  }
1190
1195
  }
1191
1196
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1322,6 +1327,7 @@ class StableBrowser {
1322
1327
  let screenshotPath = null;
1323
1328
  if (!info.log) {
1324
1329
  info.log = "";
1330
+ info.locatorLog = new LocatorLog(selectors);
1325
1331
  }
1326
1332
  info.operation = "getText";
1327
1333
  info.selectors = selectors;
@@ -1660,11 +1666,9 @@ class StableBrowser {
1660
1666
  if (!fs.existsSync(world.screenshotPath)) {
1661
1667
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1662
1668
  }
1663
- let nextIndex = 1;
1664
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1665
- nextIndex++;
1666
- }
1667
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1669
+ // to make sure the path doesn't start with -
1670
+ const uuidStr = "id_" + randomUUID();
1671
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1668
1672
  try {
1669
1673
  await this.takeScreenshot(screenshotPath);
1670
1674
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1678,7 +1682,7 @@ class StableBrowser {
1678
1682
  catch (e) {
1679
1683
  this.logger.info("unable to take screenshot, ignored");
1680
1684
  }
1681
- result.screenshotId = nextIndex;
1685
+ result.screenshotId = uuidStr;
1682
1686
  result.screenshotPath = screenshotPath;
1683
1687
  if (info && info.box) {
1684
1688
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1776,74 +1780,101 @@ class StableBrowser {
1776
1780
  }
1777
1781
  }
1778
1782
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1779
- _validateSelectors(selectors);
1780
- const startTime = Date.now();
1781
- let error = null;
1782
- let screenshotId = null;
1783
- let screenshotPath = null;
1783
+ const state = {
1784
+ selectors,
1785
+ _params,
1786
+ attribute,
1787
+ variable,
1788
+ options,
1789
+ world,
1790
+ type: Types.EXTRACT,
1791
+ text: `Extract attribute from element`,
1792
+ operation: "extractAttribute",
1793
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1794
+ };
1784
1795
  await new Promise((resolve) => setTimeout(resolve, 2000));
1785
- const info = {};
1786
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1787
- info.operation = "extract";
1788
- info.selectors = selectors;
1789
1796
  try {
1790
- const element = await this._locate(selectors, info, _params);
1791
- await this._highlightElements(element);
1792
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1797
+ await _preCommand(state, this);
1793
1798
  switch (attribute) {
1794
1799
  case "inner_text":
1795
- info.value = await element.innerText();
1800
+ state.value = await state.element.innerText();
1796
1801
  break;
1797
1802
  case "href":
1798
- info.value = await element.getAttribute("href");
1803
+ state.value = await state.element.getAttribute("href");
1804
+ break;
1805
+ case "value":
1806
+ state.value = await state.element.inputValue();
1807
+ break;
1808
+ default:
1809
+ state.value = await state.element.getAttribute(attribute);
1810
+ break;
1811
+ }
1812
+ state.info.value = state.value;
1813
+ this.setTestData({ [variable]: state.value }, world);
1814
+ this.logger.info("set test data: " + variable + "=" + state.value);
1815
+ return state.info;
1816
+ }
1817
+ catch (e) {
1818
+ await _commandError(state, e, this);
1819
+ }
1820
+ finally {
1821
+ _commandFinally(state, this);
1822
+ }
1823
+ }
1824
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1825
+ const state = {
1826
+ selectors,
1827
+ _params,
1828
+ attribute,
1829
+ value,
1830
+ options,
1831
+ world,
1832
+ type: Types.VERIFY_ATTRIBUTE,
1833
+ text: `Verify element attribute`,
1834
+ operation: "verifyAttribute",
1835
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1836
+ };
1837
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1838
+ let val;
1839
+ try {
1840
+ await _preCommand(state, this);
1841
+ switch (attribute) {
1842
+ case "innerText":
1843
+ val = String(await state.element.innerText());
1799
1844
  break;
1800
1845
  case "value":
1801
- info.value = await element.inputValue();
1846
+ val = String(await state.element.inputValue());
1847
+ break;
1848
+ case "checked":
1849
+ val = String(await state.element.isChecked());
1850
+ break;
1851
+ case "disabled":
1852
+ val = String(await state.element.isDisabled());
1802
1853
  break;
1803
1854
  default:
1804
- info.value = await element.getAttribute(attribute);
1855
+ val = String(await state.element.getAttribute(attribute));
1805
1856
  break;
1806
1857
  }
1807
- this[variable] = info.value;
1808
- if (world) {
1809
- world[variable] = info.value;
1858
+ let regex;
1859
+ if (value.startsWith("/") && value.endsWith("/")) {
1860
+ const patternBody = value.slice(1, -1);
1861
+ regex = new RegExp(patternBody, "g");
1810
1862
  }
1811
- this.setTestData({ [variable]: info.value }, world);
1812
- this.logger.info("set test data: " + variable + "=" + info.value);
1813
- return info;
1863
+ else {
1864
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1865
+ regex = new RegExp(escapedPattern, "g");
1866
+ }
1867
+ if (!val.match(regex)) {
1868
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1869
+ }
1870
+ state.info.value = val;
1871
+ return state.info;
1814
1872
  }
1815
1873
  catch (e) {
1816
- //await this.closeUnexpectedPopups();
1817
- this.logger.error("extract failed " + info.log);
1818
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1819
- info.screenshotPath = screenshotPath;
1820
- Object.assign(e, { info: info });
1821
- error = e;
1822
- throw e;
1874
+ await _commandError(state, e, this);
1823
1875
  }
1824
1876
  finally {
1825
- const endTime = Date.now();
1826
- this._reportToWorld(world, {
1827
- element_name: selectors.element_name,
1828
- type: Types.EXTRACT_ATTRIBUTE,
1829
- variable: variable,
1830
- value: info.value,
1831
- text: "Extract attribute from element",
1832
- screenshotId,
1833
- result: error
1834
- ? {
1835
- status: "FAILED",
1836
- startTime,
1837
- endTime,
1838
- message: error?.message,
1839
- }
1840
- : {
1841
- status: "PASSED",
1842
- startTime,
1843
- endTime,
1844
- },
1845
- info: info,
1846
- });
1877
+ _commandFinally(state, this);
1847
1878
  }
1848
1879
  }
1849
1880
  async extractEmailData(emailAddress, options, world) {
@@ -1864,7 +1895,7 @@ class StableBrowser {
1864
1895
  if (options && options.timeout) {
1865
1896
  timeout = options.timeout;
1866
1897
  }
1867
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1898
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1868
1899
  const request = {
1869
1900
  method: "POST",
1870
1901
  url: serviceUrl,
@@ -1920,7 +1951,8 @@ class StableBrowser {
1920
1951
  catch (e) {
1921
1952
  errorCount++;
1922
1953
  if (errorCount > 3) {
1923
- throw e;
1954
+ // throw e;
1955
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1924
1956
  }
1925
1957
  // ignore
1926
1958
  }
@@ -2031,11 +2063,12 @@ class StableBrowser {
2031
2063
  info.screenshotPath = screenshotPath;
2032
2064
  Object.assign(e, { info: info });
2033
2065
  error = e;
2034
- throw e;
2066
+ // throw e;
2067
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2035
2068
  }
2036
2069
  finally {
2037
2070
  const endTime = Date.now();
2038
- this._reportToWorld(world, {
2071
+ _reportToWorld(world, {
2039
2072
  type: Types.VERIFY_PAGE_PATH,
2040
2073
  text: "Verify page path",
2041
2074
  screenshotId,
@@ -2055,54 +2088,64 @@ class StableBrowser {
2055
2088
  });
2056
2089
  }
2057
2090
  }
2091
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2092
+ const frames = this.page.frames();
2093
+ let results = [];
2094
+ for (let i = 0; i < frames.length; i++) {
2095
+ if (dateAlternatives.date) {
2096
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2097
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2098
+ result.frame = frames[i];
2099
+ results.push(result);
2100
+ }
2101
+ }
2102
+ else if (numberAlternatives.number) {
2103
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2104
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2105
+ result.frame = frames[i];
2106
+ results.push(result);
2107
+ }
2108
+ }
2109
+ else {
2110
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2111
+ result.frame = frames[i];
2112
+ results.push(result);
2113
+ }
2114
+ }
2115
+ state.info.results = results;
2116
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2117
+ return resultWithElementsFound;
2118
+ }
2058
2119
  async verifyTextExistInPage(text, options = {}, world = null) {
2059
2120
  text = unEscapeString(text);
2060
- const startTime = Date.now();
2121
+ const state = {
2122
+ text_search: text,
2123
+ options,
2124
+ world,
2125
+ locate: false,
2126
+ scroll: false,
2127
+ highlight: false,
2128
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2129
+ text: `Verify text exists in page`,
2130
+ operation: "verifyTextExistInPage",
2131
+ log: "***** verify text " + text + " exists in page *****\n",
2132
+ };
2061
2133
  const timeout = this._getLoadTimeout(options);
2062
- let error = null;
2063
- let screenshotId = null;
2064
- let screenshotPath = null;
2065
2134
  await new Promise((resolve) => setTimeout(resolve, 2000));
2066
- const info = {};
2067
- info.log = "***** verify text " + text + " exists in page *****\n";
2068
- info.operation = "verifyTextExistInPage";
2069
2135
  const newValue = await this._replaceWithLocalData(text, world);
2070
2136
  if (newValue !== text) {
2071
2137
  this.logger.info(text + "=" + newValue);
2072
2138
  text = newValue;
2073
2139
  }
2074
- info.text = text;
2075
2140
  let dateAlternatives = findDateAlternatives(text);
2076
2141
  let numberAlternatives = findNumberAlternatives(text);
2077
2142
  try {
2143
+ await _preCommand(state, this);
2144
+ state.info.text = text;
2078
2145
  while (true) {
2079
- const frames = this.page.frames();
2080
- let results = [];
2081
- for (let i = 0; i < frames.length; i++) {
2082
- if (dateAlternatives.date) {
2083
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2084
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2085
- result.frame = frames[i];
2086
- results.push(result);
2087
- }
2088
- }
2089
- else if (numberAlternatives.number) {
2090
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2091
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2092
- result.frame = frames[i];
2093
- results.push(result);
2094
- }
2095
- }
2096
- else {
2097
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2098
- result.frame = frames[i];
2099
- results.push(result);
2100
- }
2101
- }
2102
- info.results = results;
2103
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2146
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2104
2147
  if (resultWithElementsFound.length === 0) {
2105
- if (Date.now() - startTime > timeout) {
2148
+ if (Date.now() - state.startTime > timeout) {
2106
2149
  throw new Error(`Text ${text} not found in page`);
2107
2150
  }
2108
2151
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2114,55 +2157,163 @@ class StableBrowser {
2114
2157
  await this._highlightElements(frame, dataAttribute);
2115
2158
  const element = await frame.$(dataAttribute);
2116
2159
  if (element) {
2117
- await this.scrollIfNeeded(element, info);
2160
+ await this.scrollIfNeeded(element, state.info);
2118
2161
  await element.dispatchEvent("bvt_verify_page_contains_text");
2119
2162
  }
2120
2163
  }
2121
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2122
- return info;
2164
+ await _screenshot(state, this);
2165
+ return state.info;
2123
2166
  }
2124
2167
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2125
2168
  }
2126
2169
  catch (e) {
2127
- //await this.closeUnexpectedPopups();
2128
- this.logger.error("verify text exist in page failed " + info.log);
2129
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2130
- info.screenshotPath = screenshotPath;
2131
- Object.assign(e, { info: info });
2132
- error = e;
2133
- throw e;
2170
+ await _commandError(state, e, this);
2134
2171
  }
2135
2172
  finally {
2136
- const endTime = Date.now();
2137
- this._reportToWorld(world, {
2138
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2139
- text: "Verify text exists in page",
2140
- screenshotId,
2141
- result: error
2142
- ? {
2143
- status: "FAILED",
2144
- startTime,
2145
- endTime,
2146
- message: error?.message,
2147
- }
2148
- : {
2149
- status: "PASSED",
2150
- startTime,
2151
- endTime,
2152
- },
2153
- info: info,
2154
- });
2173
+ _commandFinally(state, this);
2174
+ }
2175
+ }
2176
+ async waitForTextToDisappear(text, options = {}, world = null) {
2177
+ text = unEscapeString(text);
2178
+ const state = {
2179
+ text_search: text,
2180
+ options,
2181
+ world,
2182
+ locate: false,
2183
+ scroll: false,
2184
+ highlight: false,
2185
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2186
+ text: `Verify text does not exist in page`,
2187
+ operation: "verifyTextNotExistInPage",
2188
+ log: "***** verify text " + text + " does not exist in page *****\n",
2189
+ };
2190
+ const timeout = this._getLoadTimeout(options);
2191
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2192
+ const newValue = await this._replaceWithLocalData(text, world);
2193
+ if (newValue !== text) {
2194
+ this.logger.info(text + "=" + newValue);
2195
+ text = newValue;
2196
+ }
2197
+ let dateAlternatives = findDateAlternatives(text);
2198
+ let numberAlternatives = findNumberAlternatives(text);
2199
+ try {
2200
+ await _preCommand(state, this);
2201
+ state.info.text = text;
2202
+ while (true) {
2203
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2204
+ if (resultWithElementsFound.length === 0) {
2205
+ await _screenshot(state, this);
2206
+ return state.info;
2207
+ }
2208
+ if (Date.now() - state.startTime > timeout) {
2209
+ throw new Error(`Text ${text} found in page`);
2210
+ }
2211
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2212
+ }
2213
+ }
2214
+ catch (e) {
2215
+ await _commandError(state, e, this);
2216
+ }
2217
+ finally {
2218
+ _commandFinally(state, this);
2155
2219
  }
2156
2220
  }
2157
- _getServerUrl() {
2158
- let serviceUrl = "https://api.blinq.io";
2159
- if (process.env.NODE_ENV_BLINQ === "dev") {
2160
- serviceUrl = "https://dev.api.blinq.io";
2221
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2222
+ textAnchor = unEscapeString(textAnchor);
2223
+ textToVerify = unEscapeString(textToVerify);
2224
+ const state = {
2225
+ text_search: textToVerify,
2226
+ options,
2227
+ world,
2228
+ locate: false,
2229
+ scroll: false,
2230
+ highlight: false,
2231
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2232
+ text: `Verify text with relation to another text`,
2233
+ operation: "verify_text_with_relation",
2234
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2235
+ };
2236
+ const timeout = this._getLoadTimeout(options);
2237
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2238
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2239
+ if (newValue !== textAnchor) {
2240
+ this.logger.info(textAnchor + "=" + newValue);
2241
+ textAnchor = newValue;
2242
+ }
2243
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2244
+ if (newValue !== textToVerify) {
2245
+ this.logger.info(textToVerify + "=" + newValue);
2246
+ textToVerify = newValue;
2247
+ }
2248
+ let dateAlternatives = findDateAlternatives(textToVerify);
2249
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2250
+ let foundAncore = false;
2251
+ try {
2252
+ await _preCommand(state, this);
2253
+ state.info.text = textToVerify;
2254
+ while (true) {
2255
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2256
+ if (resultWithElementsFound.length === 0) {
2257
+ if (Date.now() - state.startTime > timeout) {
2258
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2259
+ }
2260
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2261
+ continue;
2262
+ }
2263
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2264
+ foundAncore = true;
2265
+ const result = resultWithElementsFound[i];
2266
+ const token = result.randomToken;
2267
+ const frame = result.frame;
2268
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2269
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2270
+ const elements = Array.from(document.querySelectorAll(css));
2271
+ for (let i = 0; i < elements.length; i++) {
2272
+ const element = elements[i];
2273
+ let climbParent = element;
2274
+ for (let j = 0; j < climb; j++) {
2275
+ climbParent = climbParent.parentElement;
2276
+ if (!climbParent) {
2277
+ break;
2278
+ }
2279
+ }
2280
+ if (!climbParent) {
2281
+ continue;
2282
+ }
2283
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2284
+ if (foundElements.length > 0) {
2285
+ // set the container element attribute
2286
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2287
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2288
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2289
+ return { found: true };
2290
+ }
2291
+ }
2292
+ return { found: false };
2293
+ }, [css, climb, textToVerify, result.randomToken]);
2294
+ if (findResult.found === true) {
2295
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2296
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2297
+ await this._highlightElements(frame, dataAttribute);
2298
+ await this._highlightElements(frame, cssAnchor);
2299
+ const element = await frame.$(dataAttribute);
2300
+ if (element) {
2301
+ await this.scrollIfNeeded(element, state.info);
2302
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2303
+ }
2304
+ await _screenshot(state, this);
2305
+ return state.info;
2306
+ }
2307
+ }
2308
+ }
2309
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2310
+ }
2311
+ catch (e) {
2312
+ await _commandError(state, e, this);
2161
2313
  }
2162
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2163
- serviceUrl = "https://stage.api.blinq.io";
2314
+ finally {
2315
+ _commandFinally(state, this);
2164
2316
  }
2165
- return serviceUrl;
2166
2317
  }
2167
2318
  async visualVerification(text, options = {}, world = null) {
2168
2319
  const startTime = Date.now();
@@ -2178,7 +2329,7 @@ class StableBrowser {
2178
2329
  throw new Error("TOKEN is not set");
2179
2330
  }
2180
2331
  try {
2181
- let serviceUrl = this._getServerUrl();
2332
+ let serviceUrl = _getServerUrl();
2182
2333
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2183
2334
  info.screenshotPath = screenshotPath;
2184
2335
  const screenshot = await this.takeScreenshot();
@@ -2214,11 +2365,12 @@ class StableBrowser {
2214
2365
  info.screenshotPath = screenshotPath;
2215
2366
  Object.assign(e, { info: info });
2216
2367
  error = e;
2217
- throw e;
2368
+ // throw e;
2369
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2218
2370
  }
2219
2371
  finally {
2220
2372
  const endTime = Date.now();
2221
- this._reportToWorld(world, {
2373
+ _reportToWorld(world, {
2222
2374
  type: Types.VERIFY_VISUAL,
2223
2375
  text: "Visual verification",
2224
2376
  screenshotId,
@@ -2266,6 +2418,7 @@ class StableBrowser {
2266
2418
  let screenshotPath = null;
2267
2419
  const info = {};
2268
2420
  info.log = "";
2421
+ info.locatorLog = new LocatorLog(selectors);
2269
2422
  info.operation = "getTableData";
2270
2423
  info.selectors = selectors;
2271
2424
  try {
@@ -2281,11 +2434,12 @@ class StableBrowser {
2281
2434
  info.screenshotPath = screenshotPath;
2282
2435
  Object.assign(e, { info: info });
2283
2436
  error = e;
2284
- throw e;
2437
+ // throw e;
2438
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2285
2439
  }
2286
2440
  finally {
2287
2441
  const endTime = Date.now();
2288
- this._reportToWorld(world, {
2442
+ _reportToWorld(world, {
2289
2443
  element_name: selectors.element_name,
2290
2444
  type: Types.GET_TABLE_DATA,
2291
2445
  text: "Get table data",
@@ -2340,7 +2494,7 @@ class StableBrowser {
2340
2494
  info.operation = "analyzeTable";
2341
2495
  info.selectors = selectors;
2342
2496
  info.query = query;
2343
- query = this._fixUsingParams(query, _params);
2497
+ query = _fixUsingParams(query, _params);
2344
2498
  info.query_fixed = query;
2345
2499
  info.operator = operator;
2346
2500
  info.value = value;
@@ -2446,11 +2600,12 @@ class StableBrowser {
2446
2600
  info.screenshotPath = screenshotPath;
2447
2601
  Object.assign(e, { info: info });
2448
2602
  error = e;
2449
- throw e;
2603
+ // throw e;
2604
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2450
2605
  }
2451
2606
  finally {
2452
2607
  const endTime = Date.now();
2453
- this._reportToWorld(world, {
2608
+ _reportToWorld(world, {
2454
2609
  element_name: selectors.element_name,
2455
2610
  type: Types.ANALYZE_TABLE,
2456
2611
  text: "Analyze table",
@@ -2523,7 +2678,7 @@ class StableBrowser {
2523
2678
  await new Promise((resolve) => setTimeout(resolve, 2000));
2524
2679
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2525
2680
  const endTime = Date.now();
2526
- this._reportToWorld(world, {
2681
+ _reportToWorld(world, {
2527
2682
  type: Types.GET_PAGE_STATUS,
2528
2683
  text: "Wait for page load",
2529
2684
  screenshotId,
@@ -2543,41 +2698,35 @@ class StableBrowser {
2543
2698
  }
2544
2699
  }
2545
2700
  async closePage(options = {}, world = null) {
2546
- const startTime = Date.now();
2547
- let error = null;
2548
- let screenshotId = null;
2549
- let screenshotPath = null;
2550
- const info = {};
2701
+ const state = {
2702
+ options,
2703
+ world,
2704
+ locate: false,
2705
+ scroll: false,
2706
+ highlight: false,
2707
+ type: Types.CLOSE_PAGE,
2708
+ text: `Close page`,
2709
+ operation: "closePage",
2710
+ log: "***** close page *****\n",
2711
+ throwError: false,
2712
+ };
2551
2713
  try {
2714
+ await _preCommand(state, this);
2552
2715
  await this.page.close();
2553
2716
  }
2554
2717
  catch (e) {
2555
2718
  console.log(".");
2719
+ await _commandError(state, e, this);
2556
2720
  }
2557
2721
  finally {
2558
- await new Promise((resolve) => setTimeout(resolve, 2000));
2559
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2560
- const endTime = Date.now();
2561
- this._reportToWorld(world, {
2562
- type: Types.CLOSE_PAGE,
2563
- text: "close page",
2564
- screenshotId,
2565
- result: error
2566
- ? {
2567
- status: "FAILED",
2568
- startTime,
2569
- endTime,
2570
- message: error?.message,
2571
- }
2572
- : {
2573
- status: "PASSED",
2574
- startTime,
2575
- endTime,
2576
- },
2577
- info: info,
2578
- });
2722
+ _commandFinally(state, this);
2579
2723
  }
2580
2724
  }
2725
+ saveTestDataAsGlobal(options, world) {
2726
+ const dataFile = this._getDataFile(world);
2727
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2728
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2729
+ }
2581
2730
  async setViewportSize(width, hight, options = {}, world = null) {
2582
2731
  const startTime = Date.now();
2583
2732
  let error = null;
@@ -2595,12 +2744,13 @@ class StableBrowser {
2595
2744
  }
2596
2745
  catch (e) {
2597
2746
  console.log(".");
2747
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2598
2748
  }
2599
2749
  finally {
2600
2750
  await new Promise((resolve) => setTimeout(resolve, 2000));
2601
2751
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2602
2752
  const endTime = Date.now();
2603
- this._reportToWorld(world, {
2753
+ _reportToWorld(world, {
2604
2754
  type: Types.SET_VIEWPORT,
2605
2755
  text: "set viewport size to " + width + "x" + hight,
2606
2756
  screenshotId,
@@ -2631,12 +2781,13 @@ class StableBrowser {
2631
2781
  }
2632
2782
  catch (e) {
2633
2783
  console.log(".");
2784
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2634
2785
  }
2635
2786
  finally {
2636
2787
  await new Promise((resolve) => setTimeout(resolve, 2000));
2637
2788
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2638
2789
  const endTime = Date.now();
2639
- this._reportToWorld(world, {
2790
+ _reportToWorld(world, {
2640
2791
  type: Types.GET_PAGE_STATUS,
2641
2792
  text: "page relaod",
2642
2793
  screenshotId,
@@ -2672,11 +2823,37 @@ class StableBrowser {
2672
2823
  console.log("#-#");
2673
2824
  }
2674
2825
  }
2675
- _reportToWorld(world, properties) {
2676
- if (!world || !world.attach) {
2677
- return;
2826
+ async beforeStep(world, step) {
2827
+ this.stepName = step.pickleStep.text;
2828
+ this.logger.info("step: " + this.stepName);
2829
+ if (this.stepIndex === undefined) {
2830
+ this.stepIndex = 0;
2831
+ }
2832
+ else {
2833
+ this.stepIndex++;
2834
+ }
2835
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2836
+ if (this.context.browserObject.context) {
2837
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2838
+ }
2839
+ }
2840
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2841
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2842
+ // check if @global_test_data tag is present
2843
+ if (this.tags.includes("@global_test_data")) {
2844
+ this.saveTestDataAsGlobal({}, world);
2845
+ }
2846
+ }
2847
+ }
2848
+ async afterStep(world, step) {
2849
+ this.stepName = null;
2850
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2851
+ if (this.context.browserObject.context) {
2852
+ await this.context.browserObject.context.tracing.stopChunk({
2853
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2854
+ });
2855
+ }
2678
2856
  }
2679
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2680
2857
  }
2681
2858
  }
2682
2859
  function createTimedPromise(promise, label) {
@@ -2684,156 +2861,5 @@ function createTimedPromise(promise, label) {
2684
2861
  .then((result) => ({ status: "fulfilled", label, result }))
2685
2862
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2686
2863
  }
2687
- const KEYBOARD_EVENTS = [
2688
- "ALT",
2689
- "AltGraph",
2690
- "CapsLock",
2691
- "Control",
2692
- "Fn",
2693
- "FnLock",
2694
- "Hyper",
2695
- "Meta",
2696
- "NumLock",
2697
- "ScrollLock",
2698
- "Shift",
2699
- "Super",
2700
- "Symbol",
2701
- "SymbolLock",
2702
- "Enter",
2703
- "Tab",
2704
- "ArrowDown",
2705
- "ArrowLeft",
2706
- "ArrowRight",
2707
- "ArrowUp",
2708
- "End",
2709
- "Home",
2710
- "PageDown",
2711
- "PageUp",
2712
- "Backspace",
2713
- "Clear",
2714
- "Copy",
2715
- "CrSel",
2716
- "Cut",
2717
- "Delete",
2718
- "EraseEof",
2719
- "ExSel",
2720
- "Insert",
2721
- "Paste",
2722
- "Redo",
2723
- "Undo",
2724
- "Accept",
2725
- "Again",
2726
- "Attn",
2727
- "Cancel",
2728
- "ContextMenu",
2729
- "Escape",
2730
- "Execute",
2731
- "Find",
2732
- "Finish",
2733
- "Help",
2734
- "Pause",
2735
- "Play",
2736
- "Props",
2737
- "Select",
2738
- "ZoomIn",
2739
- "ZoomOut",
2740
- "BrightnessDown",
2741
- "BrightnessUp",
2742
- "Eject",
2743
- "LogOff",
2744
- "Power",
2745
- "PowerOff",
2746
- "PrintScreen",
2747
- "Hibernate",
2748
- "Standby",
2749
- "WakeUp",
2750
- "AllCandidates",
2751
- "Alphanumeric",
2752
- "CodeInput",
2753
- "Compose",
2754
- "Convert",
2755
- "Dead",
2756
- "FinalMode",
2757
- "GroupFirst",
2758
- "GroupLast",
2759
- "GroupNext",
2760
- "GroupPrevious",
2761
- "ModeChange",
2762
- "NextCandidate",
2763
- "NonConvert",
2764
- "PreviousCandidate",
2765
- "Process",
2766
- "SingleCandidate",
2767
- "HangulMode",
2768
- "HanjaMode",
2769
- "JunjaMode",
2770
- "Eisu",
2771
- "Hankaku",
2772
- "Hiragana",
2773
- "HiraganaKatakana",
2774
- "KanaMode",
2775
- "KanjiMode",
2776
- "Katakana",
2777
- "Romaji",
2778
- "Zenkaku",
2779
- "ZenkakuHanaku",
2780
- "F1",
2781
- "F2",
2782
- "F3",
2783
- "F4",
2784
- "F5",
2785
- "F6",
2786
- "F7",
2787
- "F8",
2788
- "F9",
2789
- "F10",
2790
- "F11",
2791
- "F12",
2792
- "Soft1",
2793
- "Soft2",
2794
- "Soft3",
2795
- "Soft4",
2796
- "ChannelDown",
2797
- "ChannelUp",
2798
- "Close",
2799
- "MailForward",
2800
- "MailReply",
2801
- "MailSend",
2802
- "MediaFastForward",
2803
- "MediaPause",
2804
- "MediaPlay",
2805
- "MediaPlayPause",
2806
- "MediaRecord",
2807
- "MediaRewind",
2808
- "MediaStop",
2809
- "MediaTrackNext",
2810
- "MediaTrackPrevious",
2811
- "AudioBalanceLeft",
2812
- "AudioBalanceRight",
2813
- "AudioBassBoostDown",
2814
- "AudioBassBoostToggle",
2815
- "AudioBassBoostUp",
2816
- "AudioFaderFront",
2817
- "AudioFaderRear",
2818
- "AudioSurroundModeNext",
2819
- "AudioTrebleDown",
2820
- "AudioTrebleUp",
2821
- "AudioVolumeDown",
2822
- "AudioVolumeMute",
2823
- "AudioVolumeUp",
2824
- "MicrophoneToggle",
2825
- "MicrophoneVolumeDown",
2826
- "MicrophoneVolumeMute",
2827
- "MicrophoneVolumeUp",
2828
- "TV",
2829
- "TV3DMode",
2830
- "TVAntennaCable",
2831
- "TVAudioDescription",
2832
- ];
2833
- function unEscapeString(str) {
2834
- const placeholder = "__NEWLINE__";
2835
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2836
- return str;
2837
- }
2838
2864
  export { StableBrowser };
2839
2865
  //# sourceMappingURL=stable_browser.js.map