automation_model 1.0.481-dev → 1.0.481-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 +583 -599
  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) {
@@ -800,10 +764,29 @@ class StableBrowser {
800
764
  });
801
765
  result.locatorIndex = i;
802
766
  }
767
+ if (foundLocators.length > 1) {
768
+ info.failCause.foundMultiple = true;
769
+ if (info.locatorLog) {
770
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
771
+ }
772
+ }
803
773
  }
804
774
  return result;
805
775
  }
806
776
  async simpleClick(elementDescription, _params, options = {}, world = null) {
777
+ const state = {
778
+ locate: false,
779
+ scroll: false,
780
+ highlight: false,
781
+ _params,
782
+ options,
783
+ world,
784
+ type: Types.CLICK,
785
+ text: "Click element",
786
+ operation: "simpleClick",
787
+ log: "***** click on " + elementDescription + " *****\n",
788
+ };
789
+ _preCommand(state, this);
807
790
  const startTime = Date.now();
808
791
  let timeout = 30000;
809
792
  if (options && options.timeout) {
@@ -827,13 +810,32 @@ class StableBrowser {
827
810
  }
828
811
  catch (e) {
829
812
  if (performance.now() - startTime > timeout) {
830
- throw e;
813
+ // throw e;
814
+ try {
815
+ await _commandError(state, "timeout looking for " + elementDescription, this);
816
+ }
817
+ finally {
818
+ _commandFinally(state, this);
819
+ }
831
820
  }
832
821
  }
833
822
  await new Promise((resolve) => setTimeout(resolve, 3000));
834
823
  }
835
824
  }
836
825
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
826
+ const state = {
827
+ locate: false,
828
+ scroll: false,
829
+ highlight: false,
830
+ _params,
831
+ options,
832
+ world,
833
+ type: Types.FILL,
834
+ text: "Fill element",
835
+ operation: "simpleClickType",
836
+ log: "***** click type on " + elementDescription + " *****\n",
837
+ };
838
+ _preCommand(state, this);
837
839
  const startTime = Date.now();
838
840
  let timeout = 30000;
839
841
  if (options && options.timeout) {
@@ -857,7 +859,13 @@ class StableBrowser {
857
859
  }
858
860
  catch (e) {
859
861
  if (performance.now() - startTime > timeout) {
860
- throw e;
862
+ // throw e;
863
+ try {
864
+ await _commandError(state, "timeout looking for " + elementDescription, this);
865
+ }
866
+ finally {
867
+ _commandFinally(state, this);
868
+ }
861
869
  }
862
870
  }
863
871
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -881,13 +889,13 @@ class StableBrowser {
881
889
  }
882
890
  try {
883
891
  await state.element.click();
884
- await new Promise((resolve) => setTimeout(resolve, 1000));
892
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
885
893
  }
886
894
  catch (e) {
887
895
  // await this.closeUnexpectedPopups();
888
896
  state.element = await this._locate(selectors, state.info, _params);
889
897
  await state.element.dispatchEvent("click");
890
- await new Promise((resolve) => setTimeout(resolve, 1000));
898
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
891
899
  }
892
900
  await this.waitForPageLoad();
893
901
  return state.info;
@@ -966,7 +974,6 @@ class StableBrowser {
966
974
  await state.element.hover({ timeout: 10000 });
967
975
  await new Promise((resolve) => setTimeout(resolve, 1000));
968
976
  }
969
- await _screenshot(state, this);
970
977
  await this.waitForPageLoad();
971
978
  return state.info;
972
979
  }
@@ -1094,33 +1101,30 @@ class StableBrowser {
1094
1101
  }
1095
1102
  }
1096
1103
  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;
1104
+ const state = {
1105
+ selectors,
1106
+ _params,
1107
+ value: await this._replaceWithLocalData(value, this),
1108
+ options,
1109
+ world,
1110
+ type: Types.SET_DATE_TIME,
1111
+ text: `Set date time value: ${value}`,
1112
+ operation: "setDateTime",
1113
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1114
+ throwError: false,
1115
+ };
1107
1116
  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);
1117
+ await _preCommand(state, this);
1114
1118
  try {
1115
- await element.click();
1119
+ await state.element.click();
1116
1120
  await new Promise((resolve) => setTimeout(resolve, 500));
1117
1121
  if (format) {
1118
- value = dayjs(value).format(format);
1119
- await element.fill(value);
1122
+ state.value = dayjs(state.value).format(format);
1123
+ await state.element.fill(state.value);
1120
1124
  }
1121
1125
  else {
1122
- const dateTimeValue = await getDateTimeValue({ value, element });
1123
- await element.evaluateHandle((el, dateTimeValue) => {
1126
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1127
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1124
1128
  el.value = ""; // clear input
1125
1129
  el.value = dateTimeValue;
1126
1130
  }, dateTimeValue);
@@ -1133,20 +1137,19 @@ class StableBrowser {
1133
1137
  }
1134
1138
  catch (err) {
1135
1139
  //await this.closeUnexpectedPopups();
1136
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1140
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1137
1141
  this.logger.info("Trying again");
1138
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1139
- info.screenshotPath = screenshotPath;
1140
- Object.assign(err, { info: info });
1142
+ await _screenshot(state, this);
1143
+ Object.assign(err, { info: state.info });
1141
1144
  await element.click();
1142
1145
  await new Promise((resolve) => setTimeout(resolve, 500));
1143
1146
  if (format) {
1144
- value = dayjs(value).format(format);
1145
- await element.fill(value);
1147
+ state.value = dayjs(state.value).format(format);
1148
+ await state.element.fill(state.value);
1146
1149
  }
1147
1150
  else {
1148
- const dateTimeValue = await getDateTimeValue({ value, element });
1149
- await element.evaluateHandle((el, dateTimeValue) => {
1151
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1152
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1150
1153
  el.value = ""; // clear input
1151
1154
  el.value = dateTimeValue;
1152
1155
  }, dateTimeValue);
@@ -1159,50 +1162,30 @@ class StableBrowser {
1159
1162
  }
1160
1163
  }
1161
1164
  catch (e) {
1162
- error = e;
1163
- throw e;
1165
+ await _commandError(state, e, this);
1164
1166
  }
1165
1167
  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
- });
1168
+ _commandFinally(state, this);
1187
1169
  }
1188
1170
  }
1189
1171
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
1172
+ _value = unEscapeString(_value);
1173
+ const newValue = await this._replaceWithLocalData(_value, world);
1190
1174
  const state = {
1191
1175
  selectors,
1192
1176
  _params,
1193
- value: unEscapeString(_value),
1177
+ value: newValue,
1178
+ originalValue: _value,
1194
1179
  options,
1195
1180
  world,
1196
1181
  type: Types.FILL,
1197
1182
  text: `Click type input with value: ${_value}`,
1198
1183
  operation: "clickType",
1199
- log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
1184
+ log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1200
1185
  };
1201
- const newValue = await this._replaceWithLocalData(state.value, world);
1202
1186
  if (newValue !== _value) {
1203
1187
  //this.logger.info(_value + "=" + newValue);
1204
1188
  _value = newValue;
1205
- state.value = newValue;
1206
1189
  }
1207
1190
  try {
1208
1191
  await _preCommand(state, this);
@@ -1211,7 +1194,7 @@ class StableBrowser {
1211
1194
  try {
1212
1195
  let currentValue = await state.element.inputValue();
1213
1196
  if (currentValue) {
1214
- await element.fill("");
1197
+ await state.element.fill("");
1215
1198
  }
1216
1199
  }
1217
1200
  catch (e) {
@@ -1319,6 +1302,7 @@ class StableBrowser {
1319
1302
  let screenshotPath = null;
1320
1303
  if (!info.log) {
1321
1304
  info.log = "";
1305
+ info.locatorLog = new LocatorLog(selectors);
1322
1306
  }
1323
1307
  info.operation = "getText";
1324
1308
  info.selectors = selectors;
@@ -1657,11 +1641,9 @@ class StableBrowser {
1657
1641
  if (!fs.existsSync(world.screenshotPath)) {
1658
1642
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1659
1643
  }
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");
1644
+ // to make sure the path doesn't start with -
1645
+ const uuidStr = "id_" + randomUUID();
1646
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1665
1647
  try {
1666
1648
  await this.takeScreenshot(screenshotPath);
1667
1649
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1675,7 +1657,7 @@ class StableBrowser {
1675
1657
  catch (e) {
1676
1658
  this.logger.info("unable to take screenshot, ignored");
1677
1659
  }
1678
- result.screenshotId = nextIndex;
1660
+ result.screenshotId = uuidStr;
1679
1661
  result.screenshotPath = screenshotPath;
1680
1662
  if (info && info.box) {
1681
1663
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1773,74 +1755,101 @@ class StableBrowser {
1773
1755
  }
1774
1756
  }
1775
1757
  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;
1758
+ const state = {
1759
+ selectors,
1760
+ _params,
1761
+ attribute,
1762
+ variable,
1763
+ options,
1764
+ world,
1765
+ type: Types.EXTRACT,
1766
+ text: `Extract attribute from element`,
1767
+ operation: "extractAttribute",
1768
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1769
+ };
1781
1770
  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
1771
  try {
1787
- const element = await this._locate(selectors, info, _params);
1788
- await this._highlightElements(element);
1789
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1772
+ await _preCommand(state, this);
1790
1773
  switch (attribute) {
1791
1774
  case "inner_text":
1792
- info.value = await element.innerText();
1775
+ state.value = await state.element.innerText();
1793
1776
  break;
1794
1777
  case "href":
1795
- info.value = await element.getAttribute("href");
1778
+ state.value = await state.element.getAttribute("href");
1796
1779
  break;
1797
1780
  case "value":
1798
- info.value = await element.inputValue();
1781
+ state.value = await state.element.inputValue();
1799
1782
  break;
1800
1783
  default:
1801
- info.value = await element.getAttribute(attribute);
1784
+ state.value = await state.element.getAttribute(attribute);
1802
1785
  break;
1803
1786
  }
1804
- this[variable] = info.value;
1805
- if (world) {
1806
- world[variable] = info.value;
1787
+ state.info.value = state.value;
1788
+ this.setTestData({ [variable]: state.value }, world);
1789
+ this.logger.info("set test data: " + variable + "=" + state.value);
1790
+ return state.info;
1791
+ }
1792
+ catch (e) {
1793
+ await _commandError(state, e, this);
1794
+ }
1795
+ finally {
1796
+ _commandFinally(state, this);
1797
+ }
1798
+ }
1799
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1800
+ const state = {
1801
+ selectors,
1802
+ _params,
1803
+ attribute,
1804
+ value,
1805
+ options,
1806
+ world,
1807
+ type: Types.VERIFY_ATTRIBUTE,
1808
+ text: `Verify element attribute`,
1809
+ operation: "verifyAttribute",
1810
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1811
+ };
1812
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1813
+ let val;
1814
+ try {
1815
+ await _preCommand(state, this);
1816
+ switch (attribute) {
1817
+ case "innerText":
1818
+ val = String(await state.element.innerText());
1819
+ break;
1820
+ case "value":
1821
+ val = String(await state.element.inputValue());
1822
+ break;
1823
+ case "checked":
1824
+ val = String(await state.element.isChecked());
1825
+ break;
1826
+ case "disabled":
1827
+ val = String(await state.element.isDisabled());
1828
+ break;
1829
+ default:
1830
+ val = String(await state.element.getAttribute(attribute));
1831
+ break;
1807
1832
  }
1808
- this.setTestData({ [variable]: info.value }, world);
1809
- this.logger.info("set test data: " + variable + "=" + info.value);
1810
- return info;
1833
+ let regex;
1834
+ if (value.startsWith("/") && value.endsWith("/")) {
1835
+ const patternBody = value.slice(1, -1);
1836
+ regex = new RegExp(patternBody, "g");
1837
+ }
1838
+ else {
1839
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1840
+ regex = new RegExp(escapedPattern, "g");
1841
+ }
1842
+ if (!val.match(regex)) {
1843
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1844
+ }
1845
+ state.info.value = val;
1846
+ return state.info;
1811
1847
  }
1812
1848
  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;
1849
+ await _commandError(state, e, this);
1820
1850
  }
1821
1851
  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
- });
1852
+ _commandFinally(state, this);
1844
1853
  }
1845
1854
  }
1846
1855
  async extractEmailData(emailAddress, options, world) {
@@ -1861,7 +1870,7 @@ class StableBrowser {
1861
1870
  if (options && options.timeout) {
1862
1871
  timeout = options.timeout;
1863
1872
  }
1864
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1873
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1865
1874
  const request = {
1866
1875
  method: "POST",
1867
1876
  url: serviceUrl,
@@ -1917,7 +1926,8 @@ class StableBrowser {
1917
1926
  catch (e) {
1918
1927
  errorCount++;
1919
1928
  if (errorCount > 3) {
1920
- throw e;
1929
+ // throw e;
1930
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1921
1931
  }
1922
1932
  // ignore
1923
1933
  }
@@ -2028,11 +2038,12 @@ class StableBrowser {
2028
2038
  info.screenshotPath = screenshotPath;
2029
2039
  Object.assign(e, { info: info });
2030
2040
  error = e;
2031
- throw e;
2041
+ // throw e;
2042
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2032
2043
  }
2033
2044
  finally {
2034
2045
  const endTime = Date.now();
2035
- this._reportToWorld(world, {
2046
+ _reportToWorld(world, {
2036
2047
  type: Types.VERIFY_PAGE_PATH,
2037
2048
  text: "Verify page path",
2038
2049
  screenshotId,
@@ -2052,54 +2063,64 @@ class StableBrowser {
2052
2063
  });
2053
2064
  }
2054
2065
  }
2066
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2067
+ const frames = this.page.frames();
2068
+ let results = [];
2069
+ for (let i = 0; i < frames.length; i++) {
2070
+ if (dateAlternatives.date) {
2071
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2072
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2073
+ result.frame = frames[i];
2074
+ results.push(result);
2075
+ }
2076
+ }
2077
+ else if (numberAlternatives.number) {
2078
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2079
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2080
+ result.frame = frames[i];
2081
+ results.push(result);
2082
+ }
2083
+ }
2084
+ else {
2085
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2086
+ result.frame = frames[i];
2087
+ results.push(result);
2088
+ }
2089
+ }
2090
+ state.info.results = results;
2091
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2092
+ return resultWithElementsFound;
2093
+ }
2055
2094
  async verifyTextExistInPage(text, options = {}, world = null) {
2056
2095
  text = unEscapeString(text);
2057
- const startTime = Date.now();
2096
+ const state = {
2097
+ text_search: text,
2098
+ options,
2099
+ world,
2100
+ locate: false,
2101
+ scroll: false,
2102
+ highlight: false,
2103
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2104
+ text: `Verify text exists in page`,
2105
+ operation: "verifyTextExistInPage",
2106
+ log: "***** verify text " + text + " exists in page *****\n",
2107
+ };
2058
2108
  const timeout = this._getLoadTimeout(options);
2059
- let error = null;
2060
- let screenshotId = null;
2061
- let screenshotPath = null;
2062
2109
  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
2110
  const newValue = await this._replaceWithLocalData(text, world);
2067
2111
  if (newValue !== text) {
2068
2112
  this.logger.info(text + "=" + newValue);
2069
2113
  text = newValue;
2070
2114
  }
2071
- info.text = text;
2072
2115
  let dateAlternatives = findDateAlternatives(text);
2073
2116
  let numberAlternatives = findNumberAlternatives(text);
2074
2117
  try {
2118
+ await _preCommand(state, this);
2119
+ state.info.text = text;
2075
2120
  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);
2121
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2101
2122
  if (resultWithElementsFound.length === 0) {
2102
- if (Date.now() - startTime > timeout) {
2123
+ if (Date.now() - state.startTime > timeout) {
2103
2124
  throw new Error(`Text ${text} not found in page`);
2104
2125
  }
2105
2126
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2111,55 +2132,163 @@ class StableBrowser {
2111
2132
  await this._highlightElements(frame, dataAttribute);
2112
2133
  const element = await frame.$(dataAttribute);
2113
2134
  if (element) {
2114
- await this.scrollIfNeeded(element, info);
2135
+ await this.scrollIfNeeded(element, state.info);
2115
2136
  await element.dispatchEvent("bvt_verify_page_contains_text");
2116
2137
  }
2117
2138
  }
2118
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2119
- return info;
2139
+ await _screenshot(state, this);
2140
+ return state.info;
2120
2141
  }
2121
2142
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2122
2143
  }
2123
2144
  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;
2145
+ await _commandError(state, e, this);
2131
2146
  }
2132
2147
  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
- });
2148
+ _commandFinally(state, this);
2152
2149
  }
2153
2150
  }
2154
- _getServerUrl() {
2155
- let serviceUrl = "https://api.blinq.io";
2156
- if (process.env.NODE_ENV_BLINQ === "dev") {
2157
- serviceUrl = "https://dev.api.blinq.io";
2151
+ async waitForTextToDisappear(text, options = {}, world = null) {
2152
+ text = unEscapeString(text);
2153
+ const state = {
2154
+ text_search: text,
2155
+ options,
2156
+ world,
2157
+ locate: false,
2158
+ scroll: false,
2159
+ highlight: false,
2160
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2161
+ text: `Verify text does not exist in page`,
2162
+ operation: "verifyTextNotExistInPage",
2163
+ log: "***** verify text " + text + " does not exist in page *****\n",
2164
+ };
2165
+ const timeout = this._getLoadTimeout(options);
2166
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2167
+ const newValue = await this._replaceWithLocalData(text, world);
2168
+ if (newValue !== text) {
2169
+ this.logger.info(text + "=" + newValue);
2170
+ text = newValue;
2158
2171
  }
2159
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2160
- serviceUrl = "https://stage.api.blinq.io";
2172
+ let dateAlternatives = findDateAlternatives(text);
2173
+ let numberAlternatives = findNumberAlternatives(text);
2174
+ try {
2175
+ await _preCommand(state, this);
2176
+ state.info.text = text;
2177
+ while (true) {
2178
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2179
+ if (resultWithElementsFound.length === 0) {
2180
+ await _screenshot(state, this);
2181
+ return state.info;
2182
+ }
2183
+ if (Date.now() - state.startTime > timeout) {
2184
+ throw new Error(`Text ${text} found in page`);
2185
+ }
2186
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2187
+ }
2188
+ }
2189
+ catch (e) {
2190
+ await _commandError(state, e, this);
2191
+ }
2192
+ finally {
2193
+ _commandFinally(state, this);
2194
+ }
2195
+ }
2196
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2197
+ textAnchor = unEscapeString(textAnchor);
2198
+ textToVerify = unEscapeString(textToVerify);
2199
+ const state = {
2200
+ text_search: textToVerify,
2201
+ options,
2202
+ world,
2203
+ locate: false,
2204
+ scroll: false,
2205
+ highlight: false,
2206
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2207
+ text: `Verify text with relation to another text`,
2208
+ operation: "verify_text_with_relation",
2209
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2210
+ };
2211
+ const timeout = this._getLoadTimeout(options);
2212
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2213
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2214
+ if (newValue !== textAnchor) {
2215
+ this.logger.info(textAnchor + "=" + newValue);
2216
+ textAnchor = newValue;
2217
+ }
2218
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2219
+ if (newValue !== textToVerify) {
2220
+ this.logger.info(textToVerify + "=" + newValue);
2221
+ textToVerify = newValue;
2222
+ }
2223
+ let dateAlternatives = findDateAlternatives(textToVerify);
2224
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2225
+ let foundAncore = false;
2226
+ try {
2227
+ await _preCommand(state, this);
2228
+ state.info.text = textToVerify;
2229
+ while (true) {
2230
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2231
+ if (resultWithElementsFound.length === 0) {
2232
+ if (Date.now() - state.startTime > timeout) {
2233
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2234
+ }
2235
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2236
+ continue;
2237
+ }
2238
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2239
+ foundAncore = true;
2240
+ const result = resultWithElementsFound[i];
2241
+ const token = result.randomToken;
2242
+ const frame = result.frame;
2243
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2244
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2245
+ const elements = Array.from(document.querySelectorAll(css));
2246
+ for (let i = 0; i < elements.length; i++) {
2247
+ const element = elements[i];
2248
+ let climbParent = element;
2249
+ for (let j = 0; j < climb; j++) {
2250
+ climbParent = climbParent.parentElement;
2251
+ if (!climbParent) {
2252
+ break;
2253
+ }
2254
+ }
2255
+ if (!climbParent) {
2256
+ continue;
2257
+ }
2258
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2259
+ if (foundElements.length > 0) {
2260
+ // set the container element attribute
2261
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2262
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2263
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2264
+ return { found: true };
2265
+ }
2266
+ }
2267
+ return { found: false };
2268
+ }, [css, climb, textToVerify, result.randomToken]);
2269
+ if (findResult.found === true) {
2270
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2271
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2272
+ await this._highlightElements(frame, dataAttribute);
2273
+ await this._highlightElements(frame, cssAnchor);
2274
+ const element = await frame.$(dataAttribute);
2275
+ if (element) {
2276
+ await this.scrollIfNeeded(element, state.info);
2277
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2278
+ }
2279
+ await _screenshot(state, this);
2280
+ return state.info;
2281
+ }
2282
+ }
2283
+ }
2284
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2285
+ }
2286
+ catch (e) {
2287
+ await _commandError(state, e, this);
2288
+ }
2289
+ finally {
2290
+ _commandFinally(state, this);
2161
2291
  }
2162
- return serviceUrl;
2163
2292
  }
2164
2293
  async visualVerification(text, options = {}, world = null) {
2165
2294
  const startTime = Date.now();
@@ -2175,7 +2304,7 @@ class StableBrowser {
2175
2304
  throw new Error("TOKEN is not set");
2176
2305
  }
2177
2306
  try {
2178
- let serviceUrl = this._getServerUrl();
2307
+ let serviceUrl = _getServerUrl();
2179
2308
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2180
2309
  info.screenshotPath = screenshotPath;
2181
2310
  const screenshot = await this.takeScreenshot();
@@ -2211,11 +2340,12 @@ class StableBrowser {
2211
2340
  info.screenshotPath = screenshotPath;
2212
2341
  Object.assign(e, { info: info });
2213
2342
  error = e;
2214
- throw e;
2343
+ // throw e;
2344
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2215
2345
  }
2216
2346
  finally {
2217
2347
  const endTime = Date.now();
2218
- this._reportToWorld(world, {
2348
+ _reportToWorld(world, {
2219
2349
  type: Types.VERIFY_VISUAL,
2220
2350
  text: "Visual verification",
2221
2351
  screenshotId,
@@ -2263,6 +2393,7 @@ class StableBrowser {
2263
2393
  let screenshotPath = null;
2264
2394
  const info = {};
2265
2395
  info.log = "";
2396
+ info.locatorLog = new LocatorLog(selectors);
2266
2397
  info.operation = "getTableData";
2267
2398
  info.selectors = selectors;
2268
2399
  try {
@@ -2278,11 +2409,12 @@ class StableBrowser {
2278
2409
  info.screenshotPath = screenshotPath;
2279
2410
  Object.assign(e, { info: info });
2280
2411
  error = e;
2281
- throw e;
2412
+ // throw e;
2413
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2282
2414
  }
2283
2415
  finally {
2284
2416
  const endTime = Date.now();
2285
- this._reportToWorld(world, {
2417
+ _reportToWorld(world, {
2286
2418
  element_name: selectors.element_name,
2287
2419
  type: Types.GET_TABLE_DATA,
2288
2420
  text: "Get table data",
@@ -2337,7 +2469,7 @@ class StableBrowser {
2337
2469
  info.operation = "analyzeTable";
2338
2470
  info.selectors = selectors;
2339
2471
  info.query = query;
2340
- query = this._fixUsingParams(query, _params);
2472
+ query = _fixUsingParams(query, _params);
2341
2473
  info.query_fixed = query;
2342
2474
  info.operator = operator;
2343
2475
  info.value = value;
@@ -2443,11 +2575,12 @@ class StableBrowser {
2443
2575
  info.screenshotPath = screenshotPath;
2444
2576
  Object.assign(e, { info: info });
2445
2577
  error = e;
2446
- throw e;
2578
+ // throw e;
2579
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2447
2580
  }
2448
2581
  finally {
2449
2582
  const endTime = Date.now();
2450
- this._reportToWorld(world, {
2583
+ _reportToWorld(world, {
2451
2584
  element_name: selectors.element_name,
2452
2585
  type: Types.ANALYZE_TABLE,
2453
2586
  text: "Analyze table",
@@ -2469,27 +2602,7 @@ class StableBrowser {
2469
2602
  }
2470
2603
  }
2471
2604
  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;
2605
+ return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
2493
2606
  }
2494
2607
  _getLoadTimeout(options) {
2495
2608
  let timeout = 15000;
@@ -2540,7 +2653,7 @@ class StableBrowser {
2540
2653
  await new Promise((resolve) => setTimeout(resolve, 2000));
2541
2654
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2542
2655
  const endTime = Date.now();
2543
- this._reportToWorld(world, {
2656
+ _reportToWorld(world, {
2544
2657
  type: Types.GET_PAGE_STATUS,
2545
2658
  text: "Wait for page load",
2546
2659
  screenshotId,
@@ -2560,41 +2673,35 @@ class StableBrowser {
2560
2673
  }
2561
2674
  }
2562
2675
  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 = {};
2676
+ const state = {
2677
+ options,
2678
+ world,
2679
+ locate: false,
2680
+ scroll: false,
2681
+ highlight: false,
2682
+ type: Types.CLOSE_PAGE,
2683
+ text: `Close page`,
2684
+ operation: "closePage",
2685
+ log: "***** close page *****\n",
2686
+ throwError: false,
2687
+ };
2568
2688
  try {
2689
+ await _preCommand(state, this);
2569
2690
  await this.page.close();
2570
2691
  }
2571
2692
  catch (e) {
2572
2693
  console.log(".");
2694
+ await _commandError(state, e, this);
2573
2695
  }
2574
2696
  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
- });
2697
+ _commandFinally(state, this);
2596
2698
  }
2597
2699
  }
2700
+ saveTestDataAsGlobal(options, world) {
2701
+ const dataFile = this._getDataFile(world);
2702
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2703
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2704
+ }
2598
2705
  async setViewportSize(width, hight, options = {}, world = null) {
2599
2706
  const startTime = Date.now();
2600
2707
  let error = null;
@@ -2612,12 +2719,13 @@ class StableBrowser {
2612
2719
  }
2613
2720
  catch (e) {
2614
2721
  console.log(".");
2722
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2615
2723
  }
2616
2724
  finally {
2617
2725
  await new Promise((resolve) => setTimeout(resolve, 2000));
2618
2726
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2619
2727
  const endTime = Date.now();
2620
- this._reportToWorld(world, {
2728
+ _reportToWorld(world, {
2621
2729
  type: Types.SET_VIEWPORT,
2622
2730
  text: "set viewport size to " + width + "x" + hight,
2623
2731
  screenshotId,
@@ -2648,12 +2756,13 @@ class StableBrowser {
2648
2756
  }
2649
2757
  catch (e) {
2650
2758
  console.log(".");
2759
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2651
2760
  }
2652
2761
  finally {
2653
2762
  await new Promise((resolve) => setTimeout(resolve, 2000));
2654
2763
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2655
2764
  const endTime = Date.now();
2656
- this._reportToWorld(world, {
2765
+ _reportToWorld(world, {
2657
2766
  type: Types.GET_PAGE_STATUS,
2658
2767
  text: "page relaod",
2659
2768
  screenshotId,
@@ -2689,11 +2798,37 @@ class StableBrowser {
2689
2798
  console.log("#-#");
2690
2799
  }
2691
2800
  }
2692
- _reportToWorld(world, properties) {
2693
- if (!world || !world.attach) {
2694
- return;
2801
+ async beforeStep(world, step) {
2802
+ this.stepName = step.pickleStep.text;
2803
+ this.logger.info("step: " + this.stepName);
2804
+ if (this.stepIndex === undefined) {
2805
+ this.stepIndex = 0;
2806
+ }
2807
+ else {
2808
+ this.stepIndex++;
2809
+ }
2810
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2811
+ if (this.context.browserObject.context) {
2812
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2813
+ }
2814
+ }
2815
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2816
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2817
+ // check if @global_test_data tag is present
2818
+ if (this.tags.includes("@global_test_data")) {
2819
+ this.saveTestDataAsGlobal({}, world);
2820
+ }
2821
+ }
2822
+ }
2823
+ async afterStep(world, step) {
2824
+ this.stepName = null;
2825
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2826
+ if (this.context.browserObject.context) {
2827
+ await this.context.browserObject.context.tracing.stopChunk({
2828
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2829
+ });
2830
+ }
2695
2831
  }
2696
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2697
2832
  }
2698
2833
  }
2699
2834
  function createTimedPromise(promise, label) {
@@ -2701,156 +2836,5 @@ function createTimedPromise(promise, label) {
2701
2836
  .then((result) => ({ status: "fulfilled", label, result }))
2702
2837
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2703
2838
  }
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
2839
  export { StableBrowser };
2856
2840
  //# sourceMappingURL=stable_browser.js.map