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