automation_model 1.0.529-dev → 1.0.529-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.
@@ -10,7 +10,7 @@ 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, _getDataFile, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
@@ -19,7 +19,8 @@ import { locate_element } from "./locate_element.js";
19
19
  import { randomUUID } from "crypto";
20
20
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
23
24
  CLICK: "click_element",
24
25
  NAVIGATE: "navigate",
25
26
  FILL: "fill_element",
@@ -30,6 +31,8 @@ const Types = {
30
31
  GET_PAGE_STATUS: "get_page_status",
31
32
  CLICK_ROW_ACTION: "click_row_action",
32
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",
33
36
  ANALYZE_TABLE: "analyze_table",
34
37
  SELECT: "select_combobox",
35
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -46,8 +49,13 @@ const Types = {
46
49
  LOAD_DATA: "load_data",
47
50
  SET_INPUT: "set_input",
48
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",
49
54
  };
50
55
  export const apps = {};
56
+ const formatElementName = (elementName) => {
57
+ return elementName ? JSON.stringify(elementName) : "element";
58
+ };
51
59
  class StableBrowser {
52
60
  browser;
53
61
  page;
@@ -60,7 +68,6 @@ class StableBrowser {
60
68
  configuration = null;
61
69
  appName = "main";
62
70
  tags = null;
63
- isRecording = false;
64
71
  constructor(browser, page, logger = null, context = null, world = null) {
65
72
  this.browser = browser;
66
73
  this.page = page;
@@ -91,35 +98,17 @@ class StableBrowser {
91
98
  catch (e) {
92
99
  this.logger.error("unable to read ai_config.json");
93
100
  }
94
- context.pageLoading = { status: false };
95
- context.pages = [this.page];
96
101
  const logFolder = path.join(this.project_path, "logs", "web");
97
102
  this.world = world;
103
+ context.pages = [this.page];
104
+ context.pageLoading = { status: false };
98
105
  this.registerEventListeners(this.context);
99
106
  registerNetworkEvents(this.world, this, this.context, this.page);
100
107
  registerDownloadEvent(this.page, this.world, this.context);
101
108
  }
102
- async scrollPageToLoadLazyElements() {
103
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
104
- let retry = 0;
105
- while (true) {
106
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
107
- await new Promise((resolve) => setTimeout(resolve, 1000));
108
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
109
- if (newHeight === lastHeight) {
110
- break;
111
- }
112
- lastHeight = newHeight;
113
- retry++;
114
- if (retry > 10) {
115
- break;
116
- }
117
- }
118
- await this.page.evaluate(() => window.scrollTo(0, 0));
119
- }
120
109
  registerEventListeners(context) {
121
110
  this.registerConsoleLogListener(this.page, context);
122
- this.registerRequestListener(this.page, context, this.webLogFile);
111
+ // this.registerRequestListener(this.page, context, this.webLogFile);
123
112
  if (!context.pageLoading) {
124
113
  context.pageLoading = { status: false };
125
114
  }
@@ -164,7 +153,7 @@ class StableBrowser {
164
153
  if (this.appName === appName) {
165
154
  return;
166
155
  }
167
- let newContextCreated = false;
156
+ let navigate = false;
168
157
  if (!apps[appName]) {
169
158
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
170
159
  newContextCreated = true;
@@ -175,32 +164,15 @@ class StableBrowser {
175
164
  };
176
165
  }
177
166
  const tempContext = {};
178
- this._copyContext(this, tempContext);
179
- this._copyContext(apps[appName], this);
167
+ _copyContext(this, tempContext);
168
+ _copyContext(apps[appName], this);
180
169
  apps[this.appName] = tempContext;
181
170
  this.appName = appName;
182
- if (newContextCreated) {
183
- this.registerEventListeners(this.context);
171
+ if (navigate) {
184
172
  await this.goto(this.context.environment.baseUrl);
185
173
  await this.waitForPageLoad();
186
174
  }
187
175
  }
188
- _copyContext(from, to) {
189
- to.browser = from.browser;
190
- to.page = from.page;
191
- to.context = from.context;
192
- }
193
- getWebLogFile(logFolder) {
194
- if (!fs.existsSync(logFolder)) {
195
- fs.mkdirSync(logFolder, { recursive: true });
196
- }
197
- let nextIndex = 1;
198
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
199
- nextIndex++;
200
- }
201
- const fileName = nextIndex + ".json";
202
- return path.join(logFolder, fileName);
203
- }
204
176
  registerConsoleLogListener(page, context) {
205
177
  if (!this.context.webLogger) {
206
178
  this.context.webLogger = [];
@@ -264,55 +236,48 @@ class StableBrowser {
264
236
  // async closeUnexpectedPopups() {
265
237
  // await closeUnexpectedPopups(this.page);
266
238
  // }
267
- async goto(url) {
239
+ async goto(url, world = null) {
268
240
  if (!url.startsWith("http")) {
269
241
  url = "https://" + url;
270
242
  }
271
- await this.page.goto(url, {
272
- timeout: 60000,
273
- });
274
- }
275
- _fixUsingParams(text, _params) {
276
- if (!_params || typeof text !== "string") {
277
- return text;
243
+ const state = {
244
+ value: url,
245
+ world: world,
246
+ type: Types.NAVIGATE,
247
+ text: `Navigate Page to: ${url}`,
248
+ operation: "goto",
249
+ log: "***** navigate page to " + url + " *****\n",
250
+ info: {},
251
+ locate: false,
252
+ scroll: false,
253
+ screenshot: false,
254
+ highlight: false,
255
+ };
256
+ try {
257
+ await _preCommand(state, this);
258
+ await this.page.goto(url, {
259
+ timeout: 60000,
260
+ });
261
+ await _screenshot(state, this);
278
262
  }
279
- for (let key in _params) {
280
- let regValue = key;
281
- if (key.startsWith("_")) {
282
- // remove the _ prefix
283
- regValue = key.substring(1);
284
- }
285
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
263
+ catch (error) {
264
+ console.error("Error on goto", error);
265
+ _commandError(state, error, this);
286
266
  }
287
- return text;
288
- }
289
- _fixLocatorUsingParams(locator, _params) {
290
- // check if not null
291
- if (!locator) {
292
- return locator;
267
+ finally {
268
+ _commandFinally(state, this);
293
269
  }
294
- // clone the locator
295
- locator = JSON.parse(JSON.stringify(locator));
296
- this.scanAndManipulate(locator, _params);
297
- return locator;
298
- }
299
- _isObject(value) {
300
- return value && typeof value === "object" && value.constructor === Object;
301
270
  }
302
- scanAndManipulate(currentObj, _params) {
303
- for (const key in currentObj) {
304
- if (typeof currentObj[key] === "string") {
305
- // Perform string manipulation
306
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
307
- }
308
- else if (this._isObject(currentObj[key])) {
309
- // Recursively scan nested objects
310
- this.scanAndManipulate(currentObj[key], _params);
271
+ async _getLocator(locator, scope, _params) {
272
+ locator = _fixLocatorUsingParams(locator, _params);
273
+ // locator = await this._replaceWithLocalData(locator);
274
+ for (let key in locator) {
275
+ if (typeof locator[key] !== "string")
276
+ continue;
277
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
278
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
311
279
  }
312
280
  }
313
- }
314
- _getLocator(locator, scope, _params) {
315
- locator = this._fixLocatorUsingParams(locator, _params);
316
281
  let locatorReturn;
317
282
  if (locator.role) {
318
283
  if (locator.role[1].nameReg) {
@@ -320,7 +285,7 @@ class StableBrowser {
320
285
  delete locator.role[1].nameReg;
321
286
  }
322
287
  // if (locator.role[1].name) {
323
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
288
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
324
289
  // }
325
290
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
326
291
  }
@@ -363,140 +328,54 @@ class StableBrowser {
363
328
  if (css && css.locator) {
364
329
  css = css.locator;
365
330
  }
366
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
331
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
367
332
  if (result.elementCount === 0) {
368
333
  return;
369
334
  }
370
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
335
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
371
336
  // css climb to parent element
372
337
  const climbArray = [];
373
338
  for (let i = 0; i < climb; i++) {
374
339
  climbArray.push("..");
375
340
  }
376
341
  let climbXpath = "xpath=" + climbArray.join("/");
377
- return textElementCss + " >> " + climbXpath + " >> " + css;
378
- }
379
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
380
- //const stringifyText = JSON.stringify(text);
381
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
382
- function isParent(parent, child) {
383
- let currentNode = child.parentNode;
384
- while (currentNode !== null) {
385
- if (currentNode === parent) {
386
- return true;
387
- }
388
- currentNode = currentNode.parentNode;
389
- }
390
- return false;
391
- }
392
- document.isParent = isParent;
393
- function getRegex(str) {
394
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
395
- if (!match) {
396
- return null;
397
- }
398
- let [_, pattern, flags] = match;
399
- return new RegExp(pattern, flags);
400
- }
401
- document.getRegex = getRegex;
402
- function collectAllShadowDomElements(element, result = []) {
403
- // Check and add the element if it has a shadow root
404
- if (element.shadowRoot) {
405
- result.push(element);
406
- // Also search within the shadow root
407
- document.collectAllShadowDomElements(element.shadowRoot, result);
408
- }
409
- // Iterate over child nodes
410
- element.childNodes.forEach((child) => {
411
- // Recursively call the function for each child node
412
- document.collectAllShadowDomElements(child, result);
413
- });
414
- return result;
415
- }
416
- document.collectAllShadowDomElements = collectAllShadowDomElements;
417
- if (!tag) {
418
- tag = "*:not(script, style, head)";
419
- }
420
- let regexpSearch = document.getRegex(text);
421
- if (regexpSearch) {
422
- regex = true;
423
- }
424
- let elements = Array.from(document.querySelectorAll(tag));
425
- let shadowHosts = [];
426
- document.collectAllShadowDomElements(document, shadowHosts);
427
- for (let i = 0; i < shadowHosts.length; i++) {
428
- let shadowElement = shadowHosts[i].shadowRoot;
429
- if (!shadowElement) {
430
- console.log("shadowElement is null, for host " + shadowHosts[i]);
431
- continue;
432
- }
433
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
434
- elements = elements.concat(shadowElements);
435
- }
436
- let randomToken = null;
437
- const foundElements = [];
438
- if (regex) {
439
- if (!regexpSearch) {
440
- regexpSearch = new RegExp(text, "im");
441
- }
442
- for (let i = 0; i < elements.length; i++) {
443
- const element = elements[i];
444
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
445
- (element.value && regexpSearch.test(element.value))) {
446
- foundElements.push(element);
447
- }
448
- }
449
- }
450
- else {
451
- text = text.trim();
452
- for (let i = 0; i < elements.length; i++) {
453
- const element = elements[i];
454
- if (partial) {
455
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
456
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
457
- foundElements.push(element);
458
- }
459
- }
460
- else {
461
- if ((element.innerText && element.innerText.trim() === text) ||
462
- (element.value && element.value === text)) {
463
- foundElements.push(element);
464
- }
465
- }
466
- }
467
- }
468
- let noChildElements = [];
469
- for (let i = 0; i < foundElements.length; i++) {
470
- let element = foundElements[i];
471
- let hasChild = false;
472
- for (let j = 0; j < foundElements.length; j++) {
473
- if (i === j) {
474
- continue;
475
- }
476
- if (isParent(element, foundElements[j])) {
477
- hasChild = true;
478
- break;
342
+ let resultCss = textElementCss + " >> " + climbXpath;
343
+ if (css) {
344
+ resultCss = resultCss + " >> " + css;
345
+ }
346
+ return resultCss;
347
+ }
348
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
349
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
350
+ const locator = scope.locator(query);
351
+ const count = await locator.count();
352
+ if (!tag1) {
353
+ tag1 = "*";
354
+ }
355
+ const randomToken = Math.random().toString(36).substring(7);
356
+ let tagCount = 0;
357
+ for (let i = 0; i < count; i++) {
358
+ const element = locator.nth(i);
359
+ // check if the tag matches
360
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
361
+ if (!tag.startsWith("*")) {
362
+ if (el.tagName.toLowerCase() !== tag) {
363
+ return false;
479
364
  }
480
365
  }
481
- if (!hasChild) {
482
- noChildElements.push(element);
366
+ if (!el.setAttribute) {
367
+ el = el.parentElement;
483
368
  }
369
+ el.setAttribute("data-blinq-id-" + randomToken, "");
370
+ return true;
371
+ }, [tag1, randomToken]))) {
372
+ continue;
484
373
  }
485
- let elementCount = 0;
486
- if (noChildElements.length > 0) {
487
- for (let i = 0; i < noChildElements.length; i++) {
488
- if (randomToken === null) {
489
- randomToken = Math.random().toString(36).substring(7);
490
- }
491
- let element = noChildElements[i];
492
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
493
- elementCount++;
494
- }
495
- }
496
- return { elementCount: elementCount, randomToken: randomToken };
497
- }, [text1, tag1, regex1, partial1]);
374
+ tagCount++;
375
+ }
376
+ return { elementCount: tagCount, randomToken };
498
377
  }
499
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
378
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
500
379
  if (!info) {
501
380
  info = {};
502
381
  }
@@ -505,10 +384,13 @@ class StableBrowser {
505
384
  }
506
385
  if (!info.log) {
507
386
  info.log = "";
387
+ info.locatorLog = new LocatorLog(selectorHierarchy);
508
388
  }
509
389
  let locatorSearch = selectorHierarchy[index];
390
+ let originalLocatorSearch = "";
510
391
  try {
511
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
392
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
393
+ locatorSearch = JSON.parse(originalLocatorSearch);
512
394
  }
513
395
  catch (e) {
514
396
  console.error(e);
@@ -516,30 +398,31 @@ class StableBrowser {
516
398
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
517
399
  let locator = null;
518
400
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
519
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
401
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
402
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
520
403
  if (!locatorString) {
521
404
  info.failCause.textNotFound = true;
522
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
405
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
523
406
  return;
524
407
  }
525
- locator = this._getLocator({ css: locatorString }, scope, _params);
408
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
526
409
  }
527
410
  else if (locatorSearch.text) {
528
- let text = this._fixUsingParams(locatorSearch.text, _params);
529
- let result = await this._locateElementByText(scope, text, 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, true, _params);
530
413
  if (result.elementCount === 0) {
531
414
  info.failCause.textNotFound = true;
532
- info.failCause.lastError = "failed to locate element by text: " + text;
415
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
533
416
  return;
534
417
  }
535
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
418
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
536
419
  if (locatorSearch.childCss) {
537
420
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
538
421
  }
539
- locator = this._getLocator(locatorSearch, scope, _params);
422
+ locator = await this._getLocator(locatorSearch, scope, _params);
540
423
  }
541
424
  else {
542
- locator = this._getLocator(locatorSearch, scope, _params);
425
+ locator = await this._getLocator(locatorSearch, scope, _params);
543
426
  }
544
427
  // let cssHref = false;
545
428
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -552,18 +435,27 @@ class StableBrowser {
552
435
  //info.log += "total elements found " + count + "\n";
553
436
  //let visibleCount = 0;
554
437
  let visibleLocator = null;
555
- if (locatorSearch.index && locatorSearch.index < count) {
438
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
556
439
  foundLocators.push(locator.nth(locatorSearch.index));
440
+ if (info.locatorLog) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
442
+ }
557
443
  return;
558
444
  }
445
+ if (info.locatorLog && count === 0) {
446
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
447
+ }
559
448
  for (let j = 0; j < count; j++) {
560
449
  let visible = await locator.nth(j).isVisible();
561
450
  const enabled = await locator.nth(j).isEnabled();
562
451
  if (!visibleOnly) {
563
452
  visible = true;
564
453
  }
565
- if (visible && enabled) {
454
+ if (visible && (allowDisabled || enabled)) {
566
455
  foundLocators.push(locator.nth(j));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
567
459
  }
568
460
  else {
569
461
  info.failCause.visible = visible;
@@ -571,8 +463,16 @@ class StableBrowser {
571
463
  if (!info.printMessages) {
572
464
  info.printMessages = {};
573
465
  }
466
+ if (info.locatorLog && !visible) {
467
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
468
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
469
+ }
470
+ if (info.locatorLog && !enabled) {
471
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
472
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
473
+ }
574
474
  if (!info.printMessages[j.toString()]) {
575
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
475
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
576
476
  info.printMessages[j.toString()] = true;
577
477
  }
578
478
  }
@@ -588,7 +488,7 @@ class StableBrowser {
588
488
  if (!info) {
589
489
  info = {};
590
490
  }
591
- info.log += "scan for popup handlers" + "\n";
491
+ //info.log += "scan for popup handlers" + "\n";
592
492
  const handlerGroup = [];
593
493
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
594
494
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -615,16 +515,28 @@ class StableBrowser {
615
515
  }
616
516
  if (result.foundElements.length > 0) {
617
517
  let dialogCloseLocator = result.foundElements[0].locator;
618
- await dialogCloseLocator.click();
619
- // wait for the dialog to close
620
- await dialogCloseLocator.waitFor({ state: "hidden" });
518
+ try {
519
+ await scope?.evaluate(() => {
520
+ window.__isClosingPopups = true;
521
+ });
522
+ await dialogCloseLocator.click();
523
+ // wait for the dialog to close
524
+ await dialogCloseLocator.waitFor({ state: "hidden" });
525
+ }
526
+ catch (e) {
527
+ }
528
+ finally {
529
+ await scope?.evaluate(() => {
530
+ window.__isClosingPopups = false;
531
+ });
532
+ }
621
533
  return { rerun: true };
622
534
  }
623
535
  }
624
536
  }
625
537
  return { rerun: false };
626
538
  }
627
- async _locate(selectors, info, _params, timeout) {
539
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
628
540
  if (!timeout) {
629
541
  timeout = 30000;
630
542
  }
@@ -634,9 +546,15 @@ class StableBrowser {
634
546
  let selector = selectors.locators[j];
635
547
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
636
548
  }
637
- let element = await this._locate_internal(selectors, info, _params, timeout);
549
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
638
550
  if (!element.rerun) {
639
- return element;
551
+ const randomToken = Math.random().toString(36).substring(7);
552
+ element.evaluate((el, randomToken) => {
553
+ el.setAttribute("data-blinq-id-" + randomToken, "");
554
+ }, randomToken);
555
+ const scope = element._frame ?? element.page();
556
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
557
+ return newSelector;
640
558
  }
641
559
  }
642
560
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -647,6 +565,7 @@ class StableBrowser {
647
565
  info.failCause = {};
648
566
  info.log = "";
649
567
  }
568
+ let startTime = Date.now();
650
569
  let scope = this.page;
651
570
  if (selectors.frame) {
652
571
  return selectors.frame;
@@ -677,9 +596,11 @@ class StableBrowser {
677
596
  }
678
597
  return framescope;
679
598
  };
599
+ let fLocator = null;
680
600
  while (true) {
681
601
  let frameFound = false;
682
602
  if (selectors.nestFrmLoc) {
603
+ fLocator = selectors.nestFrmLoc;
683
604
  scope = await findFrame(selectors.nestFrmLoc, scope);
684
605
  frameFound = true;
685
606
  break;
@@ -688,6 +609,7 @@ class StableBrowser {
688
609
  for (let i = 0; i < selectors.frameLocators.length; i++) {
689
610
  let frameLocator = selectors.frameLocators[i];
690
611
  if (frameLocator.css) {
612
+ fLocator = frameLocator.css;
691
613
  scope = scope.frameLocator(frameLocator.css);
692
614
  frameFound = true;
693
615
  break;
@@ -695,18 +617,25 @@ class StableBrowser {
695
617
  }
696
618
  }
697
619
  if (!frameFound && selectors.iframe_src) {
620
+ fLocator = selectors.iframe_src;
698
621
  scope = this.page.frame({ url: selectors.iframe_src });
699
622
  }
700
623
  if (!scope) {
701
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
702
- if (performance.now() - startTime > timeout) {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
626
+ }
627
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
628
+ if (Date.now() - startTime > timeout) {
703
629
  info.failCause.iframeNotFound = true;
704
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
630
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
705
631
  throw new Error("unable to locate iframe " + selectors.iframe_src);
706
632
  }
707
633
  await new Promise((resolve) => setTimeout(resolve, 1000));
708
634
  }
709
635
  else {
636
+ if (info && info.locatorLog) {
637
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
638
+ }
710
639
  break;
711
640
  }
712
641
  }
@@ -723,16 +652,18 @@ class StableBrowser {
723
652
  return bodyContent;
724
653
  });
725
654
  }
726
- async _locate_internal(selectors, info, _params, timeout = 30000) {
655
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
727
656
  if (!info) {
728
657
  info = {};
729
658
  info.failCause = {};
730
659
  info.log = "";
660
+ info.locatorLog = new LocatorLog(selectors);
731
661
  }
732
662
  let highPriorityTimeout = 5000;
733
663
  let visibleOnlyTimeout = 6000;
734
- let startTime = performance.now();
664
+ let startTime = Date.now();
735
665
  let locatorsCount = 0;
666
+ let lazy_scroll = false;
736
667
  //let arrayMode = Array.isArray(selectors);
737
668
  let scope = await this._findFrameScope(selectors, timeout, info);
738
669
  let selectorsLocators = null;
@@ -770,17 +701,17 @@ class StableBrowser {
770
701
  }
771
702
  // info.log += "scanning locators in priority 1" + "\n";
772
703
  let onlyPriority3 = selectorsLocators[0].priority === 3;
773
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
704
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
774
705
  if (result.foundElements.length === 0) {
775
706
  // info.log += "scanning locators in priority 2" + "\n";
776
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
707
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
777
708
  }
778
709
  if (result.foundElements.length === 0 && onlyPriority3) {
779
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
710
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
780
711
  }
781
712
  else {
782
713
  if (result.foundElements.length === 0 && !highPriorityOnly) {
783
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
714
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
784
715
  }
785
716
  }
786
717
  let foundElements = result.foundElements;
@@ -821,26 +752,38 @@ class StableBrowser {
821
752
  return maxCountElement.locator;
822
753
  }
823
754
  }
824
- if (performance.now() - startTime > timeout) {
755
+ if (Date.now() - startTime > timeout) {
825
756
  break;
826
757
  }
827
- if (performance.now() - startTime > highPriorityTimeout) {
828
- info.log += "high priority timeout, will try all elements" + "\n";
758
+ if (Date.now() - startTime > highPriorityTimeout) {
759
+ //info.log += "high priority timeout, will try all elements" + "\n";
829
760
  highPriorityOnly = false;
761
+ if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
762
+ lazy_scroll = true;
763
+ await scrollPageToLoadLazyElements(this.page);
764
+ }
830
765
  }
831
- if (performance.now() - startTime > visibleOnlyTimeout) {
832
- info.log += "visible only timeout, will try all elements" + "\n";
766
+ if (Date.now() - startTime > visibleOnlyTimeout) {
767
+ //info.log += "visible only timeout, will try all elements" + "\n";
833
768
  visibleOnly = false;
834
769
  }
835
770
  await new Promise((resolve) => setTimeout(resolve, 1000));
836
771
  }
837
772
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
838
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
773
+ // if (info.locatorLog) {
774
+ // const lines = info.locatorLog.toString().split("\n");
775
+ // for (let line of lines) {
776
+ // this.logger.debug(line);
777
+ // }
778
+ // }
779
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
839
780
  info.failCause.locatorNotFound = true;
840
- info.failCause.lastError = "failed to locate unique element";
781
+ if (!info?.failCause?.lastError) {
782
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
783
+ }
841
784
  throw new Error("failed to locate first element no elements found, " + info.log);
842
785
  }
843
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
786
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
844
787
  let foundElements = [];
845
788
  const result = {
846
789
  foundElements: foundElements,
@@ -848,14 +791,15 @@ class StableBrowser {
848
791
  for (let i = 0; i < locatorsGroup.length; i++) {
849
792
  let foundLocators = [];
850
793
  try {
851
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
794
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
852
795
  }
853
796
  catch (e) {
854
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
855
- this.logger.debug(e);
797
+ // this call can fail it the browser is navigating
798
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
799
+ // this.logger.debug(e);
856
800
  foundLocators = [];
857
801
  try {
858
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
802
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
859
803
  }
860
804
  catch (e) {
861
805
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -871,11 +815,27 @@ class StableBrowser {
871
815
  }
872
816
  if (foundLocators.length > 1) {
873
817
  info.failCause.foundMultiple = true;
818
+ if (info.locatorLog) {
819
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
820
+ }
874
821
  }
875
822
  }
876
823
  return result;
877
824
  }
878
825
  async simpleClick(elementDescription, _params, options = {}, world = null) {
826
+ const state = {
827
+ locate: false,
828
+ scroll: false,
829
+ highlight: false,
830
+ _params,
831
+ options,
832
+ world,
833
+ type: Types.CLICK,
834
+ text: "Click element",
835
+ operation: "simpleClick",
836
+ log: "***** click on " + elementDescription + " *****\n",
837
+ };
838
+ _preCommand(state, this);
879
839
  const startTime = Date.now();
880
840
  let timeout = 30000;
881
841
  if (options && options.timeout) {
@@ -900,13 +860,31 @@ class StableBrowser {
900
860
  catch (e) {
901
861
  if (performance.now() - startTime > timeout) {
902
862
  // throw e;
903
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
863
+ try {
864
+ await _commandError(state, "timeout looking for " + elementDescription, this);
865
+ }
866
+ finally {
867
+ _commandFinally(state, this);
868
+ }
904
869
  }
905
870
  }
906
871
  await new Promise((resolve) => setTimeout(resolve, 3000));
907
872
  }
908
873
  }
909
874
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
875
+ const state = {
876
+ locate: false,
877
+ scroll: false,
878
+ highlight: false,
879
+ _params,
880
+ options,
881
+ world,
882
+ type: Types.FILL,
883
+ text: "Fill element",
884
+ operation: "simpleClickType",
885
+ log: "***** click type on " + elementDescription + " *****\n",
886
+ };
887
+ _preCommand(state, this);
910
888
  const startTime = Date.now();
911
889
  let timeout = 30000;
912
890
  if (options && options.timeout) {
@@ -931,7 +909,12 @@ class StableBrowser {
931
909
  catch (e) {
932
910
  if (performance.now() - startTime > timeout) {
933
911
  // throw e;
934
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
912
+ try {
913
+ await _commandError(state, "timeout looking for " + elementDescription, this);
914
+ }
915
+ finally {
916
+ _commandFinally(state, this);
917
+ }
935
918
  }
936
919
  }
937
920
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -950,9 +933,9 @@ class StableBrowser {
950
933
  };
951
934
  try {
952
935
  await _preCommand(state, this);
953
- if (state.options && state.options.context) {
954
- state.selectors.locators[0].text = state.options.context;
955
- }
936
+ // if (state.options && state.options.context) {
937
+ // state.selectors.locators[0].text = state.options.context;
938
+ // }
956
939
  try {
957
940
  await state.element.click();
958
941
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -990,9 +973,15 @@ class StableBrowser {
990
973
  // let element = await this._locate(selectors, info, _params);
991
974
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
992
975
  try {
993
- // await this._highlightElements(element);
976
+ // if (world && world.screenshot && !world.screenshotPath) {
977
+ // console.log(`Highlighting while running from recorder`);
978
+ await this._highlightElements(element);
994
979
  await state.element.setChecked(checked);
995
980
  await new Promise((resolve) => setTimeout(resolve, 1000));
981
+ // await this._unHighlightElements(element);
982
+ // }
983
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
984
+ // await this._unHighlightElements(element);
996
985
  }
997
986
  catch (e) {
998
987
  if (e.message && e.message.includes("did not change its state")) {
@@ -1031,6 +1020,7 @@ class StableBrowser {
1031
1020
  await _preCommand(state, this);
1032
1021
  try {
1033
1022
  await state.element.hover();
1023
+ // await _screenshot(state, this);
1034
1024
  await new Promise((resolve) => setTimeout(resolve, 1000));
1035
1025
  }
1036
1026
  catch (e) {
@@ -1038,6 +1028,7 @@ class StableBrowser {
1038
1028
  state.info.log += "hover failed, will try again" + "\n";
1039
1029
  state.element = await this._locate(selectors, state.info, _params);
1040
1030
  await state.element.hover({ timeout: 10000 });
1031
+ // await _screenshot(state, this);
1041
1032
  await new Promise((resolve) => setTimeout(resolve, 1000));
1042
1033
  }
1043
1034
  await _screenshot(state, this);
@@ -1312,7 +1303,12 @@ class StableBrowser {
1312
1303
  await this.waitForPageLoad();
1313
1304
  }
1314
1305
  else if (enter === false) {
1315
- await state.element.dispatchEvent("change");
1306
+ try {
1307
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1308
+ }
1309
+ catch (e) {
1310
+ // ignore
1311
+ }
1316
1312
  //await this.page.keyboard.press("Tab");
1317
1313
  }
1318
1314
  else {
@@ -1369,6 +1365,7 @@ class StableBrowser {
1369
1365
  let screenshotPath = null;
1370
1366
  if (!info.log) {
1371
1367
  info.log = "";
1368
+ info.locatorLog = new LocatorLog(selectors);
1372
1369
  }
1373
1370
  info.operation = "getText";
1374
1371
  info.selectors = selectors;
@@ -1391,6 +1388,18 @@ class StableBrowser {
1391
1388
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1392
1389
  try {
1393
1390
  await this._highlightElements(element);
1391
+ // if (world && world.screenshot && !world.screenshotPath) {
1392
+ // // console.log(`Highlighting for get text while running from recorder`);
1393
+ // this._highlightElements(element)
1394
+ // .then(async () => {
1395
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1396
+ // this._unhighlightElements(element).then(
1397
+ // () => {}
1398
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1399
+ // );
1400
+ // })
1401
+ // .catch(e);
1402
+ // }
1394
1403
  const elementText = await element.innerText();
1395
1404
  return {
1396
1405
  text: elementText,
@@ -1402,7 +1411,7 @@ class StableBrowser {
1402
1411
  }
1403
1412
  catch (e) {
1404
1413
  //await this.closeUnexpectedPopups();
1405
- this.logger.info("no innerText will use textContent");
1414
+ this.logger.info("no innerText, will use textContent");
1406
1415
  const elementText = await element.textContent();
1407
1416
  return { text: elementText, screenshotId, screenshotPath, value: value };
1408
1417
  }
@@ -1528,22 +1537,6 @@ class StableBrowser {
1528
1537
  _commandFinally(state, this);
1529
1538
  }
1530
1539
  }
1531
- _getDataFile(world = null) {
1532
- let dataFile = null;
1533
- if (world && world.reportFolder) {
1534
- dataFile = path.join(world.reportFolder, "data.json");
1535
- }
1536
- else if (this.reportFolder) {
1537
- dataFile = path.join(this.reportFolder, "data.json");
1538
- }
1539
- else if (this.context && this.context.reportFolder) {
1540
- dataFile = path.join(this.context.reportFolder, "data.json");
1541
- }
1542
- else {
1543
- dataFile = "data.json";
1544
- }
1545
- return dataFile;
1546
- }
1547
1540
  async waitForUserInput(message, world = null) {
1548
1541
  if (!message) {
1549
1542
  message = "# Wait for user input. Press any key to continue";
@@ -1572,7 +1565,7 @@ class StableBrowser {
1572
1565
  return;
1573
1566
  }
1574
1567
  // if data file exists, load it
1575
- const dataFile = this._getDataFile(world);
1568
+ const dataFile = _getDataFile(world, this.context, this);
1576
1569
  let data = this.getTestData(world);
1577
1570
  // merge the testData with the existing data
1578
1571
  Object.assign(data, testData);
@@ -1675,7 +1668,7 @@ class StableBrowser {
1675
1668
  }
1676
1669
  }
1677
1670
  getTestData(world = null) {
1678
- const dataFile = this._getDataFile(world);
1671
+ const dataFile = _getDataFile(world, this.context, this);
1679
1672
  let data = {};
1680
1673
  if (fs.existsSync(dataFile)) {
1681
1674
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1719,15 +1712,15 @@ class StableBrowser {
1719
1712
  // this.logger.info("unable to save screenshot " + screenshotPath);
1720
1713
  // }
1721
1714
  // });
1715
+ result.screenshotId = uuidStr;
1716
+ result.screenshotPath = screenshotPath;
1717
+ if (info && info.box) {
1718
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1719
+ }
1722
1720
  }
1723
1721
  catch (e) {
1724
1722
  this.logger.info("unable to take screenshot, ignored");
1725
1723
  }
1726
- result.screenshotId = uuidStr;
1727
- result.screenshotPath = screenshotPath;
1728
- if (info && info.box) {
1729
- await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1730
- }
1731
1724
  }
1732
1725
  else if (options && options.screenshot) {
1733
1726
  result.screenshotPath = options.screenshotPath;
@@ -1762,6 +1755,15 @@ class StableBrowser {
1762
1755
  document.documentElement.clientWidth,
1763
1756
  ])));
1764
1757
  let screenshotBuffer = null;
1758
+ // if (focusedElement) {
1759
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1760
+ // await this._unhighlightElements(focusedElement);
1761
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1762
+ // console.log(`Unhighlighted previous element`);
1763
+ // }
1764
+ // if (focusedElement) {
1765
+ // await this._highlightElements(focusedElement);
1766
+ // }
1765
1767
  if (this.context.browserName === "chromium") {
1766
1768
  const client = await playContext.newCDPSession(this.page);
1767
1769
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1783,6 +1785,10 @@ class StableBrowser {
1783
1785
  else {
1784
1786
  screenshotBuffer = await this.page.screenshot();
1785
1787
  }
1788
+ // if (focusedElement) {
1789
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1790
+ // await this._unhighlightElements(focusedElement);
1791
+ // }
1786
1792
  let image = await Jimp.read(screenshotBuffer);
1787
1793
  // Get the image dimensions
1788
1794
  const { width, height } = image.bitmap;
@@ -1795,6 +1801,7 @@ class StableBrowser {
1795
1801
  else {
1796
1802
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1797
1803
  }
1804
+ return screenshotBuffer;
1798
1805
  }
1799
1806
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1800
1807
  const state = {
@@ -1832,6 +1839,7 @@ class StableBrowser {
1832
1839
  text: `Extract attribute from element`,
1833
1840
  operation: "extractAttribute",
1834
1841
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1842
+ allowDisabled: true,
1835
1843
  };
1836
1844
  await new Promise((resolve) => setTimeout(resolve, 2000));
1837
1845
  try {
@@ -1853,6 +1861,76 @@ class StableBrowser {
1853
1861
  state.info.value = state.value;
1854
1862
  this.setTestData({ [variable]: state.value }, world);
1855
1863
  this.logger.info("set test data: " + variable + "=" + state.value);
1864
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1865
+ return state.info;
1866
+ }
1867
+ catch (e) {
1868
+ await _commandError(state, e, this);
1869
+ }
1870
+ finally {
1871
+ _commandFinally(state, this);
1872
+ }
1873
+ }
1874
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1875
+ const state = {
1876
+ selectors,
1877
+ _params,
1878
+ attribute,
1879
+ value,
1880
+ options,
1881
+ world,
1882
+ type: Types.VERIFY_ATTRIBUTE,
1883
+ highlight: true,
1884
+ screenshot: true,
1885
+ text: `Verify element attribute`,
1886
+ operation: "verifyAttribute",
1887
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1888
+ allowDisabled: true,
1889
+ };
1890
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1891
+ let val;
1892
+ let expectedValue;
1893
+ try {
1894
+ await _preCommand(state, this);
1895
+ expectedValue = state.value;
1896
+ state.info.expectedValue = expectedValue;
1897
+ switch (attribute) {
1898
+ case "innerText":
1899
+ val = String(await state.element.innerText());
1900
+ break;
1901
+ case "value":
1902
+ val = String(await state.element.inputValue());
1903
+ break;
1904
+ case "checked":
1905
+ val = String(await state.element.isChecked());
1906
+ break;
1907
+ case "disabled":
1908
+ val = String(await state.element.isDisabled());
1909
+ break;
1910
+ case "readOnly":
1911
+ const isEditable = await state.element.isEditable();
1912
+ val = String(!isEditable);
1913
+ break;
1914
+ default:
1915
+ val = String(await state.element.getAttribute(attribute));
1916
+ break;
1917
+ }
1918
+ state.info.value = val;
1919
+ let regex;
1920
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1921
+ const patternBody = expectedValue.slice(1, -1);
1922
+ regex = new RegExp(patternBody, "g");
1923
+ }
1924
+ else {
1925
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1926
+ regex = new RegExp(escapedPattern, "g");
1927
+ }
1928
+ if (!val.match(regex)) {
1929
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1930
+ state.info.failCause.assertionFailed = true;
1931
+ state.info.failCause.lastError = errorMessage;
1932
+ throw new Error(errorMessage);
1933
+ }
1856
1934
  return state.info;
1857
1935
  }
1858
1936
  catch (e) {
@@ -1880,7 +1958,7 @@ class StableBrowser {
1880
1958
  if (options && options.timeout) {
1881
1959
  timeout = options.timeout;
1882
1960
  }
1883
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1961
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1884
1962
  const request = {
1885
1963
  method: "POST",
1886
1964
  url: serviceUrl,
@@ -1951,27 +2029,32 @@ class StableBrowser {
1951
2029
  async _highlightElements(scope, css) {
1952
2030
  try {
1953
2031
  if (!scope) {
2032
+ // console.log(`Scope is not defined`);
1954
2033
  return;
1955
2034
  }
1956
2035
  if (!css) {
1957
2036
  scope
1958
2037
  .evaluate((node) => {
1959
2038
  if (node && node.style) {
1960
- let originalBorder = node.style.border;
1961
- node.style.border = "2px solid red";
2039
+ let originalOutline = node.style.outline;
2040
+ // console.log(`Original outline was: ${originalOutline}`);
2041
+ // node.__previousOutline = originalOutline;
2042
+ node.style.outline = "2px solid red";
2043
+ // console.log(`New outline is: ${node.style.outline}`);
1962
2044
  if (window) {
1963
2045
  window.addEventListener("beforeunload", function (e) {
1964
- node.style.border = originalBorder;
2046
+ node.style.outline = originalOutline;
1965
2047
  });
1966
2048
  }
1967
2049
  setTimeout(function () {
1968
- node.style.border = originalBorder;
2050
+ node.style.outline = originalOutline;
1969
2051
  }, 2000);
1970
2052
  }
1971
2053
  })
1972
2054
  .then(() => { })
1973
2055
  .catch((e) => {
1974
2056
  // ignore
2057
+ // console.error(`Could not highlight node : ${e}`);
1975
2058
  });
1976
2059
  }
1977
2060
  else {
@@ -1987,17 +2070,18 @@ class StableBrowser {
1987
2070
  if (!element.style) {
1988
2071
  return;
1989
2072
  }
1990
- var originalBorder = element.style.border;
2073
+ let originalOutline = element.style.outline;
2074
+ element.__previousOutline = originalOutline;
1991
2075
  // Set the new border to be red and 2px solid
1992
- element.style.border = "2px solid red";
2076
+ element.style.outline = "2px solid red";
1993
2077
  if (window) {
1994
2078
  window.addEventListener("beforeunload", function (e) {
1995
- element.style.border = originalBorder;
2079
+ element.style.outline = originalOutline;
1996
2080
  });
1997
2081
  }
1998
2082
  // Set a timeout to revert to the original border after 2 seconds
1999
2083
  setTimeout(function () {
2000
- element.style.border = originalBorder;
2084
+ element.style.outline = originalOutline;
2001
2085
  }, 2000);
2002
2086
  }
2003
2087
  return;
@@ -2005,6 +2089,7 @@ class StableBrowser {
2005
2089
  .then(() => { })
2006
2090
  .catch((e) => {
2007
2091
  // ignore
2092
+ // console.error(`Could not highlight css: ${e}`);
2008
2093
  });
2009
2094
  }
2010
2095
  }
@@ -2012,6 +2097,54 @@ class StableBrowser {
2012
2097
  console.debug(error);
2013
2098
  }
2014
2099
  }
2100
+ // async _unhighlightElements(scope, css) {
2101
+ // try {
2102
+ // if (!scope) {
2103
+ // return;
2104
+ // }
2105
+ // if (!css) {
2106
+ // scope
2107
+ // .evaluate((node) => {
2108
+ // if (node && node.style) {
2109
+ // if (!node.__previousOutline) {
2110
+ // node.style.outline = "";
2111
+ // } else {
2112
+ // node.style.outline = node.__previousOutline;
2113
+ // }
2114
+ // }
2115
+ // })
2116
+ // .then(() => {})
2117
+ // .catch((e) => {
2118
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2119
+ // });
2120
+ // } else {
2121
+ // scope
2122
+ // .evaluate(([css]) => {
2123
+ // if (!css) {
2124
+ // return;
2125
+ // }
2126
+ // let elements = Array.from(document.querySelectorAll(css));
2127
+ // for (i = 0; i < elements.length; i++) {
2128
+ // let element = elements[i];
2129
+ // if (!element.style) {
2130
+ // return;
2131
+ // }
2132
+ // if (!element.__previousOutline) {
2133
+ // element.style.outline = "";
2134
+ // } else {
2135
+ // element.style.outline = element.__previousOutline;
2136
+ // }
2137
+ // }
2138
+ // })
2139
+ // .then(() => {})
2140
+ // .catch((e) => {
2141
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2142
+ // });
2143
+ // }
2144
+ // } catch (error) {
2145
+ // // console.debug(error);
2146
+ // }
2147
+ // }
2015
2148
  async verifyPagePath(pathPart, options = {}, world = null) {
2016
2149
  const startTime = Date.now();
2017
2150
  let error = null;
@@ -2073,6 +2206,35 @@ class StableBrowser {
2073
2206
  });
2074
2207
  }
2075
2208
  }
2209
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2210
+ const frames = this.page.frames();
2211
+ let results = [];
2212
+ let ignoreCase = false;
2213
+ for (let i = 0; i < frames.length; i++) {
2214
+ if (dateAlternatives.date) {
2215
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2216
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2217
+ result.frame = frames[i];
2218
+ results.push(result);
2219
+ }
2220
+ }
2221
+ else if (numberAlternatives.number) {
2222
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2223
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2224
+ result.frame = frames[i];
2225
+ results.push(result);
2226
+ }
2227
+ }
2228
+ else {
2229
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2230
+ result.frame = frames[i];
2231
+ results.push(result);
2232
+ }
2233
+ }
2234
+ state.info.results = results;
2235
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2236
+ return resultWithElementsFound;
2237
+ }
2076
2238
  async verifyTextExistInPage(text, options = {}, world = null) {
2077
2239
  text = unEscapeString(text);
2078
2240
  const state = {
@@ -2082,7 +2244,7 @@ class StableBrowser {
2082
2244
  locate: false,
2083
2245
  scroll: false,
2084
2246
  highlight: false,
2085
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2247
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2086
2248
  text: `Verify text exists in page`,
2087
2249
  operation: "verifyTextExistInPage",
2088
2250
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2100,31 +2262,7 @@ class StableBrowser {
2100
2262
  await _preCommand(state, this);
2101
2263
  state.info.text = text;
2102
2264
  while (true) {
2103
- const frames = this.page.frames();
2104
- let results = [];
2105
- for (let i = 0; i < frames.length; i++) {
2106
- if (dateAlternatives.date) {
2107
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2108
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2109
- result.frame = frames[i];
2110
- results.push(result);
2111
- }
2112
- }
2113
- else if (numberAlternatives.number) {
2114
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2115
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2116
- result.frame = frames[i];
2117
- results.push(result);
2118
- }
2119
- }
2120
- else {
2121
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2122
- result.frame = frames[i];
2123
- results.push(result);
2124
- }
2125
- }
2126
- state.info.results = results;
2127
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2265
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2128
2266
  if (resultWithElementsFound.length === 0) {
2129
2267
  if (Date.now() - state.startTime > timeout) {
2130
2268
  throw new Error(`Text ${text} not found in page`);
@@ -2134,12 +2272,29 @@ class StableBrowser {
2134
2272
  }
2135
2273
  if (resultWithElementsFound[0].randomToken) {
2136
2274
  const frame = resultWithElementsFound[0].frame;
2137
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2275
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2138
2276
  await this._highlightElements(frame, dataAttribute);
2139
- const element = await frame.$(dataAttribute);
2277
+ // if (world && world.screenshot && !world.screenshotPath) {
2278
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2279
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2280
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2281
+ // this._unhighlightElements(frame, dataAttribute)
2282
+ // .then(async () => {
2283
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2284
+ // })
2285
+ // .catch(
2286
+ // (e) => {}
2287
+ // console.error(e)
2288
+ // );
2289
+ // });
2290
+ // }
2291
+ const element = await frame.locator(dataAttribute).first();
2292
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2293
+ // await this._unhighlightElements(frame, dataAttribute);
2140
2294
  if (element) {
2141
2295
  await this.scrollIfNeeded(element, state.info);
2142
2296
  await element.dispatchEvent("bvt_verify_page_contains_text");
2297
+ // await _screenshot(state, this, element);
2143
2298
  }
2144
2299
  }
2145
2300
  await _screenshot(state, this);
@@ -2181,31 +2336,7 @@ class StableBrowser {
2181
2336
  await _preCommand(state, this);
2182
2337
  state.info.text = text;
2183
2338
  while (true) {
2184
- const frames = this.page.frames();
2185
- let results = [];
2186
- for (let i = 0; i < frames.length; i++) {
2187
- if (dateAlternatives.date) {
2188
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2189
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2190
- result.frame = frames[i];
2191
- results.push(result);
2192
- }
2193
- }
2194
- else if (numberAlternatives.number) {
2195
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2196
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2197
- result.frame = frames[i];
2198
- results.push(result);
2199
- }
2200
- }
2201
- else {
2202
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2203
- result.frame = frames[i];
2204
- results.push(result);
2205
- }
2206
- }
2207
- state.info.results = results;
2208
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2339
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2209
2340
  if (resultWithElementsFound.length === 0) {
2210
2341
  await _screenshot(state, this);
2211
2342
  return state.info;
@@ -2223,15 +2354,102 @@ class StableBrowser {
2223
2354
  _commandFinally(state, this);
2224
2355
  }
2225
2356
  }
2226
- _getServerUrl() {
2227
- let serviceUrl = "https://api.blinq.io";
2228
- if (process.env.NODE_ENV_BLINQ === "dev") {
2229
- serviceUrl = "https://dev.api.blinq.io";
2357
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2358
+ textAnchor = unEscapeString(textAnchor);
2359
+ textToVerify = unEscapeString(textToVerify);
2360
+ const state = {
2361
+ text_search: textToVerify,
2362
+ options,
2363
+ world,
2364
+ locate: false,
2365
+ scroll: false,
2366
+ highlight: false,
2367
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2368
+ text: `Verify text with relation to another text`,
2369
+ operation: "verify_text_with_relation",
2370
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2371
+ };
2372
+ const timeout = this._getLoadTimeout(options);
2373
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2374
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2375
+ if (newValue !== textAnchor) {
2376
+ this.logger.info(textAnchor + "=" + newValue);
2377
+ textAnchor = newValue;
2378
+ }
2379
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2380
+ if (newValue !== textToVerify) {
2381
+ this.logger.info(textToVerify + "=" + newValue);
2382
+ textToVerify = newValue;
2383
+ }
2384
+ let dateAlternatives = findDateAlternatives(textToVerify);
2385
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2386
+ let foundAncore = false;
2387
+ try {
2388
+ await _preCommand(state, this);
2389
+ state.info.text = textToVerify;
2390
+ while (true) {
2391
+ const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2392
+ if (resultWithElementsFound.length === 0) {
2393
+ if (Date.now() - state.startTime > timeout) {
2394
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2395
+ }
2396
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2397
+ continue;
2398
+ }
2399
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2400
+ foundAncore = true;
2401
+ const result = resultWithElementsFound[i];
2402
+ const token = result.randomToken;
2403
+ const frame = result.frame;
2404
+ let css = `[data-blinq-id-${token}]`;
2405
+ const climbArray1 = [];
2406
+ for (let i = 0; i < climb; i++) {
2407
+ climbArray1.push("..");
2408
+ }
2409
+ let climbXpath = "xpath=" + climbArray1.join("/");
2410
+ css = css + " >> " + climbXpath;
2411
+ const count = await frame.locator(css).count();
2412
+ for (let j = 0; j < count; j++) {
2413
+ const continer = await frame.locator(css).nth(j);
2414
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2415
+ if (result.elementCount > 0) {
2416
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2417
+ await this._highlightElements(frame, dataAttribute);
2418
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2419
+ // if (world && world.screenshot && !world.screenshotPath) {
2420
+ // console.log(`Highlighting for vtrt while running from recorder`);
2421
+ // this._highlightElements(frame, dataAttribute)
2422
+ // .then(async () => {
2423
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2424
+ // this._unhighlightElements(frame, dataAttribute).then(
2425
+ // () => {}
2426
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2427
+ // );
2428
+ // })
2429
+ // .catch(e);
2430
+ // }
2431
+ //await this._highlightElements(frame, cssAnchor);
2432
+ const element = await frame.locator(dataAttribute).first();
2433
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2434
+ // await this._unhighlightElements(frame, dataAttribute);
2435
+ if (element) {
2436
+ await this.scrollIfNeeded(element, state.info);
2437
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2438
+ }
2439
+ await _screenshot(state, this);
2440
+ return state.info;
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2230
2446
  }
2231
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2232
- serviceUrl = "https://stage.api.blinq.io";
2447
+ catch (e) {
2448
+ await _commandError(state, e, this);
2449
+ }
2450
+ finally {
2451
+ _commandFinally(state, this);
2233
2452
  }
2234
- return serviceUrl;
2235
2453
  }
2236
2454
  async visualVerification(text, options = {}, world = null) {
2237
2455
  const startTime = Date.now();
@@ -2247,7 +2465,7 @@ class StableBrowser {
2247
2465
  throw new Error("TOKEN is not set");
2248
2466
  }
2249
2467
  try {
2250
- let serviceUrl = this._getServerUrl();
2468
+ let serviceUrl = _getServerUrl();
2251
2469
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2252
2470
  info.screenshotPath = screenshotPath;
2253
2471
  const screenshot = await this.takeScreenshot();
@@ -2336,6 +2554,7 @@ class StableBrowser {
2336
2554
  let screenshotPath = null;
2337
2555
  const info = {};
2338
2556
  info.log = "";
2557
+ info.locatorLog = new LocatorLog(selectors);
2339
2558
  info.operation = "getTableData";
2340
2559
  info.selectors = selectors;
2341
2560
  try {
@@ -2411,7 +2630,7 @@ class StableBrowser {
2411
2630
  info.operation = "analyzeTable";
2412
2631
  info.selectors = selectors;
2413
2632
  info.query = query;
2414
- query = this._fixUsingParams(query, _params);
2633
+ query = _fixUsingParams(query, _params);
2415
2634
  info.query_fixed = query;
2416
2635
  info.operator = operator;
2417
2636
  info.value = value;
@@ -2556,6 +2775,17 @@ class StableBrowser {
2556
2775
  }
2557
2776
  return timeout;
2558
2777
  }
2778
+ async saveStoreState(path = null, world = null) {
2779
+ const storageState = await this.page.context().storageState();
2780
+ //const testDataFile = _getDataFile(world, this.context, this);
2781
+ if (path) {
2782
+ // save { storageState: storageState } into the path
2783
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2784
+ }
2785
+ else {
2786
+ await this.setTestData({ storageState: storageState }, world);
2787
+ }
2788
+ }
2559
2789
  async waitForPageLoad(options = {}, world = null) {
2560
2790
  let timeout = this._getLoadTimeout(options);
2561
2791
  const promiseArray = [];
@@ -2578,9 +2808,6 @@ class StableBrowser {
2578
2808
  let screenshotPath = null;
2579
2809
  try {
2580
2810
  await Promise.all(promiseArray);
2581
- if (this.isRecording === false && this.configuration.load_all_lazy === true) {
2582
- await this.scrollPageToLoadLazyElements();
2583
- }
2584
2811
  }
2585
2812
  catch (e) {
2586
2813
  if (e.label === "networkidle") {
@@ -2744,14 +2971,22 @@ class StableBrowser {
2744
2971
  }
2745
2972
  }
2746
2973
  async beforeStep(world, step) {
2747
- this.stepName = step.pickleStep.text;
2748
- this.logger.info("step: " + this.stepName);
2749
2974
  if (this.stepIndex === undefined) {
2750
2975
  this.stepIndex = 0;
2751
2976
  }
2752
2977
  else {
2753
2978
  this.stepIndex++;
2754
2979
  }
2980
+ if (step && step.pickleStep && step.pickleStep.text) {
2981
+ this.stepName = step.pickleStep.text;
2982
+ this.logger.info("step: " + this.stepName);
2983
+ }
2984
+ else if (step && step.text) {
2985
+ this.stepName = step.text;
2986
+ }
2987
+ else {
2988
+ this.stepName = "step " + this.stepIndex;
2989
+ }
2755
2990
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2756
2991
  if (this.context.browserObject.context) {
2757
2992
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2781,156 +3016,5 @@ function createTimedPromise(promise, label) {
2781
3016
  .then((result) => ({ status: "fulfilled", label, result }))
2782
3017
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2783
3018
  }
2784
- const KEYBOARD_EVENTS = [
2785
- "ALT",
2786
- "AltGraph",
2787
- "CapsLock",
2788
- "Control",
2789
- "Fn",
2790
- "FnLock",
2791
- "Hyper",
2792
- "Meta",
2793
- "NumLock",
2794
- "ScrollLock",
2795
- "Shift",
2796
- "Super",
2797
- "Symbol",
2798
- "SymbolLock",
2799
- "Enter",
2800
- "Tab",
2801
- "ArrowDown",
2802
- "ArrowLeft",
2803
- "ArrowRight",
2804
- "ArrowUp",
2805
- "End",
2806
- "Home",
2807
- "PageDown",
2808
- "PageUp",
2809
- "Backspace",
2810
- "Clear",
2811
- "Copy",
2812
- "CrSel",
2813
- "Cut",
2814
- "Delete",
2815
- "EraseEof",
2816
- "ExSel",
2817
- "Insert",
2818
- "Paste",
2819
- "Redo",
2820
- "Undo",
2821
- "Accept",
2822
- "Again",
2823
- "Attn",
2824
- "Cancel",
2825
- "ContextMenu",
2826
- "Escape",
2827
- "Execute",
2828
- "Find",
2829
- "Finish",
2830
- "Help",
2831
- "Pause",
2832
- "Play",
2833
- "Props",
2834
- "Select",
2835
- "ZoomIn",
2836
- "ZoomOut",
2837
- "BrightnessDown",
2838
- "BrightnessUp",
2839
- "Eject",
2840
- "LogOff",
2841
- "Power",
2842
- "PowerOff",
2843
- "PrintScreen",
2844
- "Hibernate",
2845
- "Standby",
2846
- "WakeUp",
2847
- "AllCandidates",
2848
- "Alphanumeric",
2849
- "CodeInput",
2850
- "Compose",
2851
- "Convert",
2852
- "Dead",
2853
- "FinalMode",
2854
- "GroupFirst",
2855
- "GroupLast",
2856
- "GroupNext",
2857
- "GroupPrevious",
2858
- "ModeChange",
2859
- "NextCandidate",
2860
- "NonConvert",
2861
- "PreviousCandidate",
2862
- "Process",
2863
- "SingleCandidate",
2864
- "HangulMode",
2865
- "HanjaMode",
2866
- "JunjaMode",
2867
- "Eisu",
2868
- "Hankaku",
2869
- "Hiragana",
2870
- "HiraganaKatakana",
2871
- "KanaMode",
2872
- "KanjiMode",
2873
- "Katakana",
2874
- "Romaji",
2875
- "Zenkaku",
2876
- "ZenkakuHanaku",
2877
- "F1",
2878
- "F2",
2879
- "F3",
2880
- "F4",
2881
- "F5",
2882
- "F6",
2883
- "F7",
2884
- "F8",
2885
- "F9",
2886
- "F10",
2887
- "F11",
2888
- "F12",
2889
- "Soft1",
2890
- "Soft2",
2891
- "Soft3",
2892
- "Soft4",
2893
- "ChannelDown",
2894
- "ChannelUp",
2895
- "Close",
2896
- "MailForward",
2897
- "MailReply",
2898
- "MailSend",
2899
- "MediaFastForward",
2900
- "MediaPause",
2901
- "MediaPlay",
2902
- "MediaPlayPause",
2903
- "MediaRecord",
2904
- "MediaRewind",
2905
- "MediaStop",
2906
- "MediaTrackNext",
2907
- "MediaTrackPrevious",
2908
- "AudioBalanceLeft",
2909
- "AudioBalanceRight",
2910
- "AudioBassBoostDown",
2911
- "AudioBassBoostToggle",
2912
- "AudioBassBoostUp",
2913
- "AudioFaderFront",
2914
- "AudioFaderRear",
2915
- "AudioSurroundModeNext",
2916
- "AudioTrebleDown",
2917
- "AudioTrebleUp",
2918
- "AudioVolumeDown",
2919
- "AudioVolumeMute",
2920
- "AudioVolumeUp",
2921
- "MicrophoneToggle",
2922
- "MicrophoneVolumeDown",
2923
- "MicrophoneVolumeMute",
2924
- "MicrophoneVolumeUp",
2925
- "TV",
2926
- "TV3DMode",
2927
- "TVAntennaCable",
2928
- "TVAudioDescription",
2929
- ];
2930
- function unEscapeString(str) {
2931
- const placeholder = "__NEWLINE__";
2932
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2933
- return str;
2934
- }
2935
3019
  export { StableBrowser };
2936
3020
  //# sourceMappingURL=stable_browser.js.map