automation_model 1.0.498-dev → 1.0.498-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 (49) 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 +19 -2
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +6 -3
  8. package/lib/browser_manager.js +108 -41
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +54 -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 +20 -6
  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.js +16 -3
  34. package/lib/network.js.map +1 -1
  35. package/lib/scripts/find_text.js +126 -0
  36. package/lib/stable_browser.d.ts +50 -18
  37. package/lib/stable_browser.js +632 -579
  38. package/lib/stable_browser.js.map +1 -1
  39. package/lib/table.d.ts +13 -0
  40. package/lib/table.js +187 -0
  41. package/lib/table.js.map +1 -0
  42. package/lib/test_context.d.ts +1 -0
  43. package/lib/test_context.js +1 -0
  44. package/lib/test_context.js.map +1 -1
  45. package/lib/utils.d.ts +12 -1
  46. package/lib/utils.js +235 -3
  47. package/lib/utils.js.map +1 -1
  48. package/package.json +7 -5
  49. /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";
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
- const Types = {
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,17 +95,17 @@ 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);
96
104
  registerDownloadEvent(this.page, this.world, this.context);
97
105
  }
98
106
  registerEventListeners(context) {
99
107
  this.registerConsoleLogListener(this.page, context);
100
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
101
109
  if (!context.pageLoading) {
102
110
  context.pageLoading = { status: false };
103
111
  }
@@ -142,9 +150,9 @@ class StableBrowser {
142
150
  if (this.appName === appName) {
143
151
  return;
144
152
  }
145
- let newContextCreated = false;
153
+ let navigate = false;
146
154
  if (!apps[appName]) {
147
- 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);
148
156
  newContextCreated = true;
149
157
  apps[appName] = {
150
158
  context: newContext,
@@ -153,32 +161,15 @@ class StableBrowser {
153
161
  };
154
162
  }
155
163
  const tempContext = {};
156
- this._copyContext(this, tempContext);
157
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
158
166
  apps[this.appName] = tempContext;
159
167
  this.appName = appName;
160
- if (newContextCreated) {
161
- this.registerEventListeners(this.context);
168
+ if (navigate) {
162
169
  await this.goto(this.context.environment.baseUrl);
163
170
  await this.waitForPageLoad();
164
171
  }
165
172
  }
166
- _copyContext(from, to) {
167
- to.browser = from.browser;
168
- to.page = from.page;
169
- to.context = from.context;
170
- }
171
- getWebLogFile(logFolder) {
172
- if (!fs.existsSync(logFolder)) {
173
- fs.mkdirSync(logFolder, { recursive: true });
174
- }
175
- let nextIndex = 1;
176
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
177
- nextIndex++;
178
- }
179
- const fileName = nextIndex + ".json";
180
- return path.join(logFolder, fileName);
181
- }
182
173
  registerConsoleLogListener(page, context) {
183
174
  if (!this.context.webLogger) {
184
175
  this.context.webLogger = [];
@@ -228,7 +219,7 @@ class StableBrowser {
228
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
229
220
  }
230
221
  catch (error) {
231
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
232
223
  context.networkLogger.push({
233
224
  error: "not able to listen",
234
225
  message: error.message,
@@ -242,55 +233,48 @@ class StableBrowser {
242
233
  // async closeUnexpectedPopups() {
243
234
  // await closeUnexpectedPopups(this.page);
244
235
  // }
245
- async goto(url) {
236
+ async goto(url, world = null) {
246
237
  if (!url.startsWith("http")) {
247
238
  url = "https://" + url;
248
239
  }
249
- await this.page.goto(url, {
250
- timeout: 60000,
251
- });
252
- }
253
- _fixUsingParams(text, _params) {
254
- if (!_params || typeof text !== "string") {
255
- return text;
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);
256
259
  }
257
- for (let key in _params) {
258
- let regValue = key;
259
- if (key.startsWith("_")) {
260
- // remove the _ prefix
261
- regValue = key.substring(1);
262
- }
263
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
264
263
  }
265
- return text;
266
- }
267
- _fixLocatorUsingParams(locator, _params) {
268
- // check if not null
269
- if (!locator) {
270
- return locator;
264
+ finally {
265
+ _commandFinally(state, this);
271
266
  }
272
- // clone the locator
273
- locator = JSON.parse(JSON.stringify(locator));
274
- this.scanAndManipulate(locator, _params);
275
- return locator;
276
- }
277
- _isObject(value) {
278
- return value && typeof value === "object" && value.constructor === Object;
279
267
  }
280
- scanAndManipulate(currentObj, _params) {
281
- for (const key in currentObj) {
282
- if (typeof currentObj[key] === "string") {
283
- // Perform string manipulation
284
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
285
- }
286
- else if (this._isObject(currentObj[key])) {
287
- // Recursively scan nested objects
288
- this.scanAndManipulate(currentObj[key], _params);
268
+ async _getLocator(locator, scope, _params) {
269
+ locator = _fixLocatorUsingParams(locator, _params);
270
+ // locator = await this._replaceWithLocalData(locator);
271
+ for (let key in locator) {
272
+ if (typeof locator[key] !== "string")
273
+ continue;
274
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
275
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
289
276
  }
290
277
  }
291
- }
292
- _getLocator(locator, scope, _params) {
293
- locator = this._fixLocatorUsingParams(locator, _params);
294
278
  let locatorReturn;
295
279
  if (locator.role) {
296
280
  if (locator.role[1].nameReg) {
@@ -298,7 +282,7 @@ class StableBrowser {
298
282
  delete locator.role[1].nameReg;
299
283
  }
300
284
  // if (locator.role[1].name) {
301
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
285
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
302
286
  // }
303
287
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
304
288
  }
@@ -341,7 +325,7 @@ class StableBrowser {
341
325
  if (css && css.locator) {
342
326
  css = css.locator;
343
327
  }
344
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
328
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
345
329
  if (result.elementCount === 0) {
346
330
  return;
347
331
  }
@@ -357,116 +341,42 @@ class StableBrowser {
357
341
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
358
342
  //const stringifyText = JSON.stringify(text);
359
343
  return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
360
- function isParent(parent, child) {
361
- let currentNode = child.parentNode;
362
- while (currentNode !== null) {
363
- if (currentNode === parent) {
364
- return true;
365
- }
366
- currentNode = currentNode.parentNode;
367
- }
368
- return false;
369
- }
370
- document.isParent = isParent;
371
- function getRegex(str) {
372
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
373
- if (!match) {
374
- return null;
375
- }
376
- let [_, pattern, flags] = match;
377
- return new RegExp(pattern, flags);
378
- }
379
- document.getRegex = getRegex;
380
- function collectAllShadowDomElements(element, result = []) {
381
- // Check and add the element if it has a shadow root
382
- if (element.shadowRoot) {
383
- result.push(element);
384
- // Also search within the shadow root
385
- document.collectAllShadowDomElements(element.shadowRoot, result);
386
- }
387
- // Iterate over child nodes
388
- element.childNodes.forEach((child) => {
389
- // Recursively call the function for each child node
390
- document.collectAllShadowDomElements(child, result);
391
- });
392
- return result;
393
- }
394
- document.collectAllShadowDomElements = collectAllShadowDomElements;
395
- if (!tag) {
396
- tag = "*";
397
- }
398
- let regexpSearch = document.getRegex(text);
399
- if (regexpSearch) {
400
- regex = true;
401
- }
402
- let elements = Array.from(document.querySelectorAll(tag));
403
- let shadowHosts = [];
404
- document.collectAllShadowDomElements(document, shadowHosts);
405
- for (let i = 0; i < shadowHosts.length; i++) {
406
- let shadowElement = shadowHosts[i].shadowRoot;
407
- if (!shadowElement) {
408
- console.log("shadowElement is null, for host " + shadowHosts[i]);
409
- continue;
410
- }
411
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
412
- elements = elements.concat(shadowElements);
413
- }
414
- let randomToken = null;
415
- const foundElements = [];
344
+ const options = {
345
+ innerText: true,
346
+ };
416
347
  if (regex) {
417
- if (!regexpSearch) {
418
- regexpSearch = new RegExp(text, "im");
419
- }
420
- for (let i = 0; i < elements.length; i++) {
421
- const element = elements[i];
422
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
423
- (element.value && regexpSearch.test(element.value))) {
424
- foundElements.push(element);
425
- }
426
- }
348
+ options.singleRegex = true;
427
349
  }
428
- else {
429
- text = text.trim();
430
- for (let i = 0; i < elements.length; i++) {
431
- const element = elements[i];
432
- if (partial) {
433
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
434
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
435
- foundElements.push(element);
436
- }
437
- }
438
- else {
439
- if ((element.innerText && element.innerText.trim() === text) ||
440
- (element.value && element.value === text)) {
441
- foundElements.push(element);
442
- }
443
- }
444
- }
350
+ if (tag) {
351
+ options.tag = tag;
445
352
  }
446
- let noChildElements = [];
447
- for (let i = 0; i < foundElements.length; i++) {
448
- let element = foundElements[i];
449
- let hasChild = false;
450
- for (let j = 0; j < foundElements.length; j++) {
451
- if (i === j) {
452
- continue;
453
- }
454
- if (isParent(element, foundElements[j])) {
455
- hasChild = true;
456
- break;
457
- }
353
+ if (!(partial === true)) {
354
+ options.exactMatch = true;
355
+ }
356
+ if (text.startsWith("/") && text.endsWith("/")) {
357
+ if (text.length < 3) {
358
+ throw new Error("Invalid regex pattern: empty pattern");
359
+ }
360
+ try {
361
+ const pattern = text.slice(1, -1);
362
+ new RegExp(pattern); // Validate pattern
363
+ text = pattern;
364
+ options.singleRegex = true;
458
365
  }
459
- if (!hasChild) {
460
- noChildElements.push(element);
366
+ catch (e) {
367
+ throw new Error(`Invalid regex pattern: ${e.message}`);
461
368
  }
462
369
  }
370
+ const elements = window.findMatchingElements(text, options);
371
+ let randomToken = null;
372
+ const foundElements = [];
463
373
  let elementCount = 0;
464
- if (noChildElements.length > 0) {
465
- for (let i = 0; i < noChildElements.length; i++) {
374
+ if (elements.length > 0) {
375
+ for (let i = 0; i < elements.length; i++) {
466
376
  if (randomToken === null) {
467
377
  randomToken = Math.random().toString(36).substring(7);
468
378
  }
469
- let element = noChildElements[i];
379
+ let element = elements[i];
470
380
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
471
381
  elementCount++;
472
382
  }
@@ -475,9 +385,21 @@ class StableBrowser {
475
385
  }, [text1, tag1, regex1, partial1]);
476
386
  }
477
387
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
388
+ if (!info) {
389
+ info = {};
390
+ }
391
+ if (!info.failCause) {
392
+ info.failCause = {};
393
+ }
394
+ if (!info.log) {
395
+ info.log = "";
396
+ info.locatorLog = new LocatorLog(selectorHierarchy);
397
+ }
478
398
  let locatorSearch = selectorHierarchy[index];
399
+ let originalLocatorSearch = "";
479
400
  try {
480
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
401
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
402
+ locatorSearch = JSON.parse(originalLocatorSearch);
481
403
  }
482
404
  catch (e) {
483
405
  console.error(e);
@@ -487,36 +409,50 @@ class StableBrowser {
487
409
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
488
410
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
489
411
  if (!locatorString) {
412
+ info.failCause.textNotFound = true;
413
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
490
414
  return;
491
415
  }
492
- locator = this._getLocator({ css: locatorString }, scope, _params);
416
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
493
417
  }
494
418
  else if (locatorSearch.text) {
495
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
419
+ let text = _fixUsingParams(locatorSearch.text, _params);
420
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
496
421
  if (result.elementCount === 0) {
422
+ info.failCause.textNotFound = true;
423
+ info.failCause.lastError = "failed to locate element by text: " + text;
497
424
  return;
498
425
  }
499
426
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
500
427
  if (locatorSearch.childCss) {
501
428
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
502
429
  }
503
- locator = this._getLocator(locatorSearch, scope, _params);
430
+ locator = await this._getLocator(locatorSearch, scope, _params);
504
431
  }
505
432
  else {
506
- locator = this._getLocator(locatorSearch, scope, _params);
433
+ locator = await this._getLocator(locatorSearch, scope, _params);
507
434
  }
508
435
  // let cssHref = false;
509
436
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
510
437
  // cssHref = true;
511
438
  // }
512
439
  let count = await locator.count();
440
+ if (count > 0 && !info.failCause.count) {
441
+ info.failCause.count = count;
442
+ }
513
443
  //info.log += "total elements found " + count + "\n";
514
444
  //let visibleCount = 0;
515
445
  let visibleLocator = null;
516
- if (locatorSearch.index && locatorSearch.index < count) {
446
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
517
447
  foundLocators.push(locator.nth(locatorSearch.index));
448
+ if (info.locatorLog) {
449
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
450
+ }
518
451
  return;
519
452
  }
453
+ if (info.locatorLog && count === 0) {
454
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
455
+ }
520
456
  for (let j = 0; j < count; j++) {
521
457
  let visible = await locator.nth(j).isVisible();
522
458
  const enabled = await locator.nth(j).isEnabled();
@@ -525,24 +461,40 @@ class StableBrowser {
525
461
  }
526
462
  if (visible && enabled) {
527
463
  foundLocators.push(locator.nth(j));
464
+ if (info.locatorLog) {
465
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
466
+ }
528
467
  }
529
468
  else {
469
+ info.failCause.visible = visible;
470
+ info.failCause.enabled = enabled;
530
471
  if (!info.printMessages) {
531
472
  info.printMessages = {};
532
473
  }
474
+ if (info.locatorLog && !visible) {
475
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
476
+ }
477
+ if (info.locatorLog && !enabled) {
478
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
479
+ }
533
480
  if (!info.printMessages[j.toString()]) {
534
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
481
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
535
482
  info.printMessages[j.toString()] = true;
536
483
  }
537
484
  }
538
485
  }
539
486
  }
540
487
  async closeUnexpectedPopups(info, _params) {
488
+ if (!info) {
489
+ info = {};
490
+ info.failCause = {};
491
+ info.log = "";
492
+ }
541
493
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
542
494
  if (!info) {
543
495
  info = {};
544
496
  }
545
- info.log += "scan for popup handlers" + "\n";
497
+ //info.log += "scan for popup handlers" + "\n";
546
498
  const handlerGroup = [];
547
499
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
548
500
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -569,9 +521,21 @@ class StableBrowser {
569
521
  }
570
522
  if (result.foundElements.length > 0) {
571
523
  let dialogCloseLocator = result.foundElements[0].locator;
572
- await dialogCloseLocator.click();
573
- // wait for the dialog to close
574
- await dialogCloseLocator.waitFor({ state: "hidden" });
524
+ try {
525
+ await scope?.evaluate(() => {
526
+ window.__isClosingPopups = true;
527
+ });
528
+ await dialogCloseLocator.click();
529
+ // wait for the dialog to close
530
+ await dialogCloseLocator.waitFor({ state: "hidden" });
531
+ }
532
+ catch (e) {
533
+ }
534
+ finally {
535
+ await scope?.evaluate(() => {
536
+ window.__isClosingPopups = false;
537
+ });
538
+ }
575
539
  return { rerun: true };
576
540
  }
577
541
  }
@@ -595,7 +559,13 @@ class StableBrowser {
595
559
  }
596
560
  throw new Error("unable to locate element " + JSON.stringify(selectors));
597
561
  }
598
- async _findFrameScope(selectors, timeout = 30000) {
562
+ async _findFrameScope(selectors, timeout = 30000, info) {
563
+ if (!info) {
564
+ info = {};
565
+ info.failCause = {};
566
+ info.log = "";
567
+ }
568
+ let startTime = Date.now();
599
569
  let scope = this.page;
600
570
  if (selectors.frame) {
601
571
  return selectors.frame;
@@ -626,9 +596,11 @@ class StableBrowser {
626
596
  }
627
597
  return framescope;
628
598
  };
599
+ let fLocator = null;
629
600
  while (true) {
630
601
  let frameFound = false;
631
602
  if (selectors.nestFrmLoc) {
603
+ fLocator = selectors.nestFrmLoc;
632
604
  scope = await findFrame(selectors.nestFrmLoc, scope);
633
605
  frameFound = true;
634
606
  break;
@@ -637,6 +609,7 @@ class StableBrowser {
637
609
  for (let i = 0; i < selectors.frameLocators.length; i++) {
638
610
  let frameLocator = selectors.frameLocators[i];
639
611
  if (frameLocator.css) {
612
+ fLocator = frameLocator.css;
640
613
  scope = scope.frameLocator(frameLocator.css);
641
614
  frameFound = true;
642
615
  break;
@@ -644,16 +617,25 @@ class StableBrowser {
644
617
  }
645
618
  }
646
619
  if (!frameFound && selectors.iframe_src) {
620
+ fLocator = selectors.iframe_src;
647
621
  scope = this.page.frame({ url: selectors.iframe_src });
648
622
  }
649
623
  if (!scope) {
650
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
651
- if (performance.now() - startTime > timeout) {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
626
+ }
627
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
628
+ if (Date.now() - startTime > timeout) {
629
+ info.failCause.iframeNotFound = true;
630
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
652
631
  throw new Error("unable to locate iframe " + selectors.iframe_src);
653
632
  }
654
633
  await new Promise((resolve) => setTimeout(resolve, 1000));
655
634
  }
656
635
  else {
636
+ if (info && info.locatorLog) {
637
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
638
+ }
657
639
  break;
658
640
  }
659
641
  }
@@ -663,20 +645,27 @@ class StableBrowser {
663
645
  }
664
646
  return scope;
665
647
  }
666
- async _getDocumentBody(selectors, timeout = 30000) {
667
- let scope = await this._findFrameScope(selectors, timeout);
648
+ async _getDocumentBody(selectors, timeout = 30000, info) {
649
+ let scope = await this._findFrameScope(selectors, timeout, info);
668
650
  return scope.evaluate(() => {
669
651
  var bodyContent = document.body.innerHTML;
670
652
  return bodyContent;
671
653
  });
672
654
  }
673
655
  async _locate_internal(selectors, info, _params, timeout = 30000) {
656
+ if (!info) {
657
+ info = {};
658
+ info.failCause = {};
659
+ info.log = "";
660
+ info.locatorLog = new LocatorLog(selectors);
661
+ }
674
662
  let highPriorityTimeout = 5000;
675
663
  let visibleOnlyTimeout = 6000;
676
- let startTime = performance.now();
664
+ let startTime = Date.now();
677
665
  let locatorsCount = 0;
666
+ let lazy_scroll = false;
678
667
  //let arrayMode = Array.isArray(selectors);
679
- let scope = await this._findFrameScope(selectors, timeout);
668
+ let scope = await this._findFrameScope(selectors, timeout, info);
680
669
  let selectorsLocators = null;
681
670
  selectorsLocators = selectors.locators;
682
671
  // group selectors by priority
@@ -763,21 +752,33 @@ class StableBrowser {
763
752
  return maxCountElement.locator;
764
753
  }
765
754
  }
766
- if (performance.now() - startTime > timeout) {
755
+ if (Date.now() - startTime > timeout) {
767
756
  break;
768
757
  }
769
- if (performance.now() - startTime > highPriorityTimeout) {
758
+ if (Date.now() - startTime > highPriorityTimeout) {
770
759
  info.log += "high priority timeout, will try all elements" + "\n";
771
760
  highPriorityOnly = false;
761
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
762
+ lazy_scroll = true;
763
+ await scrollPageToLoadLazyElements(this.page);
764
+ }
772
765
  }
773
- if (performance.now() - startTime > visibleOnlyTimeout) {
766
+ if (Date.now() - startTime > visibleOnlyTimeout) {
774
767
  info.log += "visible only timeout, will try all elements" + "\n";
775
768
  visibleOnly = false;
776
769
  }
777
770
  await new Promise((resolve) => setTimeout(resolve, 1000));
778
771
  }
779
772
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
780
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
773
+ // if (info.locatorLog) {
774
+ // const lines = info.locatorLog.toString().split("\n");
775
+ // for (let line of lines) {
776
+ // this.logger.debug(line);
777
+ // }
778
+ // }
779
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
780
+ info.failCause.locatorNotFound = true;
781
+ info.failCause.lastError = "failed to locate unique element";
781
782
  throw new Error("failed to locate first element no elements found, " + info.log);
782
783
  }
783
784
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -791,8 +792,9 @@ class StableBrowser {
791
792
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
792
793
  }
793
794
  catch (e) {
794
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
795
- this.logger.debug(e);
795
+ // this call can fail it the browser is navigating
796
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
797
+ // this.logger.debug(e);
796
798
  foundLocators = [];
797
799
  try {
798
800
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -809,10 +811,29 @@ class StableBrowser {
809
811
  });
810
812
  result.locatorIndex = i;
811
813
  }
814
+ if (foundLocators.length > 1) {
815
+ info.failCause.foundMultiple = true;
816
+ if (info.locatorLog) {
817
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
818
+ }
819
+ }
812
820
  }
813
821
  return result;
814
822
  }
815
823
  async simpleClick(elementDescription, _params, options = {}, world = null) {
824
+ const state = {
825
+ locate: false,
826
+ scroll: false,
827
+ highlight: false,
828
+ _params,
829
+ options,
830
+ world,
831
+ type: Types.CLICK,
832
+ text: "Click element",
833
+ operation: "simpleClick",
834
+ log: "***** click on " + elementDescription + " *****\n",
835
+ };
836
+ _preCommand(state, this);
816
837
  const startTime = Date.now();
817
838
  let timeout = 30000;
818
839
  if (options && options.timeout) {
@@ -836,13 +857,32 @@ class StableBrowser {
836
857
  }
837
858
  catch (e) {
838
859
  if (performance.now() - startTime > timeout) {
839
- throw e;
860
+ // throw e;
861
+ try {
862
+ await _commandError(state, "timeout looking for " + elementDescription, this);
863
+ }
864
+ finally {
865
+ _commandFinally(state, this);
866
+ }
840
867
  }
841
868
  }
842
869
  await new Promise((resolve) => setTimeout(resolve, 3000));
843
870
  }
844
871
  }
845
872
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
873
+ const state = {
874
+ locate: false,
875
+ scroll: false,
876
+ highlight: false,
877
+ _params,
878
+ options,
879
+ world,
880
+ type: Types.FILL,
881
+ text: "Fill element",
882
+ operation: "simpleClickType",
883
+ log: "***** click type on " + elementDescription + " *****\n",
884
+ };
885
+ _preCommand(state, this);
846
886
  const startTime = Date.now();
847
887
  let timeout = 30000;
848
888
  if (options && options.timeout) {
@@ -866,7 +906,13 @@ class StableBrowser {
866
906
  }
867
907
  catch (e) {
868
908
  if (performance.now() - startTime > timeout) {
869
- throw e;
909
+ // throw e;
910
+ try {
911
+ await _commandError(state, "timeout looking for " + elementDescription, this);
912
+ }
913
+ finally {
914
+ _commandFinally(state, this);
915
+ }
870
916
  }
871
917
  }
872
918
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -885,18 +931,18 @@ class StableBrowser {
885
931
  };
886
932
  try {
887
933
  await _preCommand(state, this);
888
- if (state.options && state.options.context) {
889
- state.selectors.locators[0].text = state.options.context;
890
- }
934
+ // if (state.options && state.options.context) {
935
+ // state.selectors.locators[0].text = state.options.context;
936
+ // }
891
937
  try {
892
938
  await state.element.click();
893
- await new Promise((resolve) => setTimeout(resolve, 1000));
939
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
894
940
  }
895
941
  catch (e) {
896
942
  // await this.closeUnexpectedPopups();
897
943
  state.element = await this._locate(selectors, state.info, _params);
898
944
  await state.element.dispatchEvent("click");
899
- await new Promise((resolve) => setTimeout(resolve, 1000));
945
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
900
946
  }
901
947
  await this.waitForPageLoad();
902
948
  return state.info;
@@ -975,7 +1021,6 @@ class StableBrowser {
975
1021
  await state.element.hover({ timeout: 10000 });
976
1022
  await new Promise((resolve) => setTimeout(resolve, 1000));
977
1023
  }
978
- await _screenshot(state, this);
979
1024
  await this.waitForPageLoad();
980
1025
  return state.info;
981
1026
  }
@@ -1103,33 +1148,30 @@ class StableBrowser {
1103
1148
  }
1104
1149
  }
1105
1150
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1106
- _validateSelectors(selectors);
1107
- const startTime = Date.now();
1108
- let error = null;
1109
- let screenshotId = null;
1110
- let screenshotPath = null;
1111
- const info = {};
1112
- info.log = "";
1113
- info.operation = Types.SET_DATE_TIME;
1114
- info.selectors = selectors;
1115
- info.value = value;
1151
+ const state = {
1152
+ selectors,
1153
+ _params,
1154
+ value: await this._replaceWithLocalData(value, this),
1155
+ options,
1156
+ world,
1157
+ type: Types.SET_DATE_TIME,
1158
+ text: `Set date time value: ${value}`,
1159
+ operation: "setDateTime",
1160
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1161
+ throwError: false,
1162
+ };
1116
1163
  try {
1117
- value = await this._replaceWithLocalData(value, this);
1118
- let element = await this._locate(selectors, info, _params);
1119
- //insert red border around the element
1120
- await this.scrollIfNeeded(element, info);
1121
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1122
- await this._highlightElements(element);
1164
+ await _preCommand(state, this);
1123
1165
  try {
1124
- await element.click();
1166
+ await state.element.click();
1125
1167
  await new Promise((resolve) => setTimeout(resolve, 500));
1126
1168
  if (format) {
1127
- value = dayjs(value).format(format);
1128
- await element.fill(value);
1169
+ state.value = dayjs(state.value).format(format);
1170
+ await state.element.fill(state.value);
1129
1171
  }
1130
1172
  else {
1131
- const dateTimeValue = await getDateTimeValue({ value, element });
1132
- await element.evaluateHandle((el, dateTimeValue) => {
1173
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1174
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1133
1175
  el.value = ""; // clear input
1134
1176
  el.value = dateTimeValue;
1135
1177
  }, dateTimeValue);
@@ -1142,20 +1184,19 @@ class StableBrowser {
1142
1184
  }
1143
1185
  catch (err) {
1144
1186
  //await this.closeUnexpectedPopups();
1145
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1187
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1146
1188
  this.logger.info("Trying again");
1147
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1148
- info.screenshotPath = screenshotPath;
1149
- Object.assign(err, { info: info });
1189
+ await _screenshot(state, this);
1190
+ Object.assign(err, { info: state.info });
1150
1191
  await element.click();
1151
1192
  await new Promise((resolve) => setTimeout(resolve, 500));
1152
1193
  if (format) {
1153
- value = dayjs(value).format(format);
1154
- await element.fill(value);
1194
+ state.value = dayjs(state.value).format(format);
1195
+ await state.element.fill(state.value);
1155
1196
  }
1156
1197
  else {
1157
- const dateTimeValue = await getDateTimeValue({ value, element });
1158
- await element.evaluateHandle((el, dateTimeValue) => {
1198
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1199
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1159
1200
  el.value = ""; // clear input
1160
1201
  el.value = dateTimeValue;
1161
1202
  }, dateTimeValue);
@@ -1168,31 +1209,10 @@ class StableBrowser {
1168
1209
  }
1169
1210
  }
1170
1211
  catch (e) {
1171
- error = e;
1172
- throw e;
1212
+ await _commandError(state, e, this);
1173
1213
  }
1174
1214
  finally {
1175
- const endTime = Date.now();
1176
- this._reportToWorld(world, {
1177
- element_name: selectors.element_name,
1178
- type: Types.SET_DATE_TIME,
1179
- screenshotId,
1180
- value: value,
1181
- text: `setDateTime input with value: ${value}`,
1182
- result: error
1183
- ? {
1184
- status: "FAILED",
1185
- startTime,
1186
- endTime,
1187
- message: error === null || error === void 0 ? void 0 : error.message,
1188
- }
1189
- : {
1190
- status: "PASSED",
1191
- startTime,
1192
- endTime,
1193
- },
1194
- info: info,
1195
- });
1215
+ _commandFinally(state, this);
1196
1216
  }
1197
1217
  }
1198
1218
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1329,6 +1349,7 @@ class StableBrowser {
1329
1349
  let screenshotPath = null;
1330
1350
  if (!info.log) {
1331
1351
  info.log = "";
1352
+ info.locatorLog = new LocatorLog(selectors);
1332
1353
  }
1333
1354
  info.operation = "getText";
1334
1355
  info.selectors = selectors;
@@ -1667,11 +1688,9 @@ class StableBrowser {
1667
1688
  if (!fs.existsSync(world.screenshotPath)) {
1668
1689
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1669
1690
  }
1670
- let nextIndex = 1;
1671
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1672
- nextIndex++;
1673
- }
1674
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1691
+ // to make sure the path doesn't start with -
1692
+ const uuidStr = "id_" + randomUUID();
1693
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1675
1694
  try {
1676
1695
  await this.takeScreenshot(screenshotPath);
1677
1696
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1685,7 +1704,7 @@ class StableBrowser {
1685
1704
  catch (e) {
1686
1705
  this.logger.info("unable to take screenshot, ignored");
1687
1706
  }
1688
- result.screenshotId = nextIndex;
1707
+ result.screenshotId = uuidStr;
1689
1708
  result.screenshotPath = screenshotPath;
1690
1709
  if (info && info.box) {
1691
1710
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1783,74 +1802,105 @@ class StableBrowser {
1783
1802
  }
1784
1803
  }
1785
1804
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1786
- _validateSelectors(selectors);
1787
- const startTime = Date.now();
1788
- let error = null;
1789
- let screenshotId = null;
1790
- let screenshotPath = null;
1805
+ const state = {
1806
+ selectors,
1807
+ _params,
1808
+ attribute,
1809
+ variable,
1810
+ options,
1811
+ world,
1812
+ type: Types.EXTRACT,
1813
+ text: `Extract attribute from element`,
1814
+ operation: "extractAttribute",
1815
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1816
+ };
1791
1817
  await new Promise((resolve) => setTimeout(resolve, 2000));
1792
- const info = {};
1793
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1794
- info.operation = "extract";
1795
- info.selectors = selectors;
1796
1818
  try {
1797
- const element = await this._locate(selectors, info, _params);
1798
- await this._highlightElements(element);
1799
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1819
+ await _preCommand(state, this);
1800
1820
  switch (attribute) {
1801
1821
  case "inner_text":
1802
- info.value = await element.innerText();
1822
+ state.value = await state.element.innerText();
1803
1823
  break;
1804
1824
  case "href":
1805
- info.value = await element.getAttribute("href");
1825
+ state.value = await state.element.getAttribute("href");
1806
1826
  break;
1807
1827
  case "value":
1808
- info.value = await element.inputValue();
1828
+ state.value = await state.element.inputValue();
1809
1829
  break;
1810
1830
  default:
1811
- info.value = await element.getAttribute(attribute);
1831
+ state.value = await state.element.getAttribute(attribute);
1812
1832
  break;
1813
1833
  }
1814
- this[variable] = info.value;
1815
- if (world) {
1816
- world[variable] = info.value;
1834
+ state.info.value = state.value;
1835
+ this.setTestData({ [variable]: state.value }, world);
1836
+ this.logger.info("set test data: " + variable + "=" + state.value);
1837
+ return state.info;
1838
+ }
1839
+ catch (e) {
1840
+ await _commandError(state, e, this);
1841
+ }
1842
+ finally {
1843
+ _commandFinally(state, this);
1844
+ }
1845
+ }
1846
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1847
+ const state = {
1848
+ selectors,
1849
+ _params,
1850
+ attribute,
1851
+ value,
1852
+ options,
1853
+ world,
1854
+ type: Types.VERIFY_ATTRIBUTE,
1855
+ text: `Verify element attribute`,
1856
+ operation: "verifyAttribute",
1857
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1858
+ };
1859
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1860
+ let val;
1861
+ try {
1862
+ await _preCommand(state, this);
1863
+ switch (attribute) {
1864
+ case "innerText":
1865
+ val = String(await state.element.innerText());
1866
+ break;
1867
+ case "value":
1868
+ val = String(await state.element.inputValue());
1869
+ break;
1870
+ case "checked":
1871
+ val = String(await state.element.isChecked());
1872
+ break;
1873
+ case "disabled":
1874
+ val = String(await state.element.isDisabled());
1875
+ break;
1876
+ case "readOnly":
1877
+ const isEditable = await state.element.isEditable();
1878
+ val = String(!isEditable);
1879
+ break;
1880
+ default:
1881
+ val = String(await state.element.getAttribute(attribute));
1882
+ break;
1817
1883
  }
1818
- this.setTestData({ [variable]: info.value }, world);
1819
- this.logger.info("set test data: " + variable + "=" + info.value);
1820
- return info;
1884
+ state.info.expectedValue = val;
1885
+ let regex;
1886
+ if (value.startsWith("/") && value.endsWith("/")) {
1887
+ const patternBody = value.slice(1, -1);
1888
+ regex = new RegExp(patternBody, "g");
1889
+ }
1890
+ else {
1891
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1892
+ regex = new RegExp(escapedPattern, "g");
1893
+ }
1894
+ if (!val.match(regex)) {
1895
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1896
+ }
1897
+ return state.info;
1821
1898
  }
1822
1899
  catch (e) {
1823
- //await this.closeUnexpectedPopups();
1824
- this.logger.error("extract failed " + info.log);
1825
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1826
- info.screenshotPath = screenshotPath;
1827
- Object.assign(e, { info: info });
1828
- error = e;
1829
- throw e;
1900
+ await _commandError(state, e, this);
1830
1901
  }
1831
1902
  finally {
1832
- const endTime = Date.now();
1833
- this._reportToWorld(world, {
1834
- element_name: selectors.element_name,
1835
- type: Types.EXTRACT_ATTRIBUTE,
1836
- variable: variable,
1837
- value: info.value,
1838
- text: "Extract attribute from element",
1839
- screenshotId,
1840
- result: error
1841
- ? {
1842
- status: "FAILED",
1843
- startTime,
1844
- endTime,
1845
- message: error?.message,
1846
- }
1847
- : {
1848
- status: "PASSED",
1849
- startTime,
1850
- endTime,
1851
- },
1852
- info: info,
1853
- });
1903
+ _commandFinally(state, this);
1854
1904
  }
1855
1905
  }
1856
1906
  async extractEmailData(emailAddress, options, world) {
@@ -1871,7 +1921,7 @@ class StableBrowser {
1871
1921
  if (options && options.timeout) {
1872
1922
  timeout = options.timeout;
1873
1923
  }
1874
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1924
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1875
1925
  const request = {
1876
1926
  method: "POST",
1877
1927
  url: serviceUrl,
@@ -1927,7 +1977,8 @@ class StableBrowser {
1927
1977
  catch (e) {
1928
1978
  errorCount++;
1929
1979
  if (errorCount > 3) {
1930
- throw e;
1980
+ // throw e;
1981
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1931
1982
  }
1932
1983
  // ignore
1933
1984
  }
@@ -2038,11 +2089,12 @@ class StableBrowser {
2038
2089
  info.screenshotPath = screenshotPath;
2039
2090
  Object.assign(e, { info: info });
2040
2091
  error = e;
2041
- throw e;
2092
+ // throw e;
2093
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2042
2094
  }
2043
2095
  finally {
2044
2096
  const endTime = Date.now();
2045
- this._reportToWorld(world, {
2097
+ _reportToWorld(world, {
2046
2098
  type: Types.VERIFY_PAGE_PATH,
2047
2099
  text: "Verify page path",
2048
2100
  screenshotId,
@@ -2062,54 +2114,64 @@ class StableBrowser {
2062
2114
  });
2063
2115
  }
2064
2116
  }
2117
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2118
+ const frames = this.page.frames();
2119
+ let results = [];
2120
+ for (let i = 0; i < frames.length; i++) {
2121
+ if (dateAlternatives.date) {
2122
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2123
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2124
+ result.frame = frames[i];
2125
+ results.push(result);
2126
+ }
2127
+ }
2128
+ else if (numberAlternatives.number) {
2129
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2130
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2131
+ result.frame = frames[i];
2132
+ results.push(result);
2133
+ }
2134
+ }
2135
+ else {
2136
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2137
+ result.frame = frames[i];
2138
+ results.push(result);
2139
+ }
2140
+ }
2141
+ state.info.results = results;
2142
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2143
+ return resultWithElementsFound;
2144
+ }
2065
2145
  async verifyTextExistInPage(text, options = {}, world = null) {
2066
2146
  text = unEscapeString(text);
2067
- const startTime = Date.now();
2147
+ const state = {
2148
+ text_search: text,
2149
+ options,
2150
+ world,
2151
+ locate: false,
2152
+ scroll: false,
2153
+ highlight: false,
2154
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2155
+ text: `Verify text exists in page`,
2156
+ operation: "verifyTextExistInPage",
2157
+ log: "***** verify text " + text + " exists in page *****\n",
2158
+ };
2068
2159
  const timeout = this._getLoadTimeout(options);
2069
- let error = null;
2070
- let screenshotId = null;
2071
- let screenshotPath = null;
2072
2160
  await new Promise((resolve) => setTimeout(resolve, 2000));
2073
- const info = {};
2074
- info.log = "***** verify text " + text + " exists in page *****\n";
2075
- info.operation = "verifyTextExistInPage";
2076
2161
  const newValue = await this._replaceWithLocalData(text, world);
2077
2162
  if (newValue !== text) {
2078
2163
  this.logger.info(text + "=" + newValue);
2079
2164
  text = newValue;
2080
2165
  }
2081
- info.text = text;
2082
2166
  let dateAlternatives = findDateAlternatives(text);
2083
2167
  let numberAlternatives = findNumberAlternatives(text);
2084
2168
  try {
2169
+ await _preCommand(state, this);
2170
+ state.info.text = text;
2085
2171
  while (true) {
2086
- const frames = this.page.frames();
2087
- let results = [];
2088
- for (let i = 0; i < frames.length; i++) {
2089
- if (dateAlternatives.date) {
2090
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2091
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2092
- result.frame = frames[i];
2093
- results.push(result);
2094
- }
2095
- }
2096
- else if (numberAlternatives.number) {
2097
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2098
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2099
- result.frame = frames[i];
2100
- results.push(result);
2101
- }
2102
- }
2103
- else {
2104
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2105
- result.frame = frames[i];
2106
- results.push(result);
2107
- }
2108
- }
2109
- info.results = results;
2110
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2172
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2111
2173
  if (resultWithElementsFound.length === 0) {
2112
- if (Date.now() - startTime > timeout) {
2174
+ if (Date.now() - state.startTime > timeout) {
2113
2175
  throw new Error(`Text ${text} not found in page`);
2114
2176
  }
2115
2177
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2119,57 +2181,165 @@ class StableBrowser {
2119
2181
  const frame = resultWithElementsFound[0].frame;
2120
2182
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2121
2183
  await this._highlightElements(frame, dataAttribute);
2122
- const element = await frame.$(dataAttribute);
2184
+ const element = await frame.locator(dataAttribute).first();
2123
2185
  if (element) {
2124
- await this.scrollIfNeeded(element, info);
2186
+ await this.scrollIfNeeded(element, state.info);
2125
2187
  await element.dispatchEvent("bvt_verify_page_contains_text");
2126
2188
  }
2127
2189
  }
2128
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2129
- return info;
2190
+ await _screenshot(state, this);
2191
+ return state.info;
2130
2192
  }
2131
2193
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2132
2194
  }
2133
2195
  catch (e) {
2134
- //await this.closeUnexpectedPopups();
2135
- this.logger.error("verify text exist in page failed " + info.log);
2136
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2137
- info.screenshotPath = screenshotPath;
2138
- Object.assign(e, { info: info });
2139
- error = e;
2140
- throw e;
2196
+ await _commandError(state, e, this);
2141
2197
  }
2142
2198
  finally {
2143
- const endTime = Date.now();
2144
- this._reportToWorld(world, {
2145
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2146
- text: "Verify text exists in page",
2147
- screenshotId,
2148
- result: error
2149
- ? {
2150
- status: "FAILED",
2151
- startTime,
2152
- endTime,
2153
- message: error?.message,
2154
- }
2155
- : {
2156
- status: "PASSED",
2157
- startTime,
2158
- endTime,
2159
- },
2160
- info: info,
2161
- });
2199
+ _commandFinally(state, this);
2200
+ }
2201
+ }
2202
+ async waitForTextToDisappear(text, options = {}, world = null) {
2203
+ text = unEscapeString(text);
2204
+ const state = {
2205
+ text_search: text,
2206
+ options,
2207
+ world,
2208
+ locate: false,
2209
+ scroll: false,
2210
+ highlight: false,
2211
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2212
+ text: `Verify text does not exist in page`,
2213
+ operation: "verifyTextNotExistInPage",
2214
+ log: "***** verify text " + text + " does not exist in page *****\n",
2215
+ };
2216
+ const timeout = this._getLoadTimeout(options);
2217
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2218
+ const newValue = await this._replaceWithLocalData(text, world);
2219
+ if (newValue !== text) {
2220
+ this.logger.info(text + "=" + newValue);
2221
+ text = newValue;
2222
+ }
2223
+ let dateAlternatives = findDateAlternatives(text);
2224
+ let numberAlternatives = findNumberAlternatives(text);
2225
+ try {
2226
+ await _preCommand(state, this);
2227
+ state.info.text = text;
2228
+ while (true) {
2229
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2230
+ if (resultWithElementsFound.length === 0) {
2231
+ await _screenshot(state, this);
2232
+ return state.info;
2233
+ }
2234
+ if (Date.now() - state.startTime > timeout) {
2235
+ throw new Error(`Text ${text} found in page`);
2236
+ }
2237
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2238
+ }
2239
+ }
2240
+ catch (e) {
2241
+ await _commandError(state, e, this);
2242
+ }
2243
+ finally {
2244
+ _commandFinally(state, this);
2162
2245
  }
2163
2246
  }
2164
- _getServerUrl() {
2165
- let serviceUrl = "https://api.blinq.io";
2166
- if (process.env.NODE_ENV_BLINQ === "dev") {
2167
- serviceUrl = "https://dev.api.blinq.io";
2247
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2248
+ textAnchor = unEscapeString(textAnchor);
2249
+ textToVerify = unEscapeString(textToVerify);
2250
+ const state = {
2251
+ text_search: textToVerify,
2252
+ options,
2253
+ world,
2254
+ locate: false,
2255
+ scroll: false,
2256
+ highlight: false,
2257
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2258
+ text: `Verify text with relation to another text`,
2259
+ operation: "verify_text_with_relation",
2260
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2261
+ };
2262
+ const timeout = this._getLoadTimeout(options);
2263
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2264
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2265
+ if (newValue !== textAnchor) {
2266
+ this.logger.info(textAnchor + "=" + newValue);
2267
+ textAnchor = newValue;
2268
+ }
2269
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2270
+ if (newValue !== textToVerify) {
2271
+ this.logger.info(textToVerify + "=" + newValue);
2272
+ textToVerify = newValue;
2273
+ }
2274
+ let dateAlternatives = findDateAlternatives(textToVerify);
2275
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2276
+ let foundAncore = false;
2277
+ try {
2278
+ await _preCommand(state, this);
2279
+ state.info.text = textToVerify;
2280
+ while (true) {
2281
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2282
+ if (resultWithElementsFound.length === 0) {
2283
+ if (Date.now() - state.startTime > timeout) {
2284
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2285
+ }
2286
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2287
+ continue;
2288
+ }
2289
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2290
+ foundAncore = true;
2291
+ const result = resultWithElementsFound[i];
2292
+ const token = result.randomToken;
2293
+ const frame = result.frame;
2294
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2295
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2296
+ const elements = Array.from(document.querySelectorAll(css));
2297
+ for (let i = 0; i < elements.length; i++) {
2298
+ const element = elements[i];
2299
+ let climbParent = element;
2300
+ for (let j = 0; j < climb; j++) {
2301
+ climbParent = climbParent.parentElement;
2302
+ if (!climbParent) {
2303
+ break;
2304
+ }
2305
+ }
2306
+ if (!climbParent) {
2307
+ continue;
2308
+ }
2309
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2310
+ if (foundElements.length > 0) {
2311
+ // set the container element attribute
2312
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2313
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2314
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2315
+ return { found: true };
2316
+ }
2317
+ }
2318
+ return { found: false };
2319
+ }, [css, climb, textToVerify, result.randomToken]);
2320
+ if (findResult.found === true) {
2321
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2322
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2323
+ await this._highlightElements(frame, dataAttribute);
2324
+ await this._highlightElements(frame, cssAnchor);
2325
+ const element = await frame.locator(dataAttribute).first();
2326
+ if (element) {
2327
+ await this.scrollIfNeeded(element, state.info);
2328
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2329
+ }
2330
+ await _screenshot(state, this);
2331
+ return state.info;
2332
+ }
2333
+ }
2334
+ }
2335
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2168
2336
  }
2169
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2170
- serviceUrl = "https://stage.api.blinq.io";
2337
+ catch (e) {
2338
+ await _commandError(state, e, this);
2339
+ }
2340
+ finally {
2341
+ _commandFinally(state, this);
2171
2342
  }
2172
- return serviceUrl;
2173
2343
  }
2174
2344
  async visualVerification(text, options = {}, world = null) {
2175
2345
  const startTime = Date.now();
@@ -2185,7 +2355,7 @@ class StableBrowser {
2185
2355
  throw new Error("TOKEN is not set");
2186
2356
  }
2187
2357
  try {
2188
- let serviceUrl = this._getServerUrl();
2358
+ let serviceUrl = _getServerUrl();
2189
2359
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2190
2360
  info.screenshotPath = screenshotPath;
2191
2361
  const screenshot = await this.takeScreenshot();
@@ -2221,11 +2391,12 @@ class StableBrowser {
2221
2391
  info.screenshotPath = screenshotPath;
2222
2392
  Object.assign(e, { info: info });
2223
2393
  error = e;
2224
- throw e;
2394
+ // throw e;
2395
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2225
2396
  }
2226
2397
  finally {
2227
2398
  const endTime = Date.now();
2228
- this._reportToWorld(world, {
2399
+ _reportToWorld(world, {
2229
2400
  type: Types.VERIFY_VISUAL,
2230
2401
  text: "Visual verification",
2231
2402
  screenshotId,
@@ -2273,6 +2444,7 @@ class StableBrowser {
2273
2444
  let screenshotPath = null;
2274
2445
  const info = {};
2275
2446
  info.log = "";
2447
+ info.locatorLog = new LocatorLog(selectors);
2276
2448
  info.operation = "getTableData";
2277
2449
  info.selectors = selectors;
2278
2450
  try {
@@ -2288,11 +2460,12 @@ class StableBrowser {
2288
2460
  info.screenshotPath = screenshotPath;
2289
2461
  Object.assign(e, { info: info });
2290
2462
  error = e;
2291
- throw e;
2463
+ // throw e;
2464
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2292
2465
  }
2293
2466
  finally {
2294
2467
  const endTime = Date.now();
2295
- this._reportToWorld(world, {
2468
+ _reportToWorld(world, {
2296
2469
  element_name: selectors.element_name,
2297
2470
  type: Types.GET_TABLE_DATA,
2298
2471
  text: "Get table data",
@@ -2347,7 +2520,7 @@ class StableBrowser {
2347
2520
  info.operation = "analyzeTable";
2348
2521
  info.selectors = selectors;
2349
2522
  info.query = query;
2350
- query = this._fixUsingParams(query, _params);
2523
+ query = _fixUsingParams(query, _params);
2351
2524
  info.query_fixed = query;
2352
2525
  info.operator = operator;
2353
2526
  info.value = value;
@@ -2453,11 +2626,12 @@ class StableBrowser {
2453
2626
  info.screenshotPath = screenshotPath;
2454
2627
  Object.assign(e, { info: info });
2455
2628
  error = e;
2456
- throw e;
2629
+ // throw e;
2630
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2457
2631
  }
2458
2632
  finally {
2459
2633
  const endTime = Date.now();
2460
- this._reportToWorld(world, {
2634
+ _reportToWorld(world, {
2461
2635
  element_name: selectors.element_name,
2462
2636
  type: Types.ANALYZE_TABLE,
2463
2637
  text: "Analyze table",
@@ -2530,7 +2704,7 @@ class StableBrowser {
2530
2704
  await new Promise((resolve) => setTimeout(resolve, 2000));
2531
2705
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2532
2706
  const endTime = Date.now();
2533
- this._reportToWorld(world, {
2707
+ _reportToWorld(world, {
2534
2708
  type: Types.GET_PAGE_STATUS,
2535
2709
  text: "Wait for page load",
2536
2710
  screenshotId,
@@ -2550,41 +2724,35 @@ class StableBrowser {
2550
2724
  }
2551
2725
  }
2552
2726
  async closePage(options = {}, world = null) {
2553
- const startTime = Date.now();
2554
- let error = null;
2555
- let screenshotId = null;
2556
- let screenshotPath = null;
2557
- const info = {};
2727
+ const state = {
2728
+ options,
2729
+ world,
2730
+ locate: false,
2731
+ scroll: false,
2732
+ highlight: false,
2733
+ type: Types.CLOSE_PAGE,
2734
+ text: `Close page`,
2735
+ operation: "closePage",
2736
+ log: "***** close page *****\n",
2737
+ throwError: false,
2738
+ };
2558
2739
  try {
2740
+ await _preCommand(state, this);
2559
2741
  await this.page.close();
2560
2742
  }
2561
2743
  catch (e) {
2562
2744
  console.log(".");
2745
+ await _commandError(state, e, this);
2563
2746
  }
2564
2747
  finally {
2565
- await new Promise((resolve) => setTimeout(resolve, 2000));
2566
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2567
- const endTime = Date.now();
2568
- this._reportToWorld(world, {
2569
- type: Types.CLOSE_PAGE,
2570
- text: "close page",
2571
- screenshotId,
2572
- result: error
2573
- ? {
2574
- status: "FAILED",
2575
- startTime,
2576
- endTime,
2577
- message: error?.message,
2578
- }
2579
- : {
2580
- status: "PASSED",
2581
- startTime,
2582
- endTime,
2583
- },
2584
- info: info,
2585
- });
2748
+ _commandFinally(state, this);
2586
2749
  }
2587
2750
  }
2751
+ saveTestDataAsGlobal(options, world) {
2752
+ const dataFile = this._getDataFile(world);
2753
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2754
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2755
+ }
2588
2756
  async setViewportSize(width, hight, options = {}, world = null) {
2589
2757
  const startTime = Date.now();
2590
2758
  let error = null;
@@ -2602,12 +2770,13 @@ class StableBrowser {
2602
2770
  }
2603
2771
  catch (e) {
2604
2772
  console.log(".");
2773
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2605
2774
  }
2606
2775
  finally {
2607
2776
  await new Promise((resolve) => setTimeout(resolve, 2000));
2608
2777
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2609
2778
  const endTime = Date.now();
2610
- this._reportToWorld(world, {
2779
+ _reportToWorld(world, {
2611
2780
  type: Types.SET_VIEWPORT,
2612
2781
  text: "set viewport size to " + width + "x" + hight,
2613
2782
  screenshotId,
@@ -2638,12 +2807,13 @@ class StableBrowser {
2638
2807
  }
2639
2808
  catch (e) {
2640
2809
  console.log(".");
2810
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2641
2811
  }
2642
2812
  finally {
2643
2813
  await new Promise((resolve) => setTimeout(resolve, 2000));
2644
2814
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2645
2815
  const endTime = Date.now();
2646
- this._reportToWorld(world, {
2816
+ _reportToWorld(world, {
2647
2817
  type: Types.GET_PAGE_STATUS,
2648
2818
  text: "page relaod",
2649
2819
  screenshotId,
@@ -2679,11 +2849,45 @@ class StableBrowser {
2679
2849
  console.log("#-#");
2680
2850
  }
2681
2851
  }
2682
- _reportToWorld(world, properties) {
2683
- if (!world || !world.attach) {
2684
- return;
2852
+ async beforeStep(world, step) {
2853
+ if (this.stepIndex === undefined) {
2854
+ this.stepIndex = 0;
2855
+ }
2856
+ else {
2857
+ this.stepIndex++;
2858
+ }
2859
+ if (step && step.pickleStep && step.pickleStep.text) {
2860
+ this.stepName = step.pickleStep.text;
2861
+ this.logger.info("step: " + this.stepName);
2862
+ }
2863
+ else if (step && step.text) {
2864
+ this.stepName = step.text;
2865
+ }
2866
+ else {
2867
+ this.stepName = "step " + this.stepIndex;
2868
+ }
2869
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2870
+ if (this.context.browserObject.context) {
2871
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2872
+ }
2873
+ }
2874
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2875
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2876
+ // check if @global_test_data tag is present
2877
+ if (this.tags.includes("@global_test_data")) {
2878
+ this.saveTestDataAsGlobal({}, world);
2879
+ }
2880
+ }
2881
+ }
2882
+ async afterStep(world, step) {
2883
+ this.stepName = null;
2884
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2885
+ if (this.context.browserObject.context) {
2886
+ await this.context.browserObject.context.tracing.stopChunk({
2887
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2888
+ });
2889
+ }
2685
2890
  }
2686
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2687
2891
  }
2688
2892
  }
2689
2893
  function createTimedPromise(promise, label) {
@@ -2691,156 +2895,5 @@ function createTimedPromise(promise, label) {
2691
2895
  .then((result) => ({ status: "fulfilled", label, result }))
2692
2896
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2693
2897
  }
2694
- const KEYBOARD_EVENTS = [
2695
- "ALT",
2696
- "AltGraph",
2697
- "CapsLock",
2698
- "Control",
2699
- "Fn",
2700
- "FnLock",
2701
- "Hyper",
2702
- "Meta",
2703
- "NumLock",
2704
- "ScrollLock",
2705
- "Shift",
2706
- "Super",
2707
- "Symbol",
2708
- "SymbolLock",
2709
- "Enter",
2710
- "Tab",
2711
- "ArrowDown",
2712
- "ArrowLeft",
2713
- "ArrowRight",
2714
- "ArrowUp",
2715
- "End",
2716
- "Home",
2717
- "PageDown",
2718
- "PageUp",
2719
- "Backspace",
2720
- "Clear",
2721
- "Copy",
2722
- "CrSel",
2723
- "Cut",
2724
- "Delete",
2725
- "EraseEof",
2726
- "ExSel",
2727
- "Insert",
2728
- "Paste",
2729
- "Redo",
2730
- "Undo",
2731
- "Accept",
2732
- "Again",
2733
- "Attn",
2734
- "Cancel",
2735
- "ContextMenu",
2736
- "Escape",
2737
- "Execute",
2738
- "Find",
2739
- "Finish",
2740
- "Help",
2741
- "Pause",
2742
- "Play",
2743
- "Props",
2744
- "Select",
2745
- "ZoomIn",
2746
- "ZoomOut",
2747
- "BrightnessDown",
2748
- "BrightnessUp",
2749
- "Eject",
2750
- "LogOff",
2751
- "Power",
2752
- "PowerOff",
2753
- "PrintScreen",
2754
- "Hibernate",
2755
- "Standby",
2756
- "WakeUp",
2757
- "AllCandidates",
2758
- "Alphanumeric",
2759
- "CodeInput",
2760
- "Compose",
2761
- "Convert",
2762
- "Dead",
2763
- "FinalMode",
2764
- "GroupFirst",
2765
- "GroupLast",
2766
- "GroupNext",
2767
- "GroupPrevious",
2768
- "ModeChange",
2769
- "NextCandidate",
2770
- "NonConvert",
2771
- "PreviousCandidate",
2772
- "Process",
2773
- "SingleCandidate",
2774
- "HangulMode",
2775
- "HanjaMode",
2776
- "JunjaMode",
2777
- "Eisu",
2778
- "Hankaku",
2779
- "Hiragana",
2780
- "HiraganaKatakana",
2781
- "KanaMode",
2782
- "KanjiMode",
2783
- "Katakana",
2784
- "Romaji",
2785
- "Zenkaku",
2786
- "ZenkakuHanaku",
2787
- "F1",
2788
- "F2",
2789
- "F3",
2790
- "F4",
2791
- "F5",
2792
- "F6",
2793
- "F7",
2794
- "F8",
2795
- "F9",
2796
- "F10",
2797
- "F11",
2798
- "F12",
2799
- "Soft1",
2800
- "Soft2",
2801
- "Soft3",
2802
- "Soft4",
2803
- "ChannelDown",
2804
- "ChannelUp",
2805
- "Close",
2806
- "MailForward",
2807
- "MailReply",
2808
- "MailSend",
2809
- "MediaFastForward",
2810
- "MediaPause",
2811
- "MediaPlay",
2812
- "MediaPlayPause",
2813
- "MediaRecord",
2814
- "MediaRewind",
2815
- "MediaStop",
2816
- "MediaTrackNext",
2817
- "MediaTrackPrevious",
2818
- "AudioBalanceLeft",
2819
- "AudioBalanceRight",
2820
- "AudioBassBoostDown",
2821
- "AudioBassBoostToggle",
2822
- "AudioBassBoostUp",
2823
- "AudioFaderFront",
2824
- "AudioFaderRear",
2825
- "AudioSurroundModeNext",
2826
- "AudioTrebleDown",
2827
- "AudioTrebleUp",
2828
- "AudioVolumeDown",
2829
- "AudioVolumeMute",
2830
- "AudioVolumeUp",
2831
- "MicrophoneToggle",
2832
- "MicrophoneVolumeDown",
2833
- "MicrophoneVolumeMute",
2834
- "MicrophoneVolumeUp",
2835
- "TV",
2836
- "TV3DMode",
2837
- "TVAntennaCable",
2838
- "TVAudioDescription",
2839
- ];
2840
- function unEscapeString(str) {
2841
- const placeholder = "__NEWLINE__";
2842
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2843
- return str;
2844
- }
2845
2898
  export { StableBrowser };
2846
2899
  //# sourceMappingURL=stable_browser.js.map