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