automation_model 1.0.502-dev → 1.0.502-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 (48) hide show
  1. package/lib/api.d.ts +2 -1
  2. package/lib/api.js +98 -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 +90 -32
  9. package/lib/browser_manager.js.map +1 -1
  10. package/lib/command_common.d.ts +1 -0
  11. package/lib/command_common.js +47 -7
  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 +20 -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.js +16 -3
  34. package/lib/network.js.map +1 -1
  35. package/lib/stable_browser.d.ts +55 -17
  36. package/lib/stable_browser.js +591 -595
  37. package/lib/stable_browser.js.map +1 -1
  38. package/lib/table.d.ts +13 -0
  39. package/lib/table.js +187 -0
  40. package/lib/table.js.map +1 -0
  41. package/lib/test_context.d.ts +1 -0
  42. package/lib/test_context.js +1 -0
  43. package/lib/test_context.js.map +1 -1
  44. package/lib/utils.d.ts +13 -1
  45. package/lib/utils.js +273 -3
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +7 -5
  48. /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 { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
17
17
  import { getContext } from "./init_browser.js";
18
18
  import { locate_element } from "./locate_element.js";
19
- import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
19
+ import { randomUUID } from "crypto";
20
+ import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
20
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
21
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
22
24
  CLICK: "click_element",
23
25
  NAVIGATE: "navigate",
24
26
  FILL: "fill_element",
@@ -29,6 +31,8 @@ const Types = {
29
31
  GET_PAGE_STATUS: "get_page_status",
30
32
  CLICK_ROW_ACTION: "click_row_action",
31
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
32
36
  ANALYZE_TABLE: "analyze_table",
33
37
  SELECT: "select_combobox",
34
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -44,6 +48,9 @@ const Types = {
44
48
  VERIFY_VISUAL: "verify_visual",
45
49
  LOAD_DATA: "load_data",
46
50
  SET_INPUT: "set_input",
51
+ WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
47
54
  };
48
55
  export const apps = {};
49
56
  class StableBrowser {
@@ -57,6 +64,7 @@ class StableBrowser {
57
64
  networkLogger = null;
58
65
  configuration = null;
59
66
  appName = "main";
67
+ tags = null;
60
68
  constructor(browser, page, logger = null, context = null, world = null) {
61
69
  this.browser = browser;
62
70
  this.page = page;
@@ -87,17 +95,17 @@ class StableBrowser {
87
95
  catch (e) {
88
96
  this.logger.error("unable to read ai_config.json");
89
97
  }
90
- context.pageLoading = { status: false };
91
- context.pages = [this.page];
92
98
  const logFolder = path.join(this.project_path, "logs", "web");
93
99
  this.world = world;
100
+ context.pages = [this.page];
101
+ context.pageLoading = { status: false };
94
102
  this.registerEventListeners(this.context);
95
103
  registerNetworkEvents(this.world, this, this.context, this.page);
96
104
  registerDownloadEvent(this.page, this.world, this.context);
97
105
  }
98
106
  registerEventListeners(context) {
99
107
  this.registerConsoleLogListener(this.page, context);
100
- this.registerRequestListener(this.page, context, this.webLogFile);
108
+ // this.registerRequestListener(this.page, context, this.webLogFile);
101
109
  if (!context.pageLoading) {
102
110
  context.pageLoading = { status: false };
103
111
  }
@@ -142,9 +150,9 @@ class StableBrowser {
142
150
  if (this.appName === appName) {
143
151
  return;
144
152
  }
145
- let newContextCreated = false;
153
+ let navigate = false;
146
154
  if (!apps[appName]) {
147
- 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);
148
156
  newContextCreated = true;
149
157
  apps[appName] = {
150
158
  context: newContext,
@@ -153,32 +161,15 @@ class StableBrowser {
153
161
  };
154
162
  }
155
163
  const tempContext = {};
156
- this._copyContext(this, tempContext);
157
- this._copyContext(apps[appName], this);
164
+ _copyContext(this, tempContext);
165
+ _copyContext(apps[appName], this);
158
166
  apps[this.appName] = tempContext;
159
167
  this.appName = appName;
160
- if (newContextCreated) {
161
- this.registerEventListeners(this.context);
168
+ if (navigate) {
162
169
  await this.goto(this.context.environment.baseUrl);
163
170
  await this.waitForPageLoad();
164
171
  }
165
172
  }
166
- _copyContext(from, to) {
167
- to.browser = from.browser;
168
- to.page = from.page;
169
- to.context = from.context;
170
- }
171
- getWebLogFile(logFolder) {
172
- if (!fs.existsSync(logFolder)) {
173
- fs.mkdirSync(logFolder, { recursive: true });
174
- }
175
- let nextIndex = 1;
176
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
177
- nextIndex++;
178
- }
179
- const fileName = nextIndex + ".json";
180
- return path.join(logFolder, fileName);
181
- }
182
173
  registerConsoleLogListener(page, context) {
183
174
  if (!this.context.webLogger) {
184
175
  this.context.webLogger = [];
@@ -228,7 +219,7 @@ class StableBrowser {
228
219
  this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
229
220
  }
230
221
  catch (error) {
231
- console.error("Error in request listener", error);
222
+ // console.error("Error in request listener", error);
232
223
  context.networkLogger.push({
233
224
  error: "not able to listen",
234
225
  message: error.message,
@@ -242,55 +233,48 @@ class StableBrowser {
242
233
  // async closeUnexpectedPopups() {
243
234
  // await closeUnexpectedPopups(this.page);
244
235
  // }
245
- async goto(url) {
236
+ async goto(url, world = null) {
246
237
  if (!url.startsWith("http")) {
247
238
  url = "https://" + url;
248
239
  }
249
- await this.page.goto(url, {
250
- timeout: 60000,
251
- });
252
- }
253
- _fixUsingParams(text, _params) {
254
- if (!_params || typeof text !== "string") {
255
- return text;
240
+ const state = {
241
+ value: url,
242
+ world: world,
243
+ type: Types.NAVIGATE,
244
+ text: `Navigate Page to: ${url}`,
245
+ operation: "goto",
246
+ log: "***** navigate page to " + url + " *****\n",
247
+ info: {},
248
+ locate: false,
249
+ scroll: false,
250
+ screenshot: false,
251
+ highlight: false,
252
+ };
253
+ try {
254
+ await _preCommand(state, this);
255
+ await this.page.goto(url, {
256
+ timeout: 60000,
257
+ });
258
+ await _screenshot(state, this);
256
259
  }
257
- for (let key in _params) {
258
- let regValue = key;
259
- if (key.startsWith("_")) {
260
- // remove the _ prefix
261
- regValue = key.substring(1);
262
- }
263
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
260
+ catch (error) {
261
+ console.error("Error on goto", error);
262
+ _commandError(state, error, this);
264
263
  }
265
- return text;
266
- }
267
- _fixLocatorUsingParams(locator, _params) {
268
- // check if not null
269
- if (!locator) {
270
- return locator;
264
+ finally {
265
+ _commandFinally(state, this);
271
266
  }
272
- // clone the locator
273
- locator = JSON.parse(JSON.stringify(locator));
274
- this.scanAndManipulate(locator, _params);
275
- return locator;
276
267
  }
277
- _isObject(value) {
278
- return value && typeof value === "object" && value.constructor === Object;
279
- }
280
- scanAndManipulate(currentObj, _params) {
281
- for (const key in currentObj) {
282
- if (typeof currentObj[key] === "string") {
283
- // Perform string manipulation
284
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
285
- }
286
- else if (this._isObject(currentObj[key])) {
287
- // Recursively scan nested objects
288
- this.scanAndManipulate(currentObj[key], _params);
268
+ async _getLocator(locator, scope, _params) {
269
+ locator = _fixLocatorUsingParams(locator, _params);
270
+ // locator = await this._replaceWithLocalData(locator);
271
+ for (let key in locator) {
272
+ if (typeof locator[key] !== "string")
273
+ continue;
274
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
275
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
289
276
  }
290
277
  }
291
- }
292
- _getLocator(locator, scope, _params) {
293
- locator = this._fixLocatorUsingParams(locator, _params);
294
278
  let locatorReturn;
295
279
  if (locator.role) {
296
280
  if (locator.role[1].nameReg) {
@@ -298,7 +282,7 @@ class StableBrowser {
298
282
  delete locator.role[1].nameReg;
299
283
  }
300
284
  // if (locator.role[1].name) {
301
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
285
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
302
286
  // }
303
287
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
304
288
  }
@@ -341,7 +325,7 @@ class StableBrowser {
341
325
  if (css && css.locator) {
342
326
  css = css.locator;
343
327
  }
344
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
328
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
345
329
  if (result.elementCount === 0) {
346
330
  return;
347
331
  }
@@ -352,132 +336,58 @@ class StableBrowser {
352
336
  climbArray.push("..");
353
337
  }
354
338
  let climbXpath = "xpath=" + climbArray.join("/");
355
- return textElementCss + " >> " + climbXpath + " >> " + css;
356
- }
357
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
358
- //const stringifyText = JSON.stringify(text);
359
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
360
- function isParent(parent, child) {
361
- let currentNode = child.parentNode;
362
- while (currentNode !== null) {
363
- if (currentNode === parent) {
364
- return true;
365
- }
366
- currentNode = currentNode.parentNode;
367
- }
368
- return false;
369
- }
370
- document.isParent = isParent;
371
- function getRegex(str) {
372
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
373
- if (!match) {
374
- return null;
375
- }
376
- let [_, pattern, flags] = match;
377
- return new RegExp(pattern, flags);
378
- }
379
- document.getRegex = getRegex;
380
- function collectAllShadowDomElements(element, result = []) {
381
- // Check and add the element if it has a shadow root
382
- if (element.shadowRoot) {
383
- result.push(element);
384
- // Also search within the shadow root
385
- document.collectAllShadowDomElements(element.shadowRoot, result);
386
- }
387
- // Iterate over child nodes
388
- element.childNodes.forEach((child) => {
389
- // Recursively call the function for each child node
390
- document.collectAllShadowDomElements(child, result);
391
- });
392
- return result;
393
- }
394
- document.collectAllShadowDomElements = collectAllShadowDomElements;
395
- if (!tag) {
396
- tag = "*:not(script, style, head)";
397
- }
398
- let regexpSearch = document.getRegex(text);
399
- if (regexpSearch) {
400
- regex = true;
401
- }
402
- let elements = Array.from(document.querySelectorAll(tag));
403
- let shadowHosts = [];
404
- document.collectAllShadowDomElements(document, shadowHosts);
405
- for (let i = 0; i < shadowHosts.length; i++) {
406
- let shadowElement = shadowHosts[i].shadowRoot;
407
- if (!shadowElement) {
408
- console.log("shadowElement is null, for host " + shadowHosts[i]);
409
- continue;
410
- }
411
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
412
- elements = elements.concat(shadowElements);
413
- }
414
- let randomToken = null;
415
- const foundElements = [];
416
- if (regex) {
417
- if (!regexpSearch) {
418
- regexpSearch = new RegExp(text, "im");
419
- }
420
- for (let i = 0; i < elements.length; i++) {
421
- const element = elements[i];
422
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
423
- (element.value && regexpSearch.test(element.value))) {
424
- foundElements.push(element);
425
- }
426
- }
427
- }
428
- else {
429
- text = text.trim();
430
- for (let i = 0; i < elements.length; i++) {
431
- const element = elements[i];
432
- if (partial) {
433
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
434
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
435
- foundElements.push(element);
436
- }
437
- }
438
- else {
439
- if ((element.innerText && element.innerText.trim() === text) ||
440
- (element.value && element.value === text)) {
441
- foundElements.push(element);
442
- }
443
- }
444
- }
445
- }
446
- let noChildElements = [];
447
- for (let i = 0; i < foundElements.length; i++) {
448
- let element = foundElements[i];
449
- let hasChild = false;
450
- for (let j = 0; j < foundElements.length; j++) {
451
- if (i === j) {
452
- continue;
453
- }
454
- if (isParent(element, foundElements[j])) {
455
- hasChild = true;
456
- break;
339
+ let resultCss = textElementCss + " >> " + climbXpath;
340
+ if (css) {
341
+ resultCss = resultCss + " >> " + css;
342
+ }
343
+ return resultCss;
344
+ }
345
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
346
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
347
+ const locator = scope.locator(query);
348
+ const count = await locator.count();
349
+ if (!tag1) {
350
+ tag1 = "*";
351
+ }
352
+ const randomToken = Math.random().toString(36).substring(7);
353
+ let tagCount = 0;
354
+ for (let i = 0; i < count; i++) {
355
+ const element = locator.nth(i);
356
+ // check if the tag matches
357
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
358
+ if (!tag.startsWith("*")) {
359
+ if (el.tagName.toLowerCase() !== tag) {
360
+ return false;
457
361
  }
458
362
  }
459
- if (!hasChild) {
460
- noChildElements.push(element);
461
- }
462
- }
463
- let elementCount = 0;
464
- if (noChildElements.length > 0) {
465
- for (let i = 0; i < noChildElements.length; i++) {
466
- if (randomToken === null) {
467
- randomToken = Math.random().toString(36).substring(7);
468
- }
469
- let element = noChildElements[i];
470
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
471
- elementCount++;
363
+ if (!el.setAttribute) {
364
+ el = el.parentElement;
472
365
  }
366
+ el.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
367
+ return true;
368
+ }, [tag1, randomToken]))) {
369
+ continue;
473
370
  }
474
- return { elementCount: elementCount, randomToken: randomToken };
475
- }, [text1, tag1, regex1, partial1]);
371
+ tagCount++;
372
+ }
373
+ return { elementCount: tagCount, randomToken };
476
374
  }
477
375
  async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
376
+ if (!info) {
377
+ info = {};
378
+ }
379
+ if (!info.failCause) {
380
+ info.failCause = {};
381
+ }
382
+ if (!info.log) {
383
+ info.log = "";
384
+ info.locatorLog = new LocatorLog(selectorHierarchy);
385
+ }
478
386
  let locatorSearch = selectorHierarchy[index];
387
+ let originalLocatorSearch = "";
479
388
  try {
480
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
389
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
390
+ locatorSearch = JSON.parse(originalLocatorSearch);
481
391
  }
482
392
  catch (e) {
483
393
  console.error(e);
@@ -491,11 +401,11 @@ class StableBrowser {
491
401
  info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
492
402
  return;
493
403
  }
494
- locator = this._getLocator({ css: locatorString }, scope, _params);
404
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
495
405
  }
496
406
  else if (locatorSearch.text) {
497
- let text = this._fixUsingParams(locatorSearch.text, _params);
498
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
407
+ let text = _fixUsingParams(locatorSearch.text, _params);
408
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
499
409
  if (result.elementCount === 0) {
500
410
  info.failCause.textNotFound = true;
501
411
  info.failCause.lastError = "failed to locate element by text: " + text;
@@ -505,10 +415,10 @@ class StableBrowser {
505
415
  if (locatorSearch.childCss) {
506
416
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
507
417
  }
508
- locator = this._getLocator(locatorSearch, scope, _params);
418
+ locator = await this._getLocator(locatorSearch, scope, _params);
509
419
  }
510
420
  else {
511
- locator = this._getLocator(locatorSearch, scope, _params);
421
+ locator = await this._getLocator(locatorSearch, scope, _params);
512
422
  }
513
423
  // let cssHref = false;
514
424
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -521,10 +431,16 @@ class StableBrowser {
521
431
  //info.log += "total elements found " + count + "\n";
522
432
  //let visibleCount = 0;
523
433
  let visibleLocator = null;
524
- if (locatorSearch.index && locatorSearch.index < count) {
434
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
525
435
  foundLocators.push(locator.nth(locatorSearch.index));
436
+ if (info.locatorLog) {
437
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
438
+ }
526
439
  return;
527
440
  }
441
+ if (info.locatorLog && count === 0) {
442
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
443
+ }
528
444
  for (let j = 0; j < count; j++) {
529
445
  let visible = await locator.nth(j).isVisible();
530
446
  const enabled = await locator.nth(j).isEnabled();
@@ -533,6 +449,9 @@ class StableBrowser {
533
449
  }
534
450
  if (visible && enabled) {
535
451
  foundLocators.push(locator.nth(j));
452
+ if (info.locatorLog) {
453
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
454
+ }
536
455
  }
537
456
  else {
538
457
  info.failCause.visible = visible;
@@ -540,8 +459,14 @@ class StableBrowser {
540
459
  if (!info.printMessages) {
541
460
  info.printMessages = {};
542
461
  }
462
+ if (info.locatorLog && !visible) {
463
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
464
+ }
465
+ if (info.locatorLog && !enabled) {
466
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
467
+ }
543
468
  if (!info.printMessages[j.toString()]) {
544
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
469
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
545
470
  info.printMessages[j.toString()] = true;
546
471
  }
547
472
  }
@@ -557,7 +482,7 @@ class StableBrowser {
557
482
  if (!info) {
558
483
  info = {};
559
484
  }
560
- info.log += "scan for popup handlers" + "\n";
485
+ //info.log += "scan for popup handlers" + "\n";
561
486
  const handlerGroup = [];
562
487
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
563
488
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -584,9 +509,21 @@ class StableBrowser {
584
509
  }
585
510
  if (result.foundElements.length > 0) {
586
511
  let dialogCloseLocator = result.foundElements[0].locator;
587
- await dialogCloseLocator.click();
588
- // wait for the dialog to close
589
- await dialogCloseLocator.waitFor({ state: "hidden" });
512
+ try {
513
+ await scope?.evaluate(() => {
514
+ window.__isClosingPopups = true;
515
+ });
516
+ await dialogCloseLocator.click();
517
+ // wait for the dialog to close
518
+ await dialogCloseLocator.waitFor({ state: "hidden" });
519
+ }
520
+ catch (e) {
521
+ }
522
+ finally {
523
+ await scope?.evaluate(() => {
524
+ window.__isClosingPopups = false;
525
+ });
526
+ }
590
527
  return { rerun: true };
591
528
  }
592
529
  }
@@ -616,6 +553,7 @@ class StableBrowser {
616
553
  info.failCause = {};
617
554
  info.log = "";
618
555
  }
556
+ let startTime = Date.now();
619
557
  let scope = this.page;
620
558
  if (selectors.frame) {
621
559
  return selectors.frame;
@@ -646,9 +584,11 @@ class StableBrowser {
646
584
  }
647
585
  return framescope;
648
586
  };
587
+ let fLocator = null;
649
588
  while (true) {
650
589
  let frameFound = false;
651
590
  if (selectors.nestFrmLoc) {
591
+ fLocator = selectors.nestFrmLoc;
652
592
  scope = await findFrame(selectors.nestFrmLoc, scope);
653
593
  frameFound = true;
654
594
  break;
@@ -657,6 +597,7 @@ class StableBrowser {
657
597
  for (let i = 0; i < selectors.frameLocators.length; i++) {
658
598
  let frameLocator = selectors.frameLocators[i];
659
599
  if (frameLocator.css) {
600
+ fLocator = frameLocator.css;
660
601
  scope = scope.frameLocator(frameLocator.css);
661
602
  frameFound = true;
662
603
  break;
@@ -664,11 +605,15 @@ class StableBrowser {
664
605
  }
665
606
  }
666
607
  if (!frameFound && selectors.iframe_src) {
608
+ fLocator = selectors.iframe_src;
667
609
  scope = this.page.frame({ url: selectors.iframe_src });
668
610
  }
669
611
  if (!scope) {
670
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
671
- if (performance.now() - startTime > timeout) {
612
+ if (info && info.locatorLog) {
613
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
614
+ }
615
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
616
+ if (Date.now() - startTime > timeout) {
672
617
  info.failCause.iframeNotFound = true;
673
618
  info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
674
619
  throw new Error("unable to locate iframe " + selectors.iframe_src);
@@ -676,6 +621,9 @@ class StableBrowser {
676
621
  await new Promise((resolve) => setTimeout(resolve, 1000));
677
622
  }
678
623
  else {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
626
+ }
679
627
  break;
680
628
  }
681
629
  }
@@ -697,11 +645,13 @@ class StableBrowser {
697
645
  info = {};
698
646
  info.failCause = {};
699
647
  info.log = "";
648
+ info.locatorLog = new LocatorLog(selectors);
700
649
  }
701
650
  let highPriorityTimeout = 5000;
702
651
  let visibleOnlyTimeout = 6000;
703
- let startTime = performance.now();
652
+ let startTime = Date.now();
704
653
  let locatorsCount = 0;
654
+ let lazy_scroll = false;
705
655
  //let arrayMode = Array.isArray(selectors);
706
656
  let scope = await this._findFrameScope(selectors, timeout, info);
707
657
  let selectorsLocators = null;
@@ -790,21 +740,31 @@ class StableBrowser {
790
740
  return maxCountElement.locator;
791
741
  }
792
742
  }
793
- if (performance.now() - startTime > timeout) {
743
+ if (Date.now() - startTime > timeout) {
794
744
  break;
795
745
  }
796
- if (performance.now() - startTime > highPriorityTimeout) {
797
- info.log += "high priority timeout, will try all elements" + "\n";
746
+ if (Date.now() - startTime > highPriorityTimeout) {
747
+ //info.log += "high priority timeout, will try all elements" + "\n";
798
748
  highPriorityOnly = false;
749
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
750
+ lazy_scroll = true;
751
+ await scrollPageToLoadLazyElements(this.page);
752
+ }
799
753
  }
800
- if (performance.now() - startTime > visibleOnlyTimeout) {
801
- info.log += "visible only timeout, will try all elements" + "\n";
754
+ if (Date.now() - startTime > visibleOnlyTimeout) {
755
+ //info.log += "visible only timeout, will try all elements" + "\n";
802
756
  visibleOnly = false;
803
757
  }
804
758
  await new Promise((resolve) => setTimeout(resolve, 1000));
805
759
  }
806
760
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
807
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
761
+ // if (info.locatorLog) {
762
+ // const lines = info.locatorLog.toString().split("\n");
763
+ // for (let line of lines) {
764
+ // this.logger.debug(line);
765
+ // }
766
+ // }
767
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
808
768
  info.failCause.locatorNotFound = true;
809
769
  info.failCause.lastError = "failed to locate unique element";
810
770
  throw new Error("failed to locate first element no elements found, " + info.log);
@@ -820,8 +780,9 @@ class StableBrowser {
820
780
  await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
821
781
  }
822
782
  catch (e) {
823
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
824
- this.logger.debug(e);
783
+ // this call can fail it the browser is navigating
784
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
785
+ // this.logger.debug(e);
825
786
  foundLocators = [];
826
787
  try {
827
788
  await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
@@ -840,11 +801,27 @@ class StableBrowser {
840
801
  }
841
802
  if (foundLocators.length > 1) {
842
803
  info.failCause.foundMultiple = true;
804
+ if (info.locatorLog) {
805
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
806
+ }
843
807
  }
844
808
  }
845
809
  return result;
846
810
  }
847
811
  async simpleClick(elementDescription, _params, options = {}, world = null) {
812
+ const state = {
813
+ locate: false,
814
+ scroll: false,
815
+ highlight: false,
816
+ _params,
817
+ options,
818
+ world,
819
+ type: Types.CLICK,
820
+ text: "Click element",
821
+ operation: "simpleClick",
822
+ log: "***** click on " + elementDescription + " *****\n",
823
+ };
824
+ _preCommand(state, this);
848
825
  const startTime = Date.now();
849
826
  let timeout = 30000;
850
827
  if (options && options.timeout) {
@@ -868,13 +845,32 @@ class StableBrowser {
868
845
  }
869
846
  catch (e) {
870
847
  if (performance.now() - startTime > timeout) {
871
- throw e;
848
+ // throw e;
849
+ try {
850
+ await _commandError(state, "timeout looking for " + elementDescription, this);
851
+ }
852
+ finally {
853
+ _commandFinally(state, this);
854
+ }
872
855
  }
873
856
  }
874
857
  await new Promise((resolve) => setTimeout(resolve, 3000));
875
858
  }
876
859
  }
877
860
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
861
+ const state = {
862
+ locate: false,
863
+ scroll: false,
864
+ highlight: false,
865
+ _params,
866
+ options,
867
+ world,
868
+ type: Types.FILL,
869
+ text: "Fill element",
870
+ operation: "simpleClickType",
871
+ log: "***** click type on " + elementDescription + " *****\n",
872
+ };
873
+ _preCommand(state, this);
878
874
  const startTime = Date.now();
879
875
  let timeout = 30000;
880
876
  if (options && options.timeout) {
@@ -898,7 +894,13 @@ class StableBrowser {
898
894
  }
899
895
  catch (e) {
900
896
  if (performance.now() - startTime > timeout) {
901
- throw e;
897
+ // throw e;
898
+ try {
899
+ await _commandError(state, "timeout looking for " + elementDescription, this);
900
+ }
901
+ finally {
902
+ _commandFinally(state, this);
903
+ }
902
904
  }
903
905
  }
904
906
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -917,18 +919,18 @@ class StableBrowser {
917
919
  };
918
920
  try {
919
921
  await _preCommand(state, this);
920
- if (state.options && state.options.context) {
921
- state.selectors.locators[0].text = state.options.context;
922
- }
922
+ // if (state.options && state.options.context) {
923
+ // state.selectors.locators[0].text = state.options.context;
924
+ // }
923
925
  try {
924
926
  await state.element.click();
925
- await new Promise((resolve) => setTimeout(resolve, 1000));
927
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
926
928
  }
927
929
  catch (e) {
928
930
  // await this.closeUnexpectedPopups();
929
931
  state.element = await this._locate(selectors, state.info, _params);
930
932
  await state.element.dispatchEvent("click");
931
- await new Promise((resolve) => setTimeout(resolve, 1000));
933
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
932
934
  }
933
935
  await this.waitForPageLoad();
934
936
  return state.info;
@@ -1007,7 +1009,6 @@ class StableBrowser {
1007
1009
  await state.element.hover({ timeout: 10000 });
1008
1010
  await new Promise((resolve) => setTimeout(resolve, 1000));
1009
1011
  }
1010
- await _screenshot(state, this);
1011
1012
  await this.waitForPageLoad();
1012
1013
  return state.info;
1013
1014
  }
@@ -1135,33 +1136,30 @@ class StableBrowser {
1135
1136
  }
1136
1137
  }
1137
1138
  async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
1138
- _validateSelectors(selectors);
1139
- const startTime = Date.now();
1140
- let error = null;
1141
- let screenshotId = null;
1142
- let screenshotPath = null;
1143
- const info = {};
1144
- info.log = "";
1145
- info.operation = Types.SET_DATE_TIME;
1146
- info.selectors = selectors;
1147
- info.value = value;
1139
+ const state = {
1140
+ selectors,
1141
+ _params,
1142
+ value: await this._replaceWithLocalData(value, this),
1143
+ options,
1144
+ world,
1145
+ type: Types.SET_DATE_TIME,
1146
+ text: `Set date time value: ${value}`,
1147
+ operation: "setDateTime",
1148
+ log: "***** set date time value " + selectors.element_name + " *****\n",
1149
+ throwError: false,
1150
+ };
1148
1151
  try {
1149
- value = await this._replaceWithLocalData(value, this);
1150
- let element = await this._locate(selectors, info, _params);
1151
- //insert red border around the element
1152
- await this.scrollIfNeeded(element, info);
1153
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1154
- await this._highlightElements(element);
1152
+ await _preCommand(state, this);
1155
1153
  try {
1156
- await element.click();
1154
+ await state.element.click();
1157
1155
  await new Promise((resolve) => setTimeout(resolve, 500));
1158
1156
  if (format) {
1159
- value = dayjs(value).format(format);
1160
- await element.fill(value);
1157
+ state.value = dayjs(state.value).format(format);
1158
+ await state.element.fill(state.value);
1161
1159
  }
1162
1160
  else {
1163
- const dateTimeValue = await getDateTimeValue({ value, element });
1164
- await element.evaluateHandle((el, dateTimeValue) => {
1161
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1162
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1165
1163
  el.value = ""; // clear input
1166
1164
  el.value = dateTimeValue;
1167
1165
  }, dateTimeValue);
@@ -1174,20 +1172,19 @@ class StableBrowser {
1174
1172
  }
1175
1173
  catch (err) {
1176
1174
  //await this.closeUnexpectedPopups();
1177
- this.logger.error("setting date time input failed " + JSON.stringify(info));
1175
+ this.logger.error("setting date time input failed " + JSON.stringify(state.info));
1178
1176
  this.logger.info("Trying again");
1179
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1180
- info.screenshotPath = screenshotPath;
1181
- Object.assign(err, { info: info });
1177
+ await _screenshot(state, this);
1178
+ Object.assign(err, { info: state.info });
1182
1179
  await element.click();
1183
1180
  await new Promise((resolve) => setTimeout(resolve, 500));
1184
1181
  if (format) {
1185
- value = dayjs(value).format(format);
1186
- await element.fill(value);
1182
+ state.value = dayjs(state.value).format(format);
1183
+ await state.element.fill(state.value);
1187
1184
  }
1188
1185
  else {
1189
- const dateTimeValue = await getDateTimeValue({ value, element });
1190
- await element.evaluateHandle((el, dateTimeValue) => {
1186
+ const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
1187
+ await state.element.evaluateHandle((el, dateTimeValue) => {
1191
1188
  el.value = ""; // clear input
1192
1189
  el.value = dateTimeValue;
1193
1190
  }, dateTimeValue);
@@ -1200,31 +1197,10 @@ class StableBrowser {
1200
1197
  }
1201
1198
  }
1202
1199
  catch (e) {
1203
- error = e;
1204
- throw e;
1200
+ await _commandError(state, e, this);
1205
1201
  }
1206
1202
  finally {
1207
- const endTime = Date.now();
1208
- this._reportToWorld(world, {
1209
- element_name: selectors.element_name,
1210
- type: Types.SET_DATE_TIME,
1211
- screenshotId,
1212
- value: value,
1213
- text: `setDateTime input with value: ${value}`,
1214
- result: error
1215
- ? {
1216
- status: "FAILED",
1217
- startTime,
1218
- endTime,
1219
- message: error === null || error === void 0 ? void 0 : error.message,
1220
- }
1221
- : {
1222
- status: "PASSED",
1223
- startTime,
1224
- endTime,
1225
- },
1226
- info: info,
1227
- });
1203
+ _commandFinally(state, this);
1228
1204
  }
1229
1205
  }
1230
1206
  async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
@@ -1361,6 +1337,7 @@ class StableBrowser {
1361
1337
  let screenshotPath = null;
1362
1338
  if (!info.log) {
1363
1339
  info.log = "";
1340
+ info.locatorLog = new LocatorLog(selectors);
1364
1341
  }
1365
1342
  info.operation = "getText";
1366
1343
  info.selectors = selectors;
@@ -1699,11 +1676,9 @@ class StableBrowser {
1699
1676
  if (!fs.existsSync(world.screenshotPath)) {
1700
1677
  fs.mkdirSync(world.screenshotPath, { recursive: true });
1701
1678
  }
1702
- let nextIndex = 1;
1703
- while (fs.existsSync(path.join(world.screenshotPath, nextIndex + ".png"))) {
1704
- nextIndex++;
1705
- }
1706
- const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
1679
+ // to make sure the path doesn't start with -
1680
+ const uuidStr = "id_" + randomUUID();
1681
+ const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
1707
1682
  try {
1708
1683
  await this.takeScreenshot(screenshotPath);
1709
1684
  // let buffer = await this.page.screenshot({ timeout: 4000 });
@@ -1717,7 +1692,7 @@ class StableBrowser {
1717
1692
  catch (e) {
1718
1693
  this.logger.info("unable to take screenshot, ignored");
1719
1694
  }
1720
- result.screenshotId = nextIndex;
1695
+ result.screenshotId = uuidStr;
1721
1696
  result.screenshotPath = screenshotPath;
1722
1697
  if (info && info.box) {
1723
1698
  await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
@@ -1815,74 +1790,105 @@ class StableBrowser {
1815
1790
  }
1816
1791
  }
1817
1792
  async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
1818
- _validateSelectors(selectors);
1819
- const startTime = Date.now();
1820
- let error = null;
1821
- let screenshotId = null;
1822
- let screenshotPath = null;
1793
+ const state = {
1794
+ selectors,
1795
+ _params,
1796
+ attribute,
1797
+ variable,
1798
+ options,
1799
+ world,
1800
+ type: Types.EXTRACT,
1801
+ text: `Extract attribute from element`,
1802
+ operation: "extractAttribute",
1803
+ log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1804
+ };
1823
1805
  await new Promise((resolve) => setTimeout(resolve, 2000));
1824
- const info = {};
1825
- info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
1826
- info.operation = "extract";
1827
- info.selectors = selectors;
1828
1806
  try {
1829
- const element = await this._locate(selectors, info, _params);
1830
- await this._highlightElements(element);
1831
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1807
+ await _preCommand(state, this);
1832
1808
  switch (attribute) {
1833
1809
  case "inner_text":
1834
- info.value = await element.innerText();
1810
+ state.value = await state.element.innerText();
1835
1811
  break;
1836
1812
  case "href":
1837
- info.value = await element.getAttribute("href");
1813
+ state.value = await state.element.getAttribute("href");
1838
1814
  break;
1839
1815
  case "value":
1840
- info.value = await element.inputValue();
1816
+ state.value = await state.element.inputValue();
1841
1817
  break;
1842
1818
  default:
1843
- info.value = await element.getAttribute(attribute);
1819
+ state.value = await state.element.getAttribute(attribute);
1844
1820
  break;
1845
1821
  }
1846
- this[variable] = info.value;
1847
- if (world) {
1848
- world[variable] = info.value;
1822
+ state.info.value = state.value;
1823
+ this.setTestData({ [variable]: state.value }, world);
1824
+ this.logger.info("set test data: " + variable + "=" + state.value);
1825
+ return state.info;
1826
+ }
1827
+ catch (e) {
1828
+ await _commandError(state, e, this);
1829
+ }
1830
+ finally {
1831
+ _commandFinally(state, this);
1832
+ }
1833
+ }
1834
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1835
+ const state = {
1836
+ selectors,
1837
+ _params,
1838
+ attribute,
1839
+ value,
1840
+ options,
1841
+ world,
1842
+ type: Types.VERIFY_ATTRIBUTE,
1843
+ text: `Verify element attribute`,
1844
+ operation: "verifyAttribute",
1845
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1846
+ };
1847
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1848
+ let val;
1849
+ try {
1850
+ await _preCommand(state, this);
1851
+ switch (attribute) {
1852
+ case "innerText":
1853
+ val = String(await state.element.innerText());
1854
+ break;
1855
+ case "value":
1856
+ val = String(await state.element.inputValue());
1857
+ break;
1858
+ case "checked":
1859
+ val = String(await state.element.isChecked());
1860
+ break;
1861
+ case "disabled":
1862
+ val = String(await state.element.isDisabled());
1863
+ break;
1864
+ case "readOnly":
1865
+ const isEditable = await state.element.isEditable();
1866
+ val = String(!isEditable);
1867
+ break;
1868
+ default:
1869
+ val = String(await state.element.getAttribute(attribute));
1870
+ break;
1849
1871
  }
1850
- this.setTestData({ [variable]: info.value }, world);
1851
- this.logger.info("set test data: " + variable + "=" + info.value);
1852
- return info;
1872
+ state.info.expectedValue = val;
1873
+ let regex;
1874
+ if (value.startsWith("/") && value.endsWith("/")) {
1875
+ const patternBody = value.slice(1, -1);
1876
+ regex = new RegExp(patternBody, "g");
1877
+ }
1878
+ else {
1879
+ const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1880
+ regex = new RegExp(escapedPattern, "g");
1881
+ }
1882
+ if (!val.match(regex)) {
1883
+ throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1884
+ }
1885
+ return state.info;
1853
1886
  }
1854
1887
  catch (e) {
1855
- //await this.closeUnexpectedPopups();
1856
- this.logger.error("extract failed " + info.log);
1857
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1858
- info.screenshotPath = screenshotPath;
1859
- Object.assign(e, { info: info });
1860
- error = e;
1861
- throw e;
1888
+ await _commandError(state, e, this);
1862
1889
  }
1863
1890
  finally {
1864
- const endTime = Date.now();
1865
- this._reportToWorld(world, {
1866
- element_name: selectors.element_name,
1867
- type: Types.EXTRACT_ATTRIBUTE,
1868
- variable: variable,
1869
- value: info.value,
1870
- text: "Extract attribute from element",
1871
- screenshotId,
1872
- result: error
1873
- ? {
1874
- status: "FAILED",
1875
- startTime,
1876
- endTime,
1877
- message: error?.message,
1878
- }
1879
- : {
1880
- status: "PASSED",
1881
- startTime,
1882
- endTime,
1883
- },
1884
- info: info,
1885
- });
1891
+ _commandFinally(state, this);
1886
1892
  }
1887
1893
  }
1888
1894
  async extractEmailData(emailAddress, options, world) {
@@ -1903,7 +1909,7 @@ class StableBrowser {
1903
1909
  if (options && options.timeout) {
1904
1910
  timeout = options.timeout;
1905
1911
  }
1906
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1912
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1907
1913
  const request = {
1908
1914
  method: "POST",
1909
1915
  url: serviceUrl,
@@ -1959,7 +1965,8 @@ class StableBrowser {
1959
1965
  catch (e) {
1960
1966
  errorCount++;
1961
1967
  if (errorCount > 3) {
1962
- throw e;
1968
+ // throw e;
1969
+ await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
1963
1970
  }
1964
1971
  // ignore
1965
1972
  }
@@ -2070,11 +2077,12 @@ class StableBrowser {
2070
2077
  info.screenshotPath = screenshotPath;
2071
2078
  Object.assign(e, { info: info });
2072
2079
  error = e;
2073
- throw e;
2080
+ // throw e;
2081
+ await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
2074
2082
  }
2075
2083
  finally {
2076
2084
  const endTime = Date.now();
2077
- this._reportToWorld(world, {
2085
+ _reportToWorld(world, {
2078
2086
  type: Types.VERIFY_PAGE_PATH,
2079
2087
  text: "Verify page path",
2080
2088
  screenshotId,
@@ -2094,54 +2102,65 @@ class StableBrowser {
2094
2102
  });
2095
2103
  }
2096
2104
  }
2105
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2106
+ const frames = this.page.frames();
2107
+ let results = [];
2108
+ let ignoreCase = !(text.startsWith("/") && text.endsWith("/"));
2109
+ for (let i = 0; i < frames.length; i++) {
2110
+ if (dateAlternatives.date) {
2111
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2112
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2113
+ result.frame = frames[i];
2114
+ results.push(result);
2115
+ }
2116
+ }
2117
+ else if (numberAlternatives.number) {
2118
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2119
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2120
+ result.frame = frames[i];
2121
+ results.push(result);
2122
+ }
2123
+ }
2124
+ else {
2125
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2126
+ result.frame = frames[i];
2127
+ results.push(result);
2128
+ }
2129
+ }
2130
+ state.info.results = results;
2131
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2132
+ return resultWithElementsFound;
2133
+ }
2097
2134
  async verifyTextExistInPage(text, options = {}, world = null) {
2098
2135
  text = unEscapeString(text);
2099
- const startTime = Date.now();
2136
+ const state = {
2137
+ text_search: text,
2138
+ options,
2139
+ world,
2140
+ locate: false,
2141
+ scroll: false,
2142
+ highlight: false,
2143
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2144
+ text: `Verify text exists in page`,
2145
+ operation: "verifyTextExistInPage",
2146
+ log: "***** verify text " + text + " exists in page *****\n",
2147
+ };
2100
2148
  const timeout = this._getLoadTimeout(options);
2101
- let error = null;
2102
- let screenshotId = null;
2103
- let screenshotPath = null;
2104
2149
  await new Promise((resolve) => setTimeout(resolve, 2000));
2105
- const info = {};
2106
- info.log = "***** verify text " + text + " exists in page *****\n";
2107
- info.operation = "verifyTextExistInPage";
2108
2150
  const newValue = await this._replaceWithLocalData(text, world);
2109
2151
  if (newValue !== text) {
2110
2152
  this.logger.info(text + "=" + newValue);
2111
2153
  text = newValue;
2112
2154
  }
2113
- info.text = text;
2114
2155
  let dateAlternatives = findDateAlternatives(text);
2115
2156
  let numberAlternatives = findNumberAlternatives(text);
2116
2157
  try {
2158
+ await _preCommand(state, this);
2159
+ state.info.text = text;
2117
2160
  while (true) {
2118
- const frames = this.page.frames();
2119
- let results = [];
2120
- for (let i = 0; i < frames.length; i++) {
2121
- if (dateAlternatives.date) {
2122
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2123
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2124
- result.frame = frames[i];
2125
- results.push(result);
2126
- }
2127
- }
2128
- else if (numberAlternatives.number) {
2129
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2130
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2131
- result.frame = frames[i];
2132
- results.push(result);
2133
- }
2134
- }
2135
- else {
2136
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2137
- result.frame = frames[i];
2138
- results.push(result);
2139
- }
2140
- }
2141
- info.results = results;
2142
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2161
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2143
2162
  if (resultWithElementsFound.length === 0) {
2144
- if (Date.now() - startTime > timeout) {
2163
+ if (Date.now() - state.startTime > timeout) {
2145
2164
  throw new Error(`Text ${text} not found in page`);
2146
2165
  }
2147
2166
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -2151,57 +2170,151 @@ class StableBrowser {
2151
2170
  const frame = resultWithElementsFound[0].frame;
2152
2171
  const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2153
2172
  await this._highlightElements(frame, dataAttribute);
2154
- const element = await frame.$(dataAttribute);
2173
+ const element = await frame.locator(dataAttribute).first();
2155
2174
  if (element) {
2156
- await this.scrollIfNeeded(element, info);
2175
+ await this.scrollIfNeeded(element, state.info);
2157
2176
  await element.dispatchEvent("bvt_verify_page_contains_text");
2158
2177
  }
2159
2178
  }
2160
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2161
- return info;
2179
+ await _screenshot(state, this);
2180
+ return state.info;
2162
2181
  }
2163
2182
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2164
2183
  }
2165
2184
  catch (e) {
2166
- //await this.closeUnexpectedPopups();
2167
- this.logger.error("verify text exist in page failed " + info.log);
2168
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2169
- info.screenshotPath = screenshotPath;
2170
- Object.assign(e, { info: info });
2171
- error = e;
2172
- throw e;
2185
+ await _commandError(state, e, this);
2173
2186
  }
2174
2187
  finally {
2175
- const endTime = Date.now();
2176
- this._reportToWorld(world, {
2177
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2178
- text: "Verify text exists in page",
2179
- screenshotId,
2180
- result: error
2181
- ? {
2182
- status: "FAILED",
2183
- startTime,
2184
- endTime,
2185
- message: error?.message,
2186
- }
2187
- : {
2188
- status: "PASSED",
2189
- startTime,
2190
- endTime,
2191
- },
2192
- info: info,
2193
- });
2188
+ _commandFinally(state, this);
2189
+ }
2190
+ }
2191
+ async waitForTextToDisappear(text, options = {}, world = null) {
2192
+ text = unEscapeString(text);
2193
+ const state = {
2194
+ text_search: text,
2195
+ options,
2196
+ world,
2197
+ locate: false,
2198
+ scroll: false,
2199
+ highlight: false,
2200
+ type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2201
+ text: `Verify text does not exist in page`,
2202
+ operation: "verifyTextNotExistInPage",
2203
+ log: "***** verify text " + text + " does not exist in page *****\n",
2204
+ };
2205
+ const timeout = this._getLoadTimeout(options);
2206
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2207
+ const newValue = await this._replaceWithLocalData(text, world);
2208
+ if (newValue !== text) {
2209
+ this.logger.info(text + "=" + newValue);
2210
+ text = newValue;
2211
+ }
2212
+ let dateAlternatives = findDateAlternatives(text);
2213
+ let numberAlternatives = findNumberAlternatives(text);
2214
+ try {
2215
+ await _preCommand(state, this);
2216
+ state.info.text = text;
2217
+ while (true) {
2218
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2219
+ if (resultWithElementsFound.length === 0) {
2220
+ await _screenshot(state, this);
2221
+ return state.info;
2222
+ }
2223
+ if (Date.now() - state.startTime > timeout) {
2224
+ throw new Error(`Text ${text} found in page`);
2225
+ }
2226
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2227
+ }
2228
+ }
2229
+ catch (e) {
2230
+ await _commandError(state, e, this);
2231
+ }
2232
+ finally {
2233
+ _commandFinally(state, this);
2194
2234
  }
2195
2235
  }
2196
- _getServerUrl() {
2197
- let serviceUrl = "https://api.blinq.io";
2198
- if (process.env.NODE_ENV_BLINQ === "dev") {
2199
- serviceUrl = "https://dev.api.blinq.io";
2236
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2237
+ textAnchor = unEscapeString(textAnchor);
2238
+ textToVerify = unEscapeString(textToVerify);
2239
+ const state = {
2240
+ text_search: textToVerify,
2241
+ options,
2242
+ world,
2243
+ locate: false,
2244
+ scroll: false,
2245
+ highlight: false,
2246
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2247
+ text: `Verify text with relation to another text`,
2248
+ operation: "verify_text_with_relation",
2249
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2250
+ };
2251
+ const timeout = this._getLoadTimeout(options);
2252
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2253
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2254
+ if (newValue !== textAnchor) {
2255
+ this.logger.info(textAnchor + "=" + newValue);
2256
+ textAnchor = newValue;
2257
+ }
2258
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2259
+ if (newValue !== textToVerify) {
2260
+ this.logger.info(textToVerify + "=" + newValue);
2261
+ textToVerify = newValue;
2262
+ }
2263
+ let dateAlternatives = findDateAlternatives(textToVerify);
2264
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2265
+ let foundAncore = false;
2266
+ try {
2267
+ await _preCommand(state, this);
2268
+ state.info.text = textToVerify;
2269
+ while (true) {
2270
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2271
+ if (resultWithElementsFound.length === 0) {
2272
+ if (Date.now() - state.startTime > timeout) {
2273
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2274
+ }
2275
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2276
+ continue;
2277
+ }
2278
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2279
+ foundAncore = true;
2280
+ const result = resultWithElementsFound[i];
2281
+ const token = result.randomToken;
2282
+ const frame = result.frame;
2283
+ let css = `[data-blinq-id="blinq-id-${token}"]`;
2284
+ const climbArray1 = [];
2285
+ for (let i = 0; i < climb; i++) {
2286
+ climbArray1.push("..");
2287
+ }
2288
+ let climbXpath = "xpath=" + climbArray1.join("/");
2289
+ css = css + " >> " + climbXpath;
2290
+ const count = await frame.locator(css).count();
2291
+ for (let j = 0; j < count; j++) {
2292
+ const continer = await frame.locator(css).nth(j);
2293
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2294
+ if (result.elementCount > 0) {
2295
+ const dataAttribute = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
2296
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2297
+ await this._highlightElements(frame, dataAttribute);
2298
+ //await this._highlightElements(frame, cssAnchor);
2299
+ const element = await frame.locator(dataAttribute).first();
2300
+ if (element) {
2301
+ await this.scrollIfNeeded(element, state.info);
2302
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2303
+ }
2304
+ await _screenshot(state, this);
2305
+ return state.info;
2306
+ }
2307
+ }
2308
+ }
2309
+ }
2310
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2311
+ }
2312
+ catch (e) {
2313
+ await _commandError(state, e, this);
2200
2314
  }
2201
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2202
- serviceUrl = "https://stage.api.blinq.io";
2315
+ finally {
2316
+ _commandFinally(state, this);
2203
2317
  }
2204
- return serviceUrl;
2205
2318
  }
2206
2319
  async visualVerification(text, options = {}, world = null) {
2207
2320
  const startTime = Date.now();
@@ -2217,7 +2330,7 @@ class StableBrowser {
2217
2330
  throw new Error("TOKEN is not set");
2218
2331
  }
2219
2332
  try {
2220
- let serviceUrl = this._getServerUrl();
2333
+ let serviceUrl = _getServerUrl();
2221
2334
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2222
2335
  info.screenshotPath = screenshotPath;
2223
2336
  const screenshot = await this.takeScreenshot();
@@ -2253,11 +2366,12 @@ class StableBrowser {
2253
2366
  info.screenshotPath = screenshotPath;
2254
2367
  Object.assign(e, { info: info });
2255
2368
  error = e;
2256
- throw e;
2369
+ // throw e;
2370
+ await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
2257
2371
  }
2258
2372
  finally {
2259
2373
  const endTime = Date.now();
2260
- this._reportToWorld(world, {
2374
+ _reportToWorld(world, {
2261
2375
  type: Types.VERIFY_VISUAL,
2262
2376
  text: "Visual verification",
2263
2377
  screenshotId,
@@ -2305,6 +2419,7 @@ class StableBrowser {
2305
2419
  let screenshotPath = null;
2306
2420
  const info = {};
2307
2421
  info.log = "";
2422
+ info.locatorLog = new LocatorLog(selectors);
2308
2423
  info.operation = "getTableData";
2309
2424
  info.selectors = selectors;
2310
2425
  try {
@@ -2320,11 +2435,12 @@ class StableBrowser {
2320
2435
  info.screenshotPath = screenshotPath;
2321
2436
  Object.assign(e, { info: info });
2322
2437
  error = e;
2323
- throw e;
2438
+ // throw e;
2439
+ await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
2324
2440
  }
2325
2441
  finally {
2326
2442
  const endTime = Date.now();
2327
- this._reportToWorld(world, {
2443
+ _reportToWorld(world, {
2328
2444
  element_name: selectors.element_name,
2329
2445
  type: Types.GET_TABLE_DATA,
2330
2446
  text: "Get table data",
@@ -2379,7 +2495,7 @@ class StableBrowser {
2379
2495
  info.operation = "analyzeTable";
2380
2496
  info.selectors = selectors;
2381
2497
  info.query = query;
2382
- query = this._fixUsingParams(query, _params);
2498
+ query = _fixUsingParams(query, _params);
2383
2499
  info.query_fixed = query;
2384
2500
  info.operator = operator;
2385
2501
  info.value = value;
@@ -2485,11 +2601,12 @@ class StableBrowser {
2485
2601
  info.screenshotPath = screenshotPath;
2486
2602
  Object.assign(e, { info: info });
2487
2603
  error = e;
2488
- throw e;
2604
+ // throw e;
2605
+ await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
2489
2606
  }
2490
2607
  finally {
2491
2608
  const endTime = Date.now();
2492
- this._reportToWorld(world, {
2609
+ _reportToWorld(world, {
2493
2610
  element_name: selectors.element_name,
2494
2611
  type: Types.ANALYZE_TABLE,
2495
2612
  text: "Analyze table",
@@ -2562,7 +2679,7 @@ class StableBrowser {
2562
2679
  await new Promise((resolve) => setTimeout(resolve, 2000));
2563
2680
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2564
2681
  const endTime = Date.now();
2565
- this._reportToWorld(world, {
2682
+ _reportToWorld(world, {
2566
2683
  type: Types.GET_PAGE_STATUS,
2567
2684
  text: "Wait for page load",
2568
2685
  screenshotId,
@@ -2582,41 +2699,35 @@ class StableBrowser {
2582
2699
  }
2583
2700
  }
2584
2701
  async closePage(options = {}, world = null) {
2585
- const startTime = Date.now();
2586
- let error = null;
2587
- let screenshotId = null;
2588
- let screenshotPath = null;
2589
- const info = {};
2702
+ const state = {
2703
+ options,
2704
+ world,
2705
+ locate: false,
2706
+ scroll: false,
2707
+ highlight: false,
2708
+ type: Types.CLOSE_PAGE,
2709
+ text: `Close page`,
2710
+ operation: "closePage",
2711
+ log: "***** close page *****\n",
2712
+ throwError: false,
2713
+ };
2590
2714
  try {
2715
+ await _preCommand(state, this);
2591
2716
  await this.page.close();
2592
2717
  }
2593
2718
  catch (e) {
2594
2719
  console.log(".");
2720
+ await _commandError(state, e, this);
2595
2721
  }
2596
2722
  finally {
2597
- await new Promise((resolve) => setTimeout(resolve, 2000));
2598
- ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2599
- const endTime = Date.now();
2600
- this._reportToWorld(world, {
2601
- type: Types.CLOSE_PAGE,
2602
- text: "close page",
2603
- screenshotId,
2604
- result: error
2605
- ? {
2606
- status: "FAILED",
2607
- startTime,
2608
- endTime,
2609
- message: error?.message,
2610
- }
2611
- : {
2612
- status: "PASSED",
2613
- startTime,
2614
- endTime,
2615
- },
2616
- info: info,
2617
- });
2723
+ _commandFinally(state, this);
2618
2724
  }
2619
2725
  }
2726
+ saveTestDataAsGlobal(options, world) {
2727
+ const dataFile = this._getDataFile(world);
2728
+ process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2729
+ this.logger.info("Save the scenario test data as global for the following scenarios.");
2730
+ }
2620
2731
  async setViewportSize(width, hight, options = {}, world = null) {
2621
2732
  const startTime = Date.now();
2622
2733
  let error = null;
@@ -2634,12 +2745,13 @@ class StableBrowser {
2634
2745
  }
2635
2746
  catch (e) {
2636
2747
  console.log(".");
2748
+ await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
2637
2749
  }
2638
2750
  finally {
2639
2751
  await new Promise((resolve) => setTimeout(resolve, 2000));
2640
2752
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
2641
2753
  const endTime = Date.now();
2642
- this._reportToWorld(world, {
2754
+ _reportToWorld(world, {
2643
2755
  type: Types.SET_VIEWPORT,
2644
2756
  text: "set viewport size to " + width + "x" + hight,
2645
2757
  screenshotId,
@@ -2670,12 +2782,13 @@ class StableBrowser {
2670
2782
  }
2671
2783
  catch (e) {
2672
2784
  console.log(".");
2785
+ await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
2673
2786
  }
2674
2787
  finally {
2675
2788
  await new Promise((resolve) => setTimeout(resolve, 2000));
2676
2789
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2677
2790
  const endTime = Date.now();
2678
- this._reportToWorld(world, {
2791
+ _reportToWorld(world, {
2679
2792
  type: Types.GET_PAGE_STATUS,
2680
2793
  text: "page relaod",
2681
2794
  screenshotId,
@@ -2711,11 +2824,45 @@ class StableBrowser {
2711
2824
  console.log("#-#");
2712
2825
  }
2713
2826
  }
2714
- _reportToWorld(world, properties) {
2715
- if (!world || !world.attach) {
2716
- return;
2827
+ async beforeStep(world, step) {
2828
+ if (this.stepIndex === undefined) {
2829
+ this.stepIndex = 0;
2830
+ }
2831
+ else {
2832
+ this.stepIndex++;
2833
+ }
2834
+ if (step && step.pickleStep && step.pickleStep.text) {
2835
+ this.stepName = step.pickleStep.text;
2836
+ this.logger.info("step: " + this.stepName);
2837
+ }
2838
+ else if (step && step.text) {
2839
+ this.stepName = step.text;
2840
+ }
2841
+ else {
2842
+ this.stepName = "step " + this.stepIndex;
2843
+ }
2844
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2845
+ if (this.context.browserObject.context) {
2846
+ await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
2847
+ }
2848
+ }
2849
+ if (this.tags === null && step && step.pickle && step.pickle.tags) {
2850
+ this.tags = step.pickle.tags.map((tag) => tag.name);
2851
+ // check if @global_test_data tag is present
2852
+ if (this.tags.includes("@global_test_data")) {
2853
+ this.saveTestDataAsGlobal({}, world);
2854
+ }
2855
+ }
2856
+ }
2857
+ async afterStep(world, step) {
2858
+ this.stepName = null;
2859
+ if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2860
+ if (this.context.browserObject.context) {
2861
+ await this.context.browserObject.context.tracing.stopChunk({
2862
+ path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2863
+ });
2864
+ }
2717
2865
  }
2718
- world.attach(JSON.stringify(properties), { mediaType: "application/json" });
2719
2866
  }
2720
2867
  }
2721
2868
  function createTimedPromise(promise, label) {
@@ -2723,156 +2870,5 @@ function createTimedPromise(promise, label) {
2723
2870
  .then((result) => ({ status: "fulfilled", label, result }))
2724
2871
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2725
2872
  }
2726
- const KEYBOARD_EVENTS = [
2727
- "ALT",
2728
- "AltGraph",
2729
- "CapsLock",
2730
- "Control",
2731
- "Fn",
2732
- "FnLock",
2733
- "Hyper",
2734
- "Meta",
2735
- "NumLock",
2736
- "ScrollLock",
2737
- "Shift",
2738
- "Super",
2739
- "Symbol",
2740
- "SymbolLock",
2741
- "Enter",
2742
- "Tab",
2743
- "ArrowDown",
2744
- "ArrowLeft",
2745
- "ArrowRight",
2746
- "ArrowUp",
2747
- "End",
2748
- "Home",
2749
- "PageDown",
2750
- "PageUp",
2751
- "Backspace",
2752
- "Clear",
2753
- "Copy",
2754
- "CrSel",
2755
- "Cut",
2756
- "Delete",
2757
- "EraseEof",
2758
- "ExSel",
2759
- "Insert",
2760
- "Paste",
2761
- "Redo",
2762
- "Undo",
2763
- "Accept",
2764
- "Again",
2765
- "Attn",
2766
- "Cancel",
2767
- "ContextMenu",
2768
- "Escape",
2769
- "Execute",
2770
- "Find",
2771
- "Finish",
2772
- "Help",
2773
- "Pause",
2774
- "Play",
2775
- "Props",
2776
- "Select",
2777
- "ZoomIn",
2778
- "ZoomOut",
2779
- "BrightnessDown",
2780
- "BrightnessUp",
2781
- "Eject",
2782
- "LogOff",
2783
- "Power",
2784
- "PowerOff",
2785
- "PrintScreen",
2786
- "Hibernate",
2787
- "Standby",
2788
- "WakeUp",
2789
- "AllCandidates",
2790
- "Alphanumeric",
2791
- "CodeInput",
2792
- "Compose",
2793
- "Convert",
2794
- "Dead",
2795
- "FinalMode",
2796
- "GroupFirst",
2797
- "GroupLast",
2798
- "GroupNext",
2799
- "GroupPrevious",
2800
- "ModeChange",
2801
- "NextCandidate",
2802
- "NonConvert",
2803
- "PreviousCandidate",
2804
- "Process",
2805
- "SingleCandidate",
2806
- "HangulMode",
2807
- "HanjaMode",
2808
- "JunjaMode",
2809
- "Eisu",
2810
- "Hankaku",
2811
- "Hiragana",
2812
- "HiraganaKatakana",
2813
- "KanaMode",
2814
- "KanjiMode",
2815
- "Katakana",
2816
- "Romaji",
2817
- "Zenkaku",
2818
- "ZenkakuHanaku",
2819
- "F1",
2820
- "F2",
2821
- "F3",
2822
- "F4",
2823
- "F5",
2824
- "F6",
2825
- "F7",
2826
- "F8",
2827
- "F9",
2828
- "F10",
2829
- "F11",
2830
- "F12",
2831
- "Soft1",
2832
- "Soft2",
2833
- "Soft3",
2834
- "Soft4",
2835
- "ChannelDown",
2836
- "ChannelUp",
2837
- "Close",
2838
- "MailForward",
2839
- "MailReply",
2840
- "MailSend",
2841
- "MediaFastForward",
2842
- "MediaPause",
2843
- "MediaPlay",
2844
- "MediaPlayPause",
2845
- "MediaRecord",
2846
- "MediaRewind",
2847
- "MediaStop",
2848
- "MediaTrackNext",
2849
- "MediaTrackPrevious",
2850
- "AudioBalanceLeft",
2851
- "AudioBalanceRight",
2852
- "AudioBassBoostDown",
2853
- "AudioBassBoostToggle",
2854
- "AudioBassBoostUp",
2855
- "AudioFaderFront",
2856
- "AudioFaderRear",
2857
- "AudioSurroundModeNext",
2858
- "AudioTrebleDown",
2859
- "AudioTrebleUp",
2860
- "AudioVolumeDown",
2861
- "AudioVolumeMute",
2862
- "AudioVolumeUp",
2863
- "MicrophoneToggle",
2864
- "MicrophoneVolumeDown",
2865
- "MicrophoneVolumeMute",
2866
- "MicrophoneVolumeUp",
2867
- "TV",
2868
- "TV3DMode",
2869
- "TVAntennaCable",
2870
- "TVAudioDescription",
2871
- ];
2872
- function unEscapeString(str) {
2873
- const placeholder = "__NEWLINE__";
2874
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2875
- return str;
2876
- }
2877
2873
  export { StableBrowser };
2878
2874
  //# sourceMappingURL=stable_browser.js.map