automation_model 1.0.490-dev → 1.0.490-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 +29 -9
  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 +55 -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 +2 -1
  34. package/lib/network.js +33 -1
  35. package/lib/network.js.map +1 -1
  36. package/lib/scripts/find_text.js +126 -0
  37. package/lib/stable_browser.d.ts +49 -17
  38. package/lib/stable_browser.js +614 -574
  39. package/lib/stable_browser.js.map +1 -1
  40. package/lib/table.d.ts +13 -0
  41. package/lib/table.js +187 -0
  42. package/lib/table.js.map +1 -0
  43. package/lib/test_context.d.ts +1 -0
  44. package/lib/test_context.js +1 -0
  45. package/lib/test_context.js.map +1 -1
  46. package/lib/utils.d.ts +12 -1
  47. package/lib/utils.js +236 -4
  48. package/lib/utils.js.map +1 -1
  49. package/package.json +7 -5
  50. /package/lib/{axe → scripts}/axe.mini.js +0 -0
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
20
- import { registerNetworkEvents } from "./network.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,25 +95,32 @@ class StableBrowser {
87
95
  catch (e) {
88
96
  this.logger.error("unable to read ai_config.json");
89
97
  }
90
- context.pageLoading = { status: false };
91
- context.pages = [this.page];
92
98
  const logFolder = path.join(this.project_path, "logs", "web");
93
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
94
102
  this.registerEventListeners(this.context);
95
103
  registerNetworkEvents(this.world, this, this.context, this.page);
104
+ registerDownloadEvent(this.page, this.world, this.context);
96
105
  }
97
106
  registerEventListeners(context) {
98
107
  this.registerConsoleLogListener(this.page, context);
99
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
100
109
  if (!context.pageLoading) {
101
110
  context.pageLoading = { status: false };
102
111
  }
103
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
+ }
104
118
  context.pageLoading.status = true;
105
119
  this.page = page;
106
120
  context.page = page;
107
121
  context.pages.push(page);
108
122
  registerNetworkEvents(this.world, this, context, this.page);
123
+ registerDownloadEvent(this.page, this.world, context);
109
124
  page.on("close", async () => {
110
125
  if (this.context && this.context.pages && this.context.pages.length > 1) {
111
126
  this.context.pages.pop();
@@ -135,9 +150,9 @@ class StableBrowser {
135
150
  if (this.appName === appName) {
136
151
  return;
137
152
  }
138
- let newContextCreated = false;
153
+ let navigate = false;
139
154
  if (!apps[appName]) {
140
- 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);
141
156
  newContextCreated = true;
142
157
  apps[appName] = {
143
158
  context: newContext,
@@ -146,32 +161,15 @@ class StableBrowser {
146
161
  };
147
162
  }
148
163
  const tempContext = {};
149
- this._copyContext(this, tempContext);
150
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
151
166
  apps[this.appName] = tempContext;
152
167
  this.appName = appName;
153
- if (newContextCreated) {
154
- this.registerEventListeners(this.context);
168
+ if (navigate) {
155
169
  await this.goto(this.context.environment.baseUrl);
156
170
  await this.waitForPageLoad();
157
171
  }
158
172
  }
159
- _copyContext(from, to) {
160
- to.browser = from.browser;
161
- to.page = from.page;
162
- to.context = from.context;
163
- }
164
- getWebLogFile(logFolder) {
165
- if (!fs.existsSync(logFolder)) {
166
- fs.mkdirSync(logFolder, { recursive: true });
167
- }
168
- let nextIndex = 1;
169
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
170
- nextIndex++;
171
- }
172
- const fileName = nextIndex + ".json";
173
- return path.join(logFolder, fileName);
174
- }
175
173
  registerConsoleLogListener(page, context) {
176
174
  if (!this.context.webLogger) {
177
175
  this.context.webLogger = [];
@@ -221,7 +219,7 @@ class StableBrowser {
221
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
222
220
  }
223
221
  catch (error) {
224
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
225
223
  context.networkLogger.push({
226
224
  error: "not able to listen",
227
225
  message: error.message,
@@ -235,55 +233,40 @@ class StableBrowser {
235
233
  // async closeUnexpectedPopups() {
236
234
  // await closeUnexpectedPopups(this.page);
237
235
  // }
238
- async goto(url) {
236
+ async goto(url, world = null) {
239
237
  if (!url.startsWith("http")) {
240
238
  url = "https://" + url;
241
239
  }
242
- await this.page.goto(url, {
243
- timeout: 60000,
244
- });
245
- }
246
- _fixUsingParams(text, _params) {
247
- if (!_params || typeof text !== "string") {
248
- return text;
249
- }
250
- for (let key in _params) {
251
- let regValue = key;
252
- if (key.startsWith("_")) {
253
- // remove the _ prefix
254
- regValue = key.substring(1);
255
- }
256
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
240
+ const state = {
241
+ value: url,
242
+ world: world,
243
+ type: Types.NAVIGATE,
244
+ text: `Navigate Page to: ${url}`,
245
+ operation: "goto",
246
+ log: "***** navigate page to " + url + " *****\n",
247
+ info: {},
248
+ locate: false,
249
+ scroll: false,
250
+ screenshot: false,
251
+ highlight: false,
252
+ };
253
+ try {
254
+ await _preCommand(state, this);
255
+ await this.page.goto(url, {
256
+ timeout: 60000,
257
+ });
258
+ await _screenshot(state, this);
257
259
  }
258
- return text;
259
- }
260
- _fixLocatorUsingParams(locator, _params) {
261
- // check if not null
262
- if (!locator) {
263
- return locator;
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
264
263
  }
265
- // clone the locator
266
- locator = JSON.parse(JSON.stringify(locator));
267
- this.scanAndManipulate(locator, _params);
268
- return locator;
269
- }
270
- _isObject(value) {
271
- return value && typeof value === "object" && value.constructor === Object;
272
- }
273
- scanAndManipulate(currentObj, _params) {
274
- for (const key in currentObj) {
275
- if (typeof currentObj[key] === "string") {
276
- // Perform string manipulation
277
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
278
- }
279
- else if (this._isObject(currentObj[key])) {
280
- // Recursively scan nested objects
281
- this.scanAndManipulate(currentObj[key], _params);
282
- }
264
+ finally {
265
+ _commandFinally(state, this);
283
266
  }
284
267
  }
285
268
  _getLocator(locator, scope, _params) {
286
- locator = this._fixLocatorUsingParams(locator, _params);
269
+ locator = _fixLocatorUsingParams(locator, _params);
287
270
  let locatorReturn;
288
271
  if (locator.role) {
289
272
  if (locator.role[1].nameReg) {
@@ -291,7 +274,7 @@ class StableBrowser {
291
274
  delete locator.role[1].nameReg;
292
275
  }
293
276
  // if (locator.role[1].name) {
294
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
277
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
295
278
  // }
296
279
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
297
280
  }
@@ -334,7 +317,7 @@ class StableBrowser {
334
317
  if (css && css.locator) {
335
318
  css = css.locator;
336
319
  }
337
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
320
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
338
321
  if (result.elementCount === 0) {
339
322
  return;
340
323
  }
@@ -350,116 +333,42 @@ class StableBrowser {
350
333
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
351
334
  //const stringifyText = JSON.stringify(text);
352
335
  return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
353
- function isParent(parent, child) {
354
- let currentNode = child.parentNode;
355
- while (currentNode !== null) {
356
- if (currentNode === parent) {
357
- return true;
358
- }
359
- currentNode = currentNode.parentNode;
360
- }
361
- return false;
362
- }
363
- document.isParent = isParent;
364
- function getRegex(str) {
365
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
366
- if (!match) {
367
- return null;
368
- }
369
- let [_, pattern, flags] = match;
370
- return new RegExp(pattern, flags);
371
- }
372
- document.getRegex = getRegex;
373
- function collectAllShadowDomElements(element, result = []) {
374
- // Check and add the element if it has a shadow root
375
- if (element.shadowRoot) {
376
- result.push(element);
377
- // Also search within the shadow root
378
- document.collectAllShadowDomElements(element.shadowRoot, result);
379
- }
380
- // Iterate over child nodes
381
- element.childNodes.forEach((child) => {
382
- // Recursively call the function for each child node
383
- document.collectAllShadowDomElements(child, result);
384
- });
385
- return result;
386
- }
387
- document.collectAllShadowDomElements = collectAllShadowDomElements;
388
- if (!tag) {
389
- tag = "*";
390
- }
391
- let regexpSearch = document.getRegex(text);
392
- if (regexpSearch) {
393
- regex = true;
394
- }
395
- let elements = Array.from(document.querySelectorAll(tag));
396
- let shadowHosts = [];
397
- document.collectAllShadowDomElements(document, shadowHosts);
398
- for (let i = 0; i < shadowHosts.length; i++) {
399
- let shadowElement = shadowHosts[i].shadowRoot;
400
- if (!shadowElement) {
401
- console.log("shadowElement is null, for host " + shadowHosts[i]);
402
- continue;
403
- }
404
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
405
- elements = elements.concat(shadowElements);
406
- }
407
- let randomToken = null;
408
- const foundElements = [];
336
+ const options = {
337
+ innerText: true,
338
+ };
409
339
  if (regex) {
410
- if (!regexpSearch) {
411
- regexpSearch = new RegExp(text, "im");
412
- }
413
- for (let i = 0; i < elements.length; i++) {
414
- const element = elements[i];
415
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
416
- (element.value && regexpSearch.test(element.value))) {
417
- foundElements.push(element);
418
- }
419
- }
340
+ options.singleRegex = true;
420
341
  }
421
- else {
422
- text = text.trim();
423
- for (let i = 0; i < elements.length; i++) {
424
- const element = elements[i];
425
- if (partial) {
426
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
427
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
428
- foundElements.push(element);
429
- }
430
- }
431
- else {
432
- if ((element.innerText && element.innerText.trim() === text) ||
433
- (element.value && element.value === text)) {
434
- foundElements.push(element);
435
- }
436
- }
437
- }
342
+ if (tag) {
343
+ options.tag = tag;
438
344
  }
439
- let noChildElements = [];
440
- for (let i = 0; i < foundElements.length; i++) {
441
- let element = foundElements[i];
442
- let hasChild = false;
443
- for (let j = 0; j < foundElements.length; j++) {
444
- if (i === j) {
445
- continue;
446
- }
447
- if (isParent(element, foundElements[j])) {
448
- hasChild = true;
449
- break;
450
- }
345
+ if (!(partial === true)) {
346
+ options.exactMatch = true;
347
+ }
348
+ if (text.startsWith("/") && text.endsWith("/")) {
349
+ if (text.length < 3) {
350
+ throw new Error("Invalid regex pattern: empty pattern");
451
351
  }
452
- if (!hasChild) {
453
- noChildElements.push(element);
352
+ try {
353
+ const pattern = text.slice(1, -1);
354
+ new RegExp(pattern); // Validate pattern
355
+ text = pattern;
356
+ options.singleRegex = true;
357
+ }
358
+ catch (e) {
359
+ throw new Error(`Invalid regex pattern: ${e.message}`);
454
360
  }
455
361
  }
362
+ const elements = window.findMatchingElements(text, options);
363
+ let randomToken = null;
364
+ const foundElements = [];
456
365
  let elementCount = 0;
457
- if (noChildElements.length > 0) {
458
- for (let i = 0; i < noChildElements.length; i++) {
366
+ if (elements.length > 0) {
367
+ for (let i = 0; i < elements.length; i++) {
459
368
  if (randomToken === null) {
460
369
  randomToken = Math.random().toString(36).substring(7);
461
370
  }
462
- let element = noChildElements[i];
371
+ let element = elements[i];
463
372
  element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
464
373
  elementCount++;
465
374
  }
@@ -468,9 +377,21 @@ class StableBrowser {
468
377
  }, [text1, tag1, regex1, partial1]);
469
378
  }
470
379
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
380
+ if (!info) {
381
+ info = {};
382
+ }
383
+ if (!info.failCause) {
384
+ info.failCause = {};
385
+ }
386
+ if (!info.log) {
387
+ info.log = "";
388
+ info.locatorLog = new LocatorLog(selectorHierarchy);
389
+ }
471
390
  let locatorSearch = selectorHierarchy[index];
391
+ let originalLocatorSearch = "";
472
392
  try {
473
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
393
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
394
+ locatorSearch = JSON.parse(originalLocatorSearch);
474
395
  }
475
396
  catch (e) {
476
397
  console.error(e);
@@ -480,13 +401,18 @@ class StableBrowser {
480
401
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
481
402
  let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
482
403
  if (!locatorString) {
404
+ info.failCause.textNotFound = true;
405
+ info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
483
406
  return;
484
407
  }
485
408
  locator = this._getLocator({ css: locatorString }, scope, _params);
486
409
  }
487
410
  else if (locatorSearch.text) {
488
- let result = await this._locateElementByText(scope, this._fixUsingParams(locatorSearch.text, _params), locatorSearch.tag, false, locatorSearch.partial === true, _params);
411
+ let text = _fixUsingParams(locatorSearch.text, _params);
412
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
489
413
  if (result.elementCount === 0) {
414
+ info.failCause.textNotFound = true;
415
+ info.failCause.lastError = "failed to locate element by text: " + text;
490
416
  return;
491
417
  }
492
418
  locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
@@ -503,13 +429,22 @@ class StableBrowser {
503
429
  // cssHref = true;
504
430
  // }
505
431
  let count = await locator.count();
432
+ if (count > 0 && !info.failCause.count) {
433
+ info.failCause.count = count;
434
+ }
506
435
  //info.log += "total elements found " + count + "\n";
507
436
  //let visibleCount = 0;
508
437
  let visibleLocator = null;
509
- if (locatorSearch.index && locatorSearch.index < count) {
438
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
510
439
  foundLocators.push(locator.nth(locatorSearch.index));
440
+ if (info.locatorLog) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
442
+ }
511
443
  return;
512
444
  }
445
+ if (info.locatorLog && count === 0) {
446
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
447
+ }
513
448
  for (let j = 0; j < count; j++) {
514
449
  let visible = await locator.nth(j).isVisible();
515
450
  const enabled = await locator.nth(j).isEnabled();
@@ -518,24 +453,40 @@ class StableBrowser {
518
453
  }
519
454
  if (visible && enabled) {
520
455
  foundLocators.push(locator.nth(j));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
521
459
  }
522
460
  else {
461
+ info.failCause.visible = visible;
462
+ info.failCause.enabled = enabled;
523
463
  if (!info.printMessages) {
524
464
  info.printMessages = {};
525
465
  }
466
+ if (info.locatorLog && !visible) {
467
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
468
+ }
469
+ if (info.locatorLog && !enabled) {
470
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
471
+ }
526
472
  if (!info.printMessages[j.toString()]) {
527
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
473
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
528
474
  info.printMessages[j.toString()] = true;
529
475
  }
530
476
  }
531
477
  }
532
478
  }
533
479
  async closeUnexpectedPopups(info, _params) {
480
+ if (!info) {
481
+ info = {};
482
+ info.failCause = {};
483
+ info.log = "";
484
+ }
534
485
  if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
535
486
  if (!info) {
536
487
  info = {};
537
488
  }
538
- info.log += "scan for popup handlers" + "\n";
489
+ //info.log += "scan for popup handlers" + "\n";
539
490
  const handlerGroup = [];
540
491
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
541
492
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -562,9 +513,21 @@ class StableBrowser {
562
513
  }
563
514
  if (result.foundElements.length > 0) {
564
515
  let dialogCloseLocator = result.foundElements[0].locator;
565
- await dialogCloseLocator.click();
566
- // wait for the dialog to close
567
- await dialogCloseLocator.waitFor({ state: "hidden" });
516
+ try {
517
+ await scope?.evaluate(() => {
518
+ window.__isClosingPopups = true;
519
+ });
520
+ await dialogCloseLocator.click();
521
+ // wait for the dialog to close
522
+ await dialogCloseLocator.waitFor({ state: "hidden" });
523
+ }
524
+ catch (e) {
525
+ }
526
+ finally {
527
+ await scope?.evaluate(() => {
528
+ window.__isClosingPopups = false;
529
+ });
530
+ }
568
531
  return { rerun: true };
569
532
  }
570
533
  }
@@ -588,7 +551,13 @@ class StableBrowser {
588
551
  }
589
552
  throw new Error("unable to locate element " + JSON.stringify(selectors));
590
553
  }
591
- async _findFrameScope(selectors, timeout = 30000) {
554
+ async _findFrameScope(selectors, timeout = 30000, info) {
555
+ if (!info) {
556
+ info = {};
557
+ info.failCause = {};
558
+ info.log = "";
559
+ }
560
+ let startTime = Date.now();
592
561
  let scope = this.page;
593
562
  if (selectors.frame) {
594
563
  return selectors.frame;
@@ -619,9 +588,11 @@ class StableBrowser {
619
588
  }
620
589
  return framescope;
621
590
  };
591
+ let fLocator = null;
622
592
  while (true) {
623
593
  let frameFound = false;
624
594
  if (selectors.nestFrmLoc) {
595
+ fLocator = selectors.nestFrmLoc;
625
596
  scope = await findFrame(selectors.nestFrmLoc, scope);
626
597
  frameFound = true;
627
598
  break;
@@ -630,6 +601,7 @@ class StableBrowser {
630
601
  for (let i = 0; i < selectors.frameLocators.length; i++) {
631
602
  let frameLocator = selectors.frameLocators[i];
632
603
  if (frameLocator.css) {
604
+ fLocator = frameLocator.css;
633
605
  scope = scope.frameLocator(frameLocator.css);
634
606
  frameFound = true;
635
607
  break;
@@ -637,16 +609,25 @@ class StableBrowser {
637
609
  }
638
610
  }
639
611
  if (!frameFound && selectors.iframe_src) {
612
+ fLocator = selectors.iframe_src;
640
613
  scope = this.page.frame({ url: selectors.iframe_src });
641
614
  }
642
615
  if (!scope) {
643
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
644
- if (performance.now() - startTime > timeout) {
616
+ if (info && info.locatorLog) {
617
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
618
+ }
619
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
620
+ if (Date.now() - startTime > timeout) {
621
+ info.failCause.iframeNotFound = true;
622
+ info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
645
623
  throw new Error("unable to locate iframe " + selectors.iframe_src);
646
624
  }
647
625
  await new Promise((resolve) => setTimeout(resolve, 1000));
648
626
  }
649
627
  else {
628
+ if (info && info.locatorLog) {
629
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
630
+ }
650
631
  break;
651
632
  }
652
633
  }
@@ -656,20 +637,27 @@ class StableBrowser {
656
637
  }
657
638
  return scope;
658
639
  }
659
- async _getDocumentBody(selectors, timeout = 30000) {
660
- let scope = await this._findFrameScope(selectors, timeout);
640
+ async _getDocumentBody(selectors, timeout = 30000, info) {
641
+ let scope = await this._findFrameScope(selectors, timeout, info);
661
642
  return scope.evaluate(() => {
662
643
  var bodyContent = document.body.innerHTML;
663
644
  return bodyContent;
664
645
  });
665
646
  }
666
647
  async _locate_internal(selectors, info, _params, timeout = 30000) {
648
+ if (!info) {
649
+ info = {};
650
+ info.failCause = {};
651
+ info.log = "";
652
+ info.locatorLog = new LocatorLog(selectors);
653
+ }
667
654
  let highPriorityTimeout = 5000;
668
655
  let visibleOnlyTimeout = 6000;
669
- let startTime = performance.now();
656
+ let startTime = Date.now();
670
657
  let locatorsCount = 0;
658
+ let lazy_scroll = false;
671
659
  //let arrayMode = Array.isArray(selectors);
672
- let scope = await this._findFrameScope(selectors, timeout);
660
+ let scope = await this._findFrameScope(selectors, timeout, info);
673
661
  let selectorsLocators = null;
674
662
  selectorsLocators = selectors.locators;
675
663
  // group selectors by priority
@@ -756,21 +744,33 @@ class StableBrowser {
756
744
  return maxCountElement.locator;
757
745
  }
758
746
  }
759
- if (performance.now() - startTime > timeout) {
747
+ if (Date.now() - startTime > timeout) {
760
748
  break;
761
749
  }
762
- if (performance.now() - startTime > highPriorityTimeout) {
750
+ if (Date.now() - startTime > highPriorityTimeout) {
763
751
  info.log += "high priority timeout, will try all elements" + "\n";
764
752
  highPriorityOnly = false;
753
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
754
+ lazy_scroll = true;
755
+ await scrollPageToLoadLazyElements(this.page);
756
+ }
765
757
  }
766
- if (performance.now() - startTime > visibleOnlyTimeout) {
758
+ if (Date.now() - startTime > visibleOnlyTimeout) {
767
759
  info.log += "visible only timeout, will try all elements" + "\n";
768
760
  visibleOnly = false;
769
761
  }
770
762
  await new Promise((resolve) => setTimeout(resolve, 1000));
771
763
  }
772
764
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
773
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
765
+ // if (info.locatorLog) {
766
+ // const lines = info.locatorLog.toString().split("\n");
767
+ // for (let line of lines) {
768
+ // this.logger.debug(line);
769
+ // }
770
+ // }
771
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
772
+ info.failCause.locatorNotFound = true;
773
+ info.failCause.lastError = "failed to locate unique element";
774
774
  throw new Error("failed to locate first element no elements found, " + info.log);
775
775
  }
776
776
  async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
@@ -784,8 +784,9 @@ class StableBrowser {
784
784
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
785
785
  }
786
786
  catch (e) {
787
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
788
- this.logger.debug(e);
787
+ // this call can fail it the browser is navigating
788
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
789
+ // this.logger.debug(e);
789
790
  foundLocators = [];
790
791
  try {
791
792
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -802,10 +803,29 @@ class StableBrowser {
802
803
  });
803
804
  result.locatorIndex = i;
804
805
  }
806
+ if (foundLocators.length > 1) {
807
+ info.failCause.foundMultiple = true;
808
+ if (info.locatorLog) {
809
+ info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
810
+ }
811
+ }
805
812
  }
806
813
  return result;
807
814
  }
808
815
  async simpleClick(elementDescription, _params, options = {}, world = null) {
816
+ const state = {
817
+ locate: false,
818
+ scroll: false,
819
+ highlight: false,
820
+ _params,
821
+ options,
822
+ world,
823
+ type: Types.CLICK,
824
+ text: "Click element",
825
+ operation: "simpleClick",
826
+ log: "***** click on " + elementDescription + " *****\n",
827
+ };
828
+ _preCommand(state, this);
809
829
  const startTime = Date.now();
810
830
  let timeout = 30000;
811
831
  if (options && options.timeout) {
@@ -829,13 +849,32 @@ class StableBrowser {
829
849
  }
830
850
  catch (e) {
831
851
  if (performance.now() - startTime > timeout) {
832
- throw e;
852
+ // throw e;
853
+ try {
854
+ await _commandError(state, "timeout looking for " + elementDescription, this);
855
+ }
856
+ finally {
857
+ _commandFinally(state, this);
858
+ }
833
859
  }
834
860
  }
835
861
  await new Promise((resolve) => setTimeout(resolve, 3000));
836
862
  }
837
863
  }
838
864
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
865
+ const state = {
866
+ locate: false,
867
+ scroll: false,
868
+ highlight: false,
869
+ _params,
870
+ options,
871
+ world,
872
+ type: Types.FILL,
873
+ text: "Fill element",
874
+ operation: "simpleClickType",
875
+ log: "***** click type on " + elementDescription + " *****\n",
876
+ };
877
+ _preCommand(state, this);
839
878
  const startTime = Date.now();
840
879
  let timeout = 30000;
841
880
  if (options && options.timeout) {
@@ -859,7 +898,13 @@ class StableBrowser {
859
898
  }
860
899
  catch (e) {
861
900
  if (performance.now() - startTime > timeout) {
862
- throw e;
901
+ // throw e;
902
+ try {
903
+ await _commandError(state, "timeout looking for " + elementDescription, this);
904
+ }
905
+ finally {
906
+ _commandFinally(state, this);
907
+ }
863
908
  }
864
909
  }
865
910
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -883,13 +928,13 @@ class StableBrowser {
883
928
  }
884
929
  try {
885
930
  await state.element.click();
886
- await new Promise((resolve) => setTimeout(resolve, 1000));
931
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
887
932
  }
888
933
  catch (e) {
889
934
  // await this.closeUnexpectedPopups();
890
935
  state.element = await this._locate(selectors, state.info, _params);
891
936
  await state.element.dispatchEvent("click");
892
- await new Promise((resolve) => setTimeout(resolve, 1000));
937
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
893
938
  }
894
939
  await this.waitForPageLoad();
895
940
  return state.info;
@@ -968,7 +1013,6 @@ class StableBrowser {
968
1013
  await state.element.hover({ timeout: 10000 });
969
1014
  await new Promise((resolve) => setTimeout(resolve, 1000));
970
1015
  }
971
- await _screenshot(state, this);
972
1016
  await this.waitForPageLoad();
973
1017
  return state.info;
974
1018
  }
@@ -1096,33 +1140,30 @@ class StableBrowser {
1096
1140
  }
1097
1141
  }
1098
1142
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1099
- _validateSelectors(selectors);
1100
- const startTime = Date.now();
1101
- let error = null;
1102
- let screenshotId = null;
1103
- let screenshotPath = null;
1104
- const info = {};
1105
- info.log = "";
1106
- info.operation = Types.SET_DATE_TIME;
1107
- info.selectors = selectors;
1108
- info.value = value;
1143
+ const state = {
1144
+ selectors,
1145
+ _params,
1146
+ value: await this._replaceWithLocalData(value, this),
1147
+ options,
1148
+ world,
1149
+ type: Types.SET_DATE_TIME,
1150
+ text: `Set date time value: ${value}`,
1151
+ operation: "setDateTime",
1152
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1153
+ throwError: false,
1154
+ };
1109
1155
  try {
1110
- value = await this._replaceWithLocalData(value, this);
1111
- let element = await this._locate(selectors, info, _params);
1112
- //insert red border around the element
1113
- await this.scrollIfNeeded(element, info);
1114
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1115
- await this._highlightElements(element);
1156
+ await _preCommand(state, this);
1116
1157
  try {
1117
- await element.click();
1158
+ await state.element.click();
1118
1159
  await new Promise((resolve) => setTimeout(resolve, 500));
1119
1160
  if (format) {
1120
- value = dayjs(value).format(format);
1121
- await element.fill(value);
1161
+ state.value = dayjs(state.value).format(format);
1162
+ await state.element.fill(state.value);
1122
1163
  }
1123
1164
  else {
1124
- const dateTimeValue = await getDateTimeValue({ value, element });
1125
- await element.evaluateHandle((el, dateTimeValue) => {
1165
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1166
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1126
1167
  el.value = ""; // clear input
1127
1168
  el.value = dateTimeValue;
1128
1169
  }, dateTimeValue);
@@ -1135,20 +1176,19 @@ class StableBrowser {
1135
1176
  }
1136
1177
  catch (err) {
1137
1178
  //await this.closeUnexpectedPopups();
1138
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1179
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1139
1180
  this.logger.info("Trying again");
1140
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1141
- info.screenshotPath = screenshotPath;
1142
- Object.assign(err, { info: info });
1181
+ await _screenshot(state, this);
1182
+ Object.assign(err, { info: state.info });
1143
1183
  await element.click();
1144
1184
  await new Promise((resolve) => setTimeout(resolve, 500));
1145
1185
  if (format) {
1146
- value = dayjs(value).format(format);
1147
- await element.fill(value);
1186
+ state.value = dayjs(state.value).format(format);
1187
+ await state.element.fill(state.value);
1148
1188
  }
1149
1189
  else {
1150
- const dateTimeValue = await getDateTimeValue({ value, element });
1151
- await element.evaluateHandle((el, dateTimeValue) => {
1190
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1191
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1152
1192
  el.value = ""; // clear input
1153
1193
  el.value = dateTimeValue;
1154
1194
  }, dateTimeValue);
@@ -1161,31 +1201,10 @@ class StableBrowser {
1161
1201
  }
1162
1202
  }
1163
1203
  catch (e) {
1164
- error = e;
1165
- throw e;
1204
+ await _commandError(state, e, this);
1166
1205
  }
1167
1206
  finally {
1168
- const endTime = Date.now();
1169
- this._reportToWorld(world, {
1170
- element_name: selectors.element_name,
1171
- type: Types.SET_DATE_TIME,
1172
- screenshotId,
1173
- value: value,
1174
- text: `setDateTime input with value: ${value}`,
1175
- result: error
1176
- ? {
1177
- status: "FAILED",
1178
- startTime,
1179
- endTime,
1180
- message: error === null || error === void 0 ? void 0 : error.message,
1181
- }
1182
- : {
1183
- status: "PASSED",
1184
- startTime,
1185
- endTime,
1186
- },
1187
- info: info,
1188
- });
1207
+ _commandFinally(state, this);
1189
1208
  }
1190
1209
  }
1191
1210
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1322,6 +1341,7 @@ class StableBrowser {
1322
1341
  let screenshotPath = null;
1323
1342
  if (!info.log) {
1324
1343
  info.log = "";
1344
+ info.locatorLog = new LocatorLog(selectors);
1325
1345
  }
1326
1346
  info.operation = "getText";
1327
1347
  info.selectors = selectors;
@@ -1660,11 +1680,9 @@ class StableBrowser {
1660
1680
  if (!fs.existsSync(world.screenshotPath)) {
1661
1681
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1662
1682
  }
1663
- let nextIndex = 1;
1664
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1665
- nextIndex++;
1666
- }
1667
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1683
+ // to make sure the path doesn't start with -
1684
+ const uuidStr = "id_" + randomUUID();
1685
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1668
1686
  try {
1669
1687
  await this.takeScreenshot(screenshotPath);
1670
1688
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1678,7 +1696,7 @@ class StableBrowser {
1678
1696
  catch (e) {
1679
1697
  this.logger.info("unable to take screenshot, ignored");
1680
1698
  }
1681
- result.screenshotId = nextIndex;
1699
+ result.screenshotId = uuidStr;
1682
1700
  result.screenshotPath = screenshotPath;
1683
1701
  if (info && info.box) {
1684
1702
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1776,74 +1794,101 @@ class StableBrowser {
1776
1794
  }
1777
1795
  }
1778
1796
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1779
- _validateSelectors(selectors);
1780
- const startTime = Date.now();
1781
- let error = null;
1782
- let screenshotId = null;
1783
- let screenshotPath = null;
1797
+ const state = {
1798
+ selectors,
1799
+ _params,
1800
+ attribute,
1801
+ variable,
1802
+ options,
1803
+ world,
1804
+ type: Types.EXTRACT,
1805
+ text: `Extract attribute from element`,
1806
+ operation: "extractAttribute",
1807
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1808
+ };
1784
1809
  await new Promise((resolve) => setTimeout(resolve, 2000));
1785
- const info = {};
1786
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1787
- info.operation = "extract";
1788
- info.selectors = selectors;
1789
1810
  try {
1790
- const element = await this._locate(selectors, info, _params);
1791
- await this._highlightElements(element);
1792
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1811
+ await _preCommand(state, this);
1793
1812
  switch (attribute) {
1794
1813
  case "inner_text":
1795
- info.value = await element.innerText();
1814
+ state.value = await state.element.innerText();
1796
1815
  break;
1797
1816
  case "href":
1798
- info.value = await element.getAttribute("href");
1817
+ state.value = await state.element.getAttribute("href");
1818
+ break;
1819
+ case "value":
1820
+ state.value = await state.element.inputValue();
1821
+ break;
1822
+ default:
1823
+ state.value = await state.element.getAttribute(attribute);
1824
+ break;
1825
+ }
1826
+ state.info.value = state.value;
1827
+ this.setTestData({ [variable]: state.value }, world);
1828
+ this.logger.info("set test data: " + variable + "=" + state.value);
1829
+ return state.info;
1830
+ }
1831
+ catch (e) {
1832
+ await _commandError(state, e, this);
1833
+ }
1834
+ finally {
1835
+ _commandFinally(state, this);
1836
+ }
1837
+ }
1838
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1839
+ const state = {
1840
+ selectors,
1841
+ _params,
1842
+ attribute,
1843
+ value,
1844
+ options,
1845
+ world,
1846
+ type: Types.VERIFY_ATTRIBUTE,
1847
+ text: `Verify element attribute`,
1848
+ operation: "verifyAttribute",
1849
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1850
+ };
1851
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1852
+ let val;
1853
+ try {
1854
+ await _preCommand(state, this);
1855
+ switch (attribute) {
1856
+ case "innerText":
1857
+ val = String(await state.element.innerText());
1799
1858
  break;
1800
1859
  case "value":
1801
- info.value = await element.inputValue();
1860
+ val = String(await state.element.inputValue());
1861
+ break;
1862
+ case "checked":
1863
+ val = String(await state.element.isChecked());
1864
+ break;
1865
+ case "disabled":
1866
+ val = String(await state.element.isDisabled());
1802
1867
  break;
1803
1868
  default:
1804
- info.value = await element.getAttribute(attribute);
1869
+ val = String(await state.element.getAttribute(attribute));
1805
1870
  break;
1806
1871
  }
1807
- this[variable] = info.value;
1808
- if (world) {
1809
- world[variable] = info.value;
1872
+ let regex;
1873
+ if (value.startsWith("/") && value.endsWith("/")) {
1874
+ const patternBody = value.slice(1, -1);
1875
+ regex = new RegExp(patternBody, "g");
1810
1876
  }
1811
- this.setTestData({ [variable]: info.value }, world);
1812
- this.logger.info("set test data: " + variable + "=" + info.value);
1813
- return info;
1877
+ else {
1878
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1879
+ regex = new RegExp(escapedPattern, "g");
1880
+ }
1881
+ if (!val.match(regex)) {
1882
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1883
+ }
1884
+ state.info.value = val;
1885
+ return state.info;
1814
1886
  }
1815
1887
  catch (e) {
1816
- //await this.closeUnexpectedPopups();
1817
- this.logger.error("extract failed " + info.log);
1818
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1819
- info.screenshotPath = screenshotPath;
1820
- Object.assign(e, { info: info });
1821
- error = e;
1822
- throw e;
1888
+ await _commandError(state, e, this);
1823
1889
  }
1824
1890
  finally {
1825
- const endTime = Date.now();
1826
- this._reportToWorld(world, {
1827
- element_name: selectors.element_name,
1828
- type: Types.EXTRACT_ATTRIBUTE,
1829
- variable: variable,
1830
- value: info.value,
1831
- text: "Extract attribute from element",
1832
- screenshotId,
1833
- result: error
1834
- ? {
1835
- status: "FAILED",
1836
- startTime,
1837
- endTime,
1838
- message: error?.message,
1839
- }
1840
- : {
1841
- status: "PASSED",
1842
- startTime,
1843
- endTime,
1844
- },
1845
- info: info,
1846
- });
1891
+ _commandFinally(state, this);
1847
1892
  }
1848
1893
  }
1849
1894
  async extractEmailData(emailAddress, options, world) {
@@ -1864,7 +1909,7 @@ class StableBrowser {
1864
1909
  if (options && options.timeout) {
1865
1910
  timeout = options.timeout;
1866
1911
  }
1867
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1912
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1868
1913
  const request = {
1869
1914
  method: "POST",
1870
1915
  url: serviceUrl,
@@ -1920,7 +1965,8 @@ class StableBrowser {
1920
1965
  catch (e) {
1921
1966
  errorCount++;
1922
1967
  if (errorCount > 3) {
1923
- throw e;
1968
+ // throw e;
1969
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1924
1970
  }
1925
1971
  // ignore
1926
1972
  }
@@ -2031,11 +2077,12 @@ class StableBrowser {
2031
2077
  info.screenshotPath = screenshotPath;
2032
2078
  Object.assign(e, { info: info });
2033
2079
  error = e;
2034
- throw e;
2080
+ // throw e;
2081
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2035
2082
  }
2036
2083
  finally {
2037
2084
  const endTime = Date.now();
2038
- this._reportToWorld(world, {
2085
+ _reportToWorld(world, {
2039
2086
  type: Types.VERIFY_PAGE_PATH,
2040
2087
  text: "Verify page path",
2041
2088
  screenshotId,
@@ -2055,54 +2102,64 @@ class StableBrowser {
2055
2102
  });
2056
2103
  }
2057
2104
  }
2105
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2106
+ const frames = this.page.frames();
2107
+ let results = [];
2108
+ for (let i = 0; i < frames.length; i++) {
2109
+ if (dateAlternatives.date) {
2110
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2111
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2112
+ result.frame = frames[i];
2113
+ results.push(result);
2114
+ }
2115
+ }
2116
+ else if (numberAlternatives.number) {
2117
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2118
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2119
+ result.frame = frames[i];
2120
+ results.push(result);
2121
+ }
2122
+ }
2123
+ else {
2124
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2125
+ result.frame = frames[i];
2126
+ results.push(result);
2127
+ }
2128
+ }
2129
+ state.info.results = results;
2130
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2131
+ return resultWithElementsFound;
2132
+ }
2058
2133
  async verifyTextExistInPage(text, options = {}, world = null) {
2059
2134
  text = unEscapeString(text);
2060
- const startTime = Date.now();
2135
+ const state = {
2136
+ text_search: text,
2137
+ options,
2138
+ world,
2139
+ locate: false,
2140
+ scroll: false,
2141
+ highlight: false,
2142
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2143
+ text: `Verify text exists in page`,
2144
+ operation: "verifyTextExistInPage",
2145
+ log: "***** verify text " + text + " exists in page *****\n",
2146
+ };
2061
2147
  const timeout = this._getLoadTimeout(options);
2062
- let error = null;
2063
- let screenshotId = null;
2064
- let screenshotPath = null;
2065
2148
  await new Promise((resolve) => setTimeout(resolve, 2000));
2066
- const info = {};
2067
- info.log = "***** verify text " + text + " exists in page *****\n";
2068
- info.operation = "verifyTextExistInPage";
2069
2149
  const newValue = await this._replaceWithLocalData(text, world);
2070
2150
  if (newValue !== text) {
2071
2151
  this.logger.info(text + "=" + newValue);
2072
2152
  text = newValue;
2073
2153
  }
2074
- info.text = text;
2075
2154
  let dateAlternatives = findDateAlternatives(text);
2076
2155
  let numberAlternatives = findNumberAlternatives(text);
2077
2156
  try {
2157
+ await _preCommand(state, this);
2158
+ state.info.text = text;
2078
2159
  while (true) {
2079
- const frames = this.page.frames();
2080
- let results = [];
2081
- for (let i = 0; i < frames.length; i++) {
2082
- if (dateAlternatives.date) {
2083
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2084
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
2085
- result.frame = frames[i];
2086
- results.push(result);
2087
- }
2088
- }
2089
- else if (numberAlternatives.number) {
2090
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2091
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
2092
- result.frame = frames[i];
2093
- results.push(result);
2094
- }
2095
- }
2096
- else {
2097
- const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
2098
- result.frame = frames[i];
2099
- results.push(result);
2100
- }
2101
- }
2102
- info.results = results;
2103
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2160
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2104
2161
  if (resultWithElementsFound.length === 0) {
2105
- if (Date.now() - startTime > timeout) {
2162
+ if (Date.now() - state.startTime > timeout) {
2106
2163
  throw new Error(`Text ${text} not found in page`);
2107
2164
  }
2108
2165
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2114,55 +2171,163 @@ class StableBrowser {
2114
2171
  await this._highlightElements(frame, dataAttribute);
2115
2172
  const element = await frame.$(dataAttribute);
2116
2173
  if (element) {
2117
- await this.scrollIfNeeded(element, info);
2174
+ await this.scrollIfNeeded(element, state.info);
2118
2175
  await element.dispatchEvent("bvt_verify_page_contains_text");
2119
2176
  }
2120
2177
  }
2121
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2122
- return info;
2178
+ await _screenshot(state, this);
2179
+ return state.info;
2123
2180
  }
2124
2181
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2125
2182
  }
2126
2183
  catch (e) {
2127
- //await this.closeUnexpectedPopups();
2128
- this.logger.error("verify text exist in page failed " + info.log);
2129
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2130
- info.screenshotPath = screenshotPath;
2131
- Object.assign(e, { info: info });
2132
- error = e;
2133
- throw e;
2184
+ await _commandError(state, e, this);
2134
2185
  }
2135
2186
  finally {
2136
- const endTime = Date.now();
2137
- this._reportToWorld(world, {
2138
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2139
- text: "Verify text exists in page",
2140
- screenshotId,
2141
- result: error
2142
- ? {
2143
- status: "FAILED",
2144
- startTime,
2145
- endTime,
2146
- message: error?.message,
2147
- }
2148
- : {
2149
- status: "PASSED",
2150
- startTime,
2151
- endTime,
2152
- },
2153
- info: info,
2154
- });
2187
+ _commandFinally(state, this);
2155
2188
  }
2156
2189
  }
2157
- _getServerUrl() {
2158
- let serviceUrl = "https://api.blinq.io";
2159
- if (process.env.NODE_ENV_BLINQ === "dev") {
2160
- serviceUrl = "https://dev.api.blinq.io";
2190
+ async waitForTextToDisappear(text, options = {}, world = null) {
2191
+ text = unEscapeString(text);
2192
+ const state = {
2193
+ text_search: text,
2194
+ options,
2195
+ world,
2196
+ locate: false,
2197
+ scroll: false,
2198
+ highlight: false,
2199
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2200
+ text: `Verify text does not exist in page`,
2201
+ operation: "verifyTextNotExistInPage",
2202
+ log: "***** verify text " + text + " does not exist in page *****\n",
2203
+ };
2204
+ const timeout = this._getLoadTimeout(options);
2205
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2206
+ const newValue = await this._replaceWithLocalData(text, world);
2207
+ if (newValue !== text) {
2208
+ this.logger.info(text + "=" + newValue);
2209
+ text = newValue;
2210
+ }
2211
+ let dateAlternatives = findDateAlternatives(text);
2212
+ let numberAlternatives = findNumberAlternatives(text);
2213
+ try {
2214
+ await _preCommand(state, this);
2215
+ state.info.text = text;
2216
+ while (true) {
2217
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2218
+ if (resultWithElementsFound.length === 0) {
2219
+ await _screenshot(state, this);
2220
+ return state.info;
2221
+ }
2222
+ if (Date.now() - state.startTime > timeout) {
2223
+ throw new Error(`Text ${text} found in page`);
2224
+ }
2225
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2226
+ }
2227
+ }
2228
+ catch (e) {
2229
+ await _commandError(state, e, this);
2230
+ }
2231
+ finally {
2232
+ _commandFinally(state, this);
2233
+ }
2234
+ }
2235
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2236
+ textAnchor = unEscapeString(textAnchor);
2237
+ textToVerify = unEscapeString(textToVerify);
2238
+ const state = {
2239
+ text_search: textToVerify,
2240
+ options,
2241
+ world,
2242
+ locate: false,
2243
+ scroll: false,
2244
+ highlight: false,
2245
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2246
+ text: `Verify text with relation to another text`,
2247
+ operation: "verify_text_with_relation",
2248
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2249
+ };
2250
+ const timeout = this._getLoadTimeout(options);
2251
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2252
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2253
+ if (newValue !== textAnchor) {
2254
+ this.logger.info(textAnchor + "=" + newValue);
2255
+ textAnchor = newValue;
2256
+ }
2257
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2258
+ if (newValue !== textToVerify) {
2259
+ this.logger.info(textToVerify + "=" + newValue);
2260
+ textToVerify = newValue;
2261
+ }
2262
+ let dateAlternatives = findDateAlternatives(textToVerify);
2263
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2264
+ let foundAncore = false;
2265
+ try {
2266
+ await _preCommand(state, this);
2267
+ state.info.text = textToVerify;
2268
+ while (true) {
2269
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2270
+ if (resultWithElementsFound.length === 0) {
2271
+ if (Date.now() - state.startTime > timeout) {
2272
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2273
+ }
2274
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2275
+ continue;
2276
+ }
2277
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2278
+ foundAncore = true;
2279
+ const result = resultWithElementsFound[i];
2280
+ const token = result.randomToken;
2281
+ const frame = result.frame;
2282
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2283
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2284
+ const elements = Array.from(document.querySelectorAll(css));
2285
+ for (let i = 0; i < elements.length; i++) {
2286
+ const element = elements[i];
2287
+ let climbParent = element;
2288
+ for (let j = 0; j < climb; j++) {
2289
+ climbParent = climbParent.parentElement;
2290
+ if (!climbParent) {
2291
+ break;
2292
+ }
2293
+ }
2294
+ if (!climbParent) {
2295
+ continue;
2296
+ }
2297
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2298
+ if (foundElements.length > 0) {
2299
+ // set the container element attribute
2300
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2301
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2302
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2303
+ return { found: true };
2304
+ }
2305
+ }
2306
+ return { found: false };
2307
+ }, [css, climb, textToVerify, result.randomToken]);
2308
+ if (findResult.found === true) {
2309
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2310
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2311
+ await this._highlightElements(frame, dataAttribute);
2312
+ await this._highlightElements(frame, cssAnchor);
2313
+ const element = await frame.$(dataAttribute);
2314
+ if (element) {
2315
+ await this.scrollIfNeeded(element, state.info);
2316
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2317
+ }
2318
+ await _screenshot(state, this);
2319
+ return state.info;
2320
+ }
2321
+ }
2322
+ }
2323
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2324
+ }
2325
+ catch (e) {
2326
+ await _commandError(state, e, this);
2161
2327
  }
2162
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2163
- serviceUrl = "https://stage.api.blinq.io";
2328
+ finally {
2329
+ _commandFinally(state, this);
2164
2330
  }
2165
- return serviceUrl;
2166
2331
  }
2167
2332
  async visualVerification(text, options = {}, world = null) {
2168
2333
  const startTime = Date.now();
@@ -2178,7 +2343,7 @@ class StableBrowser {
2178
2343
  throw new Error("TOKEN is not set");
2179
2344
  }
2180
2345
  try {
2181
- let serviceUrl = this._getServerUrl();
2346
+ let serviceUrl = _getServerUrl();
2182
2347
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2183
2348
  info.screenshotPath = screenshotPath;
2184
2349
  const screenshot = await this.takeScreenshot();
@@ -2214,11 +2379,12 @@ class StableBrowser {
2214
2379
  info.screenshotPath = screenshotPath;
2215
2380
  Object.assign(e, { info: info });
2216
2381
  error = e;
2217
- throw e;
2382
+ // throw e;
2383
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2218
2384
  }
2219
2385
  finally {
2220
2386
  const endTime = Date.now();
2221
- this._reportToWorld(world, {
2387
+ _reportToWorld(world, {
2222
2388
  type: Types.VERIFY_VISUAL,
2223
2389
  text: "Visual verification",
2224
2390
  screenshotId,
@@ -2266,6 +2432,7 @@ class StableBrowser {
2266
2432
  let screenshotPath = null;
2267
2433
  const info = {};
2268
2434
  info.log = "";
2435
+ info.locatorLog = new LocatorLog(selectors);
2269
2436
  info.operation = "getTableData";
2270
2437
  info.selectors = selectors;
2271
2438
  try {
@@ -2281,11 +2448,12 @@ class StableBrowser {
2281
2448
  info.screenshotPath = screenshotPath;
2282
2449
  Object.assign(e, { info: info });
2283
2450
  error = e;
2284
- throw e;
2451
+ // throw e;
2452
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2285
2453
  }
2286
2454
  finally {
2287
2455
  const endTime = Date.now();
2288
- this._reportToWorld(world, {
2456
+ _reportToWorld(world, {
2289
2457
  element_name: selectors.element_name,
2290
2458
  type: Types.GET_TABLE_DATA,
2291
2459
  text: "Get table data",
@@ -2340,7 +2508,7 @@ class StableBrowser {
2340
2508
  info.operation = "analyzeTable";
2341
2509
  info.selectors = selectors;
2342
2510
  info.query = query;
2343
- query = this._fixUsingParams(query, _params);
2511
+ query = _fixUsingParams(query, _params);
2344
2512
  info.query_fixed = query;
2345
2513
  info.operator = operator;
2346
2514
  info.value = value;
@@ -2446,11 +2614,12 @@ class StableBrowser {
2446
2614
  info.screenshotPath = screenshotPath;
2447
2615
  Object.assign(e, { info: info });
2448
2616
  error = e;
2449
- throw e;
2617
+ // throw e;
2618
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2450
2619
  }
2451
2620
  finally {
2452
2621
  const endTime = Date.now();
2453
- this._reportToWorld(world, {
2622
+ _reportToWorld(world, {
2454
2623
  element_name: selectors.element_name,
2455
2624
  type: Types.ANALYZE_TABLE,
2456
2625
  text: "Analyze table",
@@ -2523,7 +2692,7 @@ class StableBrowser {
2523
2692
  await new Promise((resolve) => setTimeout(resolve, 2000));
2524
2693
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2525
2694
  const endTime = Date.now();
2526
- this._reportToWorld(world, {
2695
+ _reportToWorld(world, {
2527
2696
  type: Types.GET_PAGE_STATUS,
2528
2697
  text: "Wait for page load",
2529
2698
  screenshotId,
@@ -2543,41 +2712,35 @@ class StableBrowser {
2543
2712
  }
2544
2713
  }
2545
2714
  async closePage(options = {}, world = null) {
2546
- const startTime = Date.now();
2547
- let error = null;
2548
- let screenshotId = null;
2549
- let screenshotPath = null;
2550
- const info = {};
2715
+ const state = {
2716
+ options,
2717
+ world,
2718
+ locate: false,
2719
+ scroll: false,
2720
+ highlight: false,
2721
+ type: Types.CLOSE_PAGE,
2722
+ text: `Close page`,
2723
+ operation: "closePage",
2724
+ log: "***** close page *****\n",
2725
+ throwError: false,
2726
+ };
2551
2727
  try {
2728
+ await _preCommand(state, this);
2552
2729
  await this.page.close();
2553
2730
  }
2554
2731
  catch (e) {
2555
2732
  console.log(".");
2733
+ await _commandError(state, e, this);
2556
2734
  }
2557
2735
  finally {
2558
- await new Promise((resolve) => setTimeout(resolve, 2000));
2559
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2560
- const endTime = Date.now();
2561
- this._reportToWorld(world, {
2562
- type: Types.CLOSE_PAGE,
2563
- text: "close page",
2564
- screenshotId,
2565
- result: error
2566
- ? {
2567
- status: "FAILED",
2568
- startTime,
2569
- endTime,
2570
- message: error?.message,
2571
- }
2572
- : {
2573
- status: "PASSED",
2574
- startTime,
2575
- endTime,
2576
- },
2577
- info: info,
2578
- });
2736
+ _commandFinally(state, this);
2579
2737
  }
2580
2738
  }
2739
+ saveTestDataAsGlobal(options, world) {
2740
+ const dataFile = this._getDataFile(world);
2741
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2742
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2743
+ }
2581
2744
  async setViewportSize(width, hight, options = {}, world = null) {
2582
2745
  const startTime = Date.now();
2583
2746
  let error = null;
@@ -2595,12 +2758,13 @@ class StableBrowser {
2595
2758
  }
2596
2759
  catch (e) {
2597
2760
  console.log(".");
2761
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2598
2762
  }
2599
2763
  finally {
2600
2764
  await new Promise((resolve) => setTimeout(resolve, 2000));
2601
2765
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2602
2766
  const endTime = Date.now();
2603
- this._reportToWorld(world, {
2767
+ _reportToWorld(world, {
2604
2768
  type: Types.SET_VIEWPORT,
2605
2769
  text: "set viewport size to " + width + "x" + hight,
2606
2770
  screenshotId,
@@ -2631,12 +2795,13 @@ class StableBrowser {
2631
2795
  }
2632
2796
  catch (e) {
2633
2797
  console.log(".");
2798
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2634
2799
  }
2635
2800
  finally {
2636
2801
  await new Promise((resolve) => setTimeout(resolve, 2000));
2637
2802
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2638
2803
  const endTime = Date.now();
2639
- this._reportToWorld(world, {
2804
+ _reportToWorld(world, {
2640
2805
  type: Types.GET_PAGE_STATUS,
2641
2806
  text: "page relaod",
2642
2807
  screenshotId,
@@ -2672,11 +2837,37 @@ class StableBrowser {
2672
2837
  console.log("#-#");
2673
2838
  }
2674
2839
  }
2675
- _reportToWorld(world, properties) {
2676
- if (!world || !world.attach) {
2677
- return;
2840
+ async beforeStep(world, step) {
2841
+ this.stepName = step.pickleStep.text;
2842
+ this.logger.info("step: " + this.stepName);
2843
+ if (this.stepIndex === undefined) {
2844
+ this.stepIndex = 0;
2845
+ }
2846
+ else {
2847
+ this.stepIndex++;
2848
+ }
2849
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2850
+ if (this.context.browserObject.context) {
2851
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2852
+ }
2853
+ }
2854
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2855
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2856
+ // check if @global_test_data tag is present
2857
+ if (this.tags.includes("@global_test_data")) {
2858
+ this.saveTestDataAsGlobal({}, world);
2859
+ }
2860
+ }
2861
+ }
2862
+ async afterStep(world, step) {
2863
+ this.stepName = null;
2864
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2865
+ if (this.context.browserObject.context) {
2866
+ await this.context.browserObject.context.tracing.stopChunk({
2867
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2868
+ });
2869
+ }
2678
2870
  }
2679
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2680
2871
  }
2681
2872
  }
2682
2873
  function createTimedPromise(promise, label) {
@@ -2684,156 +2875,5 @@ function createTimedPromise(promise, label) {
2684
2875
  .then((result) => ({ status: "fulfilled", label, result }))
2685
2876
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2686
2877
  }
2687
- const KEYBOARD_EVENTS = [
2688
- "ALT",
2689
- "AltGraph",
2690
- "CapsLock",
2691
- "Control",
2692
- "Fn",
2693
- "FnLock",
2694
- "Hyper",
2695
- "Meta",
2696
- "NumLock",
2697
- "ScrollLock",
2698
- "Shift",
2699
- "Super",
2700
- "Symbol",
2701
- "SymbolLock",
2702
- "Enter",
2703
- "Tab",
2704
- "ArrowDown",
2705
- "ArrowLeft",
2706
- "ArrowRight",
2707
- "ArrowUp",
2708
- "End",
2709
- "Home",
2710
- "PageDown",
2711
- "PageUp",
2712
- "Backspace",
2713
- "Clear",
2714
- "Copy",
2715
- "CrSel",
2716
- "Cut",
2717
- "Delete",
2718
- "EraseEof",
2719
- "ExSel",
2720
- "Insert",
2721
- "Paste",
2722
- "Redo",
2723
- "Undo",
2724
- "Accept",
2725
- "Again",
2726
- "Attn",
2727
- "Cancel",
2728
- "ContextMenu",
2729
- "Escape",
2730
- "Execute",
2731
- "Find",
2732
- "Finish",
2733
- "Help",
2734
- "Pause",
2735
- "Play",
2736
- "Props",
2737
- "Select",
2738
- "ZoomIn",
2739
- "ZoomOut",
2740
- "BrightnessDown",
2741
- "BrightnessUp",
2742
- "Eject",
2743
- "LogOff",
2744
- "Power",
2745
- "PowerOff",
2746
- "PrintScreen",
2747
- "Hibernate",
2748
- "Standby",
2749
- "WakeUp",
2750
- "AllCandidates",
2751
- "Alphanumeric",
2752
- "CodeInput",
2753
- "Compose",
2754
- "Convert",
2755
- "Dead",
2756
- "FinalMode",
2757
- "GroupFirst",
2758
- "GroupLast",
2759
- "GroupNext",
2760
- "GroupPrevious",
2761
- "ModeChange",
2762
- "NextCandidate",
2763
- "NonConvert",
2764
- "PreviousCandidate",
2765
- "Process",
2766
- "SingleCandidate",
2767
- "HangulMode",
2768
- "HanjaMode",
2769
- "JunjaMode",
2770
- "Eisu",
2771
- "Hankaku",
2772
- "Hiragana",
2773
- "HiraganaKatakana",
2774
- "KanaMode",
2775
- "KanjiMode",
2776
- "Katakana",
2777
- "Romaji",
2778
- "Zenkaku",
2779
- "ZenkakuHanaku",
2780
- "F1",
2781
- "F2",
2782
- "F3",
2783
- "F4",
2784
- "F5",
2785
- "F6",
2786
- "F7",
2787
- "F8",
2788
- "F9",
2789
- "F10",
2790
- "F11",
2791
- "F12",
2792
- "Soft1",
2793
- "Soft2",
2794
- "Soft3",
2795
- "Soft4",
2796
- "ChannelDown",
2797
- "ChannelUp",
2798
- "Close",
2799
- "MailForward",
2800
- "MailReply",
2801
- "MailSend",
2802
- "MediaFastForward",
2803
- "MediaPause",
2804
- "MediaPlay",
2805
- "MediaPlayPause",
2806
- "MediaRecord",
2807
- "MediaRewind",
2808
- "MediaStop",
2809
- "MediaTrackNext",
2810
- "MediaTrackPrevious",
2811
- "AudioBalanceLeft",
2812
- "AudioBalanceRight",
2813
- "AudioBassBoostDown",
2814
- "AudioBassBoostToggle",
2815
- "AudioBassBoostUp",
2816
- "AudioFaderFront",
2817
- "AudioFaderRear",
2818
- "AudioSurroundModeNext",
2819
- "AudioTrebleDown",
2820
- "AudioTrebleUp",
2821
- "AudioVolumeDown",
2822
- "AudioVolumeMute",
2823
- "AudioVolumeUp",
2824
- "MicrophoneToggle",
2825
- "MicrophoneVolumeDown",
2826
- "MicrophoneVolumeMute",
2827
- "MicrophoneVolumeUp",
2828
- "TV",
2829
- "TV3DMode",
2830
- "TVAntennaCable",
2831
- "TVAudioDescription",
2832
- ];
2833
- function unEscapeString(str) {
2834
- const placeholder = "__NEWLINE__";
2835
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2836
- return str;
2837
- }
2838
2878
  export { StableBrowser };
2839
2879
  //# sourceMappingURL=stable_browser.js.map