automation_model 1.0.533-dev → 1.0.533-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
- }
299
- _isObject(value) {
300
- return value && typeof value === "object" && value.constructor === Object;
301
271
  }
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,12 +489,12 @@ 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);
595
496
  }
596
- const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
497
+ const scopes = [this.page, ...this.page.frames()];
597
498
  let result = null;
598
499
  let scope = null;
599
500
  for (let i = 0; i < scopes.length; i++) {
@@ -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,18 @@ 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
+ if (element._frame) {
557
+ return element;
558
+ }
559
+ const scope = element.page();
560
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
561
+ return newSelector;
640
562
  }
641
563
  }
642
564
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -647,6 +569,7 @@ class StableBrowser {
647
569
  info.failCause = {};
648
570
  info.log = "";
649
571
  }
572
+ let startTime = Date.now();
650
573
  let scope = this.page;
651
574
  if (selectors.frame) {
652
575
  return selectors.frame;
@@ -677,9 +600,11 @@ class StableBrowser {
677
600
  }
678
601
  return framescope;
679
602
  };
603
+ let fLocator = null;
680
604
  while (true) {
681
605
  let frameFound = false;
682
606
  if (selectors.nestFrmLoc) {
607
+ fLocator = selectors.nestFrmLoc;
683
608
  scope = await findFrame(selectors.nestFrmLoc, scope);
684
609
  frameFound = true;
685
610
  break;
@@ -688,6 +613,7 @@ class StableBrowser {
688
613
  for (let i = 0; i < selectors.frameLocators.length; i++) {
689
614
  let frameLocator = selectors.frameLocators[i];
690
615
  if (frameLocator.css) {
616
+ fLocator = frameLocator.css;
691
617
  scope = scope.frameLocator(frameLocator.css);
692
618
  frameFound = true;
693
619
  break;
@@ -695,18 +621,25 @@ class StableBrowser {
695
621
  }
696
622
  }
697
623
  if (!frameFound && selectors.iframe_src) {
624
+ fLocator = selectors.iframe_src;
698
625
  scope = this.page.frame({ url: selectors.iframe_src });
699
626
  }
700
627
  if (!scope) {
701
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
702
- if (performance.now() - startTime > timeout) {
628
+ if (info && info.locatorLog) {
629
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
630
+ }
631
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
632
+ if (Date.now() - startTime > timeout) {
703
633
  info.failCause.iframeNotFound = true;
704
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
634
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
705
635
  throw new Error("unable to locate iframe " + selectors.iframe_src);
706
636
  }
707
637
  await new Promise((resolve) => setTimeout(resolve, 1000));
708
638
  }
709
639
  else {
640
+ if (info && info.locatorLog) {
641
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
642
+ }
710
643
  break;
711
644
  }
712
645
  }
@@ -723,15 +656,16 @@ class StableBrowser {
723
656
  return bodyContent;
724
657
  });
725
658
  }
726
- async _locate_internal(selectors, info, _params, timeout = 30000) {
659
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
727
660
  if (!info) {
728
661
  info = {};
729
662
  info.failCause = {};
730
663
  info.log = "";
664
+ info.locatorLog = new LocatorLog(selectors);
731
665
  }
732
666
  let highPriorityTimeout = 5000;
733
667
  let visibleOnlyTimeout = 6000;
734
- let startTime = performance.now();
668
+ let startTime = Date.now();
735
669
  let locatorsCount = 0;
736
670
  let lazy_scroll = false;
737
671
  //let arrayMode = Array.isArray(selectors);
@@ -771,17 +705,17 @@ class StableBrowser {
771
705
  }
772
706
  // info.log += "scanning locators in priority 1" + "\n";
773
707
  let onlyPriority3 = selectorsLocators[0].priority === 3;
774
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
708
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
775
709
  if (result.foundElements.length === 0) {
776
710
  // info.log += "scanning locators in priority 2" + "\n";
777
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
711
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
778
712
  }
779
713
  if (result.foundElements.length === 0 && onlyPriority3) {
780
- 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);
781
715
  }
782
716
  else {
783
717
  if (result.foundElements.length === 0 && !highPriorityOnly) {
784
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
718
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
785
719
  }
786
720
  }
787
721
  let foundElements = result.foundElements;
@@ -822,30 +756,38 @@ class StableBrowser {
822
756
  return maxCountElement.locator;
823
757
  }
824
758
  }
825
- if (performance.now() - startTime > timeout) {
759
+ if (Date.now() - startTime > timeout) {
826
760
  break;
827
761
  }
828
- if (performance.now() - startTime > highPriorityTimeout) {
829
- info.log += "high priority timeout, will try all elements" + "\n";
762
+ if (Date.now() - startTime > highPriorityTimeout) {
763
+ //info.log += "high priority timeout, will try all elements" + "\n";
830
764
  highPriorityOnly = false;
831
765
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
832
766
  lazy_scroll = true;
833
- await this.scrollPageToLoadLazyElements();
767
+ await scrollPageToLoadLazyElements(this.page);
834
768
  }
835
769
  }
836
- if (performance.now() - startTime > visibleOnlyTimeout) {
837
- info.log += "visible only timeout, will try all elements" + "\n";
770
+ if (Date.now() - startTime > visibleOnlyTimeout) {
771
+ //info.log += "visible only timeout, will try all elements" + "\n";
838
772
  visibleOnly = false;
839
773
  }
840
774
  await new Promise((resolve) => setTimeout(resolve, 1000));
841
775
  }
842
776
  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";
777
+ // if (info.locatorLog) {
778
+ // const lines = info.locatorLog.toString().split("\n");
779
+ // for (let line of lines) {
780
+ // this.logger.debug(line);
781
+ // }
782
+ // }
783
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
844
784
  info.failCause.locatorNotFound = true;
845
- info.failCause.lastError = "failed to locate unique element";
785
+ if (!info?.failCause?.lastError) {
786
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
787
+ }
846
788
  throw new Error("failed to locate first element no elements found, " + info.log);
847
789
  }
848
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
790
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
849
791
  let foundElements = [];
850
792
  const result = {
851
793
  foundElements: foundElements,
@@ -853,14 +795,15 @@ class StableBrowser {
853
795
  for (let i = 0; i < locatorsGroup.length; i++) {
854
796
  let foundLocators = [];
855
797
  try {
856
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
798
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
857
799
  }
858
800
  catch (e) {
859
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
860
- this.logger.debug(e);
801
+ // this call can fail it the browser is navigating
802
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
803
+ // this.logger.debug(e);
861
804
  foundLocators = [];
862
805
  try {
863
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
806
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
864
807
  }
865
808
  catch (e) {
866
809
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -876,11 +819,27 @@ class StableBrowser {
876
819
  }
877
820
  if (foundLocators.length > 1) {
878
821
  info.failCause.foundMultiple = true;
822
+ if (info.locatorLog) {
823
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
824
+ }
879
825
  }
880
826
  }
881
827
  return result;
882
828
  }
883
829
  async simpleClick(elementDescription, _params, options = {}, world = null) {
830
+ const state = {
831
+ locate: false,
832
+ scroll: false,
833
+ highlight: false,
834
+ _params,
835
+ options,
836
+ world,
837
+ type: Types.CLICK,
838
+ text: "Click element",
839
+ operation: "simpleClick",
840
+ log: "***** click on " + elementDescription + " *****\n",
841
+ };
842
+ _preCommand(state, this);
884
843
  const startTime = Date.now();
885
844
  let timeout = 30000;
886
845
  if (options && options.timeout) {
@@ -905,13 +864,31 @@ class StableBrowser {
905
864
  catch (e) {
906
865
  if (performance.now() - startTime > timeout) {
907
866
  // throw e;
908
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
867
+ try {
868
+ await _commandError(state, "timeout looking for " + elementDescription, this);
869
+ }
870
+ finally {
871
+ _commandFinally(state, this);
872
+ }
909
873
  }
910
874
  }
911
875
  await new Promise((resolve) => setTimeout(resolve, 3000));
912
876
  }
913
877
  }
914
878
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
879
+ const state = {
880
+ locate: false,
881
+ scroll: false,
882
+ highlight: false,
883
+ _params,
884
+ options,
885
+ world,
886
+ type: Types.FILL,
887
+ text: "Fill element",
888
+ operation: "simpleClickType",
889
+ log: "***** click type on " + elementDescription + " *****\n",
890
+ };
891
+ _preCommand(state, this);
915
892
  const startTime = Date.now();
916
893
  let timeout = 30000;
917
894
  if (options && options.timeout) {
@@ -936,7 +913,12 @@ class StableBrowser {
936
913
  catch (e) {
937
914
  if (performance.now() - startTime > timeout) {
938
915
  // throw e;
939
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
916
+ try {
917
+ await _commandError(state, "timeout looking for " + elementDescription, this);
918
+ }
919
+ finally {
920
+ _commandFinally(state, this);
921
+ }
940
922
  }
941
923
  }
942
924
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -955,9 +937,9 @@ class StableBrowser {
955
937
  };
956
938
  try {
957
939
  await _preCommand(state, this);
958
- if (state.options && state.options.context) {
959
- state.selectors.locators[0].text = state.options.context;
960
- }
940
+ // if (state.options && state.options.context) {
941
+ // state.selectors.locators[0].text = state.options.context;
942
+ // }
961
943
  try {
962
944
  await state.element.click();
963
945
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -995,9 +977,15 @@ class StableBrowser {
995
977
  // let element = await this._locate(selectors, info, _params);
996
978
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
997
979
  try {
998
- // await this._highlightElements(element);
980
+ // if (world && world.screenshot && !world.screenshotPath) {
981
+ // console.log(`Highlighting while running from recorder`);
982
+ await this._highlightElements(element);
999
983
  await state.element.setChecked(checked);
1000
984
  await new Promise((resolve) => setTimeout(resolve, 1000));
985
+ // await this._unHighlightElements(element);
986
+ // }
987
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
988
+ // await this._unHighlightElements(element);
1001
989
  }
1002
990
  catch (e) {
1003
991
  if (e.message && e.message.includes("did not change its state")) {
@@ -1036,6 +1024,7 @@ class StableBrowser {
1036
1024
  await _preCommand(state, this);
1037
1025
  try {
1038
1026
  await state.element.hover();
1027
+ // await _screenshot(state, this);
1039
1028
  await new Promise((resolve) => setTimeout(resolve, 1000));
1040
1029
  }
1041
1030
  catch (e) {
@@ -1043,6 +1032,7 @@ class StableBrowser {
1043
1032
  state.info.log += "hover failed, will try again" + "\n";
1044
1033
  state.element = await this._locate(selectors, state.info, _params);
1045
1034
  await state.element.hover({ timeout: 10000 });
1035
+ // await _screenshot(state, this);
1046
1036
  await new Promise((resolve) => setTimeout(resolve, 1000));
1047
1037
  }
1048
1038
  await _screenshot(state, this);
@@ -1317,7 +1307,12 @@ class StableBrowser {
1317
1307
  await this.waitForPageLoad();
1318
1308
  }
1319
1309
  else if (enter === false) {
1320
- await state.element.dispatchEvent("change");
1310
+ try {
1311
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1312
+ }
1313
+ catch (e) {
1314
+ // ignore
1315
+ }
1321
1316
  //await this.page.keyboard.press("Tab");
1322
1317
  }
1323
1318
  else {
@@ -1374,6 +1369,7 @@ class StableBrowser {
1374
1369
  let screenshotPath = null;
1375
1370
  if (!info.log) {
1376
1371
  info.log = "";
1372
+ info.locatorLog = new LocatorLog(selectors);
1377
1373
  }
1378
1374
  info.operation = "getText";
1379
1375
  info.selectors = selectors;
@@ -1396,6 +1392,18 @@ class StableBrowser {
1396
1392
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1397
1393
  try {
1398
1394
  await this._highlightElements(element);
1395
+ // if (world && world.screenshot && !world.screenshotPath) {
1396
+ // // console.log(`Highlighting for get text while running from recorder`);
1397
+ // this._highlightElements(element)
1398
+ // .then(async () => {
1399
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1400
+ // this._unhighlightElements(element).then(
1401
+ // () => {}
1402
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1403
+ // );
1404
+ // })
1405
+ // .catch(e);
1406
+ // }
1399
1407
  const elementText = await element.innerText();
1400
1408
  return {
1401
1409
  text: elementText,
@@ -1407,7 +1415,7 @@ class StableBrowser {
1407
1415
  }
1408
1416
  catch (e) {
1409
1417
  //await this.closeUnexpectedPopups();
1410
- this.logger.info("no innerText will use textContent");
1418
+ this.logger.info("no innerText, will use textContent");
1411
1419
  const elementText = await element.textContent();
1412
1420
  return { text: elementText, screenshotId, screenshotPath, value: value };
1413
1421
  }
@@ -1533,22 +1541,6 @@ class StableBrowser {
1533
1541
  _commandFinally(state, this);
1534
1542
  }
1535
1543
  }
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
1544
  async waitForUserInput(message, world = null) {
1553
1545
  if (!message) {
1554
1546
  message = "# Wait for user input. Press any key to continue";
@@ -1577,7 +1569,7 @@ class StableBrowser {
1577
1569
  return;
1578
1570
  }
1579
1571
  // if data file exists, load it
1580
- const dataFile = this._getDataFile(world);
1572
+ const dataFile = _getDataFile(world, this.context, this);
1581
1573
  let data = this.getTestData(world);
1582
1574
  // merge the testData with the existing data
1583
1575
  Object.assign(data, testData);
@@ -1680,7 +1672,7 @@ class StableBrowser {
1680
1672
  }
1681
1673
  }
1682
1674
  getTestData(world = null) {
1683
- const dataFile = this._getDataFile(world);
1675
+ const dataFile = _getDataFile(world, this.context, this);
1684
1676
  let data = {};
1685
1677
  if (fs.existsSync(dataFile)) {
1686
1678
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1724,15 +1716,15 @@ class StableBrowser {
1724
1716
  // this.logger.info("unable to save screenshot " + screenshotPath);
1725
1717
  // }
1726
1718
  // });
1719
+ result.screenshotId = uuidStr;
1720
+ result.screenshotPath = screenshotPath;
1721
+ if (info && info.box) {
1722
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1723
+ }
1727
1724
  }
1728
1725
  catch (e) {
1729
1726
  this.logger.info("unable to take screenshot, ignored");
1730
1727
  }
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
1728
  }
1737
1729
  else if (options && options.screenshot) {
1738
1730
  result.screenshotPath = options.screenshotPath;
@@ -1767,6 +1759,15 @@ class StableBrowser {
1767
1759
  document.documentElement.clientWidth,
1768
1760
  ])));
1769
1761
  let screenshotBuffer = null;
1762
+ // if (focusedElement) {
1763
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1764
+ // await this._unhighlightElements(focusedElement);
1765
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1766
+ // console.log(`Unhighlighted previous element`);
1767
+ // }
1768
+ // if (focusedElement) {
1769
+ // await this._highlightElements(focusedElement);
1770
+ // }
1770
1771
  if (this.context.browserName === "chromium") {
1771
1772
  const client = await playContext.newCDPSession(this.page);
1772
1773
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1788,6 +1789,10 @@ class StableBrowser {
1788
1789
  else {
1789
1790
  screenshotBuffer = await this.page.screenshot();
1790
1791
  }
1792
+ // if (focusedElement) {
1793
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1794
+ // await this._unhighlightElements(focusedElement);
1795
+ // }
1791
1796
  let image = await Jimp.read(screenshotBuffer);
1792
1797
  // Get the image dimensions
1793
1798
  const { width, height } = image.bitmap;
@@ -1800,6 +1805,7 @@ class StableBrowser {
1800
1805
  else {
1801
1806
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1802
1807
  }
1808
+ return screenshotBuffer;
1803
1809
  }
1804
1810
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1805
1811
  const state = {
@@ -1837,6 +1843,7 @@ class StableBrowser {
1837
1843
  text: `Extract attribute from element`,
1838
1844
  operation: "extractAttribute",
1839
1845
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1846
+ allowDisabled: true,
1840
1847
  };
1841
1848
  await new Promise((resolve) => setTimeout(resolve, 2000));
1842
1849
  try {
@@ -1858,6 +1865,76 @@ class StableBrowser {
1858
1865
  state.info.value = state.value;
1859
1866
  this.setTestData({ [variable]: state.value }, world);
1860
1867
  this.logger.info("set test data: " + variable + "=" + state.value);
1868
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1869
+ return state.info;
1870
+ }
1871
+ catch (e) {
1872
+ await _commandError(state, e, this);
1873
+ }
1874
+ finally {
1875
+ _commandFinally(state, this);
1876
+ }
1877
+ }
1878
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1879
+ const state = {
1880
+ selectors,
1881
+ _params,
1882
+ attribute,
1883
+ value,
1884
+ options,
1885
+ world,
1886
+ type: Types.VERIFY_ATTRIBUTE,
1887
+ highlight: true,
1888
+ screenshot: true,
1889
+ text: `Verify element attribute`,
1890
+ operation: "verifyAttribute",
1891
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1892
+ allowDisabled: true,
1893
+ };
1894
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1895
+ let val;
1896
+ let expectedValue;
1897
+ try {
1898
+ await _preCommand(state, this);
1899
+ expectedValue = state.value;
1900
+ state.info.expectedValue = expectedValue;
1901
+ switch (attribute) {
1902
+ case "innerText":
1903
+ val = String(await state.element.innerText());
1904
+ break;
1905
+ case "value":
1906
+ val = String(await state.element.inputValue());
1907
+ break;
1908
+ case "checked":
1909
+ val = String(await state.element.isChecked());
1910
+ break;
1911
+ case "disabled":
1912
+ val = String(await state.element.isDisabled());
1913
+ break;
1914
+ case "readOnly":
1915
+ const isEditable = await state.element.isEditable();
1916
+ val = String(!isEditable);
1917
+ break;
1918
+ default:
1919
+ val = String(await state.element.getAttribute(attribute));
1920
+ break;
1921
+ }
1922
+ state.info.value = val;
1923
+ let regex;
1924
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1925
+ const patternBody = expectedValue.slice(1, -1);
1926
+ regex = new RegExp(patternBody, "g");
1927
+ }
1928
+ else {
1929
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1930
+ regex = new RegExp(escapedPattern, "g");
1931
+ }
1932
+ if (!val.match(regex)) {
1933
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1934
+ state.info.failCause.assertionFailed = true;
1935
+ state.info.failCause.lastError = errorMessage;
1936
+ throw new Error(errorMessage);
1937
+ }
1861
1938
  return state.info;
1862
1939
  }
1863
1940
  catch (e) {
@@ -1885,7 +1962,7 @@ class StableBrowser {
1885
1962
  if (options && options.timeout) {
1886
1963
  timeout = options.timeout;
1887
1964
  }
1888
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1965
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1889
1966
  const request = {
1890
1967
  method: "POST",
1891
1968
  url: serviceUrl,
@@ -1956,27 +2033,32 @@ class StableBrowser {
1956
2033
  async _highlightElements(scope, css) {
1957
2034
  try {
1958
2035
  if (!scope) {
2036
+ // console.log(`Scope is not defined`);
1959
2037
  return;
1960
2038
  }
1961
2039
  if (!css) {
1962
2040
  scope
1963
2041
  .evaluate((node) => {
1964
2042
  if (node && node.style) {
1965
- let originalBorder = node.style.border;
1966
- node.style.border = "2px solid red";
2043
+ let originalOutline = node.style.outline;
2044
+ // console.log(`Original outline was: ${originalOutline}`);
2045
+ // node.__previousOutline = originalOutline;
2046
+ node.style.outline = "2px solid red";
2047
+ // console.log(`New outline is: ${node.style.outline}`);
1967
2048
  if (window) {
1968
2049
  window.addEventListener("beforeunload", function (e) {
1969
- node.style.border = originalBorder;
2050
+ node.style.outline = originalOutline;
1970
2051
  });
1971
2052
  }
1972
2053
  setTimeout(function () {
1973
- node.style.border = originalBorder;
2054
+ node.style.outline = originalOutline;
1974
2055
  }, 2000);
1975
2056
  }
1976
2057
  })
1977
2058
  .then(() => { })
1978
2059
  .catch((e) => {
1979
2060
  // ignore
2061
+ // console.error(`Could not highlight node : ${e}`);
1980
2062
  });
1981
2063
  }
1982
2064
  else {
@@ -1992,17 +2074,18 @@ class StableBrowser {
1992
2074
  if (!element.style) {
1993
2075
  return;
1994
2076
  }
1995
- var originalBorder = element.style.border;
2077
+ let originalOutline = element.style.outline;
2078
+ element.__previousOutline = originalOutline;
1996
2079
  // Set the new border to be red and 2px solid
1997
- element.style.border = "2px solid red";
2080
+ element.style.outline = "2px solid red";
1998
2081
  if (window) {
1999
2082
  window.addEventListener("beforeunload", function (e) {
2000
- element.style.border = originalBorder;
2083
+ element.style.outline = originalOutline;
2001
2084
  });
2002
2085
  }
2003
2086
  // Set a timeout to revert to the original border after 2 seconds
2004
2087
  setTimeout(function () {
2005
- element.style.border = originalBorder;
2088
+ element.style.outline = originalOutline;
2006
2089
  }, 2000);
2007
2090
  }
2008
2091
  return;
@@ -2010,6 +2093,7 @@ class StableBrowser {
2010
2093
  .then(() => { })
2011
2094
  .catch((e) => {
2012
2095
  // ignore
2096
+ // console.error(`Could not highlight css: ${e}`);
2013
2097
  });
2014
2098
  }
2015
2099
  }
@@ -2017,6 +2101,54 @@ class StableBrowser {
2017
2101
  console.debug(error);
2018
2102
  }
2019
2103
  }
2104
+ // async _unhighlightElements(scope, css) {
2105
+ // try {
2106
+ // if (!scope) {
2107
+ // return;
2108
+ // }
2109
+ // if (!css) {
2110
+ // scope
2111
+ // .evaluate((node) => {
2112
+ // if (node && node.style) {
2113
+ // if (!node.__previousOutline) {
2114
+ // node.style.outline = "";
2115
+ // } else {
2116
+ // node.style.outline = node.__previousOutline;
2117
+ // }
2118
+ // }
2119
+ // })
2120
+ // .then(() => {})
2121
+ // .catch((e) => {
2122
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2123
+ // });
2124
+ // } else {
2125
+ // scope
2126
+ // .evaluate(([css]) => {
2127
+ // if (!css) {
2128
+ // return;
2129
+ // }
2130
+ // let elements = Array.from(document.querySelectorAll(css));
2131
+ // for (i = 0; i < elements.length; i++) {
2132
+ // let element = elements[i];
2133
+ // if (!element.style) {
2134
+ // return;
2135
+ // }
2136
+ // if (!element.__previousOutline) {
2137
+ // element.style.outline = "";
2138
+ // } else {
2139
+ // element.style.outline = element.__previousOutline;
2140
+ // }
2141
+ // }
2142
+ // })
2143
+ // .then(() => {})
2144
+ // .catch((e) => {
2145
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2146
+ // });
2147
+ // }
2148
+ // } catch (error) {
2149
+ // // console.debug(error);
2150
+ // }
2151
+ // }
2020
2152
  async verifyPagePath(pathPart, options = {}, world = null) {
2021
2153
  const startTime = Date.now();
2022
2154
  let error = null;
@@ -2078,6 +2210,35 @@ class StableBrowser {
2078
2210
  });
2079
2211
  }
2080
2212
  }
2213
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2214
+ const frames = this.page.frames();
2215
+ let results = [];
2216
+ let ignoreCase = false;
2217
+ for (let i = 0; i < frames.length; i++) {
2218
+ if (dateAlternatives.date) {
2219
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2220
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2221
+ result.frame = frames[i];
2222
+ results.push(result);
2223
+ }
2224
+ }
2225
+ else if (numberAlternatives.number) {
2226
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2227
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2228
+ result.frame = frames[i];
2229
+ results.push(result);
2230
+ }
2231
+ }
2232
+ else {
2233
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2234
+ result.frame = frames[i];
2235
+ results.push(result);
2236
+ }
2237
+ }
2238
+ state.info.results = results;
2239
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2240
+ return resultWithElementsFound;
2241
+ }
2081
2242
  async verifyTextExistInPage(text, options = {}, world = null) {
2082
2243
  text = unEscapeString(text);
2083
2244
  const state = {
@@ -2087,7 +2248,7 @@ class StableBrowser {
2087
2248
  locate: false,
2088
2249
  scroll: false,
2089
2250
  highlight: false,
2090
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2251
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2091
2252
  text: `Verify text exists in page`,
2092
2253
  operation: "verifyTextExistInPage",
2093
2254
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2105,31 +2266,15 @@ class StableBrowser {
2105
2266
  await _preCommand(state, this);
2106
2267
  state.info.text = text;
2107
2268
  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
- }
2269
+ let resultWithElementsFound = {
2270
+ length: 0,
2271
+ };
2272
+ try {
2273
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2274
+ }
2275
+ catch (error) {
2276
+ // ignore
2130
2277
  }
2131
- state.info.results = results;
2132
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2133
2278
  if (resultWithElementsFound.length === 0) {
2134
2279
  if (Date.now() - state.startTime > timeout) {
2135
2280
  throw new Error(`Text ${text} not found in page`);
@@ -2137,18 +2282,40 @@ class StableBrowser {
2137
2282
  await new Promise((resolve) => setTimeout(resolve, 1000));
2138
2283
  continue;
2139
2284
  }
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");
2285
+ try {
2286
+ if (resultWithElementsFound[0].randomToken) {
2287
+ const frame = resultWithElementsFound[0].frame;
2288
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2289
+ await this._highlightElements(frame, dataAttribute);
2290
+ // if (world && world.screenshot && !world.screenshotPath) {
2291
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2292
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2293
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2294
+ // this._unhighlightElements(frame, dataAttribute)
2295
+ // .then(async () => {
2296
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2297
+ // })
2298
+ // .catch(
2299
+ // (e) => {}
2300
+ // console.error(e)
2301
+ // );
2302
+ // });
2303
+ // }
2304
+ const element = await frame.locator(dataAttribute).first();
2305
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2306
+ // await this._unhighlightElements(frame, dataAttribute);
2307
+ if (element) {
2308
+ await this.scrollIfNeeded(element, state.info);
2309
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2310
+ // await _screenshot(state, this, element);
2311
+ }
2148
2312
  }
2313
+ await _screenshot(state, this);
2314
+ return state.info;
2315
+ }
2316
+ catch (error) {
2317
+ console.error(error);
2149
2318
  }
2150
- await _screenshot(state, this);
2151
- return state.info;
2152
2319
  }
2153
2320
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2154
2321
  }
@@ -2185,32 +2352,16 @@ class StableBrowser {
2185
2352
  try {
2186
2353
  await _preCommand(state, this);
2187
2354
  state.info.text = text;
2355
+ let resultWithElementsFound = {
2356
+ length: null, // initial cannot be 0
2357
+ };
2188
2358
  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
- }
2359
+ try {
2360
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2361
+ }
2362
+ catch (error) {
2363
+ // ignore
2211
2364
  }
2212
- state.info.results = results;
2213
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2214
2365
  if (resultWithElementsFound.length === 0) {
2215
2366
  await _screenshot(state, this);
2216
2367
  return state.info;
@@ -2228,15 +2379,115 @@ class StableBrowser {
2228
2379
  _commandFinally(state, this);
2229
2380
  }
2230
2381
  }
2231
- _getServerUrl() {
2232
- let serviceUrl = "https://api.blinq.io";
2233
- if (process.env.NODE_ENV_BLINQ === "dev") {
2234
- serviceUrl = "https://dev.api.blinq.io";
2382
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2383
+ textAnchor = unEscapeString(textAnchor);
2384
+ textToVerify = unEscapeString(textToVerify);
2385
+ const state = {
2386
+ text_search: textToVerify,
2387
+ options,
2388
+ world,
2389
+ locate: false,
2390
+ scroll: false,
2391
+ highlight: false,
2392
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2393
+ text: `Verify text with relation to another text`,
2394
+ operation: "verify_text_with_relation",
2395
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2396
+ };
2397
+ const timeout = this._getLoadTimeout(options);
2398
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2399
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2400
+ if (newValue !== textAnchor) {
2401
+ this.logger.info(textAnchor + "=" + newValue);
2402
+ textAnchor = newValue;
2403
+ }
2404
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2405
+ if (newValue !== textToVerify) {
2406
+ this.logger.info(textToVerify + "=" + newValue);
2407
+ textToVerify = newValue;
2408
+ }
2409
+ let dateAlternatives = findDateAlternatives(textToVerify);
2410
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2411
+ let foundAncore = false;
2412
+ try {
2413
+ await _preCommand(state, this);
2414
+ state.info.text = textToVerify;
2415
+ let resultWithElementsFound = {
2416
+ length: 0,
2417
+ };
2418
+ while (true) {
2419
+ try {
2420
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2421
+ }
2422
+ catch (error) {
2423
+ // ignore
2424
+ }
2425
+ if (resultWithElementsFound.length === 0) {
2426
+ if (Date.now() - state.startTime > timeout) {
2427
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2428
+ }
2429
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2430
+ continue;
2431
+ }
2432
+ try {
2433
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2434
+ foundAncore = true;
2435
+ const result = resultWithElementsFound[i];
2436
+ const token = result.randomToken;
2437
+ const frame = result.frame;
2438
+ let css = `[data-blinq-id-${token}]`;
2439
+ const climbArray1 = [];
2440
+ for (let i = 0; i < climb; i++) {
2441
+ climbArray1.push("..");
2442
+ }
2443
+ let climbXpath = "xpath=" + climbArray1.join("/");
2444
+ css = css + " >> " + climbXpath;
2445
+ const count = await frame.locator(css).count();
2446
+ for (let j = 0; j < count; j++) {
2447
+ const continer = await frame.locator(css).nth(j);
2448
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2449
+ if (result.elementCount > 0) {
2450
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2451
+ await this._highlightElements(frame, dataAttribute);
2452
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2453
+ // if (world && world.screenshot && !world.screenshotPath) {
2454
+ // console.log(`Highlighting for vtrt while running from recorder`);
2455
+ // this._highlightElements(frame, dataAttribute)
2456
+ // .then(async () => {
2457
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2458
+ // this._unhighlightElements(frame, dataAttribute).then(
2459
+ // () => {}
2460
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2461
+ // );
2462
+ // })
2463
+ // .catch(e);
2464
+ // }
2465
+ //await this._highlightElements(frame, cssAnchor);
2466
+ const element = await frame.locator(dataAttribute).first();
2467
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2468
+ // await this._unhighlightElements(frame, dataAttribute);
2469
+ if (element) {
2470
+ await this.scrollIfNeeded(element, state.info);
2471
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2472
+ }
2473
+ await _screenshot(state, this);
2474
+ return state.info;
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ catch (error) {
2480
+ console.error(error);
2481
+ }
2482
+ }
2483
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2484
+ }
2485
+ catch (e) {
2486
+ await _commandError(state, e, this);
2235
2487
  }
2236
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2237
- serviceUrl = "https://stage.api.blinq.io";
2488
+ finally {
2489
+ _commandFinally(state, this);
2238
2490
  }
2239
- return serviceUrl;
2240
2491
  }
2241
2492
  async visualVerification(text, options = {}, world = null) {
2242
2493
  const startTime = Date.now();
@@ -2252,14 +2503,17 @@ class StableBrowser {
2252
2503
  throw new Error("TOKEN is not set");
2253
2504
  }
2254
2505
  try {
2255
- let serviceUrl = this._getServerUrl();
2506
+ let serviceUrl = _getServerUrl();
2256
2507
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2257
2508
  info.screenshotPath = screenshotPath;
2258
2509
  const screenshot = await this.takeScreenshot();
2259
- const request = {
2260
- method: "POST",
2510
+ let request = {
2511
+ method: "post",
2512
+ maxBodyLength: Infinity,
2261
2513
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2262
2514
  headers: {
2515
+ "x-bvt-project-id": path.basename(this.project_path),
2516
+ "x-source": "aaa",
2263
2517
  "Content-Type": "application/json",
2264
2518
  Authorization: `Bearer ${process.env.TOKEN}`,
2265
2519
  },
@@ -2268,7 +2522,7 @@ class StableBrowser {
2268
2522
  screenshot: screenshot,
2269
2523
  }),
2270
2524
  };
2271
- let result = await this.context.api.request(request);
2525
+ const result = await axios.request(request);
2272
2526
  if (result.data.status !== true) {
2273
2527
  throw new Error("Visual validation failed");
2274
2528
  }
@@ -2341,6 +2595,7 @@ class StableBrowser {
2341
2595
  let screenshotPath = null;
2342
2596
  const info = {};
2343
2597
  info.log = "";
2598
+ info.locatorLog = new LocatorLog(selectors);
2344
2599
  info.operation = "getTableData";
2345
2600
  info.selectors = selectors;
2346
2601
  try {
@@ -2416,7 +2671,7 @@ class StableBrowser {
2416
2671
  info.operation = "analyzeTable";
2417
2672
  info.selectors = selectors;
2418
2673
  info.query = query;
2419
- query = this._fixUsingParams(query, _params);
2674
+ query = _fixUsingParams(query, _params);
2420
2675
  info.query_fixed = query;
2421
2676
  info.operator = operator;
2422
2677
  info.value = value;
@@ -2561,6 +2816,17 @@ class StableBrowser {
2561
2816
  }
2562
2817
  return timeout;
2563
2818
  }
2819
+ async saveStoreState(path = null, world = null) {
2820
+ const storageState = await this.page.context().storageState();
2821
+ //const testDataFile = _getDataFile(world, this.context, this);
2822
+ if (path) {
2823
+ // save { storageState: storageState } into the path
2824
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2825
+ }
2826
+ else {
2827
+ await this.setTestData({ storageState: storageState }, world);
2828
+ }
2829
+ }
2564
2830
  async waitForPageLoad(options = {}, world = null) {
2565
2831
  let timeout = this._getLoadTimeout(options);
2566
2832
  const promiseArray = [];
@@ -2746,14 +3012,22 @@ class StableBrowser {
2746
3012
  }
2747
3013
  }
2748
3014
  async beforeStep(world, step) {
2749
- this.stepName = step.pickleStep.text;
2750
- this.logger.info("step: " + this.stepName);
2751
3015
  if (this.stepIndex === undefined) {
2752
3016
  this.stepIndex = 0;
2753
3017
  }
2754
3018
  else {
2755
3019
  this.stepIndex++;
2756
3020
  }
3021
+ if (step && step.pickleStep && step.pickleStep.text) {
3022
+ this.stepName = step.pickleStep.text;
3023
+ this.logger.info("step: " + this.stepName);
3024
+ }
3025
+ else if (step && step.text) {
3026
+ this.stepName = step.text;
3027
+ }
3028
+ else {
3029
+ this.stepName = "step " + this.stepIndex;
3030
+ }
2757
3031
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2758
3032
  if (this.context.browserObject.context) {
2759
3033
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2783,156 +3057,5 @@ function createTimedPromise(promise, label) {
2783
3057
  .then((result) => ({ status: "fulfilled", label, result }))
2784
3058
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2785
3059
  }
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
3060
  export { StableBrowser };
2938
3061
  //# sourceMappingURL=stable_browser.js.map