automation_model 1.0.494-dev → 1.0.494-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/lib/api.d.ts +2 -1
  2. package/lib/api.js +95 -98
  3. package/lib/api.js.map +1 -1
  4. package/lib/auto_page.d.ts +2 -1
  5. package/lib/auto_page.js +19 -2
  6. package/lib/auto_page.js.map +1 -1
  7. package/lib/browser_manager.d.ts +6 -3
  8. package/lib/browser_manager.js +108 -21
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +54 -6
  12. package/lib/command_common.js.map +1 -1
  13. package/lib/error-messages.d.ts +6 -0
  14. package/lib/error-messages.js +188 -0
  15. package/lib/error-messages.js.map +1 -0
  16. package/lib/generation_scripts.d.ts +4 -0
  17. package/lib/generation_scripts.js +2 -0
  18. package/lib/generation_scripts.js.map +1 -0
  19. package/lib/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/index.js.map +1 -1
  22. package/lib/init_browser.d.ts +2 -1
  23. package/lib/init_browser.js +26 -6
  24. package/lib/init_browser.js.map +1 -1
  25. package/lib/locate_element.js +15 -13
  26. package/lib/locate_element.js.map +1 -1
  27. package/lib/locator.d.ts +36 -0
  28. package/lib/locator.js +165 -0
  29. package/lib/locator.js.map +1 -1
  30. package/lib/locator_log.d.ts +26 -0
  31. package/lib/locator_log.js +69 -0
  32. package/lib/locator_log.js.map +1 -0
  33. package/lib/network.d.ts +2 -1
  34. package/lib/network.js +37 -3
  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 +618 -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 +235 -3
  48. package/lib/utils.js.map +1 -1
  49. package/package.json +7 -5
  50. /package/lib/{axe → scripts}/axe.mini.js +0 -0
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
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,105 @@ 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");
1799
1818
  break;
1800
1819
  case "value":
1801
- info.value = await element.inputValue();
1820
+ state.value = await state.element.inputValue();
1802
1821
  break;
1803
1822
  default:
1804
- info.value = await element.getAttribute(attribute);
1823
+ state.value = await state.element.getAttribute(attribute);
1805
1824
  break;
1806
1825
  }
1807
- this[variable] = info.value;
1808
- if (world) {
1809
- world[variable] = info.value;
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());
1858
+ break;
1859
+ case "value":
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());
1867
+ break;
1868
+ case "readOnly":
1869
+ const isEditable = await state.element.isEditable();
1870
+ val = String(!isEditable);
1871
+ break;
1872
+ default:
1873
+ val = String(await state.element.getAttribute(attribute));
1874
+ break;
1810
1875
  }
1811
- this.setTestData({ [variable]: info.value }, world);
1812
- this.logger.info("set test data: " + variable + "=" + info.value);
1813
- return info;
1876
+ state.info.expectedValue = val;
1877
+ let regex;
1878
+ if (value.startsWith("/") && value.endsWith("/")) {
1879
+ const patternBody = value.slice(1, -1);
1880
+ regex = new RegExp(patternBody, "g");
1881
+ }
1882
+ else {
1883
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1884
+ regex = new RegExp(escapedPattern, "g");
1885
+ }
1886
+ if (!val.match(regex)) {
1887
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1888
+ }
1889
+ return state.info;
1814
1890
  }
1815
1891
  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;
1892
+ await _commandError(state, e, this);
1823
1893
  }
1824
1894
  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
- });
1895
+ _commandFinally(state, this);
1847
1896
  }
1848
1897
  }
1849
1898
  async extractEmailData(emailAddress, options, world) {
@@ -1864,7 +1913,7 @@ class StableBrowser {
1864
1913
  if (options && options.timeout) {
1865
1914
  timeout = options.timeout;
1866
1915
  }
1867
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1916
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1868
1917
  const request = {
1869
1918
  method: "POST",
1870
1919
  url: serviceUrl,
@@ -1920,7 +1969,8 @@ class StableBrowser {
1920
1969
  catch (e) {
1921
1970
  errorCount++;
1922
1971
  if (errorCount > 3) {
1923
- throw e;
1972
+ // throw e;
1973
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1924
1974
  }
1925
1975
  // ignore
1926
1976
  }
@@ -2031,11 +2081,12 @@ class StableBrowser {
2031
2081
  info.screenshotPath = screenshotPath;
2032
2082
  Object.assign(e, { info: info });
2033
2083
  error = e;
2034
- throw e;
2084
+ // throw e;
2085
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2035
2086
  }
2036
2087
  finally {
2037
2088
  const endTime = Date.now();
2038
- this._reportToWorld(world, {
2089
+ _reportToWorld(world, {
2039
2090
  type: Types.VERIFY_PAGE_PATH,
2040
2091
  text: "Verify page path",
2041
2092
  screenshotId,
@@ -2055,54 +2106,64 @@ class StableBrowser {
2055
2106
  });
2056
2107
  }
2057
2108
  }
2109
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2110
+ const frames = this.page.frames();
2111
+ let results = [];
2112
+ for (let i = 0; i < frames.length; i++) {
2113
+ if (dateAlternatives.date) {
2114
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2115
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2116
+ result.frame = frames[i];
2117
+ results.push(result);
2118
+ }
2119
+ }
2120
+ else if (numberAlternatives.number) {
2121
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2122
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2123
+ result.frame = frames[i];
2124
+ results.push(result);
2125
+ }
2126
+ }
2127
+ else {
2128
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2129
+ result.frame = frames[i];
2130
+ results.push(result);
2131
+ }
2132
+ }
2133
+ state.info.results = results;
2134
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2135
+ return resultWithElementsFound;
2136
+ }
2058
2137
  async verifyTextExistInPage(text, options = {}, world = null) {
2059
2138
  text = unEscapeString(text);
2060
- const startTime = Date.now();
2139
+ const state = {
2140
+ text_search: text,
2141
+ options,
2142
+ world,
2143
+ locate: false,
2144
+ scroll: false,
2145
+ highlight: false,
2146
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2147
+ text: `Verify text exists in page`,
2148
+ operation: "verifyTextExistInPage",
2149
+ log: "***** verify text " + text + " exists in page *****\n",
2150
+ };
2061
2151
  const timeout = this._getLoadTimeout(options);
2062
- let error = null;
2063
- let screenshotId = null;
2064
- let screenshotPath = null;
2065
2152
  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
2153
  const newValue = await this._replaceWithLocalData(text, world);
2070
2154
  if (newValue !== text) {
2071
2155
  this.logger.info(text + "=" + newValue);
2072
2156
  text = newValue;
2073
2157
  }
2074
- info.text = text;
2075
2158
  let dateAlternatives = findDateAlternatives(text);
2076
2159
  let numberAlternatives = findNumberAlternatives(text);
2077
2160
  try {
2161
+ await _preCommand(state, this);
2162
+ state.info.text = text;
2078
2163
  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);
2164
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2104
2165
  if (resultWithElementsFound.length === 0) {
2105
- if (Date.now() - startTime > timeout) {
2166
+ if (Date.now() - state.startTime > timeout) {
2106
2167
  throw new Error(`Text ${text} not found in page`);
2107
2168
  }
2108
2169
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2114,55 +2175,163 @@ class StableBrowser {
2114
2175
  await this._highlightElements(frame, dataAttribute);
2115
2176
  const element = await frame.$(dataAttribute);
2116
2177
  if (element) {
2117
- await this.scrollIfNeeded(element, info);
2178
+ await this.scrollIfNeeded(element, state.info);
2118
2179
  await element.dispatchEvent("bvt_verify_page_contains_text");
2119
2180
  }
2120
2181
  }
2121
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2122
- return info;
2182
+ await _screenshot(state, this);
2183
+ return state.info;
2123
2184
  }
2124
2185
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2125
2186
  }
2126
2187
  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;
2188
+ await _commandError(state, e, this);
2134
2189
  }
2135
2190
  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
- });
2191
+ _commandFinally(state, this);
2155
2192
  }
2156
2193
  }
2157
- _getServerUrl() {
2158
- let serviceUrl = "https://api.blinq.io";
2159
- if (process.env.NODE_ENV_BLINQ === "dev") {
2160
- serviceUrl = "https://dev.api.blinq.io";
2194
+ async waitForTextToDisappear(text, options = {}, world = null) {
2195
+ text = unEscapeString(text);
2196
+ const state = {
2197
+ text_search: text,
2198
+ options,
2199
+ world,
2200
+ locate: false,
2201
+ scroll: false,
2202
+ highlight: false,
2203
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2204
+ text: `Verify text does not exist in page`,
2205
+ operation: "verifyTextNotExistInPage",
2206
+ log: "***** verify text " + text + " does not exist in page *****\n",
2207
+ };
2208
+ const timeout = this._getLoadTimeout(options);
2209
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2210
+ const newValue = await this._replaceWithLocalData(text, world);
2211
+ if (newValue !== text) {
2212
+ this.logger.info(text + "=" + newValue);
2213
+ text = newValue;
2214
+ }
2215
+ let dateAlternatives = findDateAlternatives(text);
2216
+ let numberAlternatives = findNumberAlternatives(text);
2217
+ try {
2218
+ await _preCommand(state, this);
2219
+ state.info.text = text;
2220
+ while (true) {
2221
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2222
+ if (resultWithElementsFound.length === 0) {
2223
+ await _screenshot(state, this);
2224
+ return state.info;
2225
+ }
2226
+ if (Date.now() - state.startTime > timeout) {
2227
+ throw new Error(`Text ${text} found in page`);
2228
+ }
2229
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2230
+ }
2231
+ }
2232
+ catch (e) {
2233
+ await _commandError(state, e, this);
2234
+ }
2235
+ finally {
2236
+ _commandFinally(state, this);
2237
+ }
2238
+ }
2239
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2240
+ textAnchor = unEscapeString(textAnchor);
2241
+ textToVerify = unEscapeString(textToVerify);
2242
+ const state = {
2243
+ text_search: textToVerify,
2244
+ options,
2245
+ world,
2246
+ locate: false,
2247
+ scroll: false,
2248
+ highlight: false,
2249
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2250
+ text: `Verify text with relation to another text`,
2251
+ operation: "verify_text_with_relation",
2252
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2253
+ };
2254
+ const timeout = this._getLoadTimeout(options);
2255
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2256
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2257
+ if (newValue !== textAnchor) {
2258
+ this.logger.info(textAnchor + "=" + newValue);
2259
+ textAnchor = newValue;
2260
+ }
2261
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2262
+ if (newValue !== textToVerify) {
2263
+ this.logger.info(textToVerify + "=" + newValue);
2264
+ textToVerify = newValue;
2265
+ }
2266
+ let dateAlternatives = findDateAlternatives(textToVerify);
2267
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2268
+ let foundAncore = false;
2269
+ try {
2270
+ await _preCommand(state, this);
2271
+ state.info.text = textToVerify;
2272
+ while (true) {
2273
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2274
+ if (resultWithElementsFound.length === 0) {
2275
+ if (Date.now() - state.startTime > timeout) {
2276
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2277
+ }
2278
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2279
+ continue;
2280
+ }
2281
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2282
+ foundAncore = true;
2283
+ const result = resultWithElementsFound[i];
2284
+ const token = result.randomToken;
2285
+ const frame = result.frame;
2286
+ const css = `[data-blinq-id="blinq-id-${token}"]`;
2287
+ const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2288
+ const elements = Array.from(document.querySelectorAll(css));
2289
+ for (let i = 0; i < elements.length; i++) {
2290
+ const element = elements[i];
2291
+ let climbParent = element;
2292
+ for (let j = 0; j < climb; j++) {
2293
+ climbParent = climbParent.parentElement;
2294
+ if (!climbParent) {
2295
+ break;
2296
+ }
2297
+ }
2298
+ if (!climbParent) {
2299
+ continue;
2300
+ }
2301
+ const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2302
+ if (foundElements.length > 0) {
2303
+ // set the container element attribute
2304
+ element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2305
+ climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2306
+ foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2307
+ return { found: true };
2308
+ }
2309
+ }
2310
+ return { found: false };
2311
+ }, [css, climb, textToVerify, result.randomToken]);
2312
+ if (findResult.found === true) {
2313
+ const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2314
+ const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2315
+ await this._highlightElements(frame, dataAttribute);
2316
+ await this._highlightElements(frame, cssAnchor);
2317
+ const element = await frame.$(dataAttribute);
2318
+ if (element) {
2319
+ await this.scrollIfNeeded(element, state.info);
2320
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2321
+ }
2322
+ await _screenshot(state, this);
2323
+ return state.info;
2324
+ }
2325
+ }
2326
+ }
2327
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2328
+ }
2329
+ catch (e) {
2330
+ await _commandError(state, e, this);
2161
2331
  }
2162
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2163
- serviceUrl = "https://stage.api.blinq.io";
2332
+ finally {
2333
+ _commandFinally(state, this);
2164
2334
  }
2165
- return serviceUrl;
2166
2335
  }
2167
2336
  async visualVerification(text, options = {}, world = null) {
2168
2337
  const startTime = Date.now();
@@ -2178,7 +2347,7 @@ class StableBrowser {
2178
2347
  throw new Error("TOKEN is not set");
2179
2348
  }
2180
2349
  try {
2181
- let serviceUrl = this._getServerUrl();
2350
+ let serviceUrl = _getServerUrl();
2182
2351
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2183
2352
  info.screenshotPath = screenshotPath;
2184
2353
  const screenshot = await this.takeScreenshot();
@@ -2214,11 +2383,12 @@ class StableBrowser {
2214
2383
  info.screenshotPath = screenshotPath;
2215
2384
  Object.assign(e, { info: info });
2216
2385
  error = e;
2217
- throw e;
2386
+ // throw e;
2387
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2218
2388
  }
2219
2389
  finally {
2220
2390
  const endTime = Date.now();
2221
- this._reportToWorld(world, {
2391
+ _reportToWorld(world, {
2222
2392
  type: Types.VERIFY_VISUAL,
2223
2393
  text: "Visual verification",
2224
2394
  screenshotId,
@@ -2266,6 +2436,7 @@ class StableBrowser {
2266
2436
  let screenshotPath = null;
2267
2437
  const info = {};
2268
2438
  info.log = "";
2439
+ info.locatorLog = new LocatorLog(selectors);
2269
2440
  info.operation = "getTableData";
2270
2441
  info.selectors = selectors;
2271
2442
  try {
@@ -2281,11 +2452,12 @@ class StableBrowser {
2281
2452
  info.screenshotPath = screenshotPath;
2282
2453
  Object.assign(e, { info: info });
2283
2454
  error = e;
2284
- throw e;
2455
+ // throw e;
2456
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2285
2457
  }
2286
2458
  finally {
2287
2459
  const endTime = Date.now();
2288
- this._reportToWorld(world, {
2460
+ _reportToWorld(world, {
2289
2461
  element_name: selectors.element_name,
2290
2462
  type: Types.GET_TABLE_DATA,
2291
2463
  text: "Get table data",
@@ -2340,7 +2512,7 @@ class StableBrowser {
2340
2512
  info.operation = "analyzeTable";
2341
2513
  info.selectors = selectors;
2342
2514
  info.query = query;
2343
- query = this._fixUsingParams(query, _params);
2515
+ query = _fixUsingParams(query, _params);
2344
2516
  info.query_fixed = query;
2345
2517
  info.operator = operator;
2346
2518
  info.value = value;
@@ -2446,11 +2618,12 @@ class StableBrowser {
2446
2618
  info.screenshotPath = screenshotPath;
2447
2619
  Object.assign(e, { info: info });
2448
2620
  error = e;
2449
- throw e;
2621
+ // throw e;
2622
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2450
2623
  }
2451
2624
  finally {
2452
2625
  const endTime = Date.now();
2453
- this._reportToWorld(world, {
2626
+ _reportToWorld(world, {
2454
2627
  element_name: selectors.element_name,
2455
2628
  type: Types.ANALYZE_TABLE,
2456
2629
  text: "Analyze table",
@@ -2523,7 +2696,7 @@ class StableBrowser {
2523
2696
  await new Promise((resolve) => setTimeout(resolve, 2000));
2524
2697
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2525
2698
  const endTime = Date.now();
2526
- this._reportToWorld(world, {
2699
+ _reportToWorld(world, {
2527
2700
  type: Types.GET_PAGE_STATUS,
2528
2701
  text: "Wait for page load",
2529
2702
  screenshotId,
@@ -2543,41 +2716,35 @@ class StableBrowser {
2543
2716
  }
2544
2717
  }
2545
2718
  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 = {};
2719
+ const state = {
2720
+ options,
2721
+ world,
2722
+ locate: false,
2723
+ scroll: false,
2724
+ highlight: false,
2725
+ type: Types.CLOSE_PAGE,
2726
+ text: `Close page`,
2727
+ operation: "closePage",
2728
+ log: "***** close page *****\n",
2729
+ throwError: false,
2730
+ };
2551
2731
  try {
2732
+ await _preCommand(state, this);
2552
2733
  await this.page.close();
2553
2734
  }
2554
2735
  catch (e) {
2555
2736
  console.log(".");
2737
+ await _commandError(state, e, this);
2556
2738
  }
2557
2739
  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
- });
2740
+ _commandFinally(state, this);
2579
2741
  }
2580
2742
  }
2743
+ saveTestDataAsGlobal(options, world) {
2744
+ const dataFile = this._getDataFile(world);
2745
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2746
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2747
+ }
2581
2748
  async setViewportSize(width, hight, options = {}, world = null) {
2582
2749
  const startTime = Date.now();
2583
2750
  let error = null;
@@ -2595,12 +2762,13 @@ class StableBrowser {
2595
2762
  }
2596
2763
  catch (e) {
2597
2764
  console.log(".");
2765
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2598
2766
  }
2599
2767
  finally {
2600
2768
  await new Promise((resolve) => setTimeout(resolve, 2000));
2601
2769
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2602
2770
  const endTime = Date.now();
2603
- this._reportToWorld(world, {
2771
+ _reportToWorld(world, {
2604
2772
  type: Types.SET_VIEWPORT,
2605
2773
  text: "set viewport size to " + width + "x" + hight,
2606
2774
  screenshotId,
@@ -2631,12 +2799,13 @@ class StableBrowser {
2631
2799
  }
2632
2800
  catch (e) {
2633
2801
  console.log(".");
2802
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2634
2803
  }
2635
2804
  finally {
2636
2805
  await new Promise((resolve) => setTimeout(resolve, 2000));
2637
2806
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2638
2807
  const endTime = Date.now();
2639
- this._reportToWorld(world, {
2808
+ _reportToWorld(world, {
2640
2809
  type: Types.GET_PAGE_STATUS,
2641
2810
  text: "page relaod",
2642
2811
  screenshotId,
@@ -2672,11 +2841,37 @@ class StableBrowser {
2672
2841
  console.log("#-#");
2673
2842
  }
2674
2843
  }
2675
- _reportToWorld(world, properties) {
2676
- if (!world || !world.attach) {
2677
- return;
2844
+ async beforeStep(world, step) {
2845
+ this.stepName = step.pickleStep.text;
2846
+ this.logger.info("step: " + this.stepName);
2847
+ if (this.stepIndex === undefined) {
2848
+ this.stepIndex = 0;
2849
+ }
2850
+ else {
2851
+ this.stepIndex++;
2852
+ }
2853
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2854
+ if (this.context.browserObject.context) {
2855
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2856
+ }
2857
+ }
2858
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2859
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2860
+ // check if @global_test_data tag is present
2861
+ if (this.tags.includes("@global_test_data")) {
2862
+ this.saveTestDataAsGlobal({}, world);
2863
+ }
2864
+ }
2865
+ }
2866
+ async afterStep(world, step) {
2867
+ this.stepName = null;
2868
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2869
+ if (this.context.browserObject.context) {
2870
+ await this.context.browserObject.context.tracing.stopChunk({
2871
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2872
+ });
2873
+ }
2678
2874
  }
2679
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2680
2875
  }
2681
2876
  }
2682
2877
  function createTimedPromise(promise, label) {
@@ -2684,156 +2879,5 @@ function createTimedPromise(promise, label) {
2684
2879
  .then((result) => ({ status: "fulfilled", label, result }))
2685
2880
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2686
2881
  }
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
2882
  export { StableBrowser };
2839
2883
  //# sourceMappingURL=stable_browser.js.map