automation_model 1.0.482-dev → 1.0.482-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 +39 -17
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +6 -3
  8. package/lib/browser_manager.js +110 -16
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +59 -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 +31 -4
  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 +3 -0
  34. package/lib/network.js +155 -0
  35. package/lib/network.js.map +1 -0
  36. package/lib/scripts/find_text.js +126 -0
  37. package/lib/stable_browser.d.ts +49 -17
  38. package/lib/stable_browser.js +585 -600
  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 +14 -1
  47. package/lib/utils.js +302 -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 objectPath from "object-path";
14
- import { decrypt } from "./utils.js";
13
+ import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
15
14
  import csv from "csv-parser";
16
15
  import { Readable } from "node:stream";
17
16
  import readline from "readline";
18
17
  import { getContext } from "./init_browser.js";
19
18
  import { locate_element } from "./locate_element.js";
20
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
21
- const Types = {
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
+ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
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,23 +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);
103
+ registerNetworkEvents(this.world, this, this.context, this.page);
104
+ registerDownloadEvent(this.page, this.world, this.context);
95
105
  }
96
106
  registerEventListeners(context) {
97
107
  this.registerConsoleLogListener(this.page, context);
98
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
99
109
  if (!context.pageLoading) {
100
110
  context.pageLoading = { status: false };
101
111
  }
102
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
+ }
103
118
  context.pageLoading.status = true;
104
119
  this.page = page;
105
120
  context.page = page;
106
121
  context.pages.push(page);
122
+ registerNetworkEvents(this.world, this, context, this.page);
123
+ registerDownloadEvent(this.page, this.world, context);
107
124
  page.on("close", async () => {
108
125
  if (this.context && this.context.pages && this.context.pages.length > 1) {
109
126
  this.context.pages.pop();
@@ -133,9 +150,9 @@ class StableBrowser {
133
150
  if (this.appName === appName) {
134
151
  return;
135
152
  }
136
- let newContextCreated = false;
153
+ let navigate = false;
137
154
  if (!apps[appName]) {
138
- 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);
139
156
  newContextCreated = true;
140
157
  apps[appName] = {
141
158
  context: newContext,
@@ -144,32 +161,15 @@ class StableBrowser {
144
161
  };
145
162
  }
146
163
  const tempContext = {};
147
- this._copyContext(this, tempContext);
148
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
149
166
  apps[this.appName] = tempContext;
150
167
  this.appName = appName;
151
- if (newContextCreated) {
152
- this.registerEventListeners(this.context);
168
+ if (navigate) {
153
169
  await this.goto(this.context.environment.baseUrl);
154
170
  await this.waitForPageLoad();
155
171
  }
156
172
  }
157
- _copyContext(from, to) {
158
- to.browser = from.browser;
159
- to.page = from.page;
160
- to.context = from.context;
161
- }
162
- getWebLogFile(logFolder) {
163
- if (!fs.existsSync(logFolder)) {
164
- fs.mkdirSync(logFolder, { recursive: true });
165
- }
166
- let nextIndex = 1;
167
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
168
- nextIndex++;
169
- }
170
- const fileName = nextIndex + ".json";
171
- return path.join(logFolder, fileName);
172
- }
173
173
  registerConsoleLogListener(page, context) {
174
174
  if (!this.context.webLogger) {
175
175
  this.context.webLogger = [];
@@ -219,7 +219,7 @@ class StableBrowser {
219
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
220
220
  }
221
221
  catch (error) {
222
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
223
223
  context.networkLogger.push({
224
224
  error: "not able to listen",
225
225
  message: error.message,
@@ -241,47 +241,8 @@ class StableBrowser {
241
241
  timeout: 60000,
242
242
  });
243
243
  }
244
- _fixUsingParams(text, _params) {
245
- if (!_params || typeof text !== "string") {
246
- return text;
247
- }
248
- for (let key in _params) {
249
- let regValue = key;
250
- if (key.startsWith("_")) {
251
- // remove the _ prefix
252
- regValue = key.substring(1);
253
- }
254
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
255
- }
256
- return text;
257
- }
258
- _fixLocatorUsingParams(locator, _params) {
259
- // check if not null
260
- if (!locator) {
261
- return locator;
262
- }
263
- // clone the locator
264
- locator = JSON.parse(JSON.stringify(locator));
265
- this.scanAndManipulate(locator, _params);
266
- return locator;
267
- }
268
- _isObject(value) {
269
- return value && typeof value === "object" && value.constructor === Object;
270
- }
271
- scanAndManipulate(currentObj, _params) {
272
- for (const key in currentObj) {
273
- if (typeof currentObj[key] === "string") {
274
- // Perform string manipulation
275
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
276
- }
277
- else if (this._isObject(currentObj[key])) {
278
- // Recursively scan nested objects
279
- this.scanAndManipulate(currentObj[key], _params);
280
- }
281
- }
282
- }
283
244
  _getLocator(locator, scope, _params) {
284
- locator = this._fixLocatorUsingParams(locator, _params);
245
+ locator = _fixLocatorUsingParams(locator, _params);
285
246
  let locatorReturn;
286
247
  if (locator.role) {
287
248
  if (locator.role[1].nameReg) {
@@ -289,7 +250,7 @@ class StableBrowser {
289
250
  delete locator.role[1].nameReg;
290
251
  }
291
252
  // if (locator.role[1].name) {
292
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
253
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
293
254
  // }
294
255
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
295
256
  }
@@ -332,7 +293,7 @@ class StableBrowser {
332
293
  if (css && css.locator) {
333
294
  css = css.locator;
334
295
  }
335
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
296
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
336
297
  if (result.elementCount === 0) {
337
298
  return;
338
299
  }
@@ -348,116 +309,28 @@ class StableBrowser {
348
309
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
349
310
  //const stringifyText = JSON.stringify(text);
350
311
  return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
351
- function isParent(parent, child) {
352
- let currentNode = child.parentNode;
353
- while (currentNode !== null) {
354
- if (currentNode === parent) {
355
- return true;
356
- }
357
- currentNode = currentNode.parentNode;
358
- }
359
- return false;
360
- }
361
- document.isParent = isParent;
362
- function getRegex(str) {
363
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
364
- if (!match) {
365
- return null;
366
- }
367
- let [_, pattern, flags] = match;
368
- return new RegExp(pattern, flags);
369
- }
370
- document.getRegex = getRegex;
371
- function collectAllShadowDomElements(element, result = []) {
372
- // Check and add the element if it has a shadow root
373
- if (element.shadowRoot) {
374
- result.push(element);
375
- // Also search within the shadow root
376
- document.collectAllShadowDomElements(element.shadowRoot, result);
377
- }
378
- // Iterate over child nodes
379
- element.childNodes.forEach((child) => {
380
- // Recursively call the function for each child node
381
- document.collectAllShadowDomElements(child, result);
382
- });
383
- return result;
384
- }
385
- document.collectAllShadowDomElements = collectAllShadowDomElements;
386
- if (!tag) {
387
- tag = "*";
388
- }
389
- let regexpSearch = document.getRegex(text);
390
- if (regexpSearch) {
391
- regex = true;
392
- }
393
- let elements = Array.from(document.querySelectorAll(tag));
394
- let shadowHosts = [];
395
- document.collectAllShadowDomElements(document, shadowHosts);
396
- for (let i = 0; i < shadowHosts.length; i++) {
397
- let shadowElement = shadowHosts[i].shadowRoot;
398
- if (!shadowElement) {
399
- console.log("shadowElement is null, for host " + shadowHosts[i]);
400
- continue;
401
- }
402
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
403
- elements = elements.concat(shadowElements);
404
- }
405
- let randomToken = null;
406
- const foundElements = [];
312
+ const options = {
313
+ innerText: true,
314
+ };
407
315
  if (regex) {
408
- if (!regexpSearch) {
409
- regexpSearch = new RegExp(text, "im");
410
- }
411
- for (let i = 0; i < elements.length; i++) {
412
- const element = elements[i];
413
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
414
- (element.value && regexpSearch.test(element.value))) {
415
- foundElements.push(element);
416
- }
417
- }
316
+ options.singleRegex = true;
418
317
  }
419
- else {
420
- text = text.trim();
421
- for (let i = 0; i < elements.length; i++) {
422
- const element = elements[i];
423
- if (partial) {
424
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
425
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
426
- foundElements.push(element);
427
- }
428
- }
429
- else {
430
- if ((element.innerText && element.innerText.trim() === text) ||
431
- (element.value && element.value === text)) {
432
- foundElements.push(element);
433
- }
434
- }
435
- }
318
+ if (tag) {
319
+ options.tag = tag;
436
320
  }
437
- let noChildElements = [];
438
- for (let i = 0; i < foundElements.length; i++) {
439
- let element = foundElements[i];
440
- let hasChild = false;
441
- for (let j = 0; j < foundElements.length; j++) {
442
- if (i === j) {
443
- continue;
444
- }
445
- if (isParent(element, foundElements[j])) {
446
- hasChild = true;
447
- break;
448
- }
449
- }
450
- if (!hasChild) {
451
- noChildElements.push(element);
452
- }
321
+ if (!(partial === true)) {
322
+ options.exactMatch = true;
453
323
  }
324
+ const elements = window.findMatchingElements(text, options);
325
+ let randomToken = null;
326
+ const foundElements = [];
454
327
  let elementCount = 0;
455
- if (noChildElements.length > 0) {
456
- for (let i = 0; i < noChildElements.length; i++) {
328
+ if (elements.length > 0) {
329
+ for (let i = 0; i < elements.length; i++) {
457
330
  if (randomToken === null) {
458
331
  randomToken = Math.random().toString(36).substring(7);
459
332
  }
460
- let element = noChildElements[i];
333
+ let element = elements[i];
461
334
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
462
335
  elementCount++;
463
336
  }
@@ -466,9 +339,21 @@ class StableBrowser {
466
339
  }, [text1, tag1, regex1, partial1]);
467
340
  }
468
341
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
342
+ if (!info) {
343
+ info = {};
344
+ }
345
+ if (!info.failCause) {
346
+ info.failCause = {};
347
+ }
348
+ if (!info.log) {
349
+ info.log = "";
350
+ info.locatorLog = new LocatorLog(selectorHierarchy);
351
+ }
469
352
  let locatorSearch = selectorHierarchy[index];
353
+ let originalLocatorSearch = "";
470
354
  try {
471
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
355
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
356
+ locatorSearch = JSON.parse(originalLocatorSearch);
472
357
  }
473
358
  catch (e) {
474
359
  console.error(e);
@@ -478,13 +363,18 @@ class StableBrowser {
478
363
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
479
364
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
480
365
  if (!locatorString) {
366
+ info.failCause.textNotFound = true;
367
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
481
368
  return;
482
369
  }
483
370
  locator = this._getLocator({ css: locatorString }, scope, _params);
484
371
  }
485
372
  else if (locatorSearch.text) {
486
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
373
+ let text = _fixUsingParams(locatorSearch.text, _params);
374
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
487
375
  if (result.elementCount === 0) {
376
+ info.failCause.textNotFound = true;
377
+ info.failCause.lastError = "failed to locate element by text: " + text;
488
378
  return;
489
379
  }
490
380
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -501,13 +391,22 @@ class StableBrowser {
501
391
  // cssHref = true;
502
392
  // }
503
393
  let count = await locator.count();
394
+ if (count > 0 && !info.failCause.count) {
395
+ info.failCause.count = count;
396
+ }
504
397
  //info.log += "total elements found " + count + "\n";
505
398
  //let visibleCount = 0;
506
399
  let visibleLocator = null;
507
- if (locatorSearch.index && locatorSearch.index < count) {
400
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
508
401
  foundLocators.push(locator.nth(locatorSearch.index));
402
+ if (info.locatorLog) {
403
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
404
+ }
509
405
  return;
510
406
  }
407
+ if (info.locatorLog && count === 0) {
408
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
409
+ }
511
410
  for (let j = 0; j < count; j++) {
512
411
  let visible = await locator.nth(j).isVisible();
513
412
  const enabled = await locator.nth(j).isEnabled();
@@ -516,24 +415,40 @@ class StableBrowser {
516
415
  }
517
416
  if (visible && enabled) {
518
417
  foundLocators.push(locator.nth(j));
418
+ if (info.locatorLog) {
419
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
420
+ }
519
421
  }
520
422
  else {
423
+ info.failCause.visible = visible;
424
+ info.failCause.enabled = enabled;
521
425
  if (!info.printMessages) {
522
426
  info.printMessages = {};
523
427
  }
428
+ if (info.locatorLog && !visible) {
429
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
430
+ }
431
+ if (info.locatorLog && !enabled) {
432
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
433
+ }
524
434
  if (!info.printMessages[j.toString()]) {
525
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
435
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
526
436
  info.printMessages[j.toString()] = true;
527
437
  }
528
438
  }
529
439
  }
530
440
  }
531
441
  async closeUnexpectedPopups(info, _params) {
442
+ if (!info) {
443
+ info = {};
444
+ info.failCause = {};
445
+ info.log = "";
446
+ }
532
447
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
533
448
  if (!info) {
534
449
  info = {};
535
450
  }
536
- info.log += "scan for popup handlers" + "\n";
451
+ //info.log += "scan for popup handlers" + "\n";
537
452
  const handlerGroup = [];
538
453
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
539
454
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -560,9 +475,21 @@ class StableBrowser {
560
475
  }
561
476
  if (result.foundElements.length > 0) {
562
477
  let dialogCloseLocator = result.foundElements[0].locator;
563
- await dialogCloseLocator.click();
564
- // wait for the dialog to close
565
- await dialogCloseLocator.waitFor({ state: "hidden" });
478
+ try {
479
+ await scope?.evaluate(() => {
480
+ window.__isClosingPopups = true;
481
+ });
482
+ await dialogCloseLocator.click();
483
+ // wait for the dialog to close
484
+ await dialogCloseLocator.waitFor({ state: "hidden" });
485
+ }
486
+ catch (e) {
487
+ }
488
+ finally {
489
+ await scope?.evaluate(() => {
490
+ window.__isClosingPopups = false;
491
+ });
492
+ }
566
493
  return { rerun: true };
567
494
  }
568
495
  }
@@ -586,7 +513,13 @@ class StableBrowser {
586
513
  }
587
514
  throw new Error("unable to locate element " + JSON.stringify(selectors));
588
515
  }
589
- async _findFrameScope(selectors, timeout = 30000) {
516
+ async _findFrameScope(selectors, timeout = 30000, info) {
517
+ if (!info) {
518
+ info = {};
519
+ info.failCause = {};
520
+ info.log = "";
521
+ }
522
+ let startTime = Date.now();
590
523
  let scope = this.page;
591
524
  if (selectors.frame) {
592
525
  return selectors.frame;
@@ -617,9 +550,11 @@ class StableBrowser {
617
550
  }
618
551
  return framescope;
619
552
  };
553
+ let fLocator = null;
620
554
  while (true) {
621
555
  let frameFound = false;
622
556
  if (selectors.nestFrmLoc) {
557
+ fLocator = selectors.nestFrmLoc;
623
558
  scope = await findFrame(selectors.nestFrmLoc, scope);
624
559
  frameFound = true;
625
560
  break;
@@ -628,6 +563,7 @@ class StableBrowser {
628
563
  for (let i = 0; i < selectors.frameLocators.length; i++) {
629
564
  let frameLocator = selectors.frameLocators[i];
630
565
  if (frameLocator.css) {
566
+ fLocator = frameLocator.css;
631
567
  scope = scope.frameLocator(frameLocator.css);
632
568
  frameFound = true;
633
569
  break;
@@ -635,16 +571,25 @@ class StableBrowser {
635
571
  }
636
572
  }
637
573
  if (!frameFound && selectors.iframe_src) {
574
+ fLocator = selectors.iframe_src;
638
575
  scope = this.page.frame({ url: selectors.iframe_src });
639
576
  }
640
577
  if (!scope) {
641
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
642
- if (performance.now() - startTime > timeout) {
578
+ if (info && info.locatorLog) {
579
+ info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "NOT_FOUND");
580
+ }
581
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
582
+ if (Date.now() - startTime > timeout) {
583
+ info.failCause.iframeNotFound = true;
584
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
643
585
  throw new Error("unable to locate iframe " + selectors.iframe_src);
644
586
  }
645
587
  await new Promise((resolve) => setTimeout(resolve, 1000));
646
588
  }
647
589
  else {
590
+ if (info && info.locatorLog) {
591
+ info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
592
+ }
648
593
  break;
649
594
  }
650
595
  }
@@ -654,20 +599,27 @@ class StableBrowser {
654
599
  }
655
600
  return scope;
656
601
  }
657
- async _getDocumentBody(selectors, timeout = 30000) {
658
- let scope = await this._findFrameScope(selectors, timeout);
602
+ async _getDocumentBody(selectors, timeout = 30000, info) {
603
+ let scope = await this._findFrameScope(selectors, timeout, info);
659
604
  return scope.evaluate(() => {
660
605
  var bodyContent = document.body.innerHTML;
661
606
  return bodyContent;
662
607
  });
663
608
  }
664
609
  async _locate_internal(selectors, info, _params, timeout = 30000) {
610
+ if (!info) {
611
+ info = {};
612
+ info.failCause = {};
613
+ info.log = "";
614
+ info.locatorLog = new LocatorLog(selectors);
615
+ }
665
616
  let highPriorityTimeout = 5000;
666
617
  let visibleOnlyTimeout = 6000;
667
- let startTime = performance.now();
618
+ let startTime = Date.now();
668
619
  let locatorsCount = 0;
620
+ let lazy_scroll = false;
669
621
  //let arrayMode = Array.isArray(selectors);
670
- let scope = await this._findFrameScope(selectors, timeout);
622
+ let scope = await this._findFrameScope(selectors, timeout, info);
671
623
  let selectorsLocators = null;
672
624
  selectorsLocators = selectors.locators;
673
625
  // group selectors by priority
@@ -754,21 +706,33 @@ class StableBrowser {
754
706
  return maxCountElement.locator;
755
707
  }
756
708
  }
757
- if (performance.now() - startTime > timeout) {
709
+ if (Date.now() - startTime > timeout) {
758
710
  break;
759
711
  }
760
- if (performance.now() - startTime > highPriorityTimeout) {
712
+ if (Date.now() - startTime > highPriorityTimeout) {
761
713
  info.log += "high priority timeout, will try all elements" + "\n";
762
714
  highPriorityOnly = false;
715
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
716
+ lazy_scroll = true;
717
+ await scrollPageToLoadLazyElements(this.page);
718
+ }
763
719
  }
764
- if (performance.now() - startTime > visibleOnlyTimeout) {
720
+ if (Date.now() - startTime > visibleOnlyTimeout) {
765
721
  info.log += "visible only timeout, will try all elements" + "\n";
766
722
  visibleOnly = false;
767
723
  }
768
724
  await new Promise((resolve) => setTimeout(resolve, 1000));
769
725
  }
770
726
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
771
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
727
+ // if (info.locatorLog) {
728
+ // const lines = info.locatorLog.toString().split("\n");
729
+ // for (let line of lines) {
730
+ // this.logger.debug(line);
731
+ // }
732
+ // }
733
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
734
+ info.failCause.locatorNotFound = true;
735
+ info.failCause.lastError = "failed to locate unique element";
772
736
  throw new Error("failed to locate first element no elements found, " + info.log);
773
737
  }
774
738
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -782,8 +746,9 @@ class StableBrowser {
782
746
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
783
747
  }
784
748
  catch (e) {
785
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
786
- this.logger.debug(e);
749
+ // this call can fail it the browser is navigating
750
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
751
+ // this.logger.debug(e);
787
752
  foundLocators = [];
788
753
  try {
789
754
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -800,10 +765,29 @@ class StableBrowser {
800
765
  });
801
766
  result.locatorIndex = i;
802
767
  }
768
+ if (foundLocators.length > 1) {
769
+ info.failCause.foundMultiple = true;
770
+ if (info.locatorLog) {
771
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
772
+ }
773
+ }
803
774
  }
804
775
  return result;
805
776
  }
806
777
  async simpleClick(elementDescription, _params, options = {}, world = null) {
778
+ const state = {
779
+ locate: false,
780
+ scroll: false,
781
+ highlight: false,
782
+ _params,
783
+ options,
784
+ world,
785
+ type: Types.CLICK,
786
+ text: "Click element",
787
+ operation: "simpleClick",
788
+ log: "***** click on " + elementDescription + " *****\n",
789
+ };
790
+ _preCommand(state, this);
807
791
  const startTime = Date.now();
808
792
  let timeout = 30000;
809
793
  if (options && options.timeout) {
@@ -827,13 +811,32 @@ class StableBrowser {
827
811
  }
828
812
  catch (e) {
829
813
  if (performance.now() - startTime > timeout) {
830
- throw e;
814
+ // throw e;
815
+ try {
816
+ await _commandError(state, "timeout looking for " + elementDescription, this);
817
+ }
818
+ finally {
819
+ _commandFinally(state, this);
820
+ }
831
821
  }
832
822
  }
833
823
  await new Promise((resolve) => setTimeout(resolve, 3000));
834
824
  }
835
825
  }
836
826
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
827
+ const state = {
828
+ locate: false,
829
+ scroll: false,
830
+ highlight: false,
831
+ _params,
832
+ options,
833
+ world,
834
+ type: Types.FILL,
835
+ text: "Fill element",
836
+ operation: "simpleClickType",
837
+ log: "***** click type on " + elementDescription + " *****\n",
838
+ };
839
+ _preCommand(state, this);
837
840
  const startTime = Date.now();
838
841
  let timeout = 30000;
839
842
  if (options && options.timeout) {
@@ -857,7 +860,13 @@ class StableBrowser {
857
860
  }
858
861
  catch (e) {
859
862
  if (performance.now() - startTime > timeout) {
860
- throw e;
863
+ // throw e;
864
+ try {
865
+ await _commandError(state, "timeout looking for " + elementDescription, this);
866
+ }
867
+ finally {
868
+ _commandFinally(state, this);
869
+ }
861
870
  }
862
871
  }
863
872
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -881,13 +890,13 @@ class StableBrowser {
881
890
  }
882
891
  try {
883
892
  await state.element.click();
884
- await new Promise((resolve) => setTimeout(resolve, 1000));
893
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
885
894
  }
886
895
  catch (e) {
887
896
  // await this.closeUnexpectedPopups();
888
897
  state.element = await this._locate(selectors, state.info, _params);
889
898
  await state.element.dispatchEvent("click");
890
- await new Promise((resolve) => setTimeout(resolve, 1000));
899
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
891
900
  }
892
901
  await this.waitForPageLoad();
893
902
  return state.info;
@@ -966,7 +975,6 @@ class StableBrowser {
966
975
  await state.element.hover({ timeout: 10000 });
967
976
  await new Promise((resolve) => setTimeout(resolve, 1000));
968
977
  }
969
- await _screenshot(state, this);
970
978
  await this.waitForPageLoad();
971
979
  return state.info;
972
980
  }
@@ -1094,33 +1102,30 @@ class StableBrowser {
1094
1102
  }
1095
1103
  }
1096
1104
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1097
- _validateSelectors(selectors);
1098
- const startTime = Date.now();
1099
- let error = null;
1100
- let screenshotId = null;
1101
- let screenshotPath = null;
1102
- const info = {};
1103
- info.log = "";
1104
- info.operation = Types.SET_DATE_TIME;
1105
- info.selectors = selectors;
1106
- info.value = value;
1105
+ const state = {
1106
+ selectors,
1107
+ _params,
1108
+ value: await this._replaceWithLocalData(value, this),
1109
+ options,
1110
+ world,
1111
+ type: Types.SET_DATE_TIME,
1112
+ text: `Set date time value: ${value}`,
1113
+ operation: "setDateTime",
1114
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1115
+ throwError: false,
1116
+ };
1107
1117
  try {
1108
- value = await this._replaceWithLocalData(value, this);
1109
- let element = await this._locate(selectors, info, _params);
1110
- //insert red border around the element
1111
- await this.scrollIfNeeded(element, info);
1112
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1113
- await this._highlightElements(element);
1118
+ await _preCommand(state, this);
1114
1119
  try {
1115
- await element.click();
1120
+ await state.element.click();
1116
1121
  await new Promise((resolve) => setTimeout(resolve, 500));
1117
1122
  if (format) {
1118
- value = dayjs(value).format(format);
1119
- await element.fill(value);
1123
+ state.value = dayjs(state.value).format(format);
1124
+ await state.element.fill(state.value);
1120
1125
  }
1121
1126
  else {
1122
- const dateTimeValue = await getDateTimeValue({ value, element });
1123
- await element.evaluateHandle((el, dateTimeValue) => {
1127
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1128
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1124
1129
  el.value = ""; // clear input
1125
1130
  el.value = dateTimeValue;
1126
1131
  }, dateTimeValue);
@@ -1133,20 +1138,19 @@ class StableBrowser {
1133
1138
  }
1134
1139
  catch (err) {
1135
1140
  //await this.closeUnexpectedPopups();
1136
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1141
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1137
1142
  this.logger.info("Trying again");
1138
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1139
- info.screenshotPath = screenshotPath;
1140
- Object.assign(err, { info: info });
1143
+ await _screenshot(state, this);
1144
+ Object.assign(err, { info: state.info });
1141
1145
  await element.click();
1142
1146
  await new Promise((resolve) => setTimeout(resolve, 500));
1143
1147
  if (format) {
1144
- value = dayjs(value).format(format);
1145
- await element.fill(value);
1148
+ state.value = dayjs(state.value).format(format);
1149
+ await state.element.fill(state.value);
1146
1150
  }
1147
1151
  else {
1148
- const dateTimeValue = await getDateTimeValue({ value, element });
1149
- await element.evaluateHandle((el, dateTimeValue) => {
1152
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1153
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1150
1154
  el.value = ""; // clear input
1151
1155
  el.value = dateTimeValue;
1152
1156
  }, dateTimeValue);
@@ -1159,50 +1163,30 @@ class StableBrowser {
1159
1163
  }
1160
1164
  }
1161
1165
  catch (e) {
1162
- error = e;
1163
- throw e;
1166
+ await _commandError(state, e, this);
1164
1167
  }
1165
1168
  finally {
1166
- const endTime = Date.now();
1167
- this._reportToWorld(world, {
1168
- element_name: selectors.element_name,
1169
- type: Types.SET_DATE_TIME,
1170
- screenshotId,
1171
- value: value,
1172
- text: `setDateTime input with value: ${value}`,
1173
- result: error
1174
- ? {
1175
- status: "FAILED",
1176
- startTime,
1177
- endTime,
1178
- message: error === null || error === void 0 ? void 0 : error.message,
1179
- }
1180
- : {
1181
- status: "PASSED",
1182
- startTime,
1183
- endTime,
1184
- },
1185
- info: info,
1186
- });
1169
+ _commandFinally(state, this);
1187
1170
  }
1188
1171
  }
1189
1172
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1173
+ _value = unEscapeString(_value);
1174
+ const newValue = await this._replaceWithLocalData(_value, world);
1190
1175
  const state = {
1191
1176
  selectors,
1192
1177
  _params,
1193
- value: unEscapeString(_value),
1178
+ value: newValue,
1179
+ originalValue: _value,
1194
1180
  options,
1195
1181
  world,
1196
1182
  type: Types.FILL,
1197
1183
  text: `Click type input with value: ${_value}`,
1198
1184
  operation: "clickType",
1199
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1185
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1200
1186
  };
1201
- const newValue = await this._replaceWithLocalData(state.value, world);
1202
1187
  if (newValue !== _value) {
1203
1188
  //this.logger.info(_value + "=" + newValue);
1204
1189
  _value = newValue;
1205
- state.value = newValue;
1206
1190
  }
1207
1191
  try {
1208
1192
  await _preCommand(state, this);
@@ -1319,6 +1303,7 @@ class StableBrowser {
1319
1303
  let screenshotPath = null;
1320
1304
  if (!info.log) {
1321
1305
  info.log = "";
1306
+ info.locatorLog = new LocatorLog(selectors);
1322
1307
  }
1323
1308
  info.operation = "getText";
1324
1309
  info.selectors = selectors;
@@ -1657,11 +1642,9 @@ class StableBrowser {
1657
1642
  if (!fs.existsSync(world.screenshotPath)) {
1658
1643
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1659
1644
  }
1660
- let nextIndex = 1;
1661
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1662
- nextIndex++;
1663
- }
1664
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1645
+ // to make sure the path doesn't start with -
1646
+ const uuidStr = "id_" + randomUUID();
1647
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1665
1648
  try {
1666
1649
  await this.takeScreenshot(screenshotPath);
1667
1650
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1675,7 +1658,7 @@ class StableBrowser {
1675
1658
  catch (e) {
1676
1659
  this.logger.info("unable to take screenshot, ignored");
1677
1660
  }
1678
- result.screenshotId = nextIndex;
1661
+ result.screenshotId = uuidStr;
1679
1662
  result.screenshotPath = screenshotPath;
1680
1663
  if (info && info.box) {
1681
1664
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1773,74 +1756,101 @@ class StableBrowser {
1773
1756
  }
1774
1757
  }
1775
1758
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1776
- _validateSelectors(selectors);
1777
- const startTime = Date.now();
1778
- let error = null;
1779
- let screenshotId = null;
1780
- let screenshotPath = null;
1759
+ const state = {
1760
+ selectors,
1761
+ _params,
1762
+ attribute,
1763
+ variable,
1764
+ options,
1765
+ world,
1766
+ type: Types.EXTRACT,
1767
+ text: `Extract attribute from element`,
1768
+ operation: "extractAttribute",
1769
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1770
+ };
1781
1771
  await new Promise((resolve) => setTimeout(resolve, 2000));
1782
- const info = {};
1783
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1784
- info.operation = "extract";
1785
- info.selectors = selectors;
1786
1772
  try {
1787
- const element = await this._locate(selectors, info, _params);
1788
- await this._highlightElements(element);
1789
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1773
+ await _preCommand(state, this);
1790
1774
  switch (attribute) {
1791
1775
  case "inner_text":
1792
- info.value = await element.innerText();
1776
+ state.value = await state.element.innerText();
1793
1777
  break;
1794
1778
  case "href":
1795
- info.value = await element.getAttribute("href");
1779
+ state.value = await state.element.getAttribute("href");
1796
1780
  break;
1797
1781
  case "value":
1798
- info.value = await element.inputValue();
1782
+ state.value = await state.element.inputValue();
1799
1783
  break;
1800
1784
  default:
1801
- info.value = await element.getAttribute(attribute);
1785
+ state.value = await state.element.getAttribute(attribute);
1802
1786
  break;
1803
1787
  }
1804
- this[variable] = info.value;
1805
- if (world) {
1806
- world[variable] = info.value;
1788
+ state.info.value = state.value;
1789
+ this.setTestData({ [variable]: state.value }, world);
1790
+ this.logger.info("set test data: " + variable + "=" + state.value);
1791
+ return state.info;
1792
+ }
1793
+ catch (e) {
1794
+ await _commandError(state, e, this);
1795
+ }
1796
+ finally {
1797
+ _commandFinally(state, this);
1798
+ }
1799
+ }
1800
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1801
+ const state = {
1802
+ selectors,
1803
+ _params,
1804
+ attribute,
1805
+ value,
1806
+ options,
1807
+ world,
1808
+ type: Types.VERIFY_ATTRIBUTE,
1809
+ text: `Verify element attribute`,
1810
+ operation: "verifyAttribute",
1811
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1812
+ };
1813
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1814
+ let val;
1815
+ try {
1816
+ await _preCommand(state, this);
1817
+ switch (attribute) {
1818
+ case "innerText":
1819
+ val = String(await state.element.innerText());
1820
+ break;
1821
+ case "value":
1822
+ val = String(await state.element.inputValue());
1823
+ break;
1824
+ case "checked":
1825
+ val = String(await state.element.isChecked());
1826
+ break;
1827
+ case "disabled":
1828
+ val = String(await state.element.isDisabled());
1829
+ break;
1830
+ default:
1831
+ val = String(await state.element.getAttribute(attribute));
1832
+ break;
1807
1833
  }
1808
- this.setTestData({ [variable]: info.value }, world);
1809
- this.logger.info("set test data: " + variable + "=" + info.value);
1810
- return info;
1834
+ let regex;
1835
+ if (value.startsWith("/") && value.endsWith("/")) {
1836
+ const patternBody = value.slice(1, -1);
1837
+ regex = new RegExp(patternBody, "g");
1838
+ }
1839
+ else {
1840
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1841
+ regex = new RegExp(escapedPattern, "g");
1842
+ }
1843
+ if (!val.match(regex)) {
1844
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1845
+ }
1846
+ state.info.value = val;
1847
+ return state.info;
1811
1848
  }
1812
1849
  catch (e) {
1813
- //await this.closeUnexpectedPopups();
1814
- this.logger.error("extract failed " + info.log);
1815
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1816
- info.screenshotPath = screenshotPath;
1817
- Object.assign(e, { info: info });
1818
- error = e;
1819
- throw e;
1850
+ await _commandError(state, e, this);
1820
1851
  }
1821
1852
  finally {
1822
- const endTime = Date.now();
1823
- this._reportToWorld(world, {
1824
- element_name: selectors.element_name,
1825
- type: Types.EXTRACT_ATTRIBUTE,
1826
- variable: variable,
1827
- value: info.value,
1828
- text: "Extract attribute from element",
1829
- screenshotId,
1830
- result: error
1831
- ? {
1832
- status: "FAILED",
1833
- startTime,
1834
- endTime,
1835
- message: error?.message,
1836
- }
1837
- : {
1838
- status: "PASSED",
1839
- startTime,
1840
- endTime,
1841
- },
1842
- info: info,
1843
- });
1853
+ _commandFinally(state, this);
1844
1854
  }
1845
1855
  }
1846
1856
  async extractEmailData(emailAddress, options, world) {
@@ -1861,7 +1871,7 @@ class StableBrowser {
1861
1871
  if (options && options.timeout) {
1862
1872
  timeout = options.timeout;
1863
1873
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1874
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1875
  const request = {
1866
1876
  method: "POST",
1867
1877
  url: serviceUrl,
@@ -1917,7 +1927,8 @@ class StableBrowser {
1917
1927
  catch (e) {
1918
1928
  errorCount++;
1919
1929
  if (errorCount > 3) {
1920
- throw e;
1930
+ // throw e;
1931
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1921
1932
  }
1922
1933
  // ignore
1923
1934
  }
@@ -2028,11 +2039,12 @@ class StableBrowser {
2028
2039
  info.screenshotPath = screenshotPath;
2029
2040
  Object.assign(e, { info: info });
2030
2041
  error = e;
2031
- throw e;
2042
+ // throw e;
2043
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2032
2044
  }
2033
2045
  finally {
2034
2046
  const endTime = Date.now();
2035
- this._reportToWorld(world, {
2047
+ _reportToWorld(world, {
2036
2048
  type: Types.VERIFY_PAGE_PATH,
2037
2049
  text: "Verify page path",
2038
2050
  screenshotId,
@@ -2052,54 +2064,64 @@ class StableBrowser {
2052
2064
  });
2053
2065
  }
2054
2066
  }
2067
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2068
+ const frames = this.page.frames();
2069
+ let results = [];
2070
+ for (let i = 0; i < frames.length; i++) {
2071
+ if (dateAlternatives.date) {
2072
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2073
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2074
+ result.frame = frames[i];
2075
+ results.push(result);
2076
+ }
2077
+ }
2078
+ else if (numberAlternatives.number) {
2079
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2080
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2081
+ result.frame = frames[i];
2082
+ results.push(result);
2083
+ }
2084
+ }
2085
+ else {
2086
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2087
+ result.frame = frames[i];
2088
+ results.push(result);
2089
+ }
2090
+ }
2091
+ state.info.results = results;
2092
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2093
+ return resultWithElementsFound;
2094
+ }
2055
2095
  async verifyTextExistInPage(text, options = {}, world = null) {
2056
2096
  text = unEscapeString(text);
2057
- const startTime = Date.now();
2097
+ const state = {
2098
+ text_search: text,
2099
+ options,
2100
+ world,
2101
+ locate: false,
2102
+ scroll: false,
2103
+ highlight: false,
2104
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2105
+ text: `Verify text exists in page`,
2106
+ operation: "verifyTextExistInPage",
2107
+ log: "***** verify text " + text + " exists in page *****\n",
2108
+ };
2058
2109
  const timeout = this._getLoadTimeout(options);
2059
- let error = null;
2060
- let screenshotId = null;
2061
- let screenshotPath = null;
2062
2110
  await new Promise((resolve) => setTimeout(resolve, 2000));
2063
- const info = {};
2064
- info.log = "***** verify text " + text + " exists in page *****\n";
2065
- info.operation = "verifyTextExistInPage";
2066
2111
  const newValue = await this._replaceWithLocalData(text, world);
2067
2112
  if (newValue !== text) {
2068
2113
  this.logger.info(text + "=" + newValue);
2069
2114
  text = newValue;
2070
2115
  }
2071
- info.text = text;
2072
2116
  let dateAlternatives = findDateAlternatives(text);
2073
2117
  let numberAlternatives = findNumberAlternatives(text);
2074
2118
  try {
2119
+ await _preCommand(state, this);
2120
+ state.info.text = text;
2075
2121
  while (true) {
2076
- const frames = this.page.frames();
2077
- let results = [];
2078
- for (let i = 0; i < frames.length; i++) {
2079
- if (dateAlternatives.date) {
2080
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2081
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2082
- result.frame = frames[i];
2083
- results.push(result);
2084
- }
2085
- }
2086
- else if (numberAlternatives.number) {
2087
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2088
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2089
- result.frame = frames[i];
2090
- results.push(result);
2091
- }
2092
- }
2093
- else {
2094
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2095
- result.frame = frames[i];
2096
- results.push(result);
2097
- }
2098
- }
2099
- info.results = results;
2100
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2122
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2101
2123
  if (resultWithElementsFound.length === 0) {
2102
- if (Date.now() - startTime > timeout) {
2124
+ if (Date.now() - state.startTime > timeout) {
2103
2125
  throw new Error(`Text ${text} not found in page`);
2104
2126
  }
2105
2127
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2111,55 +2133,163 @@ class StableBrowser {
2111
2133
  await this._highlightElements(frame, dataAttribute);
2112
2134
  const element = await frame.$(dataAttribute);
2113
2135
  if (element) {
2114
- await this.scrollIfNeeded(element, info);
2136
+ await this.scrollIfNeeded(element, state.info);
2115
2137
  await element.dispatchEvent("bvt_verify_page_contains_text");
2116
2138
  }
2117
2139
  }
2118
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2119
- return info;
2140
+ await _screenshot(state, this);
2141
+ return state.info;
2120
2142
  }
2121
2143
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2122
2144
  }
2123
2145
  catch (e) {
2124
- //await this.closeUnexpectedPopups();
2125
- this.logger.error("verify text exist in page failed " + info.log);
2126
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2127
- info.screenshotPath = screenshotPath;
2128
- Object.assign(e, { info: info });
2129
- error = e;
2130
- throw e;
2146
+ await _commandError(state, e, this);
2131
2147
  }
2132
2148
  finally {
2133
- const endTime = Date.now();
2134
- this._reportToWorld(world, {
2135
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2136
- text: "Verify text exists in page",
2137
- screenshotId,
2138
- result: error
2139
- ? {
2140
- status: "FAILED",
2141
- startTime,
2142
- endTime,
2143
- message: error?.message,
2144
- }
2145
- : {
2146
- status: "PASSED",
2147
- startTime,
2148
- endTime,
2149
- },
2150
- info: info,
2151
- });
2149
+ _commandFinally(state, this);
2152
2150
  }
2153
2151
  }
2154
- _getServerUrl() {
2155
- let serviceUrl = "https://api.blinq.io";
2156
- if (process.env.NODE_ENV_BLINQ === "dev") {
2157
- serviceUrl = "https://dev.api.blinq.io";
2152
+ async waitForTextToDisappear(text, options = {}, world = null) {
2153
+ text = unEscapeString(text);
2154
+ const state = {
2155
+ text_search: text,
2156
+ options,
2157
+ world,
2158
+ locate: false,
2159
+ scroll: false,
2160
+ highlight: false,
2161
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2162
+ text: `Verify text does not exist in page`,
2163
+ operation: "verifyTextNotExistInPage",
2164
+ log: "***** verify text " + text + " does not exist in page *****\n",
2165
+ };
2166
+ const timeout = this._getLoadTimeout(options);
2167
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2168
+ const newValue = await this._replaceWithLocalData(text, world);
2169
+ if (newValue !== text) {
2170
+ this.logger.info(text + "=" + newValue);
2171
+ text = newValue;
2158
2172
  }
2159
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2160
- serviceUrl = "https://stage.api.blinq.io";
2173
+ let dateAlternatives = findDateAlternatives(text);
2174
+ let numberAlternatives = findNumberAlternatives(text);
2175
+ try {
2176
+ await _preCommand(state, this);
2177
+ state.info.text = text;
2178
+ while (true) {
2179
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2180
+ if (resultWithElementsFound.length === 0) {
2181
+ await _screenshot(state, this);
2182
+ return state.info;
2183
+ }
2184
+ if (Date.now() - state.startTime > timeout) {
2185
+ throw new Error(`Text ${text} found in page`);
2186
+ }
2187
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2188
+ }
2189
+ }
2190
+ catch (e) {
2191
+ await _commandError(state, e, this);
2192
+ }
2193
+ finally {
2194
+ _commandFinally(state, this);
2195
+ }
2196
+ }
2197
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2198
+ textAnchor = unEscapeString(textAnchor);
2199
+ textToVerify = unEscapeString(textToVerify);
2200
+ const state = {
2201
+ text_search: textToVerify,
2202
+ options,
2203
+ world,
2204
+ locate: false,
2205
+ scroll: false,
2206
+ highlight: false,
2207
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2208
+ text: `Verify text with relation to another text`,
2209
+ operation: "verify_text_with_relation",
2210
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2211
+ };
2212
+ const timeout = this._getLoadTimeout(options);
2213
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2214
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2215
+ if (newValue !== textAnchor) {
2216
+ this.logger.info(textAnchor + "=" + newValue);
2217
+ textAnchor = newValue;
2218
+ }
2219
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2220
+ if (newValue !== textToVerify) {
2221
+ this.logger.info(textToVerify + "=" + newValue);
2222
+ textToVerify = newValue;
2223
+ }
2224
+ let dateAlternatives = findDateAlternatives(textToVerify);
2225
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2226
+ let foundAncore = false;
2227
+ try {
2228
+ await _preCommand(state, this);
2229
+ state.info.text = textToVerify;
2230
+ while (true) {
2231
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2232
+ if (resultWithElementsFound.length === 0) {
2233
+ if (Date.now() - state.startTime > timeout) {
2234
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2235
+ }
2236
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2237
+ continue;
2238
+ }
2239
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2240
+ foundAncore = true;
2241
+ const result = resultWithElementsFound[i];
2242
+ const token = result.randomToken;
2243
+ const frame = result.frame;
2244
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2245
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2246
+ const elements = Array.from(document.querySelectorAll(css));
2247
+ for (let i = 0; i < elements.length; i++) {
2248
+ const element = elements[i];
2249
+ let climbParent = element;
2250
+ for (let j = 0; j < climb; j++) {
2251
+ climbParent = climbParent.parentElement;
2252
+ if (!climbParent) {
2253
+ break;
2254
+ }
2255
+ }
2256
+ if (!climbParent) {
2257
+ continue;
2258
+ }
2259
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2260
+ if (foundElements.length > 0) {
2261
+ // set the container element attribute
2262
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2263
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2264
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2265
+ return { found: true };
2266
+ }
2267
+ }
2268
+ return { found: false };
2269
+ }, [css, climb, textToVerify, result.randomToken]);
2270
+ if (findResult.found === true) {
2271
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2272
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2273
+ await this._highlightElements(frame, dataAttribute);
2274
+ await this._highlightElements(frame, cssAnchor);
2275
+ const element = await frame.$(dataAttribute);
2276
+ if (element) {
2277
+ await this.scrollIfNeeded(element, state.info);
2278
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2279
+ }
2280
+ await _screenshot(state, this);
2281
+ return state.info;
2282
+ }
2283
+ }
2284
+ }
2285
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2286
+ }
2287
+ catch (e) {
2288
+ await _commandError(state, e, this);
2289
+ }
2290
+ finally {
2291
+ _commandFinally(state, this);
2161
2292
  }
2162
- return serviceUrl;
2163
2293
  }
2164
2294
  async visualVerification(text, options = {}, world = null) {
2165
2295
  const startTime = Date.now();
@@ -2175,7 +2305,7 @@ class StableBrowser {
2175
2305
  throw new Error("TOKEN is not set");
2176
2306
  }
2177
2307
  try {
2178
- let serviceUrl = this._getServerUrl();
2308
+ let serviceUrl = _getServerUrl();
2179
2309
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2180
2310
  info.screenshotPath = screenshotPath;
2181
2311
  const screenshot = await this.takeScreenshot();
@@ -2211,11 +2341,12 @@ class StableBrowser {
2211
2341
  info.screenshotPath = screenshotPath;
2212
2342
  Object.assign(e, { info: info });
2213
2343
  error = e;
2214
- throw e;
2344
+ // throw e;
2345
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2215
2346
  }
2216
2347
  finally {
2217
2348
  const endTime = Date.now();
2218
- this._reportToWorld(world, {
2349
+ _reportToWorld(world, {
2219
2350
  type: Types.VERIFY_VISUAL,
2220
2351
  text: "Visual verification",
2221
2352
  screenshotId,
@@ -2263,6 +2394,7 @@ class StableBrowser {
2263
2394
  let screenshotPath = null;
2264
2395
  const info = {};
2265
2396
  info.log = "";
2397
+ info.locatorLog = new LocatorLog(selectors);
2266
2398
  info.operation = "getTableData";
2267
2399
  info.selectors = selectors;
2268
2400
  try {
@@ -2278,11 +2410,12 @@ class StableBrowser {
2278
2410
  info.screenshotPath = screenshotPath;
2279
2411
  Object.assign(e, { info: info });
2280
2412
  error = e;
2281
- throw e;
2413
+ // throw e;
2414
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2282
2415
  }
2283
2416
  finally {
2284
2417
  const endTime = Date.now();
2285
- this._reportToWorld(world, {
2418
+ _reportToWorld(world, {
2286
2419
  element_name: selectors.element_name,
2287
2420
  type: Types.GET_TABLE_DATA,
2288
2421
  text: "Get table data",
@@ -2337,7 +2470,7 @@ class StableBrowser {
2337
2470
  info.operation = "analyzeTable";
2338
2471
  info.selectors = selectors;
2339
2472
  info.query = query;
2340
- query = this._fixUsingParams(query, _params);
2473
+ query = _fixUsingParams(query, _params);
2341
2474
  info.query_fixed = query;
2342
2475
  info.operator = operator;
2343
2476
  info.value = value;
@@ -2443,11 +2576,12 @@ class StableBrowser {
2443
2576
  info.screenshotPath = screenshotPath;
2444
2577
  Object.assign(e, { info: info });
2445
2578
  error = e;
2446
- throw e;
2579
+ // throw e;
2580
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2447
2581
  }
2448
2582
  finally {
2449
2583
  const endTime = Date.now();
2450
- this._reportToWorld(world, {
2584
+ _reportToWorld(world, {
2451
2585
  element_name: selectors.element_name,
2452
2586
  type: Types.ANALYZE_TABLE,
2453
2587
  text: "Analyze table",
@@ -2469,27 +2603,7 @@ class StableBrowser {
2469
2603
  }
2470
2604
  }
2471
2605
  async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
2472
- if (!value) {
2473
- return value;
2474
- }
2475
- // find all the accurance of {{(.*?)}} and replace with the value
2476
- let regex = /{{(.*?)}}/g;
2477
- let matches = value.match(regex);
2478
- if (matches) {
2479
- const testData = this.getTestData(world);
2480
- for (let i = 0; i < matches.length; i++) {
2481
- let match = matches[i];
2482
- let key = match.substring(2, match.length - 2);
2483
- let newValue = objectPath.get(testData, key, null);
2484
- if (newValue !== null) {
2485
- value = value.replace(match, newValue);
2486
- }
2487
- }
2488
- }
2489
- if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
2490
- return await decrypt(value, null, totpWait);
2491
- }
2492
- return value;
2606
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2493
2607
  }
2494
2608
  _getLoadTimeout(options) {
2495
2609
  let timeout = 15000;
@@ -2540,7 +2654,7 @@ class StableBrowser {
2540
2654
  await new Promise((resolve) => setTimeout(resolve, 2000));
2541
2655
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2542
2656
  const endTime = Date.now();
2543
- this._reportToWorld(world, {
2657
+ _reportToWorld(world, {
2544
2658
  type: Types.GET_PAGE_STATUS,
2545
2659
  text: "Wait for page load",
2546
2660
  screenshotId,
@@ -2560,41 +2674,35 @@ class StableBrowser {
2560
2674
  }
2561
2675
  }
2562
2676
  async closePage(options = {}, world = null) {
2563
- const startTime = Date.now();
2564
- let error = null;
2565
- let screenshotId = null;
2566
- let screenshotPath = null;
2567
- const info = {};
2677
+ const state = {
2678
+ options,
2679
+ world,
2680
+ locate: false,
2681
+ scroll: false,
2682
+ highlight: false,
2683
+ type: Types.CLOSE_PAGE,
2684
+ text: `Close page`,
2685
+ operation: "closePage",
2686
+ log: "***** close page *****\n",
2687
+ throwError: false,
2688
+ };
2568
2689
  try {
2690
+ await _preCommand(state, this);
2569
2691
  await this.page.close();
2570
2692
  }
2571
2693
  catch (e) {
2572
2694
  console.log(".");
2695
+ await _commandError(state, e, this);
2573
2696
  }
2574
2697
  finally {
2575
- await new Promise((resolve) => setTimeout(resolve, 2000));
2576
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2577
- const endTime = Date.now();
2578
- this._reportToWorld(world, {
2579
- type: Types.CLOSE_PAGE,
2580
- text: "close page",
2581
- screenshotId,
2582
- result: error
2583
- ? {
2584
- status: "FAILED",
2585
- startTime,
2586
- endTime,
2587
- message: error?.message,
2588
- }
2589
- : {
2590
- status: "PASSED",
2591
- startTime,
2592
- endTime,
2593
- },
2594
- info: info,
2595
- });
2698
+ _commandFinally(state, this);
2596
2699
  }
2597
2700
  }
2701
+ saveTestDataAsGlobal(options, world) {
2702
+ const dataFile = this._getDataFile(world);
2703
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2704
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2705
+ }
2598
2706
  async setViewportSize(width, hight, options = {}, world = null) {
2599
2707
  const startTime = Date.now();
2600
2708
  let error = null;
@@ -2612,12 +2720,13 @@ class StableBrowser {
2612
2720
  }
2613
2721
  catch (e) {
2614
2722
  console.log(".");
2723
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2615
2724
  }
2616
2725
  finally {
2617
2726
  await new Promise((resolve) => setTimeout(resolve, 2000));
2618
2727
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2619
2728
  const endTime = Date.now();
2620
- this._reportToWorld(world, {
2729
+ _reportToWorld(world, {
2621
2730
  type: Types.SET_VIEWPORT,
2622
2731
  text: "set viewport size to " + width + "x" + hight,
2623
2732
  screenshotId,
@@ -2648,12 +2757,13 @@ class StableBrowser {
2648
2757
  }
2649
2758
  catch (e) {
2650
2759
  console.log(".");
2760
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2651
2761
  }
2652
2762
  finally {
2653
2763
  await new Promise((resolve) => setTimeout(resolve, 2000));
2654
2764
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2655
2765
  const endTime = Date.now();
2656
- this._reportToWorld(world, {
2766
+ _reportToWorld(world, {
2657
2767
  type: Types.GET_PAGE_STATUS,
2658
2768
  text: "page relaod",
2659
2769
  screenshotId,
@@ -2689,11 +2799,37 @@ class StableBrowser {
2689
2799
  console.log("#-#");
2690
2800
  }
2691
2801
  }
2692
- _reportToWorld(world, properties) {
2693
- if (!world || !world.attach) {
2694
- return;
2802
+ async beforeStep(world, step) {
2803
+ this.stepName = step.pickleStep.text;
2804
+ this.logger.info("step: " + this.stepName);
2805
+ if (this.stepIndex === undefined) {
2806
+ this.stepIndex = 0;
2807
+ }
2808
+ else {
2809
+ this.stepIndex++;
2810
+ }
2811
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2812
+ if (this.context.browserObject.context) {
2813
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2814
+ }
2815
+ }
2816
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2817
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2818
+ // check if @global_test_data tag is present
2819
+ if (this.tags.includes("@global_test_data")) {
2820
+ this.saveTestDataAsGlobal({}, world);
2821
+ }
2822
+ }
2823
+ }
2824
+ async afterStep(world, step) {
2825
+ this.stepName = null;
2826
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2827
+ if (this.context.browserObject.context) {
2828
+ await this.context.browserObject.context.tracing.stopChunk({
2829
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2830
+ });
2831
+ }
2695
2832
  }
2696
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2697
2833
  }
2698
2834
  }
2699
2835
  function createTimedPromise(promise, label) {
@@ -2701,156 +2837,5 @@ function createTimedPromise(promise, label) {
2701
2837
  .then((result) => ({ status: "fulfilled", label, result }))
2702
2838
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2703
2839
  }
2704
- const KEYBOARD_EVENTS = [
2705
- "ALT",
2706
- "AltGraph",
2707
- "CapsLock",
2708
- "Control",
2709
- "Fn",
2710
- "FnLock",
2711
- "Hyper",
2712
- "Meta",
2713
- "NumLock",
2714
- "ScrollLock",
2715
- "Shift",
2716
- "Super",
2717
- "Symbol",
2718
- "SymbolLock",
2719
- "Enter",
2720
- "Tab",
2721
- "ArrowDown",
2722
- "ArrowLeft",
2723
- "ArrowRight",
2724
- "ArrowUp",
2725
- "End",
2726
- "Home",
2727
- "PageDown",
2728
- "PageUp",
2729
- "Backspace",
2730
- "Clear",
2731
- "Copy",
2732
- "CrSel",
2733
- "Cut",
2734
- "Delete",
2735
- "EraseEof",
2736
- "ExSel",
2737
- "Insert",
2738
- "Paste",
2739
- "Redo",
2740
- "Undo",
2741
- "Accept",
2742
- "Again",
2743
- "Attn",
2744
- "Cancel",
2745
- "ContextMenu",
2746
- "Escape",
2747
- "Execute",
2748
- "Find",
2749
- "Finish",
2750
- "Help",
2751
- "Pause",
2752
- "Play",
2753
- "Props",
2754
- "Select",
2755
- "ZoomIn",
2756
- "ZoomOut",
2757
- "BrightnessDown",
2758
- "BrightnessUp",
2759
- "Eject",
2760
- "LogOff",
2761
- "Power",
2762
- "PowerOff",
2763
- "PrintScreen",
2764
- "Hibernate",
2765
- "Standby",
2766
- "WakeUp",
2767
- "AllCandidates",
2768
- "Alphanumeric",
2769
- "CodeInput",
2770
- "Compose",
2771
- "Convert",
2772
- "Dead",
2773
- "FinalMode",
2774
- "GroupFirst",
2775
- "GroupLast",
2776
- "GroupNext",
2777
- "GroupPrevious",
2778
- "ModeChange",
2779
- "NextCandidate",
2780
- "NonConvert",
2781
- "PreviousCandidate",
2782
- "Process",
2783
- "SingleCandidate",
2784
- "HangulMode",
2785
- "HanjaMode",
2786
- "JunjaMode",
2787
- "Eisu",
2788
- "Hankaku",
2789
- "Hiragana",
2790
- "HiraganaKatakana",
2791
- "KanaMode",
2792
- "KanjiMode",
2793
- "Katakana",
2794
- "Romaji",
2795
- "Zenkaku",
2796
- "ZenkakuHanaku",
2797
- "F1",
2798
- "F2",
2799
- "F3",
2800
- "F4",
2801
- "F5",
2802
- "F6",
2803
- "F7",
2804
- "F8",
2805
- "F9",
2806
- "F10",
2807
- "F11",
2808
- "F12",
2809
- "Soft1",
2810
- "Soft2",
2811
- "Soft3",
2812
- "Soft4",
2813
- "ChannelDown",
2814
- "ChannelUp",
2815
- "Close",
2816
- "MailForward",
2817
- "MailReply",
2818
- "MailSend",
2819
- "MediaFastForward",
2820
- "MediaPause",
2821
- "MediaPlay",
2822
- "MediaPlayPause",
2823
- "MediaRecord",
2824
- "MediaRewind",
2825
- "MediaStop",
2826
- "MediaTrackNext",
2827
- "MediaTrackPrevious",
2828
- "AudioBalanceLeft",
2829
- "AudioBalanceRight",
2830
- "AudioBassBoostDown",
2831
- "AudioBassBoostToggle",
2832
- "AudioBassBoostUp",
2833
- "AudioFaderFront",
2834
- "AudioFaderRear",
2835
- "AudioSurroundModeNext",
2836
- "AudioTrebleDown",
2837
- "AudioTrebleUp",
2838
- "AudioVolumeDown",
2839
- "AudioVolumeMute",
2840
- "AudioVolumeUp",
2841
- "MicrophoneToggle",
2842
- "MicrophoneVolumeDown",
2843
- "MicrophoneVolumeMute",
2844
- "MicrophoneVolumeUp",
2845
- "TV",
2846
- "TV3DMode",
2847
- "TVAntennaCable",
2848
- "TVAudioDescription",
2849
- ];
2850
- function unEscapeString(str) {
2851
- const placeholder = "__NEWLINE__";
2852
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2853
- return str;
2854
- }
2855
2840
  export { StableBrowser };
2856
2841
  //# sourceMappingURL=stable_browser.js.map