automation_model 1.0.499-dev → 1.0.499-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 +637 -580
  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
  }
@@ -352,121 +336,51 @@ class StableBrowser {
352
336
  climbArray.push("..");
353
337
  }
354
338
  let climbXpath = "xpath=" + climbArray.join("/");
355
- return textElementCss + " >> " + climbXpath + " >> " + css;
339
+ let resultCss = textElementCss + " >> " + climbXpath;
340
+ if (css) {
341
+ resultCss = resultCss + " >> " + css;
342
+ }
343
+ return resultCss;
356
344
  }
357
345
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
358
346
  //const stringifyText = JSON.stringify(text);
359
347
  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 = [];
348
+ const options = {
349
+ innerText: true,
350
+ };
416
351
  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
- }
352
+ options.singleRegex = true;
427
353
  }
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
- }
354
+ if (tag) {
355
+ options.tag = tag;
445
356
  }
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
- }
357
+ if (!(partial === true)) {
358
+ options.exactMatch = true;
359
+ }
360
+ if (text.startsWith("/") && text.endsWith("/")) {
361
+ if (text.length < 3) {
362
+ throw new Error("Invalid regex pattern: empty pattern");
458
363
  }
459
- if (!hasChild) {
460
- noChildElements.push(element);
364
+ try {
365
+ const pattern = text.slice(1, -1);
366
+ new RegExp(pattern); // Validate pattern
367
+ text = pattern;
368
+ options.singleRegex = true;
369
+ }
370
+ catch (e) {
371
+ throw new Error(`Invalid regex pattern: ${e.message}`);
461
372
  }
462
373
  }
374
+ const elements = window.findMatchingElements(text, options);
375
+ let randomToken = null;
376
+ const foundElements = [];
463
377
  let elementCount = 0;
464
- if (noChildElements.length > 0) {
465
- for (let i = 0; i < noChildElements.length; i++) {
378
+ if (elements.length > 0) {
379
+ for (let i = 0; i < elements.length; i++) {
466
380
  if (randomToken === null) {
467
381
  randomToken = Math.random().toString(36).substring(7);
468
382
  }
469
- let element = noChildElements[i];
383
+ let element = elements[i];
470
384
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
471
385
  elementCount++;
472
386
  }
@@ -475,9 +389,21 @@ class StableBrowser {
475
389
  }, [text1, tag1, regex1, partial1]);
476
390
  }
477
391
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
392
+ if (!info) {
393
+ info = {};
394
+ }
395
+ if (!info.failCause) {
396
+ info.failCause = {};
397
+ }
398
+ if (!info.log) {
399
+ info.log = "";
400
+ info.locatorLog = new LocatorLog(selectorHierarchy);
401
+ }
478
402
  let locatorSearch = selectorHierarchy[index];
403
+ let originalLocatorSearch = "";
479
404
  try {
480
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
405
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
406
+ locatorSearch = JSON.parse(originalLocatorSearch);
481
407
  }
482
408
  catch (e) {
483
409
  console.error(e);
@@ -487,36 +413,50 @@ class StableBrowser {
487
413
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
488
414
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
489
415
  if (!locatorString) {
416
+ info.failCause.textNotFound = true;
417
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
490
418
  return;
491
419
  }
492
- locator = this._getLocator({ css: locatorString }, scope, _params);
420
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
493
421
  }
494
422
  else if (locatorSearch.text) {
495
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
423
+ let text = _fixUsingParams(locatorSearch.text, _params);
424
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
496
425
  if (result.elementCount === 0) {
426
+ info.failCause.textNotFound = true;
427
+ info.failCause.lastError = "failed to locate element by text: " + text;
497
428
  return;
498
429
  }
499
430
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
500
431
  if (locatorSearch.childCss) {
501
432
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
502
433
  }
503
- locator = this._getLocator(locatorSearch, scope, _params);
434
+ locator = await this._getLocator(locatorSearch, scope, _params);
504
435
  }
505
436
  else {
506
- locator = this._getLocator(locatorSearch, scope, _params);
437
+ locator = await this._getLocator(locatorSearch, scope, _params);
507
438
  }
508
439
  // let cssHref = false;
509
440
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
510
441
  // cssHref = true;
511
442
  // }
512
443
  let count = await locator.count();
444
+ if (count > 0 && !info.failCause.count) {
445
+ info.failCause.count = count;
446
+ }
513
447
  //info.log += "total elements found " + count + "\n";
514
448
  //let visibleCount = 0;
515
449
  let visibleLocator = null;
516
- if (locatorSearch.index && locatorSearch.index < count) {
450
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
517
451
  foundLocators.push(locator.nth(locatorSearch.index));
452
+ if (info.locatorLog) {
453
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
454
+ }
518
455
  return;
519
456
  }
457
+ if (info.locatorLog && count === 0) {
458
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
459
+ }
520
460
  for (let j = 0; j < count; j++) {
521
461
  let visible = await locator.nth(j).isVisible();
522
462
  const enabled = await locator.nth(j).isEnabled();
@@ -525,24 +465,40 @@ class StableBrowser {
525
465
  }
526
466
  if (visible && enabled) {
527
467
  foundLocators.push(locator.nth(j));
468
+ if (info.locatorLog) {
469
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
470
+ }
528
471
  }
529
472
  else {
473
+ info.failCause.visible = visible;
474
+ info.failCause.enabled = enabled;
530
475
  if (!info.printMessages) {
531
476
  info.printMessages = {};
532
477
  }
478
+ if (info.locatorLog && !visible) {
479
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
480
+ }
481
+ if (info.locatorLog && !enabled) {
482
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
483
+ }
533
484
  if (!info.printMessages[j.toString()]) {
534
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
485
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
535
486
  info.printMessages[j.toString()] = true;
536
487
  }
537
488
  }
538
489
  }
539
490
  }
540
491
  async closeUnexpectedPopups(info, _params) {
492
+ if (!info) {
493
+ info = {};
494
+ info.failCause = {};
495
+ info.log = "";
496
+ }
541
497
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
542
498
  if (!info) {
543
499
  info = {};
544
500
  }
545
- info.log += "scan for popup handlers" + "\n";
501
+ //info.log += "scan for popup handlers" + "\n";
546
502
  const handlerGroup = [];
547
503
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
548
504
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -569,9 +525,21 @@ class StableBrowser {
569
525
  }
570
526
  if (result.foundElements.length > 0) {
571
527
  let dialogCloseLocator = result.foundElements[0].locator;
572
- await dialogCloseLocator.click();
573
- // wait for the dialog to close
574
- await dialogCloseLocator.waitFor({ state: "hidden" });
528
+ try {
529
+ await scope?.evaluate(() => {
530
+ window.__isClosingPopups = true;
531
+ });
532
+ await dialogCloseLocator.click();
533
+ // wait for the dialog to close
534
+ await dialogCloseLocator.waitFor({ state: "hidden" });
535
+ }
536
+ catch (e) {
537
+ }
538
+ finally {
539
+ await scope?.evaluate(() => {
540
+ window.__isClosingPopups = false;
541
+ });
542
+ }
575
543
  return { rerun: true };
576
544
  }
577
545
  }
@@ -595,7 +563,13 @@ class StableBrowser {
595
563
  }
596
564
  throw new Error("unable to locate element " + JSON.stringify(selectors));
597
565
  }
598
- async _findFrameScope(selectors, timeout = 30000) {
566
+ async _findFrameScope(selectors, timeout = 30000, info) {
567
+ if (!info) {
568
+ info = {};
569
+ info.failCause = {};
570
+ info.log = "";
571
+ }
572
+ let startTime = Date.now();
599
573
  let scope = this.page;
600
574
  if (selectors.frame) {
601
575
  return selectors.frame;
@@ -626,9 +600,11 @@ class StableBrowser {
626
600
  }
627
601
  return framescope;
628
602
  };
603
+ let fLocator = null;
629
604
  while (true) {
630
605
  let frameFound = false;
631
606
  if (selectors.nestFrmLoc) {
607
+ fLocator = selectors.nestFrmLoc;
632
608
  scope = await findFrame(selectors.nestFrmLoc, scope);
633
609
  frameFound = true;
634
610
  break;
@@ -637,6 +613,7 @@ class StableBrowser {
637
613
  for (let i = 0; i < selectors.frameLocators.length; i++) {
638
614
  let frameLocator = selectors.frameLocators[i];
639
615
  if (frameLocator.css) {
616
+ fLocator = frameLocator.css;
640
617
  scope = scope.frameLocator(frameLocator.css);
641
618
  frameFound = true;
642
619
  break;
@@ -644,16 +621,25 @@ class StableBrowser {
644
621
  }
645
622
  }
646
623
  if (!frameFound && selectors.iframe_src) {
624
+ fLocator = selectors.iframe_src;
647
625
  scope = this.page.frame({ url: selectors.iframe_src });
648
626
  }
649
627
  if (!scope) {
650
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
651
- if (performance.now() - startTime > timeout) {
628
+ if (info && info.locatorLog) {
629
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
630
+ }
631
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
632
+ if (Date.now() - startTime > timeout) {
633
+ info.failCause.iframeNotFound = true;
634
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
652
635
  throw new Error("unable to locate iframe " + selectors.iframe_src);
653
636
  }
654
637
  await new Promise((resolve) => setTimeout(resolve, 1000));
655
638
  }
656
639
  else {
640
+ if (info && info.locatorLog) {
641
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
642
+ }
657
643
  break;
658
644
  }
659
645
  }
@@ -663,20 +649,27 @@ class StableBrowser {
663
649
  }
664
650
  return scope;
665
651
  }
666
- async _getDocumentBody(selectors, timeout = 30000) {
667
- let scope = await this._findFrameScope(selectors, timeout);
652
+ async _getDocumentBody(selectors, timeout = 30000, info) {
653
+ let scope = await this._findFrameScope(selectors, timeout, info);
668
654
  return scope.evaluate(() => {
669
655
  var bodyContent = document.body.innerHTML;
670
656
  return bodyContent;
671
657
  });
672
658
  }
673
659
  async _locate_internal(selectors, info, _params, timeout = 30000) {
660
+ if (!info) {
661
+ info = {};
662
+ info.failCause = {};
663
+ info.log = "";
664
+ info.locatorLog = new LocatorLog(selectors);
665
+ }
674
666
  let highPriorityTimeout = 5000;
675
667
  let visibleOnlyTimeout = 6000;
676
- let startTime = performance.now();
668
+ let startTime = Date.now();
677
669
  let locatorsCount = 0;
670
+ let lazy_scroll = false;
678
671
  //let arrayMode = Array.isArray(selectors);
679
- let scope = await this._findFrameScope(selectors, timeout);
672
+ let scope = await this._findFrameScope(selectors, timeout, info);
680
673
  let selectorsLocators = null;
681
674
  selectorsLocators = selectors.locators;
682
675
  // group selectors by priority
@@ -763,21 +756,33 @@ class StableBrowser {
763
756
  return maxCountElement.locator;
764
757
  }
765
758
  }
766
- if (performance.now() - startTime > timeout) {
759
+ if (Date.now() - startTime > timeout) {
767
760
  break;
768
761
  }
769
- if (performance.now() - startTime > highPriorityTimeout) {
762
+ if (Date.now() - startTime > highPriorityTimeout) {
770
763
  info.log += "high priority timeout, will try all elements" + "\n";
771
764
  highPriorityOnly = false;
765
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
766
+ lazy_scroll = true;
767
+ await scrollPageToLoadLazyElements(this.page);
768
+ }
772
769
  }
773
- if (performance.now() - startTime > visibleOnlyTimeout) {
770
+ if (Date.now() - startTime > visibleOnlyTimeout) {
774
771
  info.log += "visible only timeout, will try all elements" + "\n";
775
772
  visibleOnly = false;
776
773
  }
777
774
  await new Promise((resolve) => setTimeout(resolve, 1000));
778
775
  }
779
776
  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";
777
+ // if (info.locatorLog) {
778
+ // const lines = info.locatorLog.toString().split("\n");
779
+ // for (let line of lines) {
780
+ // this.logger.debug(line);
781
+ // }
782
+ // }
783
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
784
+ info.failCause.locatorNotFound = true;
785
+ info.failCause.lastError = "failed to locate unique element";
781
786
  throw new Error("failed to locate first element no elements found, " + info.log);
782
787
  }
783
788
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -791,8 +796,9 @@ class StableBrowser {
791
796
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
792
797
  }
793
798
  catch (e) {
794
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
795
- this.logger.debug(e);
799
+ // this call can fail it the browser is navigating
800
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
801
+ // this.logger.debug(e);
796
802
  foundLocators = [];
797
803
  try {
798
804
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -809,10 +815,29 @@ class StableBrowser {
809
815
  });
810
816
  result.locatorIndex = i;
811
817
  }
818
+ if (foundLocators.length > 1) {
819
+ info.failCause.foundMultiple = true;
820
+ if (info.locatorLog) {
821
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
822
+ }
823
+ }
812
824
  }
813
825
  return result;
814
826
  }
815
827
  async simpleClick(elementDescription, _params, options = {}, world = null) {
828
+ const state = {
829
+ locate: false,
830
+ scroll: false,
831
+ highlight: false,
832
+ _params,
833
+ options,
834
+ world,
835
+ type: Types.CLICK,
836
+ text: "Click element",
837
+ operation: "simpleClick",
838
+ log: "***** click on " + elementDescription + " *****\n",
839
+ };
840
+ _preCommand(state, this);
816
841
  const startTime = Date.now();
817
842
  let timeout = 30000;
818
843
  if (options && options.timeout) {
@@ -836,13 +861,32 @@ class StableBrowser {
836
861
  }
837
862
  catch (e) {
838
863
  if (performance.now() - startTime > timeout) {
839
- throw e;
864
+ // throw e;
865
+ try {
866
+ await _commandError(state, "timeout looking for " + elementDescription, this);
867
+ }
868
+ finally {
869
+ _commandFinally(state, this);
870
+ }
840
871
  }
841
872
  }
842
873
  await new Promise((resolve) => setTimeout(resolve, 3000));
843
874
  }
844
875
  }
845
876
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
877
+ const state = {
878
+ locate: false,
879
+ scroll: false,
880
+ highlight: false,
881
+ _params,
882
+ options,
883
+ world,
884
+ type: Types.FILL,
885
+ text: "Fill element",
886
+ operation: "simpleClickType",
887
+ log: "***** click type on " + elementDescription + " *****\n",
888
+ };
889
+ _preCommand(state, this);
846
890
  const startTime = Date.now();
847
891
  let timeout = 30000;
848
892
  if (options && options.timeout) {
@@ -866,7 +910,13 @@ class StableBrowser {
866
910
  }
867
911
  catch (e) {
868
912
  if (performance.now() - startTime > timeout) {
869
- throw e;
913
+ // throw e;
914
+ try {
915
+ await _commandError(state, "timeout looking for " + elementDescription, this);
916
+ }
917
+ finally {
918
+ _commandFinally(state, this);
919
+ }
870
920
  }
871
921
  }
872
922
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -885,18 +935,18 @@ class StableBrowser {
885
935
  };
886
936
  try {
887
937
  await _preCommand(state, this);
888
- if (state.options && state.options.context) {
889
- state.selectors.locators[0].text = state.options.context;
890
- }
938
+ // if (state.options && state.options.context) {
939
+ // state.selectors.locators[0].text = state.options.context;
940
+ // }
891
941
  try {
892
942
  await state.element.click();
893
- await new Promise((resolve) => setTimeout(resolve, 1000));
943
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
894
944
  }
895
945
  catch (e) {
896
946
  // await this.closeUnexpectedPopups();
897
947
  state.element = await this._locate(selectors, state.info, _params);
898
948
  await state.element.dispatchEvent("click");
899
- await new Promise((resolve) => setTimeout(resolve, 1000));
949
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
900
950
  }
901
951
  await this.waitForPageLoad();
902
952
  return state.info;
@@ -975,7 +1025,6 @@ class StableBrowser {
975
1025
  await state.element.hover({ timeout: 10000 });
976
1026
  await new Promise((resolve) => setTimeout(resolve, 1000));
977
1027
  }
978
- await _screenshot(state, this);
979
1028
  await this.waitForPageLoad();
980
1029
  return state.info;
981
1030
  }
@@ -1103,33 +1152,30 @@ class StableBrowser {
1103
1152
  }
1104
1153
  }
1105
1154
  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;
1155
+ const state = {
1156
+ selectors,
1157
+ _params,
1158
+ value: await this._replaceWithLocalData(value, this),
1159
+ options,
1160
+ world,
1161
+ type: Types.SET_DATE_TIME,
1162
+ text: `Set date time value: ${value}`,
1163
+ operation: "setDateTime",
1164
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1165
+ throwError: false,
1166
+ };
1116
1167
  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);
1168
+ await _preCommand(state, this);
1123
1169
  try {
1124
- await element.click();
1170
+ await state.element.click();
1125
1171
  await new Promise((resolve) => setTimeout(resolve, 500));
1126
1172
  if (format) {
1127
- value = dayjs(value).format(format);
1128
- await element.fill(value);
1173
+ state.value = dayjs(state.value).format(format);
1174
+ await state.element.fill(state.value);
1129
1175
  }
1130
1176
  else {
1131
- const dateTimeValue = await getDateTimeValue({ value, element });
1132
- await element.evaluateHandle((el, dateTimeValue) => {
1177
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1178
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1133
1179
  el.value = ""; // clear input
1134
1180
  el.value = dateTimeValue;
1135
1181
  }, dateTimeValue);
@@ -1142,20 +1188,19 @@ class StableBrowser {
1142
1188
  }
1143
1189
  catch (err) {
1144
1190
  //await this.closeUnexpectedPopups();
1145
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1191
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1146
1192
  this.logger.info("Trying again");
1147
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1148
- info.screenshotPath = screenshotPath;
1149
- Object.assign(err, { info: info });
1193
+ await _screenshot(state, this);
1194
+ Object.assign(err, { info: state.info });
1150
1195
  await element.click();
1151
1196
  await new Promise((resolve) => setTimeout(resolve, 500));
1152
1197
  if (format) {
1153
- value = dayjs(value).format(format);
1154
- await element.fill(value);
1198
+ state.value = dayjs(state.value).format(format);
1199
+ await state.element.fill(state.value);
1155
1200
  }
1156
1201
  else {
1157
- const dateTimeValue = await getDateTimeValue({ value, element });
1158
- await element.evaluateHandle((el, dateTimeValue) => {
1202
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1203
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1159
1204
  el.value = ""; // clear input
1160
1205
  el.value = dateTimeValue;
1161
1206
  }, dateTimeValue);
@@ -1168,31 +1213,10 @@ class StableBrowser {
1168
1213
  }
1169
1214
  }
1170
1215
  catch (e) {
1171
- error = e;
1172
- throw e;
1216
+ await _commandError(state, e, this);
1173
1217
  }
1174
1218
  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
- });
1219
+ _commandFinally(state, this);
1196
1220
  }
1197
1221
  }
1198
1222
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1329,6 +1353,7 @@ class StableBrowser {
1329
1353
  let screenshotPath = null;
1330
1354
  if (!info.log) {
1331
1355
  info.log = "";
1356
+ info.locatorLog = new LocatorLog(selectors);
1332
1357
  }
1333
1358
  info.operation = "getText";
1334
1359
  info.selectors = selectors;
@@ -1667,11 +1692,9 @@ class StableBrowser {
1667
1692
  if (!fs.existsSync(world.screenshotPath)) {
1668
1693
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1669
1694
  }
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");
1695
+ // to make sure the path doesn't start with -
1696
+ const uuidStr = "id_" + randomUUID();
1697
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1675
1698
  try {
1676
1699
  await this.takeScreenshot(screenshotPath);
1677
1700
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1685,7 +1708,7 @@ class StableBrowser {
1685
1708
  catch (e) {
1686
1709
  this.logger.info("unable to take screenshot, ignored");
1687
1710
  }
1688
- result.screenshotId = nextIndex;
1711
+ result.screenshotId = uuidStr;
1689
1712
  result.screenshotPath = screenshotPath;
1690
1713
  if (info && info.box) {
1691
1714
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1783,74 +1806,105 @@ class StableBrowser {
1783
1806
  }
1784
1807
  }
1785
1808
  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;
1809
+ const state = {
1810
+ selectors,
1811
+ _params,
1812
+ attribute,
1813
+ variable,
1814
+ options,
1815
+ world,
1816
+ type: Types.EXTRACT,
1817
+ text: `Extract attribute from element`,
1818
+ operation: "extractAttribute",
1819
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1820
+ };
1791
1821
  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
1822
  try {
1797
- const element = await this._locate(selectors, info, _params);
1798
- await this._highlightElements(element);
1799
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1823
+ await _preCommand(state, this);
1800
1824
  switch (attribute) {
1801
1825
  case "inner_text":
1802
- info.value = await element.innerText();
1826
+ state.value = await state.element.innerText();
1803
1827
  break;
1804
1828
  case "href":
1805
- info.value = await element.getAttribute("href");
1829
+ state.value = await state.element.getAttribute("href");
1830
+ break;
1831
+ case "value":
1832
+ state.value = await state.element.inputValue();
1833
+ break;
1834
+ default:
1835
+ state.value = await state.element.getAttribute(attribute);
1836
+ break;
1837
+ }
1838
+ state.info.value = state.value;
1839
+ this.setTestData({ [variable]: state.value }, world);
1840
+ this.logger.info("set test data: " + variable + "=" + state.value);
1841
+ return state.info;
1842
+ }
1843
+ catch (e) {
1844
+ await _commandError(state, e, this);
1845
+ }
1846
+ finally {
1847
+ _commandFinally(state, this);
1848
+ }
1849
+ }
1850
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1851
+ const state = {
1852
+ selectors,
1853
+ _params,
1854
+ attribute,
1855
+ value,
1856
+ options,
1857
+ world,
1858
+ type: Types.VERIFY_ATTRIBUTE,
1859
+ text: `Verify element attribute`,
1860
+ operation: "verifyAttribute",
1861
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1862
+ };
1863
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1864
+ let val;
1865
+ try {
1866
+ await _preCommand(state, this);
1867
+ switch (attribute) {
1868
+ case "innerText":
1869
+ val = String(await state.element.innerText());
1806
1870
  break;
1807
1871
  case "value":
1808
- info.value = await element.inputValue();
1872
+ val = String(await state.element.inputValue());
1873
+ break;
1874
+ case "checked":
1875
+ val = String(await state.element.isChecked());
1876
+ break;
1877
+ case "disabled":
1878
+ val = String(await state.element.isDisabled());
1879
+ break;
1880
+ case "readOnly":
1881
+ const isEditable = await state.element.isEditable();
1882
+ val = String(!isEditable);
1809
1883
  break;
1810
1884
  default:
1811
- info.value = await element.getAttribute(attribute);
1885
+ val = String(await state.element.getAttribute(attribute));
1812
1886
  break;
1813
1887
  }
1814
- this[variable] = info.value;
1815
- if (world) {
1816
- world[variable] = info.value;
1888
+ state.info.expectedValue = val;
1889
+ let regex;
1890
+ if (value.startsWith("/") && value.endsWith("/")) {
1891
+ const patternBody = value.slice(1, -1);
1892
+ regex = new RegExp(patternBody, "g");
1817
1893
  }
1818
- this.setTestData({ [variable]: info.value }, world);
1819
- this.logger.info("set test data: " + variable + "=" + info.value);
1820
- return info;
1894
+ else {
1895
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1896
+ regex = new RegExp(escapedPattern, "g");
1897
+ }
1898
+ if (!val.match(regex)) {
1899
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1900
+ }
1901
+ return state.info;
1821
1902
  }
1822
1903
  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;
1904
+ await _commandError(state, e, this);
1830
1905
  }
1831
1906
  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
- });
1907
+ _commandFinally(state, this);
1854
1908
  }
1855
1909
  }
1856
1910
  async extractEmailData(emailAddress, options, world) {
@@ -1871,7 +1925,7 @@ class StableBrowser {
1871
1925
  if (options && options.timeout) {
1872
1926
  timeout = options.timeout;
1873
1927
  }
1874
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1928
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1875
1929
  const request = {
1876
1930
  method: "POST",
1877
1931
  url: serviceUrl,
@@ -1927,7 +1981,8 @@ class StableBrowser {
1927
1981
  catch (e) {
1928
1982
  errorCount++;
1929
1983
  if (errorCount > 3) {
1930
- throw e;
1984
+ // throw e;
1985
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1931
1986
  }
1932
1987
  // ignore
1933
1988
  }
@@ -2038,11 +2093,12 @@ class StableBrowser {
2038
2093
  info.screenshotPath = screenshotPath;
2039
2094
  Object.assign(e, { info: info });
2040
2095
  error = e;
2041
- throw e;
2096
+ // throw e;
2097
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2042
2098
  }
2043
2099
  finally {
2044
2100
  const endTime = Date.now();
2045
- this._reportToWorld(world, {
2101
+ _reportToWorld(world, {
2046
2102
  type: Types.VERIFY_PAGE_PATH,
2047
2103
  text: "Verify page path",
2048
2104
  screenshotId,
@@ -2062,54 +2118,64 @@ class StableBrowser {
2062
2118
  });
2063
2119
  }
2064
2120
  }
2121
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2122
+ const frames = this.page.frames();
2123
+ let results = [];
2124
+ for (let i = 0; i < frames.length; i++) {
2125
+ if (dateAlternatives.date) {
2126
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2127
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2128
+ result.frame = frames[i];
2129
+ results.push(result);
2130
+ }
2131
+ }
2132
+ else if (numberAlternatives.number) {
2133
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2134
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2135
+ result.frame = frames[i];
2136
+ results.push(result);
2137
+ }
2138
+ }
2139
+ else {
2140
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2141
+ result.frame = frames[i];
2142
+ results.push(result);
2143
+ }
2144
+ }
2145
+ state.info.results = results;
2146
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2147
+ return resultWithElementsFound;
2148
+ }
2065
2149
  async verifyTextExistInPage(text, options = {}, world = null) {
2066
2150
  text = unEscapeString(text);
2067
- const startTime = Date.now();
2151
+ const state = {
2152
+ text_search: text,
2153
+ options,
2154
+ world,
2155
+ locate: false,
2156
+ scroll: false,
2157
+ highlight: false,
2158
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2159
+ text: `Verify text exists in page`,
2160
+ operation: "verifyTextExistInPage",
2161
+ log: "***** verify text " + text + " exists in page *****\n",
2162
+ };
2068
2163
  const timeout = this._getLoadTimeout(options);
2069
- let error = null;
2070
- let screenshotId = null;
2071
- let screenshotPath = null;
2072
2164
  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
2165
  const newValue = await this._replaceWithLocalData(text, world);
2077
2166
  if (newValue !== text) {
2078
2167
  this.logger.info(text + "=" + newValue);
2079
2168
  text = newValue;
2080
2169
  }
2081
- info.text = text;
2082
2170
  let dateAlternatives = findDateAlternatives(text);
2083
2171
  let numberAlternatives = findNumberAlternatives(text);
2084
2172
  try {
2173
+ await _preCommand(state, this);
2174
+ state.info.text = text;
2085
2175
  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);
2176
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2111
2177
  if (resultWithElementsFound.length === 0) {
2112
- if (Date.now() - startTime > timeout) {
2178
+ if (Date.now() - state.startTime > timeout) {
2113
2179
  throw new Error(`Text ${text} not found in page`);
2114
2180
  }
2115
2181
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2119,57 +2185,165 @@ class StableBrowser {
2119
2185
  const frame = resultWithElementsFound[0].frame;
2120
2186
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2121
2187
  await this._highlightElements(frame, dataAttribute);
2122
- const element = await frame.$(dataAttribute);
2188
+ const element = await frame.locator(dataAttribute).first();
2123
2189
  if (element) {
2124
- await this.scrollIfNeeded(element, info);
2190
+ await this.scrollIfNeeded(element, state.info);
2125
2191
  await element.dispatchEvent("bvt_verify_page_contains_text");
2126
2192
  }
2127
2193
  }
2128
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2129
- return info;
2194
+ await _screenshot(state, this);
2195
+ return state.info;
2130
2196
  }
2131
2197
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2132
2198
  }
2133
2199
  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;
2200
+ await _commandError(state, e, this);
2141
2201
  }
2142
2202
  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
- });
2203
+ _commandFinally(state, this);
2162
2204
  }
2163
2205
  }
2164
- _getServerUrl() {
2165
- let serviceUrl = "https://api.blinq.io";
2166
- if (process.env.NODE_ENV_BLINQ === "dev") {
2167
- serviceUrl = "https://dev.api.blinq.io";
2206
+ async waitForTextToDisappear(text, options = {}, world = null) {
2207
+ text = unEscapeString(text);
2208
+ const state = {
2209
+ text_search: text,
2210
+ options,
2211
+ world,
2212
+ locate: false,
2213
+ scroll: false,
2214
+ highlight: false,
2215
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2216
+ text: `Verify text does not exist in page`,
2217
+ operation: "verifyTextNotExistInPage",
2218
+ log: "***** verify text " + text + " does not exist in page *****\n",
2219
+ };
2220
+ const timeout = this._getLoadTimeout(options);
2221
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2222
+ const newValue = await this._replaceWithLocalData(text, world);
2223
+ if (newValue !== text) {
2224
+ this.logger.info(text + "=" + newValue);
2225
+ text = newValue;
2226
+ }
2227
+ let dateAlternatives = findDateAlternatives(text);
2228
+ let numberAlternatives = findNumberAlternatives(text);
2229
+ try {
2230
+ await _preCommand(state, this);
2231
+ state.info.text = text;
2232
+ while (true) {
2233
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2234
+ if (resultWithElementsFound.length === 0) {
2235
+ await _screenshot(state, this);
2236
+ return state.info;
2237
+ }
2238
+ if (Date.now() - state.startTime > timeout) {
2239
+ throw new Error(`Text ${text} found in page`);
2240
+ }
2241
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2242
+ }
2168
2243
  }
2169
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2170
- serviceUrl = "https://stage.api.blinq.io";
2244
+ catch (e) {
2245
+ await _commandError(state, e, this);
2246
+ }
2247
+ finally {
2248
+ _commandFinally(state, this);
2249
+ }
2250
+ }
2251
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2252
+ textAnchor = unEscapeString(textAnchor);
2253
+ textToVerify = unEscapeString(textToVerify);
2254
+ const state = {
2255
+ text_search: textToVerify,
2256
+ options,
2257
+ world,
2258
+ locate: false,
2259
+ scroll: false,
2260
+ highlight: false,
2261
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2262
+ text: `Verify text with relation to another text`,
2263
+ operation: "verify_text_with_relation",
2264
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2265
+ };
2266
+ const timeout = this._getLoadTimeout(options);
2267
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2268
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2269
+ if (newValue !== textAnchor) {
2270
+ this.logger.info(textAnchor + "=" + newValue);
2271
+ textAnchor = newValue;
2272
+ }
2273
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2274
+ if (newValue !== textToVerify) {
2275
+ this.logger.info(textToVerify + "=" + newValue);
2276
+ textToVerify = newValue;
2277
+ }
2278
+ let dateAlternatives = findDateAlternatives(textToVerify);
2279
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2280
+ let foundAncore = false;
2281
+ try {
2282
+ await _preCommand(state, this);
2283
+ state.info.text = textToVerify;
2284
+ while (true) {
2285
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2286
+ if (resultWithElementsFound.length === 0) {
2287
+ if (Date.now() - state.startTime > timeout) {
2288
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2289
+ }
2290
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2291
+ continue;
2292
+ }
2293
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2294
+ foundAncore = true;
2295
+ const result = resultWithElementsFound[i];
2296
+ const token = result.randomToken;
2297
+ const frame = result.frame;
2298
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2299
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2300
+ const elements = Array.from(document.querySelectorAll(css));
2301
+ for (let i = 0; i < elements.length; i++) {
2302
+ const element = elements[i];
2303
+ let climbParent = element;
2304
+ for (let j = 0; j < climb; j++) {
2305
+ climbParent = climbParent.parentElement;
2306
+ if (!climbParent) {
2307
+ break;
2308
+ }
2309
+ }
2310
+ if (!climbParent) {
2311
+ continue;
2312
+ }
2313
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2314
+ if (foundElements.length > 0) {
2315
+ // set the container element attribute
2316
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2317
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2318
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2319
+ return { found: true };
2320
+ }
2321
+ }
2322
+ return { found: false };
2323
+ }, [css, climb, textToVerify, result.randomToken]);
2324
+ if (findResult.found === true) {
2325
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2326
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2327
+ await this._highlightElements(frame, dataAttribute);
2328
+ await this._highlightElements(frame, cssAnchor);
2329
+ const element = await frame.locator(dataAttribute).first();
2330
+ if (element) {
2331
+ await this.scrollIfNeeded(element, state.info);
2332
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2333
+ }
2334
+ await _screenshot(state, this);
2335
+ return state.info;
2336
+ }
2337
+ }
2338
+ }
2339
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2340
+ }
2341
+ catch (e) {
2342
+ await _commandError(state, e, this);
2343
+ }
2344
+ finally {
2345
+ _commandFinally(state, this);
2171
2346
  }
2172
- return serviceUrl;
2173
2347
  }
2174
2348
  async visualVerification(text, options = {}, world = null) {
2175
2349
  const startTime = Date.now();
@@ -2185,7 +2359,7 @@ class StableBrowser {
2185
2359
  throw new Error("TOKEN is not set");
2186
2360
  }
2187
2361
  try {
2188
- let serviceUrl = this._getServerUrl();
2362
+ let serviceUrl = _getServerUrl();
2189
2363
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2190
2364
  info.screenshotPath = screenshotPath;
2191
2365
  const screenshot = await this.takeScreenshot();
@@ -2221,11 +2395,12 @@ class StableBrowser {
2221
2395
  info.screenshotPath = screenshotPath;
2222
2396
  Object.assign(e, { info: info });
2223
2397
  error = e;
2224
- throw e;
2398
+ // throw e;
2399
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2225
2400
  }
2226
2401
  finally {
2227
2402
  const endTime = Date.now();
2228
- this._reportToWorld(world, {
2403
+ _reportToWorld(world, {
2229
2404
  type: Types.VERIFY_VISUAL,
2230
2405
  text: "Visual verification",
2231
2406
  screenshotId,
@@ -2273,6 +2448,7 @@ class StableBrowser {
2273
2448
  let screenshotPath = null;
2274
2449
  const info = {};
2275
2450
  info.log = "";
2451
+ info.locatorLog = new LocatorLog(selectors);
2276
2452
  info.operation = "getTableData";
2277
2453
  info.selectors = selectors;
2278
2454
  try {
@@ -2288,11 +2464,12 @@ class StableBrowser {
2288
2464
  info.screenshotPath = screenshotPath;
2289
2465
  Object.assign(e, { info: info });
2290
2466
  error = e;
2291
- throw e;
2467
+ // throw e;
2468
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2292
2469
  }
2293
2470
  finally {
2294
2471
  const endTime = Date.now();
2295
- this._reportToWorld(world, {
2472
+ _reportToWorld(world, {
2296
2473
  element_name: selectors.element_name,
2297
2474
  type: Types.GET_TABLE_DATA,
2298
2475
  text: "Get table data",
@@ -2347,7 +2524,7 @@ class StableBrowser {
2347
2524
  info.operation = "analyzeTable";
2348
2525
  info.selectors = selectors;
2349
2526
  info.query = query;
2350
- query = this._fixUsingParams(query, _params);
2527
+ query = _fixUsingParams(query, _params);
2351
2528
  info.query_fixed = query;
2352
2529
  info.operator = operator;
2353
2530
  info.value = value;
@@ -2453,11 +2630,12 @@ class StableBrowser {
2453
2630
  info.screenshotPath = screenshotPath;
2454
2631
  Object.assign(e, { info: info });
2455
2632
  error = e;
2456
- throw e;
2633
+ // throw e;
2634
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2457
2635
  }
2458
2636
  finally {
2459
2637
  const endTime = Date.now();
2460
- this._reportToWorld(world, {
2638
+ _reportToWorld(world, {
2461
2639
  element_name: selectors.element_name,
2462
2640
  type: Types.ANALYZE_TABLE,
2463
2641
  text: "Analyze table",
@@ -2530,7 +2708,7 @@ class StableBrowser {
2530
2708
  await new Promise((resolve) => setTimeout(resolve, 2000));
2531
2709
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2532
2710
  const endTime = Date.now();
2533
- this._reportToWorld(world, {
2711
+ _reportToWorld(world, {
2534
2712
  type: Types.GET_PAGE_STATUS,
2535
2713
  text: "Wait for page load",
2536
2714
  screenshotId,
@@ -2550,41 +2728,35 @@ class StableBrowser {
2550
2728
  }
2551
2729
  }
2552
2730
  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 = {};
2731
+ const state = {
2732
+ options,
2733
+ world,
2734
+ locate: false,
2735
+ scroll: false,
2736
+ highlight: false,
2737
+ type: Types.CLOSE_PAGE,
2738
+ text: `Close page`,
2739
+ operation: "closePage",
2740
+ log: "***** close page *****\n",
2741
+ throwError: false,
2742
+ };
2558
2743
  try {
2744
+ await _preCommand(state, this);
2559
2745
  await this.page.close();
2560
2746
  }
2561
2747
  catch (e) {
2562
2748
  console.log(".");
2749
+ await _commandError(state, e, this);
2563
2750
  }
2564
2751
  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
- });
2752
+ _commandFinally(state, this);
2586
2753
  }
2587
2754
  }
2755
+ saveTestDataAsGlobal(options, world) {
2756
+ const dataFile = this._getDataFile(world);
2757
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2758
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2759
+ }
2588
2760
  async setViewportSize(width, hight, options = {}, world = null) {
2589
2761
  const startTime = Date.now();
2590
2762
  let error = null;
@@ -2602,12 +2774,13 @@ class StableBrowser {
2602
2774
  }
2603
2775
  catch (e) {
2604
2776
  console.log(".");
2777
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2605
2778
  }
2606
2779
  finally {
2607
2780
  await new Promise((resolve) => setTimeout(resolve, 2000));
2608
2781
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2609
2782
  const endTime = Date.now();
2610
- this._reportToWorld(world, {
2783
+ _reportToWorld(world, {
2611
2784
  type: Types.SET_VIEWPORT,
2612
2785
  text: "set viewport size to " + width + "x" + hight,
2613
2786
  screenshotId,
@@ -2638,12 +2811,13 @@ class StableBrowser {
2638
2811
  }
2639
2812
  catch (e) {
2640
2813
  console.log(".");
2814
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2641
2815
  }
2642
2816
  finally {
2643
2817
  await new Promise((resolve) => setTimeout(resolve, 2000));
2644
2818
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2645
2819
  const endTime = Date.now();
2646
- this._reportToWorld(world, {
2820
+ _reportToWorld(world, {
2647
2821
  type: Types.GET_PAGE_STATUS,
2648
2822
  text: "page relaod",
2649
2823
  screenshotId,
@@ -2679,11 +2853,45 @@ class StableBrowser {
2679
2853
  console.log("#-#");
2680
2854
  }
2681
2855
  }
2682
- _reportToWorld(world, properties) {
2683
- if (!world || !world.attach) {
2684
- return;
2856
+ async beforeStep(world, step) {
2857
+ if (this.stepIndex === undefined) {
2858
+ this.stepIndex = 0;
2859
+ }
2860
+ else {
2861
+ this.stepIndex++;
2862
+ }
2863
+ if (step && step.pickleStep && step.pickleStep.text) {
2864
+ this.stepName = step.pickleStep.text;
2865
+ this.logger.info("step: " + this.stepName);
2866
+ }
2867
+ else if (step && step.text) {
2868
+ this.stepName = step.text;
2869
+ }
2870
+ else {
2871
+ this.stepName = "step " + this.stepIndex;
2872
+ }
2873
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2874
+ if (this.context.browserObject.context) {
2875
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2876
+ }
2877
+ }
2878
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2879
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2880
+ // check if @global_test_data tag is present
2881
+ if (this.tags.includes("@global_test_data")) {
2882
+ this.saveTestDataAsGlobal({}, world);
2883
+ }
2884
+ }
2885
+ }
2886
+ async afterStep(world, step) {
2887
+ this.stepName = null;
2888
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2889
+ if (this.context.browserObject.context) {
2890
+ await this.context.browserObject.context.tracing.stopChunk({
2891
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2892
+ });
2893
+ }
2685
2894
  }
2686
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2687
2895
  }
2688
2896
  }
2689
2897
  function createTimedPromise(promise, label) {
@@ -2691,156 +2899,5 @@ function createTimedPromise(promise, label) {
2691
2899
  .then((result) => ({ status: "fulfilled", label, result }))
2692
2900
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2693
2901
  }
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
2902
  export { StableBrowser };
2846
2903
  //# sourceMappingURL=stable_browser.js.map