automation_model 1.0.538-dev → 1.0.538-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,51 @@ class StableBrowser {
264
237
  // async closeUnexpectedPopups() {
265
238
  // await closeUnexpectedPopups(this.page);
266
239
  // }
267
- async goto(url) {
240
+ async goto(url, world = null) {
241
+ if (!url) {
242
+ throw new Error("url is null, verify that the environment file is correct");
243
+ }
268
244
  if (!url.startsWith("http")) {
269
245
  url = "https://" + url;
270
246
  }
271
- await this.page.goto(url, {
272
- timeout: 60000,
273
- });
274
- }
275
- _fixUsingParams(text, _params) {
276
- if (!_params || typeof text !== "string") {
277
- return text;
247
+ const state = {
248
+ value: url,
249
+ world: world,
250
+ type: Types.NAVIGATE,
251
+ text: `Navigate Page to: ${url}`,
252
+ operation: "goto",
253
+ log: "***** navigate page to " + url + " *****\n",
254
+ info: {},
255
+ locate: false,
256
+ scroll: false,
257
+ screenshot: false,
258
+ highlight: false,
259
+ };
260
+ try {
261
+ await _preCommand(state, this);
262
+ await this.page.goto(url, {
263
+ timeout: 60000,
264
+ });
265
+ await _screenshot(state, this);
278
266
  }
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]);
267
+ catch (error) {
268
+ console.error("Error on goto", error);
269
+ _commandError(state, error, this);
286
270
  }
287
- return text;
288
- }
289
- _fixLocatorUsingParams(locator, _params) {
290
- // check if not null
291
- if (!locator) {
292
- return locator;
271
+ finally {
272
+ _commandFinally(state, this);
293
273
  }
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
274
  }
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);
275
+ async _getLocator(locator, scope, _params) {
276
+ locator = _fixLocatorUsingParams(locator, _params);
277
+ // locator = await this._replaceWithLocalData(locator);
278
+ for (let key in locator) {
279
+ if (typeof locator[key] !== "string")
280
+ continue;
281
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
282
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
311
283
  }
312
284
  }
313
- }
314
- _getLocator(locator, scope, _params) {
315
- locator = this._fixLocatorUsingParams(locator, _params);
316
285
  let locatorReturn;
317
286
  if (locator.role) {
318
287
  if (locator.role[1].nameReg) {
@@ -320,7 +289,7 @@ class StableBrowser {
320
289
  delete locator.role[1].nameReg;
321
290
  }
322
291
  // if (locator.role[1].name) {
323
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
292
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
324
293
  // }
325
294
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
326
295
  }
@@ -363,140 +332,54 @@ class StableBrowser {
363
332
  if (css && css.locator) {
364
333
  css = css.locator;
365
334
  }
366
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
335
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
367
336
  if (result.elementCount === 0) {
368
337
  return;
369
338
  }
370
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
339
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
371
340
  // css climb to parent element
372
341
  const climbArray = [];
373
342
  for (let i = 0; i < climb; i++) {
374
343
  climbArray.push("..");
375
344
  }
376
345
  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
- }
346
+ let resultCss = textElementCss + " >> " + climbXpath;
347
+ if (css) {
348
+ resultCss = resultCss + " >> " + css;
349
+ }
350
+ return resultCss;
351
+ }
352
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
353
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
354
+ const locator = scope.locator(query);
355
+ const count = await locator.count();
356
+ if (!tag1) {
357
+ tag1 = "*";
358
+ }
359
+ const randomToken = Math.random().toString(36).substring(7);
360
+ let tagCount = 0;
361
+ for (let i = 0; i < count; i++) {
362
+ const element = locator.nth(i);
363
+ // check if the tag matches
364
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
365
+ if (!tag.startsWith("*")) {
366
+ if (el.tagName.toLowerCase() !== tag) {
367
+ return false;
465
368
  }
466
369
  }
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);
483
- }
484
- }
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++;
370
+ if (!el.setAttribute) {
371
+ el = el.parentElement;
494
372
  }
373
+ el.setAttribute("data-blinq-id-" + randomToken, "");
374
+ return true;
375
+ }, [tag1, randomToken]))) {
376
+ continue;
495
377
  }
496
- return { elementCount: elementCount, randomToken: randomToken };
497
- }, [text1, tag1, regex1, partial1]);
378
+ tagCount++;
379
+ }
380
+ return { elementCount: tagCount, randomToken };
498
381
  }
499
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
382
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
500
383
  if (!info) {
501
384
  info = {};
502
385
  }
@@ -505,10 +388,13 @@ class StableBrowser {
505
388
  }
506
389
  if (!info.log) {
507
390
  info.log = "";
391
+ info.locatorLog = new LocatorLog(selectorHierarchy);
508
392
  }
509
393
  let locatorSearch = selectorHierarchy[index];
394
+ let originalLocatorSearch = "";
510
395
  try {
511
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
396
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
397
+ locatorSearch = JSON.parse(originalLocatorSearch);
512
398
  }
513
399
  catch (e) {
514
400
  console.error(e);
@@ -516,30 +402,31 @@ class StableBrowser {
516
402
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
517
403
  let locator = null;
518
404
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
519
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
405
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
406
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
520
407
  if (!locatorString) {
521
408
  info.failCause.textNotFound = true;
522
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
409
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
523
410
  return;
524
411
  }
525
- locator = this._getLocator({ css: locatorString }, scope, _params);
412
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
526
413
  }
527
414
  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);
415
+ let text = _fixUsingParams(locatorSearch.text, _params);
416
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
530
417
  if (result.elementCount === 0) {
531
418
  info.failCause.textNotFound = true;
532
- info.failCause.lastError = "failed to locate element by text: " + text;
419
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
533
420
  return;
534
421
  }
535
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
422
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
536
423
  if (locatorSearch.childCss) {
537
424
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
538
425
  }
539
- locator = this._getLocator(locatorSearch, scope, _params);
426
+ locator = await this._getLocator(locatorSearch, scope, _params);
540
427
  }
541
428
  else {
542
- locator = this._getLocator(locatorSearch, scope, _params);
429
+ locator = await this._getLocator(locatorSearch, scope, _params);
543
430
  }
544
431
  // let cssHref = false;
545
432
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -552,18 +439,27 @@ class StableBrowser {
552
439
  //info.log += "total elements found " + count + "\n";
553
440
  //let visibleCount = 0;
554
441
  let visibleLocator = null;
555
- if (locatorSearch.index && locatorSearch.index < count) {
442
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
556
443
  foundLocators.push(locator.nth(locatorSearch.index));
444
+ if (info.locatorLog) {
445
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
446
+ }
557
447
  return;
558
448
  }
449
+ if (info.locatorLog && count === 0) {
450
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
451
+ }
559
452
  for (let j = 0; j < count; j++) {
560
453
  let visible = await locator.nth(j).isVisible();
561
454
  const enabled = await locator.nth(j).isEnabled();
562
455
  if (!visibleOnly) {
563
456
  visible = true;
564
457
  }
565
- if (visible && enabled) {
458
+ if (visible && (allowDisabled || enabled)) {
566
459
  foundLocators.push(locator.nth(j));
460
+ if (info.locatorLog) {
461
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
462
+ }
567
463
  }
568
464
  else {
569
465
  info.failCause.visible = visible;
@@ -571,8 +467,16 @@ class StableBrowser {
571
467
  if (!info.printMessages) {
572
468
  info.printMessages = {};
573
469
  }
470
+ if (info.locatorLog && !visible) {
471
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
472
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
473
+ }
474
+ if (info.locatorLog && !enabled) {
475
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
476
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
477
+ }
574
478
  if (!info.printMessages[j.toString()]) {
575
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
479
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
576
480
  info.printMessages[j.toString()] = true;
577
481
  }
578
482
  }
@@ -588,12 +492,12 @@ class StableBrowser {
588
492
  if (!info) {
589
493
  info = {};
590
494
  }
591
- info.log += "scan for popup handlers" + "\n";
495
+ //info.log += "scan for popup handlers" + "\n";
592
496
  const handlerGroup = [];
593
497
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
594
498
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
595
499
  }
596
- const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
500
+ const scopes = [this.page, ...this.page.frames()];
597
501
  let result = null;
598
502
  let scope = null;
599
503
  for (let i = 0; i < scopes.length; i++) {
@@ -636,7 +540,7 @@ class StableBrowser {
636
540
  }
637
541
  return { rerun: false };
638
542
  }
639
- async _locate(selectors, info, _params, timeout) {
543
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
640
544
  if (!timeout) {
641
545
  timeout = 30000;
642
546
  }
@@ -646,9 +550,18 @@ class StableBrowser {
646
550
  let selector = selectors.locators[j];
647
551
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
648
552
  }
649
- let element = await this._locate_internal(selectors, info, _params, timeout);
553
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
650
554
  if (!element.rerun) {
651
- return element;
555
+ const randomToken = Math.random().toString(36).substring(7);
556
+ element.evaluate((el, randomToken) => {
557
+ el.setAttribute("data-blinq-id-" + randomToken, "");
558
+ }, randomToken);
559
+ if (element._frame) {
560
+ return element;
561
+ }
562
+ const scope = element.page();
563
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
564
+ return newSelector;
652
565
  }
653
566
  }
654
567
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -659,6 +572,7 @@ class StableBrowser {
659
572
  info.failCause = {};
660
573
  info.log = "";
661
574
  }
575
+ let startTime = Date.now();
662
576
  let scope = this.page;
663
577
  if (selectors.frame) {
664
578
  return selectors.frame;
@@ -689,9 +603,11 @@ class StableBrowser {
689
603
  }
690
604
  return framescope;
691
605
  };
606
+ let fLocator = null;
692
607
  while (true) {
693
608
  let frameFound = false;
694
609
  if (selectors.nestFrmLoc) {
610
+ fLocator = selectors.nestFrmLoc;
695
611
  scope = await findFrame(selectors.nestFrmLoc, scope);
696
612
  frameFound = true;
697
613
  break;
@@ -700,6 +616,7 @@ class StableBrowser {
700
616
  for (let i = 0; i < selectors.frameLocators.length; i++) {
701
617
  let frameLocator = selectors.frameLocators[i];
702
618
  if (frameLocator.css) {
619
+ fLocator = frameLocator.css;
703
620
  scope = scope.frameLocator(frameLocator.css);
704
621
  frameFound = true;
705
622
  break;
@@ -707,18 +624,25 @@ class StableBrowser {
707
624
  }
708
625
  }
709
626
  if (!frameFound && selectors.iframe_src) {
627
+ fLocator = selectors.iframe_src;
710
628
  scope = this.page.frame({ url: selectors.iframe_src });
711
629
  }
712
630
  if (!scope) {
713
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
714
- if (performance.now() - startTime > timeout) {
631
+ if (info && info.locatorLog) {
632
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
633
+ }
634
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
635
+ if (Date.now() - startTime > timeout) {
715
636
  info.failCause.iframeNotFound = true;
716
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
637
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
717
638
  throw new Error("unable to locate iframe " + selectors.iframe_src);
718
639
  }
719
640
  await new Promise((resolve) => setTimeout(resolve, 1000));
720
641
  }
721
642
  else {
643
+ if (info && info.locatorLog) {
644
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
645
+ }
722
646
  break;
723
647
  }
724
648
  }
@@ -735,15 +659,16 @@ class StableBrowser {
735
659
  return bodyContent;
736
660
  });
737
661
  }
738
- async _locate_internal(selectors, info, _params, timeout = 30000) {
662
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
739
663
  if (!info) {
740
664
  info = {};
741
665
  info.failCause = {};
742
666
  info.log = "";
667
+ info.locatorLog = new LocatorLog(selectors);
743
668
  }
744
669
  let highPriorityTimeout = 5000;
745
670
  let visibleOnlyTimeout = 6000;
746
- let startTime = performance.now();
671
+ let startTime = Date.now();
747
672
  let locatorsCount = 0;
748
673
  let lazy_scroll = false;
749
674
  //let arrayMode = Array.isArray(selectors);
@@ -783,17 +708,17 @@ class StableBrowser {
783
708
  }
784
709
  // info.log += "scanning locators in priority 1" + "\n";
785
710
  let onlyPriority3 = selectorsLocators[0].priority === 3;
786
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
711
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
787
712
  if (result.foundElements.length === 0) {
788
713
  // info.log += "scanning locators in priority 2" + "\n";
789
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
714
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
790
715
  }
791
716
  if (result.foundElements.length === 0 && onlyPriority3) {
792
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
717
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
793
718
  }
794
719
  else {
795
720
  if (result.foundElements.length === 0 && !highPriorityOnly) {
796
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
721
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
797
722
  }
798
723
  }
799
724
  let foundElements = result.foundElements;
@@ -834,30 +759,38 @@ class StableBrowser {
834
759
  return maxCountElement.locator;
835
760
  }
836
761
  }
837
- if (performance.now() - startTime > timeout) {
762
+ if (Date.now() - startTime > timeout) {
838
763
  break;
839
764
  }
840
- if (performance.now() - startTime > highPriorityTimeout) {
841
- info.log += "high priority timeout, will try all elements" + "\n";
765
+ if (Date.now() - startTime > highPriorityTimeout) {
766
+ //info.log += "high priority timeout, will try all elements" + "\n";
842
767
  highPriorityOnly = false;
843
768
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
844
769
  lazy_scroll = true;
845
- await this.scrollPageToLoadLazyElements();
770
+ await scrollPageToLoadLazyElements(this.page);
846
771
  }
847
772
  }
848
- if (performance.now() - startTime > visibleOnlyTimeout) {
849
- info.log += "visible only timeout, will try all elements" + "\n";
773
+ if (Date.now() - startTime > visibleOnlyTimeout) {
774
+ //info.log += "visible only timeout, will try all elements" + "\n";
850
775
  visibleOnly = false;
851
776
  }
852
777
  await new Promise((resolve) => setTimeout(resolve, 1000));
853
778
  }
854
779
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
855
- info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
780
+ // if (info.locatorLog) {
781
+ // const lines = info.locatorLog.toString().split("\n");
782
+ // for (let line of lines) {
783
+ // this.logger.debug(line);
784
+ // }
785
+ // }
786
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
856
787
  info.failCause.locatorNotFound = true;
857
- info.failCause.lastError = "failed to locate unique element";
788
+ if (!info?.failCause?.lastError) {
789
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
790
+ }
858
791
  throw new Error("failed to locate first element no elements found, " + info.log);
859
792
  }
860
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
793
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
861
794
  let foundElements = [];
862
795
  const result = {
863
796
  foundElements: foundElements,
@@ -865,14 +798,15 @@ class StableBrowser {
865
798
  for (let i = 0; i < locatorsGroup.length; i++) {
866
799
  let foundLocators = [];
867
800
  try {
868
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
801
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
869
802
  }
870
803
  catch (e) {
871
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
872
- this.logger.debug(e);
804
+ // this call can fail it the browser is navigating
805
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
806
+ // this.logger.debug(e);
873
807
  foundLocators = [];
874
808
  try {
875
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
809
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
876
810
  }
877
811
  catch (e) {
878
812
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -888,11 +822,27 @@ class StableBrowser {
888
822
  }
889
823
  if (foundLocators.length > 1) {
890
824
  info.failCause.foundMultiple = true;
825
+ if (info.locatorLog) {
826
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
827
+ }
891
828
  }
892
829
  }
893
830
  return result;
894
831
  }
895
832
  async simpleClick(elementDescription, _params, options = {}, world = null) {
833
+ const state = {
834
+ locate: false,
835
+ scroll: false,
836
+ highlight: false,
837
+ _params,
838
+ options,
839
+ world,
840
+ type: Types.CLICK,
841
+ text: "Click element",
842
+ operation: "simpleClick",
843
+ log: "***** click on " + elementDescription + " *****\n",
844
+ };
845
+ _preCommand(state, this);
896
846
  const startTime = Date.now();
897
847
  let timeout = 30000;
898
848
  if (options && options.timeout) {
@@ -917,13 +867,31 @@ class StableBrowser {
917
867
  catch (e) {
918
868
  if (performance.now() - startTime > timeout) {
919
869
  // throw e;
920
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
870
+ try {
871
+ await _commandError(state, "timeout looking for " + elementDescription, this);
872
+ }
873
+ finally {
874
+ _commandFinally(state, this);
875
+ }
921
876
  }
922
877
  }
923
878
  await new Promise((resolve) => setTimeout(resolve, 3000));
924
879
  }
925
880
  }
926
881
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
882
+ const state = {
883
+ locate: false,
884
+ scroll: false,
885
+ highlight: false,
886
+ _params,
887
+ options,
888
+ world,
889
+ type: Types.FILL,
890
+ text: "Fill element",
891
+ operation: "simpleClickType",
892
+ log: "***** click type on " + elementDescription + " *****\n",
893
+ };
894
+ _preCommand(state, this);
927
895
  const startTime = Date.now();
928
896
  let timeout = 30000;
929
897
  if (options && options.timeout) {
@@ -948,7 +916,12 @@ class StableBrowser {
948
916
  catch (e) {
949
917
  if (performance.now() - startTime > timeout) {
950
918
  // throw e;
951
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
919
+ try {
920
+ await _commandError(state, "timeout looking for " + elementDescription, this);
921
+ }
922
+ finally {
923
+ _commandFinally(state, this);
924
+ }
952
925
  }
953
926
  }
954
927
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -967,9 +940,9 @@ class StableBrowser {
967
940
  };
968
941
  try {
969
942
  await _preCommand(state, this);
970
- if (state.options && state.options.context) {
971
- state.selectors.locators[0].text = state.options.context;
972
- }
943
+ // if (state.options && state.options.context) {
944
+ // state.selectors.locators[0].text = state.options.context;
945
+ // }
973
946
  try {
974
947
  await state.element.click();
975
948
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1007,9 +980,15 @@ class StableBrowser {
1007
980
  // let element = await this._locate(selectors, info, _params);
1008
981
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1009
982
  try {
1010
- // await this._highlightElements(element);
983
+ // if (world && world.screenshot && !world.screenshotPath) {
984
+ // console.log(`Highlighting while running from recorder`);
985
+ await this._highlightElements(element);
1011
986
  await state.element.setChecked(checked);
1012
987
  await new Promise((resolve) => setTimeout(resolve, 1000));
988
+ // await this._unHighlightElements(element);
989
+ // }
990
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
991
+ // await this._unHighlightElements(element);
1013
992
  }
1014
993
  catch (e) {
1015
994
  if (e.message && e.message.includes("did not change its state")) {
@@ -1048,6 +1027,7 @@ class StableBrowser {
1048
1027
  await _preCommand(state, this);
1049
1028
  try {
1050
1029
  await state.element.hover();
1030
+ // await _screenshot(state, this);
1051
1031
  await new Promise((resolve) => setTimeout(resolve, 1000));
1052
1032
  }
1053
1033
  catch (e) {
@@ -1055,6 +1035,7 @@ class StableBrowser {
1055
1035
  state.info.log += "hover failed, will try again" + "\n";
1056
1036
  state.element = await this._locate(selectors, state.info, _params);
1057
1037
  await state.element.hover({ timeout: 10000 });
1038
+ // await _screenshot(state, this);
1058
1039
  await new Promise((resolve) => setTimeout(resolve, 1000));
1059
1040
  }
1060
1041
  await _screenshot(state, this);
@@ -1329,7 +1310,12 @@ class StableBrowser {
1329
1310
  await this.waitForPageLoad();
1330
1311
  }
1331
1312
  else if (enter === false) {
1332
- await state.element.dispatchEvent("change");
1313
+ try {
1314
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1315
+ }
1316
+ catch (e) {
1317
+ // ignore
1318
+ }
1333
1319
  //await this.page.keyboard.press("Tab");
1334
1320
  }
1335
1321
  else {
@@ -1386,6 +1372,7 @@ class StableBrowser {
1386
1372
  let screenshotPath = null;
1387
1373
  if (!info.log) {
1388
1374
  info.log = "";
1375
+ info.locatorLog = new LocatorLog(selectors);
1389
1376
  }
1390
1377
  info.operation = "getText";
1391
1378
  info.selectors = selectors;
@@ -1408,6 +1395,18 @@ class StableBrowser {
1408
1395
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1409
1396
  try {
1410
1397
  await this._highlightElements(element);
1398
+ // if (world && world.screenshot && !world.screenshotPath) {
1399
+ // // console.log(`Highlighting for get text while running from recorder`);
1400
+ // this._highlightElements(element)
1401
+ // .then(async () => {
1402
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1403
+ // this._unhighlightElements(element).then(
1404
+ // () => {}
1405
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1406
+ // );
1407
+ // })
1408
+ // .catch(e);
1409
+ // }
1411
1410
  const elementText = await element.innerText();
1412
1411
  return {
1413
1412
  text: elementText,
@@ -1419,7 +1418,7 @@ class StableBrowser {
1419
1418
  }
1420
1419
  catch (e) {
1421
1420
  //await this.closeUnexpectedPopups();
1422
- this.logger.info("no innerText will use textContent");
1421
+ this.logger.info("no innerText, will use textContent");
1423
1422
  const elementText = await element.textContent();
1424
1423
  return { text: elementText, screenshotId, screenshotPath, value: value };
1425
1424
  }
@@ -1545,22 +1544,6 @@ class StableBrowser {
1545
1544
  _commandFinally(state, this);
1546
1545
  }
1547
1546
  }
1548
- _getDataFile(world = null) {
1549
- let dataFile = null;
1550
- if (world && world.reportFolder) {
1551
- dataFile = path.join(world.reportFolder, "data.json");
1552
- }
1553
- else if (this.reportFolder) {
1554
- dataFile = path.join(this.reportFolder, "data.json");
1555
- }
1556
- else if (this.context && this.context.reportFolder) {
1557
- dataFile = path.join(this.context.reportFolder, "data.json");
1558
- }
1559
- else {
1560
- dataFile = "data.json";
1561
- }
1562
- return dataFile;
1563
- }
1564
1547
  async waitForUserInput(message, world = null) {
1565
1548
  if (!message) {
1566
1549
  message = "# Wait for user input. Press any key to continue";
@@ -1589,7 +1572,7 @@ class StableBrowser {
1589
1572
  return;
1590
1573
  }
1591
1574
  // if data file exists, load it
1592
- const dataFile = this._getDataFile(world);
1575
+ const dataFile = _getDataFile(world, this.context, this);
1593
1576
  let data = this.getTestData(world);
1594
1577
  // merge the testData with the existing data
1595
1578
  Object.assign(data, testData);
@@ -1692,7 +1675,7 @@ class StableBrowser {
1692
1675
  }
1693
1676
  }
1694
1677
  getTestData(world = null) {
1695
- const dataFile = this._getDataFile(world);
1678
+ const dataFile = _getDataFile(world, this.context, this);
1696
1679
  let data = {};
1697
1680
  if (fs.existsSync(dataFile)) {
1698
1681
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1779,6 +1762,15 @@ class StableBrowser {
1779
1762
  document.documentElement.clientWidth,
1780
1763
  ])));
1781
1764
  let screenshotBuffer = null;
1765
+ // if (focusedElement) {
1766
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1767
+ // await this._unhighlightElements(focusedElement);
1768
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1769
+ // console.log(`Unhighlighted previous element`);
1770
+ // }
1771
+ // if (focusedElement) {
1772
+ // await this._highlightElements(focusedElement);
1773
+ // }
1782
1774
  if (this.context.browserName === "chromium") {
1783
1775
  const client = await playContext.newCDPSession(this.page);
1784
1776
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1800,6 +1792,10 @@ class StableBrowser {
1800
1792
  else {
1801
1793
  screenshotBuffer = await this.page.screenshot();
1802
1794
  }
1795
+ // if (focusedElement) {
1796
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1797
+ // await this._unhighlightElements(focusedElement);
1798
+ // }
1803
1799
  let image = await Jimp.read(screenshotBuffer);
1804
1800
  // Get the image dimensions
1805
1801
  const { width, height } = image.bitmap;
@@ -1812,6 +1808,7 @@ class StableBrowser {
1812
1808
  else {
1813
1809
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1814
1810
  }
1811
+ return screenshotBuffer;
1815
1812
  }
1816
1813
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1817
1814
  const state = {
@@ -1849,6 +1846,7 @@ class StableBrowser {
1849
1846
  text: `Extract attribute from element`,
1850
1847
  operation: "extractAttribute",
1851
1848
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1849
+ allowDisabled: true,
1852
1850
  };
1853
1851
  await new Promise((resolve) => setTimeout(resolve, 2000));
1854
1852
  try {
@@ -1870,6 +1868,76 @@ class StableBrowser {
1870
1868
  state.info.value = state.value;
1871
1869
  this.setTestData({ [variable]: state.value }, world);
1872
1870
  this.logger.info("set test data: " + variable + "=" + state.value);
1871
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1872
+ return state.info;
1873
+ }
1874
+ catch (e) {
1875
+ await _commandError(state, e, this);
1876
+ }
1877
+ finally {
1878
+ _commandFinally(state, this);
1879
+ }
1880
+ }
1881
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1882
+ const state = {
1883
+ selectors,
1884
+ _params,
1885
+ attribute,
1886
+ value,
1887
+ options,
1888
+ world,
1889
+ type: Types.VERIFY_ATTRIBUTE,
1890
+ highlight: true,
1891
+ screenshot: true,
1892
+ text: `Verify element attribute`,
1893
+ operation: "verifyAttribute",
1894
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1895
+ allowDisabled: true,
1896
+ };
1897
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1898
+ let val;
1899
+ let expectedValue;
1900
+ try {
1901
+ await _preCommand(state, this);
1902
+ expectedValue = state.value;
1903
+ state.info.expectedValue = expectedValue;
1904
+ switch (attribute) {
1905
+ case "innerText":
1906
+ val = String(await state.element.innerText());
1907
+ break;
1908
+ case "value":
1909
+ val = String(await state.element.inputValue());
1910
+ break;
1911
+ case "checked":
1912
+ val = String(await state.element.isChecked());
1913
+ break;
1914
+ case "disabled":
1915
+ val = String(await state.element.isDisabled());
1916
+ break;
1917
+ case "readOnly":
1918
+ const isEditable = await state.element.isEditable();
1919
+ val = String(!isEditable);
1920
+ break;
1921
+ default:
1922
+ val = String(await state.element.getAttribute(attribute));
1923
+ break;
1924
+ }
1925
+ state.info.value = val;
1926
+ let regex;
1927
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1928
+ const patternBody = expectedValue.slice(1, -1);
1929
+ regex = new RegExp(patternBody, "g");
1930
+ }
1931
+ else {
1932
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1933
+ regex = new RegExp(escapedPattern, "g");
1934
+ }
1935
+ if (!val.match(regex)) {
1936
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1937
+ state.info.failCause.assertionFailed = true;
1938
+ state.info.failCause.lastError = errorMessage;
1939
+ throw new Error(errorMessage);
1940
+ }
1873
1941
  return state.info;
1874
1942
  }
1875
1943
  catch (e) {
@@ -1897,7 +1965,7 @@ class StableBrowser {
1897
1965
  if (options && options.timeout) {
1898
1966
  timeout = options.timeout;
1899
1967
  }
1900
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1968
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1901
1969
  const request = {
1902
1970
  method: "POST",
1903
1971
  url: serviceUrl,
@@ -1968,27 +2036,32 @@ class StableBrowser {
1968
2036
  async _highlightElements(scope, css) {
1969
2037
  try {
1970
2038
  if (!scope) {
2039
+ // console.log(`Scope is not defined`);
1971
2040
  return;
1972
2041
  }
1973
2042
  if (!css) {
1974
2043
  scope
1975
2044
  .evaluate((node) => {
1976
2045
  if (node && node.style) {
1977
- let originalBorder = node.style.border;
1978
- node.style.border = "2px solid red";
2046
+ let originalOutline = node.style.outline;
2047
+ // console.log(`Original outline was: ${originalOutline}`);
2048
+ // node.__previousOutline = originalOutline;
2049
+ node.style.outline = "2px solid red";
2050
+ // console.log(`New outline is: ${node.style.outline}`);
1979
2051
  if (window) {
1980
2052
  window.addEventListener("beforeunload", function (e) {
1981
- node.style.border = originalBorder;
2053
+ node.style.outline = originalOutline;
1982
2054
  });
1983
2055
  }
1984
2056
  setTimeout(function () {
1985
- node.style.border = originalBorder;
2057
+ node.style.outline = originalOutline;
1986
2058
  }, 2000);
1987
2059
  }
1988
2060
  })
1989
2061
  .then(() => { })
1990
2062
  .catch((e) => {
1991
2063
  // ignore
2064
+ // console.error(`Could not highlight node : ${e}`);
1992
2065
  });
1993
2066
  }
1994
2067
  else {
@@ -2004,17 +2077,18 @@ class StableBrowser {
2004
2077
  if (!element.style) {
2005
2078
  return;
2006
2079
  }
2007
- var originalBorder = element.style.border;
2080
+ let originalOutline = element.style.outline;
2081
+ element.__previousOutline = originalOutline;
2008
2082
  // Set the new border to be red and 2px solid
2009
- element.style.border = "2px solid red";
2083
+ element.style.outline = "2px solid red";
2010
2084
  if (window) {
2011
2085
  window.addEventListener("beforeunload", function (e) {
2012
- element.style.border = originalBorder;
2086
+ element.style.outline = originalOutline;
2013
2087
  });
2014
2088
  }
2015
2089
  // Set a timeout to revert to the original border after 2 seconds
2016
2090
  setTimeout(function () {
2017
- element.style.border = originalBorder;
2091
+ element.style.outline = originalOutline;
2018
2092
  }, 2000);
2019
2093
  }
2020
2094
  return;
@@ -2022,6 +2096,7 @@ class StableBrowser {
2022
2096
  .then(() => { })
2023
2097
  .catch((e) => {
2024
2098
  // ignore
2099
+ // console.error(`Could not highlight css: ${e}`);
2025
2100
  });
2026
2101
  }
2027
2102
  }
@@ -2029,6 +2104,54 @@ class StableBrowser {
2029
2104
  console.debug(error);
2030
2105
  }
2031
2106
  }
2107
+ // async _unhighlightElements(scope, css) {
2108
+ // try {
2109
+ // if (!scope) {
2110
+ // return;
2111
+ // }
2112
+ // if (!css) {
2113
+ // scope
2114
+ // .evaluate((node) => {
2115
+ // if (node && node.style) {
2116
+ // if (!node.__previousOutline) {
2117
+ // node.style.outline = "";
2118
+ // } else {
2119
+ // node.style.outline = node.__previousOutline;
2120
+ // }
2121
+ // }
2122
+ // })
2123
+ // .then(() => {})
2124
+ // .catch((e) => {
2125
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2126
+ // });
2127
+ // } else {
2128
+ // scope
2129
+ // .evaluate(([css]) => {
2130
+ // if (!css) {
2131
+ // return;
2132
+ // }
2133
+ // let elements = Array.from(document.querySelectorAll(css));
2134
+ // for (i = 0; i < elements.length; i++) {
2135
+ // let element = elements[i];
2136
+ // if (!element.style) {
2137
+ // return;
2138
+ // }
2139
+ // if (!element.__previousOutline) {
2140
+ // element.style.outline = "";
2141
+ // } else {
2142
+ // element.style.outline = element.__previousOutline;
2143
+ // }
2144
+ // }
2145
+ // })
2146
+ // .then(() => {})
2147
+ // .catch((e) => {
2148
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2149
+ // });
2150
+ // }
2151
+ // } catch (error) {
2152
+ // // console.debug(error);
2153
+ // }
2154
+ // }
2032
2155
  async verifyPagePath(pathPart, options = {}, world = null) {
2033
2156
  const startTime = Date.now();
2034
2157
  let error = null;
@@ -2090,6 +2213,35 @@ class StableBrowser {
2090
2213
  });
2091
2214
  }
2092
2215
  }
2216
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2217
+ const frames = this.page.frames();
2218
+ let results = [];
2219
+ let ignoreCase = false;
2220
+ for (let i = 0; i < frames.length; i++) {
2221
+ if (dateAlternatives.date) {
2222
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2223
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2224
+ result.frame = frames[i];
2225
+ results.push(result);
2226
+ }
2227
+ }
2228
+ else if (numberAlternatives.number) {
2229
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2230
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2231
+ result.frame = frames[i];
2232
+ results.push(result);
2233
+ }
2234
+ }
2235
+ else {
2236
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2237
+ result.frame = frames[i];
2238
+ results.push(result);
2239
+ }
2240
+ }
2241
+ state.info.results = results;
2242
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2243
+ return resultWithElementsFound;
2244
+ }
2093
2245
  async verifyTextExistInPage(text, options = {}, world = null) {
2094
2246
  text = unEscapeString(text);
2095
2247
  const state = {
@@ -2099,7 +2251,7 @@ class StableBrowser {
2099
2251
  locate: false,
2100
2252
  scroll: false,
2101
2253
  highlight: false,
2102
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2254
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2103
2255
  text: `Verify text exists in page`,
2104
2256
  operation: "verifyTextExistInPage",
2105
2257
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2117,31 +2269,15 @@ class StableBrowser {
2117
2269
  await _preCommand(state, this);
2118
2270
  state.info.text = text;
2119
2271
  while (true) {
2120
- const frames = this.page.frames();
2121
- let results = [];
2122
- for (let i = 0; i < frames.length; i++) {
2123
- if (dateAlternatives.date) {
2124
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2125
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2126
- result.frame = frames[i];
2127
- results.push(result);
2128
- }
2129
- }
2130
- else if (numberAlternatives.number) {
2131
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2132
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2133
- result.frame = frames[i];
2134
- results.push(result);
2135
- }
2136
- }
2137
- else {
2138
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2139
- result.frame = frames[i];
2140
- results.push(result);
2141
- }
2272
+ let resultWithElementsFound = {
2273
+ length: 0,
2274
+ };
2275
+ try {
2276
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2277
+ }
2278
+ catch (error) {
2279
+ // ignore
2142
2280
  }
2143
- state.info.results = results;
2144
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2145
2281
  if (resultWithElementsFound.length === 0) {
2146
2282
  if (Date.now() - state.startTime > timeout) {
2147
2283
  throw new Error(`Text ${text} not found in page`);
@@ -2149,18 +2285,40 @@ class StableBrowser {
2149
2285
  await new Promise((resolve) => setTimeout(resolve, 1000));
2150
2286
  continue;
2151
2287
  }
2152
- if (resultWithElementsFound[0].randomToken) {
2153
- const frame = resultWithElementsFound[0].frame;
2154
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2155
- await this._highlightElements(frame, dataAttribute);
2156
- const element = await frame.$(dataAttribute);
2157
- if (element) {
2158
- await this.scrollIfNeeded(element, state.info);
2159
- await element.dispatchEvent("bvt_verify_page_contains_text");
2288
+ try {
2289
+ if (resultWithElementsFound[0].randomToken) {
2290
+ const frame = resultWithElementsFound[0].frame;
2291
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2292
+ await this._highlightElements(frame, dataAttribute);
2293
+ // if (world && world.screenshot && !world.screenshotPath) {
2294
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2295
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2296
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2297
+ // this._unhighlightElements(frame, dataAttribute)
2298
+ // .then(async () => {
2299
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2300
+ // })
2301
+ // .catch(
2302
+ // (e) => {}
2303
+ // console.error(e)
2304
+ // );
2305
+ // });
2306
+ // }
2307
+ const element = await frame.locator(dataAttribute).first();
2308
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2309
+ // await this._unhighlightElements(frame, dataAttribute);
2310
+ if (element) {
2311
+ await this.scrollIfNeeded(element, state.info);
2312
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2313
+ // await _screenshot(state, this, element);
2314
+ }
2160
2315
  }
2316
+ await _screenshot(state, this);
2317
+ return state.info;
2318
+ }
2319
+ catch (error) {
2320
+ console.error(error);
2161
2321
  }
2162
- await _screenshot(state, this);
2163
- return state.info;
2164
2322
  }
2165
2323
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2166
2324
  }
@@ -2197,32 +2355,16 @@ class StableBrowser {
2197
2355
  try {
2198
2356
  await _preCommand(state, this);
2199
2357
  state.info.text = text;
2358
+ let resultWithElementsFound = {
2359
+ length: null, // initial cannot be 0
2360
+ };
2200
2361
  while (true) {
2201
- const frames = this.page.frames();
2202
- let results = [];
2203
- for (let i = 0; i < frames.length; i++) {
2204
- if (dateAlternatives.date) {
2205
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2206
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2207
- result.frame = frames[i];
2208
- results.push(result);
2209
- }
2210
- }
2211
- else if (numberAlternatives.number) {
2212
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2213
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2214
- result.frame = frames[i];
2215
- results.push(result);
2216
- }
2217
- }
2218
- else {
2219
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2220
- result.frame = frames[i];
2221
- results.push(result);
2222
- }
2362
+ try {
2363
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2364
+ }
2365
+ catch (error) {
2366
+ // ignore
2223
2367
  }
2224
- state.info.results = results;
2225
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2226
2368
  if (resultWithElementsFound.length === 0) {
2227
2369
  await _screenshot(state, this);
2228
2370
  return state.info;
@@ -2240,15 +2382,115 @@ class StableBrowser {
2240
2382
  _commandFinally(state, this);
2241
2383
  }
2242
2384
  }
2243
- _getServerUrl() {
2244
- let serviceUrl = "https://api.blinq.io";
2245
- if (process.env.NODE_ENV_BLINQ === "dev") {
2246
- serviceUrl = "https://dev.api.blinq.io";
2385
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2386
+ textAnchor = unEscapeString(textAnchor);
2387
+ textToVerify = unEscapeString(textToVerify);
2388
+ const state = {
2389
+ text_search: textToVerify,
2390
+ options,
2391
+ world,
2392
+ locate: false,
2393
+ scroll: false,
2394
+ highlight: false,
2395
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2396
+ text: `Verify text with relation to another text`,
2397
+ operation: "verify_text_with_relation",
2398
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2399
+ };
2400
+ const timeout = this._getLoadTimeout(options);
2401
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2402
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2403
+ if (newValue !== textAnchor) {
2404
+ this.logger.info(textAnchor + "=" + newValue);
2405
+ textAnchor = newValue;
2406
+ }
2407
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2408
+ if (newValue !== textToVerify) {
2409
+ this.logger.info(textToVerify + "=" + newValue);
2410
+ textToVerify = newValue;
2411
+ }
2412
+ let dateAlternatives = findDateAlternatives(textToVerify);
2413
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2414
+ let foundAncore = false;
2415
+ try {
2416
+ await _preCommand(state, this);
2417
+ state.info.text = textToVerify;
2418
+ let resultWithElementsFound = {
2419
+ length: 0,
2420
+ };
2421
+ while (true) {
2422
+ try {
2423
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2424
+ }
2425
+ catch (error) {
2426
+ // ignore
2427
+ }
2428
+ if (resultWithElementsFound.length === 0) {
2429
+ if (Date.now() - state.startTime > timeout) {
2430
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2431
+ }
2432
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2433
+ continue;
2434
+ }
2435
+ try {
2436
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2437
+ foundAncore = true;
2438
+ const result = resultWithElementsFound[i];
2439
+ const token = result.randomToken;
2440
+ const frame = result.frame;
2441
+ let css = `[data-blinq-id-${token}]`;
2442
+ const climbArray1 = [];
2443
+ for (let i = 0; i < climb; i++) {
2444
+ climbArray1.push("..");
2445
+ }
2446
+ let climbXpath = "xpath=" + climbArray1.join("/");
2447
+ css = css + " >> " + climbXpath;
2448
+ const count = await frame.locator(css).count();
2449
+ for (let j = 0; j < count; j++) {
2450
+ const continer = await frame.locator(css).nth(j);
2451
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2452
+ if (result.elementCount > 0) {
2453
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2454
+ await this._highlightElements(frame, dataAttribute);
2455
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2456
+ // if (world && world.screenshot && !world.screenshotPath) {
2457
+ // console.log(`Highlighting for vtrt while running from recorder`);
2458
+ // this._highlightElements(frame, dataAttribute)
2459
+ // .then(async () => {
2460
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2461
+ // this._unhighlightElements(frame, dataAttribute).then(
2462
+ // () => {}
2463
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2464
+ // );
2465
+ // })
2466
+ // .catch(e);
2467
+ // }
2468
+ //await this._highlightElements(frame, cssAnchor);
2469
+ const element = await frame.locator(dataAttribute).first();
2470
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2471
+ // await this._unhighlightElements(frame, dataAttribute);
2472
+ if (element) {
2473
+ await this.scrollIfNeeded(element, state.info);
2474
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2475
+ }
2476
+ await _screenshot(state, this);
2477
+ return state.info;
2478
+ }
2479
+ }
2480
+ }
2481
+ }
2482
+ catch (error) {
2483
+ console.error(error);
2484
+ }
2485
+ }
2486
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2247
2487
  }
2248
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2249
- serviceUrl = "https://stage.api.blinq.io";
2488
+ catch (e) {
2489
+ await _commandError(state, e, this);
2490
+ }
2491
+ finally {
2492
+ _commandFinally(state, this);
2250
2493
  }
2251
- return serviceUrl;
2252
2494
  }
2253
2495
  async visualVerification(text, options = {}, world = null) {
2254
2496
  const startTime = Date.now();
@@ -2264,14 +2506,17 @@ class StableBrowser {
2264
2506
  throw new Error("TOKEN is not set");
2265
2507
  }
2266
2508
  try {
2267
- let serviceUrl = this._getServerUrl();
2509
+ let serviceUrl = _getServerUrl();
2268
2510
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2269
2511
  info.screenshotPath = screenshotPath;
2270
2512
  const screenshot = await this.takeScreenshot();
2271
- const request = {
2272
- method: "POST",
2513
+ let request = {
2514
+ method: "post",
2515
+ maxBodyLength: Infinity,
2273
2516
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2274
2517
  headers: {
2518
+ "x-bvt-project-id": path.basename(this.project_path),
2519
+ "x-source": "aaa",
2275
2520
  "Content-Type": "application/json",
2276
2521
  Authorization: `Bearer ${process.env.TOKEN}`,
2277
2522
  },
@@ -2280,7 +2525,7 @@ class StableBrowser {
2280
2525
  screenshot: screenshot,
2281
2526
  }),
2282
2527
  };
2283
- let result = await this.context.api.request(request);
2528
+ const result = await axios.request(request);
2284
2529
  if (result.data.status !== true) {
2285
2530
  throw new Error("Visual validation failed");
2286
2531
  }
@@ -2353,6 +2598,7 @@ class StableBrowser {
2353
2598
  let screenshotPath = null;
2354
2599
  const info = {};
2355
2600
  info.log = "";
2601
+ info.locatorLog = new LocatorLog(selectors);
2356
2602
  info.operation = "getTableData";
2357
2603
  info.selectors = selectors;
2358
2604
  try {
@@ -2428,7 +2674,7 @@ class StableBrowser {
2428
2674
  info.operation = "analyzeTable";
2429
2675
  info.selectors = selectors;
2430
2676
  info.query = query;
2431
- query = this._fixUsingParams(query, _params);
2677
+ query = _fixUsingParams(query, _params);
2432
2678
  info.query_fixed = query;
2433
2679
  info.operator = operator;
2434
2680
  info.value = value;
@@ -2573,6 +2819,17 @@ class StableBrowser {
2573
2819
  }
2574
2820
  return timeout;
2575
2821
  }
2822
+ async saveStoreState(path = null, world = null) {
2823
+ const storageState = await this.page.context().storageState();
2824
+ //const testDataFile = _getDataFile(world, this.context, this);
2825
+ if (path) {
2826
+ // save { storageState: storageState } into the path
2827
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2828
+ }
2829
+ else {
2830
+ await this.setTestData({ storageState: storageState }, world);
2831
+ }
2832
+ }
2576
2833
  async waitForPageLoad(options = {}, world = null) {
2577
2834
  let timeout = this._getLoadTimeout(options);
2578
2835
  const promiseArray = [];
@@ -2758,14 +3015,22 @@ class StableBrowser {
2758
3015
  }
2759
3016
  }
2760
3017
  async beforeStep(world, step) {
2761
- this.stepName = step.pickleStep.text;
2762
- this.logger.info("step: " + this.stepName);
2763
3018
  if (this.stepIndex === undefined) {
2764
3019
  this.stepIndex = 0;
2765
3020
  }
2766
3021
  else {
2767
3022
  this.stepIndex++;
2768
3023
  }
3024
+ if (step && step.pickleStep && step.pickleStep.text) {
3025
+ this.stepName = step.pickleStep.text;
3026
+ this.logger.info("step: " + this.stepName);
3027
+ }
3028
+ else if (step && step.text) {
3029
+ this.stepName = step.text;
3030
+ }
3031
+ else {
3032
+ this.stepName = "step " + this.stepIndex;
3033
+ }
2769
3034
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2770
3035
  if (this.context.browserObject.context) {
2771
3036
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2795,156 +3060,5 @@ function createTimedPromise(promise, label) {
2795
3060
  .then((result) => ({ status: "fulfilled", label, result }))
2796
3061
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2797
3062
  }
2798
- const KEYBOARD_EVENTS = [
2799
- "ALT",
2800
- "AltGraph",
2801
- "CapsLock",
2802
- "Control",
2803
- "Fn",
2804
- "FnLock",
2805
- "Hyper",
2806
- "Meta",
2807
- "NumLock",
2808
- "ScrollLock",
2809
- "Shift",
2810
- "Super",
2811
- "Symbol",
2812
- "SymbolLock",
2813
- "Enter",
2814
- "Tab",
2815
- "ArrowDown",
2816
- "ArrowLeft",
2817
- "ArrowRight",
2818
- "ArrowUp",
2819
- "End",
2820
- "Home",
2821
- "PageDown",
2822
- "PageUp",
2823
- "Backspace",
2824
- "Clear",
2825
- "Copy",
2826
- "CrSel",
2827
- "Cut",
2828
- "Delete",
2829
- "EraseEof",
2830
- "ExSel",
2831
- "Insert",
2832
- "Paste",
2833
- "Redo",
2834
- "Undo",
2835
- "Accept",
2836
- "Again",
2837
- "Attn",
2838
- "Cancel",
2839
- "ContextMenu",
2840
- "Escape",
2841
- "Execute",
2842
- "Find",
2843
- "Finish",
2844
- "Help",
2845
- "Pause",
2846
- "Play",
2847
- "Props",
2848
- "Select",
2849
- "ZoomIn",
2850
- "ZoomOut",
2851
- "BrightnessDown",
2852
- "BrightnessUp",
2853
- "Eject",
2854
- "LogOff",
2855
- "Power",
2856
- "PowerOff",
2857
- "PrintScreen",
2858
- "Hibernate",
2859
- "Standby",
2860
- "WakeUp",
2861
- "AllCandidates",
2862
- "Alphanumeric",
2863
- "CodeInput",
2864
- "Compose",
2865
- "Convert",
2866
- "Dead",
2867
- "FinalMode",
2868
- "GroupFirst",
2869
- "GroupLast",
2870
- "GroupNext",
2871
- "GroupPrevious",
2872
- "ModeChange",
2873
- "NextCandidate",
2874
- "NonConvert",
2875
- "PreviousCandidate",
2876
- "Process",
2877
- "SingleCandidate",
2878
- "HangulMode",
2879
- "HanjaMode",
2880
- "JunjaMode",
2881
- "Eisu",
2882
- "Hankaku",
2883
- "Hiragana",
2884
- "HiraganaKatakana",
2885
- "KanaMode",
2886
- "KanjiMode",
2887
- "Katakana",
2888
- "Romaji",
2889
- "Zenkaku",
2890
- "ZenkakuHanaku",
2891
- "F1",
2892
- "F2",
2893
- "F3",
2894
- "F4",
2895
- "F5",
2896
- "F6",
2897
- "F7",
2898
- "F8",
2899
- "F9",
2900
- "F10",
2901
- "F11",
2902
- "F12",
2903
- "Soft1",
2904
- "Soft2",
2905
- "Soft3",
2906
- "Soft4",
2907
- "ChannelDown",
2908
- "ChannelUp",
2909
- "Close",
2910
- "MailForward",
2911
- "MailReply",
2912
- "MailSend",
2913
- "MediaFastForward",
2914
- "MediaPause",
2915
- "MediaPlay",
2916
- "MediaPlayPause",
2917
- "MediaRecord",
2918
- "MediaRewind",
2919
- "MediaStop",
2920
- "MediaTrackNext",
2921
- "MediaTrackPrevious",
2922
- "AudioBalanceLeft",
2923
- "AudioBalanceRight",
2924
- "AudioBassBoostDown",
2925
- "AudioBassBoostToggle",
2926
- "AudioBassBoostUp",
2927
- "AudioFaderFront",
2928
- "AudioFaderRear",
2929
- "AudioSurroundModeNext",
2930
- "AudioTrebleDown",
2931
- "AudioTrebleUp",
2932
- "AudioVolumeDown",
2933
- "AudioVolumeMute",
2934
- "AudioVolumeUp",
2935
- "MicrophoneToggle",
2936
- "MicrophoneVolumeDown",
2937
- "MicrophoneVolumeMute",
2938
- "MicrophoneVolumeUp",
2939
- "TV",
2940
- "TV3DMode",
2941
- "TVAntennaCable",
2942
- "TVAudioDescription",
2943
- ];
2944
- function unEscapeString(str) {
2945
- const placeholder = "__NEWLINE__";
2946
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2947
- return str;
2948
- }
2949
3063
  export { StableBrowser };
2950
3064
  //# sourceMappingURL=stable_browser.js.map