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