automation_model 1.0.496-dev → 1.0.496-stage

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