automation_model 1.0.531-dev → 1.0.531-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ import { getDateTimeValue } from "./date_time.js";
10
10
  import drawRectangle from "./drawRect.js";
11
11
  //import { closeUnexpectedPopups } from "./popups.js";
12
12
  import { getTableCells, getTableData } from "./table_analyze.js";
13
- import { maskValue, replaceWithLocalTestData } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } from "./utils.js";
14
14
  import csv from "csv-parser";
15
15
  import { Readable } from "node:stream";
16
16
  import readline from "readline";
@@ -19,7 +19,8 @@ import { locate_element } from "./locate_element.js";
19
19
  import { randomUUID } from "crypto";
20
20
  import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
21
21
  import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
22
- const Types = {
22
+ import { LocatorLog } from "./locator_log.js";
23
+ export const Types = {
23
24
  CLICK: "click_element",
24
25
  NAVIGATE: "navigate",
25
26
  FILL: "fill_element",
@@ -30,6 +31,8 @@ const Types = {
30
31
  GET_PAGE_STATUS: "get_page_status",
31
32
  CLICK_ROW_ACTION: "click_row_action",
32
33
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
34
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
35
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
33
36
  ANALYZE_TABLE: "analyze_table",
34
37
  SELECT: "select_combobox",
35
38
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -46,8 +49,13 @@ const Types = {
46
49
  LOAD_DATA: "load_data",
47
50
  SET_INPUT: "set_input",
48
51
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
+ VERIFY_ATTRIBUTE: "verify_element_attribute",
53
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
49
54
  };
50
55
  export const apps = {};
56
+ const formatElementName = (elementName) => {
57
+ return elementName ? JSON.stringify(elementName) : "element";
58
+ };
51
59
  class StableBrowser {
52
60
  browser;
53
61
  page;
@@ -60,7 +68,6 @@ class StableBrowser {
60
68
  configuration = null;
61
69
  appName = "main";
62
70
  tags = null;
63
- isRecording = false;
64
71
  constructor(browser, page, logger = null, context = null, world = null) {
65
72
  this.browser = browser;
66
73
  this.page = page;
@@ -91,35 +98,17 @@ class StableBrowser {
91
98
  catch (e) {
92
99
  this.logger.error("unable to read ai_config.json");
93
100
  }
94
- context.pageLoading = { status: false };
95
- context.pages = [this.page];
96
101
  const logFolder = path.join(this.project_path, "logs", "web");
97
102
  this.world = world;
103
+ context.pages = [this.page];
104
+ context.pageLoading = { status: false };
98
105
  this.registerEventListeners(this.context);
99
106
  registerNetworkEvents(this.world, this, this.context, this.page);
100
107
  registerDownloadEvent(this.page, this.world, this.context);
101
108
  }
102
- async scrollPageToLoadLazyElements() {
103
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
104
- let retry = 0;
105
- while (true) {
106
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
107
- await new Promise((resolve) => setTimeout(resolve, 1000));
108
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
109
- if (newHeight === lastHeight) {
110
- break;
111
- }
112
- lastHeight = newHeight;
113
- retry++;
114
- if (retry > 10) {
115
- break;
116
- }
117
- }
118
- await this.page.evaluate(() => window.scrollTo(0, 0));
119
- }
120
109
  registerEventListeners(context) {
121
110
  this.registerConsoleLogListener(this.page, context);
122
- this.registerRequestListener(this.page, context, this.webLogFile);
111
+ // this.registerRequestListener(this.page, context, this.webLogFile);
123
112
  if (!context.pageLoading) {
124
113
  context.pageLoading = { status: false };
125
114
  }
@@ -164,7 +153,7 @@ class StableBrowser {
164
153
  if (this.appName === appName) {
165
154
  return;
166
155
  }
167
- let newContextCreated = false;
156
+ let navigate = false;
168
157
  if (!apps[appName]) {
169
158
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
170
159
  newContextCreated = true;
@@ -175,32 +164,15 @@ class StableBrowser {
175
164
  };
176
165
  }
177
166
  const tempContext = {};
178
- this._copyContext(this, tempContext);
179
- this._copyContext(apps[appName], this);
167
+ _copyContext(this, tempContext);
168
+ _copyContext(apps[appName], this);
180
169
  apps[this.appName] = tempContext;
181
170
  this.appName = appName;
182
- if (newContextCreated) {
183
- this.registerEventListeners(this.context);
171
+ if (navigate) {
184
172
  await this.goto(this.context.environment.baseUrl);
185
173
  await this.waitForPageLoad();
186
174
  }
187
175
  }
188
- _copyContext(from, to) {
189
- to.browser = from.browser;
190
- to.page = from.page;
191
- to.context = from.context;
192
- }
193
- getWebLogFile(logFolder) {
194
- if (!fs.existsSync(logFolder)) {
195
- fs.mkdirSync(logFolder, { recursive: true });
196
- }
197
- let nextIndex = 1;
198
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
199
- nextIndex++;
200
- }
201
- const fileName = nextIndex + ".json";
202
- return path.join(logFolder, fileName);
203
- }
204
176
  registerConsoleLogListener(page, context) {
205
177
  if (!this.context.webLogger) {
206
178
  this.context.webLogger = [];
@@ -264,55 +236,48 @@ class StableBrowser {
264
236
  // async closeUnexpectedPopups() {
265
237
  // await closeUnexpectedPopups(this.page);
266
238
  // }
267
- async goto(url) {
239
+ async goto(url, world = null) {
268
240
  if (!url.startsWith("http")) {
269
241
  url = "https://" + url;
270
242
  }
271
- await this.page.goto(url, {
272
- timeout: 60000,
273
- });
274
- }
275
- _fixUsingParams(text, _params) {
276
- if (!_params || typeof text !== "string") {
277
- return text;
243
+ const state = {
244
+ value: url,
245
+ world: world,
246
+ type: Types.NAVIGATE,
247
+ text: `Navigate Page to: ${url}`,
248
+ operation: "goto",
249
+ log: "***** navigate page to " + url + " *****\n",
250
+ info: {},
251
+ locate: false,
252
+ scroll: false,
253
+ screenshot: false,
254
+ highlight: false,
255
+ };
256
+ try {
257
+ await _preCommand(state, this);
258
+ await this.page.goto(url, {
259
+ timeout: 60000,
260
+ });
261
+ await _screenshot(state, this);
278
262
  }
279
- for (let key in _params) {
280
- let regValue = key;
281
- if (key.startsWith("_")) {
282
- // remove the _ prefix
283
- regValue = key.substring(1);
284
- }
285
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
263
+ catch (error) {
264
+ console.error("Error on goto", error);
265
+ _commandError(state, error, this);
286
266
  }
287
- return text;
288
- }
289
- _fixLocatorUsingParams(locator, _params) {
290
- // check if not null
291
- if (!locator) {
292
- return locator;
267
+ finally {
268
+ _commandFinally(state, this);
293
269
  }
294
- // clone the locator
295
- locator = JSON.parse(JSON.stringify(locator));
296
- this.scanAndManipulate(locator, _params);
297
- return locator;
298
270
  }
299
- _isObject(value) {
300
- return value && typeof value === "object" && value.constructor === Object;
301
- }
302
- scanAndManipulate(currentObj, _params) {
303
- for (const key in currentObj) {
304
- if (typeof currentObj[key] === "string") {
305
- // Perform string manipulation
306
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
307
- }
308
- else if (this._isObject(currentObj[key])) {
309
- // Recursively scan nested objects
310
- this.scanAndManipulate(currentObj[key], _params);
271
+ async _getLocator(locator, scope, _params) {
272
+ locator = _fixLocatorUsingParams(locator, _params);
273
+ // locator = await this._replaceWithLocalData(locator);
274
+ for (let key in locator) {
275
+ if (typeof locator[key] !== "string")
276
+ continue;
277
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
278
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
311
279
  }
312
280
  }
313
- }
314
- _getLocator(locator, scope, _params) {
315
- locator = this._fixLocatorUsingParams(locator, _params);
316
281
  let locatorReturn;
317
282
  if (locator.role) {
318
283
  if (locator.role[1].nameReg) {
@@ -320,7 +285,7 @@ class StableBrowser {
320
285
  delete locator.role[1].nameReg;
321
286
  }
322
287
  // if (locator.role[1].name) {
323
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
288
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
324
289
  // }
325
290
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
326
291
  }
@@ -363,140 +328,54 @@ class StableBrowser {
363
328
  if (css && css.locator) {
364
329
  css = css.locator;
365
330
  }
366
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
331
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
367
332
  if (result.elementCount === 0) {
368
333
  return;
369
334
  }
370
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
335
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
371
336
  // css climb to parent element
372
337
  const climbArray = [];
373
338
  for (let i = 0; i < climb; i++) {
374
339
  climbArray.push("..");
375
340
  }
376
341
  let climbXpath = "xpath=" + climbArray.join("/");
377
- return textElementCss + " >> " + climbXpath + " >> " + css;
378
- }
379
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
380
- //const stringifyText = JSON.stringify(text);
381
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
382
- function isParent(parent, child) {
383
- let currentNode = child.parentNode;
384
- while (currentNode !== null) {
385
- if (currentNode === parent) {
386
- return true;
387
- }
388
- currentNode = currentNode.parentNode;
389
- }
390
- return false;
391
- }
392
- document.isParent = isParent;
393
- function getRegex(str) {
394
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
395
- if (!match) {
396
- return null;
397
- }
398
- let [_, pattern, flags] = match;
399
- return new RegExp(pattern, flags);
400
- }
401
- document.getRegex = getRegex;
402
- function collectAllShadowDomElements(element, result = []) {
403
- // Check and add the element if it has a shadow root
404
- if (element.shadowRoot) {
405
- result.push(element);
406
- // Also search within the shadow root
407
- document.collectAllShadowDomElements(element.shadowRoot, result);
408
- }
409
- // Iterate over child nodes
410
- element.childNodes.forEach((child) => {
411
- // Recursively call the function for each child node
412
- document.collectAllShadowDomElements(child, result);
413
- });
414
- return result;
415
- }
416
- document.collectAllShadowDomElements = collectAllShadowDomElements;
417
- if (!tag) {
418
- tag = "*:not(script, style, head)";
419
- }
420
- let regexpSearch = document.getRegex(text);
421
- if (regexpSearch) {
422
- regex = true;
423
- }
424
- let elements = Array.from(document.querySelectorAll(tag));
425
- let shadowHosts = [];
426
- document.collectAllShadowDomElements(document, shadowHosts);
427
- for (let i = 0; i < shadowHosts.length; i++) {
428
- let shadowElement = shadowHosts[i].shadowRoot;
429
- if (!shadowElement) {
430
- console.log("shadowElement is null, for host " + shadowHosts[i]);
431
- continue;
432
- }
433
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
434
- elements = elements.concat(shadowElements);
435
- }
436
- let randomToken = null;
437
- const foundElements = [];
438
- if (regex) {
439
- if (!regexpSearch) {
440
- regexpSearch = new RegExp(text, "im");
441
- }
442
- for (let i = 0; i < elements.length; i++) {
443
- const element = elements[i];
444
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
445
- (element.value && regexpSearch.test(element.value))) {
446
- foundElements.push(element);
447
- }
448
- }
449
- }
450
- else {
451
- text = text.trim();
452
- for (let i = 0; i < elements.length; i++) {
453
- const element = elements[i];
454
- if (partial) {
455
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
456
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
457
- foundElements.push(element);
458
- }
459
- }
460
- else {
461
- if ((element.innerText && element.innerText.trim() === text) ||
462
- (element.value && element.value === text)) {
463
- foundElements.push(element);
464
- }
342
+ let resultCss = textElementCss + " >> " + climbXpath;
343
+ if (css) {
344
+ resultCss = resultCss + " >> " + css;
345
+ }
346
+ return resultCss;
347
+ }
348
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
349
+ const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
350
+ const locator = scope.locator(query);
351
+ const count = await locator.count();
352
+ if (!tag1) {
353
+ tag1 = "*";
354
+ }
355
+ const randomToken = Math.random().toString(36).substring(7);
356
+ let tagCount = 0;
357
+ for (let i = 0; i < count; i++) {
358
+ const element = locator.nth(i);
359
+ // check if the tag matches
360
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
361
+ if (!tag.startsWith("*")) {
362
+ if (el.tagName.toLowerCase() !== tag) {
363
+ return false;
465
364
  }
466
365
  }
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);
366
+ if (!el.setAttribute) {
367
+ el = el.parentElement;
483
368
  }
369
+ el.setAttribute("data-blinq-id-" + randomToken, "");
370
+ return true;
371
+ }, [tag1, randomToken]))) {
372
+ continue;
484
373
  }
485
- let elementCount = 0;
486
- if (noChildElements.length > 0) {
487
- for (let i = 0; i < noChildElements.length; i++) {
488
- if (randomToken === null) {
489
- randomToken = Math.random().toString(36).substring(7);
490
- }
491
- let element = noChildElements[i];
492
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
493
- elementCount++;
494
- }
495
- }
496
- return { elementCount: elementCount, randomToken: randomToken };
497
- }, [text1, tag1, regex1, partial1]);
374
+ tagCount++;
375
+ }
376
+ return { elementCount: tagCount, randomToken };
498
377
  }
499
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
378
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
500
379
  if (!info) {
501
380
  info = {};
502
381
  }
@@ -505,10 +384,13 @@ class StableBrowser {
505
384
  }
506
385
  if (!info.log) {
507
386
  info.log = "";
387
+ info.locatorLog = new LocatorLog(selectorHierarchy);
508
388
  }
509
389
  let locatorSearch = selectorHierarchy[index];
390
+ let originalLocatorSearch = "";
510
391
  try {
511
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
392
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
393
+ locatorSearch = JSON.parse(originalLocatorSearch);
512
394
  }
513
395
  catch (e) {
514
396
  console.error(e);
@@ -516,30 +398,31 @@ class StableBrowser {
516
398
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
517
399
  let locator = null;
518
400
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
519
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
401
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
402
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
520
403
  if (!locatorString) {
521
404
  info.failCause.textNotFound = true;
522
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
405
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
523
406
  return;
524
407
  }
525
- locator = this._getLocator({ css: locatorString }, scope, _params);
408
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
526
409
  }
527
410
  else if (locatorSearch.text) {
528
- let text = this._fixUsingParams(locatorSearch.text, _params);
529
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
411
+ let text = _fixUsingParams(locatorSearch.text, _params);
412
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
530
413
  if (result.elementCount === 0) {
531
414
  info.failCause.textNotFound = true;
532
- info.failCause.lastError = "failed to locate element by text: " + text;
415
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
533
416
  return;
534
417
  }
535
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
418
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
536
419
  if (locatorSearch.childCss) {
537
420
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
538
421
  }
539
- locator = this._getLocator(locatorSearch, scope, _params);
422
+ locator = await this._getLocator(locatorSearch, scope, _params);
540
423
  }
541
424
  else {
542
- locator = this._getLocator(locatorSearch, scope, _params);
425
+ locator = await this._getLocator(locatorSearch, scope, _params);
543
426
  }
544
427
  // let cssHref = false;
545
428
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -552,18 +435,27 @@ class StableBrowser {
552
435
  //info.log += "total elements found " + count + "\n";
553
436
  //let visibleCount = 0;
554
437
  let visibleLocator = null;
555
- if (locatorSearch.index && locatorSearch.index < count) {
438
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
556
439
  foundLocators.push(locator.nth(locatorSearch.index));
440
+ if (info.locatorLog) {
441
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
442
+ }
557
443
  return;
558
444
  }
445
+ if (info.locatorLog && count === 0) {
446
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
447
+ }
559
448
  for (let j = 0; j < count; j++) {
560
449
  let visible = await locator.nth(j).isVisible();
561
450
  const enabled = await locator.nth(j).isEnabled();
562
451
  if (!visibleOnly) {
563
452
  visible = true;
564
453
  }
565
- if (visible && enabled) {
454
+ if (visible && (allowDisabled || enabled)) {
566
455
  foundLocators.push(locator.nth(j));
456
+ if (info.locatorLog) {
457
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
458
+ }
567
459
  }
568
460
  else {
569
461
  info.failCause.visible = visible;
@@ -571,8 +463,16 @@ class StableBrowser {
571
463
  if (!info.printMessages) {
572
464
  info.printMessages = {};
573
465
  }
466
+ if (info.locatorLog && !visible) {
467
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
468
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
469
+ }
470
+ if (info.locatorLog && !enabled) {
471
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
472
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
473
+ }
574
474
  if (!info.printMessages[j.toString()]) {
575
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
475
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
576
476
  info.printMessages[j.toString()] = true;
577
477
  }
578
478
  }
@@ -588,7 +488,7 @@ class StableBrowser {
588
488
  if (!info) {
589
489
  info = {};
590
490
  }
591
- info.log += "scan for popup handlers" + "\n";
491
+ //info.log += "scan for popup handlers" + "\n";
592
492
  const handlerGroup = [];
593
493
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
594
494
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -615,16 +515,28 @@ class StableBrowser {
615
515
  }
616
516
  if (result.foundElements.length > 0) {
617
517
  let dialogCloseLocator = result.foundElements[0].locator;
618
- await dialogCloseLocator.click();
619
- // wait for the dialog to close
620
- await dialogCloseLocator.waitFor({ state: "hidden" });
518
+ try {
519
+ await scope?.evaluate(() => {
520
+ window.__isClosingPopups = true;
521
+ });
522
+ await dialogCloseLocator.click();
523
+ // wait for the dialog to close
524
+ await dialogCloseLocator.waitFor({ state: "hidden" });
525
+ }
526
+ catch (e) {
527
+ }
528
+ finally {
529
+ await scope?.evaluate(() => {
530
+ window.__isClosingPopups = false;
531
+ });
532
+ }
621
533
  return { rerun: true };
622
534
  }
623
535
  }
624
536
  }
625
537
  return { rerun: false };
626
538
  }
627
- async _locate(selectors, info, _params, timeout) {
539
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
628
540
  if (!timeout) {
629
541
  timeout = 30000;
630
542
  }
@@ -634,9 +546,15 @@ class StableBrowser {
634
546
  let selector = selectors.locators[j];
635
547
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
636
548
  }
637
- let element = await this._locate_internal(selectors, info, _params, timeout);
549
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
638
550
  if (!element.rerun) {
639
- return element;
551
+ const randomToken = Math.random().toString(36).substring(7);
552
+ element.evaluate((el, randomToken) => {
553
+ el.setAttribute("data-blinq-id-" + randomToken, "");
554
+ }, randomToken);
555
+ const scope = element._frame ?? element.page();
556
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
557
+ return newSelector;
640
558
  }
641
559
  }
642
560
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -647,6 +565,7 @@ class StableBrowser {
647
565
  info.failCause = {};
648
566
  info.log = "";
649
567
  }
568
+ let startTime = Date.now();
650
569
  let scope = this.page;
651
570
  if (selectors.frame) {
652
571
  return selectors.frame;
@@ -677,9 +596,11 @@ class StableBrowser {
677
596
  }
678
597
  return framescope;
679
598
  };
599
+ let fLocator = null;
680
600
  while (true) {
681
601
  let frameFound = false;
682
602
  if (selectors.nestFrmLoc) {
603
+ fLocator = selectors.nestFrmLoc;
683
604
  scope = await findFrame(selectors.nestFrmLoc, scope);
684
605
  frameFound = true;
685
606
  break;
@@ -688,6 +609,7 @@ class StableBrowser {
688
609
  for (let i = 0; i < selectors.frameLocators.length; i++) {
689
610
  let frameLocator = selectors.frameLocators[i];
690
611
  if (frameLocator.css) {
612
+ fLocator = frameLocator.css;
691
613
  scope = scope.frameLocator(frameLocator.css);
692
614
  frameFound = true;
693
615
  break;
@@ -695,18 +617,25 @@ class StableBrowser {
695
617
  }
696
618
  }
697
619
  if (!frameFound && selectors.iframe_src) {
620
+ fLocator = selectors.iframe_src;
698
621
  scope = this.page.frame({ url: selectors.iframe_src });
699
622
  }
700
623
  if (!scope) {
701
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
702
- if (performance.now() - startTime > timeout) {
624
+ if (info && info.locatorLog) {
625
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
626
+ }
627
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
628
+ if (Date.now() - startTime > timeout) {
703
629
  info.failCause.iframeNotFound = true;
704
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
630
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
705
631
  throw new Error("unable to locate iframe " + selectors.iframe_src);
706
632
  }
707
633
  await new Promise((resolve) => setTimeout(resolve, 1000));
708
634
  }
709
635
  else {
636
+ if (info && info.locatorLog) {
637
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
638
+ }
710
639
  break;
711
640
  }
712
641
  }
@@ -723,15 +652,16 @@ class StableBrowser {
723
652
  return bodyContent;
724
653
  });
725
654
  }
726
- async _locate_internal(selectors, info, _params, timeout = 30000) {
655
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
727
656
  if (!info) {
728
657
  info = {};
729
658
  info.failCause = {};
730
659
  info.log = "";
660
+ info.locatorLog = new LocatorLog(selectors);
731
661
  }
732
662
  let highPriorityTimeout = 5000;
733
663
  let visibleOnlyTimeout = 6000;
734
- let startTime = performance.now();
664
+ let startTime = Date.now();
735
665
  let locatorsCount = 0;
736
666
  let lazy_scroll = false;
737
667
  //let arrayMode = Array.isArray(selectors);
@@ -771,17 +701,17 @@ class StableBrowser {
771
701
  }
772
702
  // info.log += "scanning locators in priority 1" + "\n";
773
703
  let onlyPriority3 = selectorsLocators[0].priority === 3;
774
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
704
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
775
705
  if (result.foundElements.length === 0) {
776
706
  // info.log += "scanning locators in priority 2" + "\n";
777
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
707
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
778
708
  }
779
709
  if (result.foundElements.length === 0 && onlyPriority3) {
780
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
710
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
781
711
  }
782
712
  else {
783
713
  if (result.foundElements.length === 0 && !highPriorityOnly) {
784
- 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);
785
715
  }
786
716
  }
787
717
  let foundElements = result.foundElements;
@@ -822,30 +752,38 @@ class StableBrowser {
822
752
  return maxCountElement.locator;
823
753
  }
824
754
  }
825
- if (performance.now() - startTime > timeout) {
755
+ if (Date.now() - startTime > timeout) {
826
756
  break;
827
757
  }
828
- if (performance.now() - startTime > highPriorityTimeout) {
829
- info.log += "high priority timeout, will try all elements" + "\n";
758
+ if (Date.now() - startTime > highPriorityTimeout) {
759
+ //info.log += "high priority timeout, will try all elements" + "\n";
830
760
  highPriorityOnly = false;
831
761
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
832
762
  lazy_scroll = true;
833
- await this.scrollPageToLoadLazyElements();
763
+ await scrollPageToLoadLazyElements(this.page);
834
764
  }
835
765
  }
836
- if (performance.now() - startTime > visibleOnlyTimeout) {
837
- info.log += "visible only timeout, will try all elements" + "\n";
766
+ if (Date.now() - startTime > visibleOnlyTimeout) {
767
+ //info.log += "visible only timeout, will try all elements" + "\n";
838
768
  visibleOnly = false;
839
769
  }
840
770
  await new Promise((resolve) => setTimeout(resolve, 1000));
841
771
  }
842
772
  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";
773
+ // if (info.locatorLog) {
774
+ // const lines = info.locatorLog.toString().split("\n");
775
+ // for (let line of lines) {
776
+ // this.logger.debug(line);
777
+ // }
778
+ // }
779
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
844
780
  info.failCause.locatorNotFound = true;
845
- info.failCause.lastError = "failed to locate unique element";
781
+ if (!info?.failCause?.lastError) {
782
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
783
+ }
846
784
  throw new Error("failed to locate first element no elements found, " + info.log);
847
785
  }
848
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
786
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
849
787
  let foundElements = [];
850
788
  const result = {
851
789
  foundElements: foundElements,
@@ -853,14 +791,15 @@ class StableBrowser {
853
791
  for (let i = 0; i < locatorsGroup.length; i++) {
854
792
  let foundLocators = [];
855
793
  try {
856
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
794
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
857
795
  }
858
796
  catch (e) {
859
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
860
- this.logger.debug(e);
797
+ // this call can fail it the browser is navigating
798
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
799
+ // this.logger.debug(e);
861
800
  foundLocators = [];
862
801
  try {
863
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
802
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
864
803
  }
865
804
  catch (e) {
866
805
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -876,11 +815,27 @@ class StableBrowser {
876
815
  }
877
816
  if (foundLocators.length > 1) {
878
817
  info.failCause.foundMultiple = true;
818
+ if (info.locatorLog) {
819
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
820
+ }
879
821
  }
880
822
  }
881
823
  return result;
882
824
  }
883
825
  async simpleClick(elementDescription, _params, options = {}, world = null) {
826
+ const state = {
827
+ locate: false,
828
+ scroll: false,
829
+ highlight: false,
830
+ _params,
831
+ options,
832
+ world,
833
+ type: Types.CLICK,
834
+ text: "Click element",
835
+ operation: "simpleClick",
836
+ log: "***** click on " + elementDescription + " *****\n",
837
+ };
838
+ _preCommand(state, this);
884
839
  const startTime = Date.now();
885
840
  let timeout = 30000;
886
841
  if (options && options.timeout) {
@@ -905,13 +860,31 @@ class StableBrowser {
905
860
  catch (e) {
906
861
  if (performance.now() - startTime > timeout) {
907
862
  // throw e;
908
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
863
+ try {
864
+ await _commandError(state, "timeout looking for " + elementDescription, this);
865
+ }
866
+ finally {
867
+ _commandFinally(state, this);
868
+ }
909
869
  }
910
870
  }
911
871
  await new Promise((resolve) => setTimeout(resolve, 3000));
912
872
  }
913
873
  }
914
874
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
875
+ const state = {
876
+ locate: false,
877
+ scroll: false,
878
+ highlight: false,
879
+ _params,
880
+ options,
881
+ world,
882
+ type: Types.FILL,
883
+ text: "Fill element",
884
+ operation: "simpleClickType",
885
+ log: "***** click type on " + elementDescription + " *****\n",
886
+ };
887
+ _preCommand(state, this);
915
888
  const startTime = Date.now();
916
889
  let timeout = 30000;
917
890
  if (options && options.timeout) {
@@ -936,7 +909,12 @@ class StableBrowser {
936
909
  catch (e) {
937
910
  if (performance.now() - startTime > timeout) {
938
911
  // throw e;
939
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
912
+ try {
913
+ await _commandError(state, "timeout looking for " + elementDescription, this);
914
+ }
915
+ finally {
916
+ _commandFinally(state, this);
917
+ }
940
918
  }
941
919
  }
942
920
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -955,9 +933,9 @@ class StableBrowser {
955
933
  };
956
934
  try {
957
935
  await _preCommand(state, this);
958
- if (state.options && state.options.context) {
959
- state.selectors.locators[0].text = state.options.context;
960
- }
936
+ // if (state.options && state.options.context) {
937
+ // state.selectors.locators[0].text = state.options.context;
938
+ // }
961
939
  try {
962
940
  await state.element.click();
963
941
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -995,9 +973,15 @@ class StableBrowser {
995
973
  // let element = await this._locate(selectors, info, _params);
996
974
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
997
975
  try {
998
- // await this._highlightElements(element);
976
+ // if (world && world.screenshot && !world.screenshotPath) {
977
+ // console.log(`Highlighting while running from recorder`);
978
+ await this._highlightElements(element);
999
979
  await state.element.setChecked(checked);
1000
980
  await new Promise((resolve) => setTimeout(resolve, 1000));
981
+ // await this._unHighlightElements(element);
982
+ // }
983
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
984
+ // await this._unHighlightElements(element);
1001
985
  }
1002
986
  catch (e) {
1003
987
  if (e.message && e.message.includes("did not change its state")) {
@@ -1036,6 +1020,7 @@ class StableBrowser {
1036
1020
  await _preCommand(state, this);
1037
1021
  try {
1038
1022
  await state.element.hover();
1023
+ // await _screenshot(state, this);
1039
1024
  await new Promise((resolve) => setTimeout(resolve, 1000));
1040
1025
  }
1041
1026
  catch (e) {
@@ -1043,6 +1028,7 @@ class StableBrowser {
1043
1028
  state.info.log += "hover failed, will try again" + "\n";
1044
1029
  state.element = await this._locate(selectors, state.info, _params);
1045
1030
  await state.element.hover({ timeout: 10000 });
1031
+ // await _screenshot(state, this);
1046
1032
  await new Promise((resolve) => setTimeout(resolve, 1000));
1047
1033
  }
1048
1034
  await _screenshot(state, this);
@@ -1317,7 +1303,12 @@ class StableBrowser {
1317
1303
  await this.waitForPageLoad();
1318
1304
  }
1319
1305
  else if (enter === false) {
1320
- await state.element.dispatchEvent("change");
1306
+ try {
1307
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1308
+ }
1309
+ catch (e) {
1310
+ // ignore
1311
+ }
1321
1312
  //await this.page.keyboard.press("Tab");
1322
1313
  }
1323
1314
  else {
@@ -1374,6 +1365,7 @@ class StableBrowser {
1374
1365
  let screenshotPath = null;
1375
1366
  if (!info.log) {
1376
1367
  info.log = "";
1368
+ info.locatorLog = new LocatorLog(selectors);
1377
1369
  }
1378
1370
  info.operation = "getText";
1379
1371
  info.selectors = selectors;
@@ -1396,6 +1388,18 @@ class StableBrowser {
1396
1388
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1397
1389
  try {
1398
1390
  await this._highlightElements(element);
1391
+ // if (world && world.screenshot && !world.screenshotPath) {
1392
+ // // console.log(`Highlighting for get text while running from recorder`);
1393
+ // this._highlightElements(element)
1394
+ // .then(async () => {
1395
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1396
+ // this._unhighlightElements(element).then(
1397
+ // () => {}
1398
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1399
+ // );
1400
+ // })
1401
+ // .catch(e);
1402
+ // }
1399
1403
  const elementText = await element.innerText();
1400
1404
  return {
1401
1405
  text: elementText,
@@ -1407,7 +1411,7 @@ class StableBrowser {
1407
1411
  }
1408
1412
  catch (e) {
1409
1413
  //await this.closeUnexpectedPopups();
1410
- this.logger.info("no innerText will use textContent");
1414
+ this.logger.info("no innerText, will use textContent");
1411
1415
  const elementText = await element.textContent();
1412
1416
  return { text: elementText, screenshotId, screenshotPath, value: value };
1413
1417
  }
@@ -1533,22 +1537,6 @@ class StableBrowser {
1533
1537
  _commandFinally(state, this);
1534
1538
  }
1535
1539
  }
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
1540
  async waitForUserInput(message, world = null) {
1553
1541
  if (!message) {
1554
1542
  message = "# Wait for user input. Press any key to continue";
@@ -1577,7 +1565,7 @@ class StableBrowser {
1577
1565
  return;
1578
1566
  }
1579
1567
  // if data file exists, load it
1580
- const dataFile = this._getDataFile(world);
1568
+ const dataFile = _getDataFile(world, this.context, this);
1581
1569
  let data = this.getTestData(world);
1582
1570
  // merge the testData with the existing data
1583
1571
  Object.assign(data, testData);
@@ -1680,7 +1668,7 @@ class StableBrowser {
1680
1668
  }
1681
1669
  }
1682
1670
  getTestData(world = null) {
1683
- const dataFile = this._getDataFile(world);
1671
+ const dataFile = _getDataFile(world, this.context, this);
1684
1672
  let data = {};
1685
1673
  if (fs.existsSync(dataFile)) {
1686
1674
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1724,15 +1712,15 @@ class StableBrowser {
1724
1712
  // this.logger.info("unable to save screenshot " + screenshotPath);
1725
1713
  // }
1726
1714
  // });
1715
+ result.screenshotId = uuidStr;
1716
+ result.screenshotPath = screenshotPath;
1717
+ if (info && info.box) {
1718
+ await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
1719
+ }
1727
1720
  }
1728
1721
  catch (e) {
1729
1722
  this.logger.info("unable to take screenshot, ignored");
1730
1723
  }
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
1724
  }
1737
1725
  else if (options && options.screenshot) {
1738
1726
  result.screenshotPath = options.screenshotPath;
@@ -1767,6 +1755,15 @@ class StableBrowser {
1767
1755
  document.documentElement.clientWidth,
1768
1756
  ])));
1769
1757
  let screenshotBuffer = null;
1758
+ // if (focusedElement) {
1759
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1760
+ // await this._unhighlightElements(focusedElement);
1761
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1762
+ // console.log(`Unhighlighted previous element`);
1763
+ // }
1764
+ // if (focusedElement) {
1765
+ // await this._highlightElements(focusedElement);
1766
+ // }
1770
1767
  if (this.context.browserName === "chromium") {
1771
1768
  const client = await playContext.newCDPSession(this.page);
1772
1769
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1788,6 +1785,10 @@ class StableBrowser {
1788
1785
  else {
1789
1786
  screenshotBuffer = await this.page.screenshot();
1790
1787
  }
1788
+ // if (focusedElement) {
1789
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1790
+ // await this._unhighlightElements(focusedElement);
1791
+ // }
1791
1792
  let image = await Jimp.read(screenshotBuffer);
1792
1793
  // Get the image dimensions
1793
1794
  const { width, height } = image.bitmap;
@@ -1800,6 +1801,7 @@ class StableBrowser {
1800
1801
  else {
1801
1802
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1802
1803
  }
1804
+ return screenshotBuffer;
1803
1805
  }
1804
1806
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1805
1807
  const state = {
@@ -1837,6 +1839,7 @@ class StableBrowser {
1837
1839
  text: `Extract attribute from element`,
1838
1840
  operation: "extractAttribute",
1839
1841
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1842
+ allowDisabled: true,
1840
1843
  };
1841
1844
  await new Promise((resolve) => setTimeout(resolve, 2000));
1842
1845
  try {
@@ -1858,6 +1861,76 @@ class StableBrowser {
1858
1861
  state.info.value = state.value;
1859
1862
  this.setTestData({ [variable]: state.value }, world);
1860
1863
  this.logger.info("set test data: " + variable + "=" + state.value);
1864
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1865
+ return state.info;
1866
+ }
1867
+ catch (e) {
1868
+ await _commandError(state, e, this);
1869
+ }
1870
+ finally {
1871
+ _commandFinally(state, this);
1872
+ }
1873
+ }
1874
+ async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
1875
+ const state = {
1876
+ selectors,
1877
+ _params,
1878
+ attribute,
1879
+ value,
1880
+ options,
1881
+ world,
1882
+ type: Types.VERIFY_ATTRIBUTE,
1883
+ highlight: true,
1884
+ screenshot: true,
1885
+ text: `Verify element attribute`,
1886
+ operation: "verifyAttribute",
1887
+ log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1888
+ allowDisabled: true,
1889
+ };
1890
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1891
+ let val;
1892
+ let expectedValue;
1893
+ try {
1894
+ await _preCommand(state, this);
1895
+ expectedValue = state.value;
1896
+ state.info.expectedValue = expectedValue;
1897
+ switch (attribute) {
1898
+ case "innerText":
1899
+ val = String(await state.element.innerText());
1900
+ break;
1901
+ case "value":
1902
+ val = String(await state.element.inputValue());
1903
+ break;
1904
+ case "checked":
1905
+ val = String(await state.element.isChecked());
1906
+ break;
1907
+ case "disabled":
1908
+ val = String(await state.element.isDisabled());
1909
+ break;
1910
+ case "readOnly":
1911
+ const isEditable = await state.element.isEditable();
1912
+ val = String(!isEditable);
1913
+ break;
1914
+ default:
1915
+ val = String(await state.element.getAttribute(attribute));
1916
+ break;
1917
+ }
1918
+ state.info.value = val;
1919
+ let regex;
1920
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1921
+ const patternBody = expectedValue.slice(1, -1);
1922
+ regex = new RegExp(patternBody, "g");
1923
+ }
1924
+ else {
1925
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1926
+ regex = new RegExp(escapedPattern, "g");
1927
+ }
1928
+ if (!val.match(regex)) {
1929
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1930
+ state.info.failCause.assertionFailed = true;
1931
+ state.info.failCause.lastError = errorMessage;
1932
+ throw new Error(errorMessage);
1933
+ }
1861
1934
  return state.info;
1862
1935
  }
1863
1936
  catch (e) {
@@ -1885,7 +1958,7 @@ class StableBrowser {
1885
1958
  if (options && options.timeout) {
1886
1959
  timeout = options.timeout;
1887
1960
  }
1888
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1961
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1889
1962
  const request = {
1890
1963
  method: "POST",
1891
1964
  url: serviceUrl,
@@ -1956,27 +2029,32 @@ class StableBrowser {
1956
2029
  async _highlightElements(scope, css) {
1957
2030
  try {
1958
2031
  if (!scope) {
2032
+ // console.log(`Scope is not defined`);
1959
2033
  return;
1960
2034
  }
1961
2035
  if (!css) {
1962
2036
  scope
1963
2037
  .evaluate((node) => {
1964
2038
  if (node && node.style) {
1965
- let originalBorder = node.style.border;
1966
- node.style.border = "2px solid red";
2039
+ let originalOutline = node.style.outline;
2040
+ // console.log(`Original outline was: ${originalOutline}`);
2041
+ // node.__previousOutline = originalOutline;
2042
+ node.style.outline = "2px solid red";
2043
+ // console.log(`New outline is: ${node.style.outline}`);
1967
2044
  if (window) {
1968
2045
  window.addEventListener("beforeunload", function (e) {
1969
- node.style.border = originalBorder;
2046
+ node.style.outline = originalOutline;
1970
2047
  });
1971
2048
  }
1972
2049
  setTimeout(function () {
1973
- node.style.border = originalBorder;
2050
+ node.style.outline = originalOutline;
1974
2051
  }, 2000);
1975
2052
  }
1976
2053
  })
1977
2054
  .then(() => { })
1978
2055
  .catch((e) => {
1979
2056
  // ignore
2057
+ // console.error(`Could not highlight node : ${e}`);
1980
2058
  });
1981
2059
  }
1982
2060
  else {
@@ -1992,17 +2070,18 @@ class StableBrowser {
1992
2070
  if (!element.style) {
1993
2071
  return;
1994
2072
  }
1995
- var originalBorder = element.style.border;
2073
+ let originalOutline = element.style.outline;
2074
+ element.__previousOutline = originalOutline;
1996
2075
  // Set the new border to be red and 2px solid
1997
- element.style.border = "2px solid red";
2076
+ element.style.outline = "2px solid red";
1998
2077
  if (window) {
1999
2078
  window.addEventListener("beforeunload", function (e) {
2000
- element.style.border = originalBorder;
2079
+ element.style.outline = originalOutline;
2001
2080
  });
2002
2081
  }
2003
2082
  // Set a timeout to revert to the original border after 2 seconds
2004
2083
  setTimeout(function () {
2005
- element.style.border = originalBorder;
2084
+ element.style.outline = originalOutline;
2006
2085
  }, 2000);
2007
2086
  }
2008
2087
  return;
@@ -2010,6 +2089,7 @@ class StableBrowser {
2010
2089
  .then(() => { })
2011
2090
  .catch((e) => {
2012
2091
  // ignore
2092
+ // console.error(`Could not highlight css: ${e}`);
2013
2093
  });
2014
2094
  }
2015
2095
  }
@@ -2017,6 +2097,54 @@ class StableBrowser {
2017
2097
  console.debug(error);
2018
2098
  }
2019
2099
  }
2100
+ // async _unhighlightElements(scope, css) {
2101
+ // try {
2102
+ // if (!scope) {
2103
+ // return;
2104
+ // }
2105
+ // if (!css) {
2106
+ // scope
2107
+ // .evaluate((node) => {
2108
+ // if (node && node.style) {
2109
+ // if (!node.__previousOutline) {
2110
+ // node.style.outline = "";
2111
+ // } else {
2112
+ // node.style.outline = node.__previousOutline;
2113
+ // }
2114
+ // }
2115
+ // })
2116
+ // .then(() => {})
2117
+ // .catch((e) => {
2118
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2119
+ // });
2120
+ // } else {
2121
+ // scope
2122
+ // .evaluate(([css]) => {
2123
+ // if (!css) {
2124
+ // return;
2125
+ // }
2126
+ // let elements = Array.from(document.querySelectorAll(css));
2127
+ // for (i = 0; i < elements.length; i++) {
2128
+ // let element = elements[i];
2129
+ // if (!element.style) {
2130
+ // return;
2131
+ // }
2132
+ // if (!element.__previousOutline) {
2133
+ // element.style.outline = "";
2134
+ // } else {
2135
+ // element.style.outline = element.__previousOutline;
2136
+ // }
2137
+ // }
2138
+ // })
2139
+ // .then(() => {})
2140
+ // .catch((e) => {
2141
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2142
+ // });
2143
+ // }
2144
+ // } catch (error) {
2145
+ // // console.debug(error);
2146
+ // }
2147
+ // }
2020
2148
  async verifyPagePath(pathPart, options = {}, world = null) {
2021
2149
  const startTime = Date.now();
2022
2150
  let error = null;
@@ -2078,6 +2206,35 @@ class StableBrowser {
2078
2206
  });
2079
2207
  }
2080
2208
  }
2209
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2210
+ const frames = this.page.frames();
2211
+ let results = [];
2212
+ let ignoreCase = false;
2213
+ for (let i = 0; i < frames.length; i++) {
2214
+ if (dateAlternatives.date) {
2215
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2216
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2217
+ result.frame = frames[i];
2218
+ results.push(result);
2219
+ }
2220
+ }
2221
+ else if (numberAlternatives.number) {
2222
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2223
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2224
+ result.frame = frames[i];
2225
+ results.push(result);
2226
+ }
2227
+ }
2228
+ else {
2229
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2230
+ result.frame = frames[i];
2231
+ results.push(result);
2232
+ }
2233
+ }
2234
+ state.info.results = results;
2235
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2236
+ return resultWithElementsFound;
2237
+ }
2081
2238
  async verifyTextExistInPage(text, options = {}, world = null) {
2082
2239
  text = unEscapeString(text);
2083
2240
  const state = {
@@ -2087,7 +2244,7 @@ class StableBrowser {
2087
2244
  locate: false,
2088
2245
  scroll: false,
2089
2246
  highlight: false,
2090
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2247
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2091
2248
  text: `Verify text exists in page`,
2092
2249
  operation: "verifyTextExistInPage",
2093
2250
  log: "***** verify text " + text + " exists in page *****\n",
@@ -2105,31 +2262,15 @@ class StableBrowser {
2105
2262
  await _preCommand(state, this);
2106
2263
  state.info.text = text;
2107
2264
  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
- }
2265
+ let resultWithElementsFound = {
2266
+ length: 0,
2267
+ };
2268
+ try {
2269
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2270
+ }
2271
+ catch (error) {
2272
+ // ignore
2130
2273
  }
2131
- state.info.results = results;
2132
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2133
2274
  if (resultWithElementsFound.length === 0) {
2134
2275
  if (Date.now() - state.startTime > timeout) {
2135
2276
  throw new Error(`Text ${text} not found in page`);
@@ -2137,18 +2278,40 @@ class StableBrowser {
2137
2278
  await new Promise((resolve) => setTimeout(resolve, 1000));
2138
2279
  continue;
2139
2280
  }
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");
2281
+ try {
2282
+ if (resultWithElementsFound[0].randomToken) {
2283
+ const frame = resultWithElementsFound[0].frame;
2284
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2285
+ await this._highlightElements(frame, dataAttribute);
2286
+ // if (world && world.screenshot && !world.screenshotPath) {
2287
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2288
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2289
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2290
+ // this._unhighlightElements(frame, dataAttribute)
2291
+ // .then(async () => {
2292
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2293
+ // })
2294
+ // .catch(
2295
+ // (e) => {}
2296
+ // console.error(e)
2297
+ // );
2298
+ // });
2299
+ // }
2300
+ const element = await frame.locator(dataAttribute).first();
2301
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2302
+ // await this._unhighlightElements(frame, dataAttribute);
2303
+ if (element) {
2304
+ await this.scrollIfNeeded(element, state.info);
2305
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2306
+ // await _screenshot(state, this, element);
2307
+ }
2148
2308
  }
2309
+ await _screenshot(state, this);
2310
+ return state.info;
2311
+ }
2312
+ catch (error) {
2313
+ console.error(error);
2149
2314
  }
2150
- await _screenshot(state, this);
2151
- return state.info;
2152
2315
  }
2153
2316
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2154
2317
  }
@@ -2185,32 +2348,16 @@ class StableBrowser {
2185
2348
  try {
2186
2349
  await _preCommand(state, this);
2187
2350
  state.info.text = text;
2351
+ let resultWithElementsFound = {
2352
+ length: null, // initial cannot be 0
2353
+ };
2188
2354
  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
- }
2355
+ try {
2356
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2357
+ }
2358
+ catch (error) {
2359
+ // ignore
2211
2360
  }
2212
- state.info.results = results;
2213
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2214
2361
  if (resultWithElementsFound.length === 0) {
2215
2362
  await _screenshot(state, this);
2216
2363
  return state.info;
@@ -2228,15 +2375,115 @@ class StableBrowser {
2228
2375
  _commandFinally(state, this);
2229
2376
  }
2230
2377
  }
2231
- _getServerUrl() {
2232
- let serviceUrl = "https://api.blinq.io";
2233
- if (process.env.NODE_ENV_BLINQ === "dev") {
2234
- serviceUrl = "https://dev.api.blinq.io";
2378
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2379
+ textAnchor = unEscapeString(textAnchor);
2380
+ textToVerify = unEscapeString(textToVerify);
2381
+ const state = {
2382
+ text_search: textToVerify,
2383
+ options,
2384
+ world,
2385
+ locate: false,
2386
+ scroll: false,
2387
+ highlight: false,
2388
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2389
+ text: `Verify text with relation to another text`,
2390
+ operation: "verify_text_with_relation",
2391
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2392
+ };
2393
+ const timeout = this._getLoadTimeout(options);
2394
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2395
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2396
+ if (newValue !== textAnchor) {
2397
+ this.logger.info(textAnchor + "=" + newValue);
2398
+ textAnchor = newValue;
2399
+ }
2400
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2401
+ if (newValue !== textToVerify) {
2402
+ this.logger.info(textToVerify + "=" + newValue);
2403
+ textToVerify = newValue;
2404
+ }
2405
+ let dateAlternatives = findDateAlternatives(textToVerify);
2406
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2407
+ let foundAncore = false;
2408
+ try {
2409
+ await _preCommand(state, this);
2410
+ state.info.text = textToVerify;
2411
+ let resultWithElementsFound = {
2412
+ length: 0,
2413
+ };
2414
+ while (true) {
2415
+ try {
2416
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2417
+ }
2418
+ catch (error) {
2419
+ // ignore
2420
+ }
2421
+ if (resultWithElementsFound.length === 0) {
2422
+ if (Date.now() - state.startTime > timeout) {
2423
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2424
+ }
2425
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2426
+ continue;
2427
+ }
2428
+ try {
2429
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2430
+ foundAncore = true;
2431
+ const result = resultWithElementsFound[i];
2432
+ const token = result.randomToken;
2433
+ const frame = result.frame;
2434
+ let css = `[data-blinq-id-${token}]`;
2435
+ const climbArray1 = [];
2436
+ for (let i = 0; i < climb; i++) {
2437
+ climbArray1.push("..");
2438
+ }
2439
+ let climbXpath = "xpath=" + climbArray1.join("/");
2440
+ css = css + " >> " + climbXpath;
2441
+ const count = await frame.locator(css).count();
2442
+ for (let j = 0; j < count; j++) {
2443
+ const continer = await frame.locator(css).nth(j);
2444
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2445
+ if (result.elementCount > 0) {
2446
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2447
+ await this._highlightElements(frame, dataAttribute);
2448
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2449
+ // if (world && world.screenshot && !world.screenshotPath) {
2450
+ // console.log(`Highlighting for vtrt while running from recorder`);
2451
+ // this._highlightElements(frame, dataAttribute)
2452
+ // .then(async () => {
2453
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2454
+ // this._unhighlightElements(frame, dataAttribute).then(
2455
+ // () => {}
2456
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2457
+ // );
2458
+ // })
2459
+ // .catch(e);
2460
+ // }
2461
+ //await this._highlightElements(frame, cssAnchor);
2462
+ const element = await frame.locator(dataAttribute).first();
2463
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2464
+ // await this._unhighlightElements(frame, dataAttribute);
2465
+ if (element) {
2466
+ await this.scrollIfNeeded(element, state.info);
2467
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2468
+ }
2469
+ await _screenshot(state, this);
2470
+ return state.info;
2471
+ }
2472
+ }
2473
+ }
2474
+ }
2475
+ catch (error) {
2476
+ console.error(error);
2477
+ }
2478
+ }
2479
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2480
+ }
2481
+ catch (e) {
2482
+ await _commandError(state, e, this);
2235
2483
  }
2236
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2237
- serviceUrl = "https://stage.api.blinq.io";
2484
+ finally {
2485
+ _commandFinally(state, this);
2238
2486
  }
2239
- return serviceUrl;
2240
2487
  }
2241
2488
  async visualVerification(text, options = {}, world = null) {
2242
2489
  const startTime = Date.now();
@@ -2252,7 +2499,7 @@ class StableBrowser {
2252
2499
  throw new Error("TOKEN is not set");
2253
2500
  }
2254
2501
  try {
2255
- let serviceUrl = this._getServerUrl();
2502
+ let serviceUrl = _getServerUrl();
2256
2503
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2257
2504
  info.screenshotPath = screenshotPath;
2258
2505
  const screenshot = await this.takeScreenshot();
@@ -2341,6 +2588,7 @@ class StableBrowser {
2341
2588
  let screenshotPath = null;
2342
2589
  const info = {};
2343
2590
  info.log = "";
2591
+ info.locatorLog = new LocatorLog(selectors);
2344
2592
  info.operation = "getTableData";
2345
2593
  info.selectors = selectors;
2346
2594
  try {
@@ -2416,7 +2664,7 @@ class StableBrowser {
2416
2664
  info.operation = "analyzeTable";
2417
2665
  info.selectors = selectors;
2418
2666
  info.query = query;
2419
- query = this._fixUsingParams(query, _params);
2667
+ query = _fixUsingParams(query, _params);
2420
2668
  info.query_fixed = query;
2421
2669
  info.operator = operator;
2422
2670
  info.value = value;
@@ -2561,6 +2809,17 @@ class StableBrowser {
2561
2809
  }
2562
2810
  return timeout;
2563
2811
  }
2812
+ async saveStoreState(path = null, world = null) {
2813
+ const storageState = await this.page.context().storageState();
2814
+ //const testDataFile = _getDataFile(world, this.context, this);
2815
+ if (path) {
2816
+ // save { storageState: storageState } into the path
2817
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2818
+ }
2819
+ else {
2820
+ await this.setTestData({ storageState: storageState }, world);
2821
+ }
2822
+ }
2564
2823
  async waitForPageLoad(options = {}, world = null) {
2565
2824
  let timeout = this._getLoadTimeout(options);
2566
2825
  const promiseArray = [];
@@ -2746,14 +3005,22 @@ class StableBrowser {
2746
3005
  }
2747
3006
  }
2748
3007
  async beforeStep(world, step) {
2749
- this.stepName = step.pickleStep.text;
2750
- this.logger.info("step: " + this.stepName);
2751
3008
  if (this.stepIndex === undefined) {
2752
3009
  this.stepIndex = 0;
2753
3010
  }
2754
3011
  else {
2755
3012
  this.stepIndex++;
2756
3013
  }
3014
+ if (step && step.pickleStep && step.pickleStep.text) {
3015
+ this.stepName = step.pickleStep.text;
3016
+ this.logger.info("step: " + this.stepName);
3017
+ }
3018
+ else if (step && step.text) {
3019
+ this.stepName = step.text;
3020
+ }
3021
+ else {
3022
+ this.stepName = "step " + this.stepIndex;
3023
+ }
2757
3024
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2758
3025
  if (this.context.browserObject.context) {
2759
3026
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2783,156 +3050,5 @@ function createTimedPromise(promise, label) {
2783
3050
  .then((result) => ({ status: "fulfilled", label, result }))
2784
3051
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2785
3052
  }
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
3053
  export { StableBrowser };
2938
3054
  //# sourceMappingURL=stable_browser.js.map