automation_model 1.0.543-dev → 1.0.543-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,16 +10,18 @@ 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";
17
- import { getContext } from "./init_browser.js";
17
+ import { getContext, refreshBrowser } from "./init_browser.js";
18
18
  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",
@@ -47,8 +51,12 @@ const Types = {
47
51
  SET_INPUT: "set_input",
48
52
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
49
53
  VERIFY_ATTRIBUTE: "verify_element_attribute",
54
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
50
55
  };
51
56
  export const apps = {};
57
+ const formatElementName = (elementName) => {
58
+ return elementName ? JSON.stringify(elementName) : "element";
59
+ };
52
60
  class StableBrowser {
53
61
  browser;
54
62
  page;
@@ -61,7 +69,6 @@ class StableBrowser {
61
69
  configuration = null;
62
70
  appName = "main";
63
71
  tags = null;
64
- isRecording = false;
65
72
  constructor(browser, page, logger = null, context = null, world = null) {
66
73
  this.browser = browser;
67
74
  this.page = page;
@@ -92,35 +99,17 @@ class StableBrowser {
92
99
  catch (e) {
93
100
  this.logger.error("unable to read ai_config.json");
94
101
  }
95
- context.pageLoading = { status: false };
96
- context.pages = [this.page];
97
102
  const logFolder = path.join(this.project_path, "logs", "web");
98
103
  this.world = world;
104
+ context.pages = [this.page];
105
+ context.pageLoading = { status: false };
99
106
  this.registerEventListeners(this.context);
100
107
  registerNetworkEvents(this.world, this, this.context, this.page);
101
108
  registerDownloadEvent(this.page, this.world, this.context);
102
109
  }
103
- async scrollPageToLoadLazyElements() {
104
- let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
105
- let retry = 0;
106
- while (true) {
107
- await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
108
- await new Promise((resolve) => setTimeout(resolve, 1000));
109
- let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
110
- if (newHeight === lastHeight) {
111
- break;
112
- }
113
- lastHeight = newHeight;
114
- retry++;
115
- if (retry > 10) {
116
- break;
117
- }
118
- }
119
- await this.page.evaluate(() => window.scrollTo(0, 0));
120
- }
121
110
  registerEventListeners(context) {
122
111
  this.registerConsoleLogListener(this.page, context);
123
- this.registerRequestListener(this.page, context, this.webLogFile);
112
+ // this.registerRequestListener(this.page, context, this.webLogFile);
124
113
  if (!context.pageLoading) {
125
114
  context.pageLoading = { status: false };
126
115
  }
@@ -165,7 +154,7 @@ class StableBrowser {
165
154
  if (this.appName === appName) {
166
155
  return;
167
156
  }
168
- let newContextCreated = false;
157
+ let navigate = false;
169
158
  if (!apps[appName]) {
170
159
  let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
171
160
  newContextCreated = true;
@@ -176,32 +165,15 @@ class StableBrowser {
176
165
  };
177
166
  }
178
167
  const tempContext = {};
179
- this._copyContext(this, tempContext);
180
- this._copyContext(apps[appName], this);
168
+ _copyContext(this, tempContext);
169
+ _copyContext(apps[appName], this);
181
170
  apps[this.appName] = tempContext;
182
171
  this.appName = appName;
183
- if (newContextCreated) {
184
- this.registerEventListeners(this.context);
172
+ if (navigate) {
185
173
  await this.goto(this.context.environment.baseUrl);
186
174
  await this.waitForPageLoad();
187
175
  }
188
176
  }
189
- _copyContext(from, to) {
190
- to.browser = from.browser;
191
- to.page = from.page;
192
- to.context = from.context;
193
- }
194
- getWebLogFile(logFolder) {
195
- if (!fs.existsSync(logFolder)) {
196
- fs.mkdirSync(logFolder, { recursive: true });
197
- }
198
- let nextIndex = 1;
199
- while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
200
- nextIndex++;
201
- }
202
- const fileName = nextIndex + ".json";
203
- return path.join(logFolder, fileName);
204
- }
205
177
  registerConsoleLogListener(page, context) {
206
178
  if (!this.context.webLogger) {
207
179
  this.context.webLogger = [];
@@ -265,55 +237,51 @@ class StableBrowser {
265
237
  // async closeUnexpectedPopups() {
266
238
  // await closeUnexpectedPopups(this.page);
267
239
  // }
268
- 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
+ }
269
244
  if (!url.startsWith("http")) {
270
245
  url = "https://" + url;
271
246
  }
272
- await this.page.goto(url, {
273
- timeout: 60000,
274
- });
275
- }
276
- _fixUsingParams(text, _params) {
277
- if (!_params || typeof text !== "string") {
278
- 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);
279
266
  }
280
- for (let key in _params) {
281
- let regValue = key;
282
- if (key.startsWith("_")) {
283
- // remove the _ prefix
284
- regValue = key.substring(1);
285
- }
286
- text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
267
+ catch (error) {
268
+ console.error("Error on goto", error);
269
+ _commandError(state, error, this);
287
270
  }
288
- return text;
289
- }
290
- _fixLocatorUsingParams(locator, _params) {
291
- // check if not null
292
- if (!locator) {
293
- return locator;
271
+ finally {
272
+ _commandFinally(state, this);
294
273
  }
295
- // clone the locator
296
- locator = JSON.parse(JSON.stringify(locator));
297
- this.scanAndManipulate(locator, _params);
298
- return locator;
299
274
  }
300
- _isObject(value) {
301
- return value && typeof value === "object" && value.constructor === Object;
302
- }
303
- scanAndManipulate(currentObj, _params) {
304
- for (const key in currentObj) {
305
- if (typeof currentObj[key] === "string") {
306
- // Perform string manipulation
307
- currentObj[key] = this._fixUsingParams(currentObj[key], _params);
308
- }
309
- else if (this._isObject(currentObj[key])) {
310
- // Recursively scan nested objects
311
- 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);
312
283
  }
313
284
  }
314
- }
315
- _getLocator(locator, scope, _params) {
316
- locator = this._fixLocatorUsingParams(locator, _params);
317
285
  let locatorReturn;
318
286
  if (locator.role) {
319
287
  if (locator.role[1].nameReg) {
@@ -321,7 +289,7 @@ class StableBrowser {
321
289
  delete locator.role[1].nameReg;
322
290
  }
323
291
  // if (locator.role[1].name) {
324
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
292
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
325
293
  // }
326
294
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
327
295
  }
@@ -364,140 +332,54 @@ class StableBrowser {
364
332
  if (css && css.locator) {
365
333
  css = css.locator;
366
334
  }
367
- 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);
368
336
  if (result.elementCount === 0) {
369
337
  return;
370
338
  }
371
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
339
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
372
340
  // css climb to parent element
373
341
  const climbArray = [];
374
342
  for (let i = 0; i < climb; i++) {
375
343
  climbArray.push("..");
376
344
  }
377
345
  let climbXpath = "xpath=" + climbArray.join("/");
378
- return textElementCss + " >> " + climbXpath + " >> " + css;
379
- }
380
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
381
- //const stringifyText = JSON.stringify(text);
382
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
383
- function isParent(parent, child) {
384
- let currentNode = child.parentNode;
385
- while (currentNode !== null) {
386
- if (currentNode === parent) {
387
- return true;
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;
388
368
  }
389
- currentNode = currentNode.parentNode;
390
- }
391
- return false;
392
- }
393
- document.isParent = isParent;
394
- function getRegex(str) {
395
- const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
396
- if (!match) {
397
- return null;
398
- }
399
- let [_, pattern, flags] = match;
400
- return new RegExp(pattern, flags);
401
- }
402
- document.getRegex = getRegex;
403
- function collectAllShadowDomElements(element, result = []) {
404
- // Check and add the element if it has a shadow root
405
- if (element.shadowRoot) {
406
- result.push(element);
407
- // Also search within the shadow root
408
- document.collectAllShadowDomElements(element.shadowRoot, result);
409
- }
410
- // Iterate over child nodes
411
- element.childNodes.forEach((child) => {
412
- // Recursively call the function for each child node
413
- document.collectAllShadowDomElements(child, result);
414
- });
415
- return result;
416
- }
417
- document.collectAllShadowDomElements = collectAllShadowDomElements;
418
- if (!tag) {
419
- tag = "*:not(script, style, head)";
420
- }
421
- let regexpSearch = document.getRegex(text);
422
- if (regexpSearch) {
423
- regex = true;
424
- }
425
- let elements = Array.from(document.querySelectorAll(tag));
426
- let shadowHosts = [];
427
- document.collectAllShadowDomElements(document, shadowHosts);
428
- for (let i = 0; i < shadowHosts.length; i++) {
429
- let shadowElement = shadowHosts[i].shadowRoot;
430
- if (!shadowElement) {
431
- console.log("shadowElement is null, for host " + shadowHosts[i]);
432
- continue;
433
- }
434
- let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
435
- elements = elements.concat(shadowElements);
436
- }
437
- let randomToken = null;
438
- const foundElements = [];
439
- if (regex) {
440
- if (!regexpSearch) {
441
- regexpSearch = new RegExp(text, "im");
442
369
  }
443
- for (let i = 0; i < elements.length; i++) {
444
- const element = elements[i];
445
- if ((element.innerText && regexpSearch.test(element.innerText)) ||
446
- (element.value && regexpSearch.test(element.value))) {
447
- foundElements.push(element);
448
- }
370
+ if (!el.setAttribute) {
371
+ el = el.parentElement;
449
372
  }
373
+ el.setAttribute("data-blinq-id-" + randomToken, "");
374
+ return true;
375
+ }, [tag1, randomToken]))) {
376
+ continue;
450
377
  }
451
- else {
452
- text = text.trim();
453
- for (let i = 0; i < elements.length; i++) {
454
- const element = elements[i];
455
- if (partial) {
456
- if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
457
- (element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
458
- foundElements.push(element);
459
- }
460
- }
461
- else {
462
- if ((element.innerText && element.innerText.trim() === text) ||
463
- (element.value && element.value === text)) {
464
- foundElements.push(element);
465
- }
466
- }
467
- }
468
- }
469
- let noChildElements = [];
470
- for (let i = 0; i < foundElements.length; i++) {
471
- let element = foundElements[i];
472
- let hasChild = false;
473
- for (let j = 0; j < foundElements.length; j++) {
474
- if (i === j) {
475
- continue;
476
- }
477
- if (isParent(element, foundElements[j])) {
478
- hasChild = true;
479
- break;
480
- }
481
- }
482
- if (!hasChild) {
483
- noChildElements.push(element);
484
- }
485
- }
486
- let elementCount = 0;
487
- if (noChildElements.length > 0) {
488
- for (let i = 0; i < noChildElements.length; i++) {
489
- if (randomToken === null) {
490
- randomToken = Math.random().toString(36).substring(7);
491
- }
492
- let element = noChildElements[i];
493
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
494
- elementCount++;
495
- }
496
- }
497
- return { elementCount: elementCount, randomToken: randomToken };
498
- }, [text1, tag1, regex1, partial1]);
378
+ tagCount++;
379
+ }
380
+ return { elementCount: tagCount, randomToken };
499
381
  }
500
- 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) {
501
383
  if (!info) {
502
384
  info = {};
503
385
  }
@@ -506,10 +388,13 @@ class StableBrowser {
506
388
  }
507
389
  if (!info.log) {
508
390
  info.log = "";
391
+ info.locatorLog = new LocatorLog(selectorHierarchy);
509
392
  }
510
393
  let locatorSearch = selectorHierarchy[index];
394
+ let originalLocatorSearch = "";
511
395
  try {
512
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
396
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
397
+ locatorSearch = JSON.parse(originalLocatorSearch);
513
398
  }
514
399
  catch (e) {
515
400
  console.error(e);
@@ -517,30 +402,31 @@ class StableBrowser {
517
402
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
518
403
  let locator = null;
519
404
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
520
- 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);
521
407
  if (!locatorString) {
522
408
  info.failCause.textNotFound = true;
523
- 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}`;
524
410
  return;
525
411
  }
526
- locator = this._getLocator({ css: locatorString }, scope, _params);
412
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
527
413
  }
528
414
  else if (locatorSearch.text) {
529
- let text = this._fixUsingParams(locatorSearch.text, _params);
530
- 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);
531
417
  if (result.elementCount === 0) {
532
418
  info.failCause.textNotFound = true;
533
- info.failCause.lastError = "failed to locate element by text: " + text;
419
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
534
420
  return;
535
421
  }
536
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
422
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
537
423
  if (locatorSearch.childCss) {
538
424
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
539
425
  }
540
- locator = this._getLocator(locatorSearch, scope, _params);
426
+ locator = await this._getLocator(locatorSearch, scope, _params);
541
427
  }
542
428
  else {
543
- locator = this._getLocator(locatorSearch, scope, _params);
429
+ locator = await this._getLocator(locatorSearch, scope, _params);
544
430
  }
545
431
  // let cssHref = false;
546
432
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -553,18 +439,27 @@ class StableBrowser {
553
439
  //info.log += "total elements found " + count + "\n";
554
440
  //let visibleCount = 0;
555
441
  let visibleLocator = null;
556
- if (locatorSearch.index && locatorSearch.index < count) {
442
+ if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
557
443
  foundLocators.push(locator.nth(locatorSearch.index));
444
+ if (info.locatorLog) {
445
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
446
+ }
558
447
  return;
559
448
  }
449
+ if (info.locatorLog && count === 0) {
450
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
451
+ }
560
452
  for (let j = 0; j < count; j++) {
561
453
  let visible = await locator.nth(j).isVisible();
562
454
  const enabled = await locator.nth(j).isEnabled();
563
455
  if (!visibleOnly) {
564
456
  visible = true;
565
457
  }
566
- if (visible && enabled) {
458
+ if (visible && (allowDisabled || enabled)) {
567
459
  foundLocators.push(locator.nth(j));
460
+ if (info.locatorLog) {
461
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
462
+ }
568
463
  }
569
464
  else {
570
465
  info.failCause.visible = visible;
@@ -572,8 +467,16 @@ class StableBrowser {
572
467
  if (!info.printMessages) {
573
468
  info.printMessages = {};
574
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
+ }
575
478
  if (!info.printMessages[j.toString()]) {
576
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
479
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
577
480
  info.printMessages[j.toString()] = true;
578
481
  }
579
482
  }
@@ -589,12 +492,12 @@ class StableBrowser {
589
492
  if (!info) {
590
493
  info = {};
591
494
  }
592
- info.log += "scan for popup handlers" + "\n";
495
+ //info.log += "scan for popup handlers" + "\n";
593
496
  const handlerGroup = [];
594
497
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
595
498
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
596
499
  }
597
- const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
500
+ const scopes = [this.page, ...this.page.frames()];
598
501
  let result = null;
599
502
  let scope = null;
600
503
  for (let i = 0; i < scopes.length; i++) {
@@ -637,7 +540,7 @@ class StableBrowser {
637
540
  }
638
541
  return { rerun: false };
639
542
  }
640
- async _locate(selectors, info, _params, timeout) {
543
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
641
544
  if (!timeout) {
642
545
  timeout = 30000;
643
546
  }
@@ -647,9 +550,18 @@ class StableBrowser {
647
550
  let selector = selectors.locators[j];
648
551
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
649
552
  }
650
- let element = await this._locate_internal(selectors, info, _params, timeout);
553
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
651
554
  if (!element.rerun) {
652
- 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;
653
565
  }
654
566
  }
655
567
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -691,9 +603,11 @@ class StableBrowser {
691
603
  }
692
604
  return framescope;
693
605
  };
606
+ let fLocator = null;
694
607
  while (true) {
695
608
  let frameFound = false;
696
609
  if (selectors.nestFrmLoc) {
610
+ fLocator = selectors.nestFrmLoc;
697
611
  scope = await findFrame(selectors.nestFrmLoc, scope);
698
612
  frameFound = true;
699
613
  break;
@@ -702,6 +616,7 @@ class StableBrowser {
702
616
  for (let i = 0; i < selectors.frameLocators.length; i++) {
703
617
  let frameLocator = selectors.frameLocators[i];
704
618
  if (frameLocator.css) {
619
+ fLocator = frameLocator.css;
705
620
  scope = scope.frameLocator(frameLocator.css);
706
621
  frameFound = true;
707
622
  break;
@@ -709,18 +624,25 @@ class StableBrowser {
709
624
  }
710
625
  }
711
626
  if (!frameFound && selectors.iframe_src) {
627
+ fLocator = selectors.iframe_src;
712
628
  scope = this.page.frame({ url: selectors.iframe_src });
713
629
  }
714
630
  if (!scope) {
715
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
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";
716
635
  if (Date.now() - startTime > timeout) {
717
636
  info.failCause.iframeNotFound = true;
718
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
637
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
719
638
  throw new Error("unable to locate iframe " + selectors.iframe_src);
720
639
  }
721
640
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
641
  }
723
642
  else {
643
+ if (info && info.locatorLog) {
644
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
645
+ }
724
646
  break;
725
647
  }
726
648
  }
@@ -737,11 +659,12 @@ class StableBrowser {
737
659
  return bodyContent;
738
660
  });
739
661
  }
740
- async _locate_internal(selectors, info, _params, timeout = 30000) {
662
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
741
663
  if (!info) {
742
664
  info = {};
743
665
  info.failCause = {};
744
666
  info.log = "";
667
+ info.locatorLog = new LocatorLog(selectors);
745
668
  }
746
669
  let highPriorityTimeout = 5000;
747
670
  let visibleOnlyTimeout = 6000;
@@ -785,17 +708,17 @@ class StableBrowser {
785
708
  }
786
709
  // info.log += "scanning locators in priority 1" + "\n";
787
710
  let onlyPriority3 = selectorsLocators[0].priority === 3;
788
- 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);
789
712
  if (result.foundElements.length === 0) {
790
713
  // info.log += "scanning locators in priority 2" + "\n";
791
- 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);
792
715
  }
793
716
  if (result.foundElements.length === 0 && onlyPriority3) {
794
- 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);
795
718
  }
796
719
  else {
797
720
  if (result.foundElements.length === 0 && !highPriorityOnly) {
798
- 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);
799
722
  }
800
723
  }
801
724
  let foundElements = result.foundElements;
@@ -840,26 +763,34 @@ class StableBrowser {
840
763
  break;
841
764
  }
842
765
  if (Date.now() - startTime > highPriorityTimeout) {
843
- info.log += "high priority timeout, will try all elements" + "\n";
766
+ //info.log += "high priority timeout, will try all elements" + "\n";
844
767
  highPriorityOnly = false;
845
768
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
846
769
  lazy_scroll = true;
847
- await this.scrollPageToLoadLazyElements();
770
+ await scrollPageToLoadLazyElements(this.page);
848
771
  }
849
772
  }
850
773
  if (Date.now() - startTime > visibleOnlyTimeout) {
851
- info.log += "visible only timeout, will try all elements" + "\n";
774
+ //info.log += "visible only timeout, will try all elements" + "\n";
852
775
  visibleOnly = false;
853
776
  }
854
777
  await new Promise((resolve) => setTimeout(resolve, 1000));
855
778
  }
856
779
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
857
- 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";
858
787
  info.failCause.locatorNotFound = true;
859
- 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
+ }
860
791
  throw new Error("failed to locate first element no elements found, " + info.log);
861
792
  }
862
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
793
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
863
794
  let foundElements = [];
864
795
  const result = {
865
796
  foundElements: foundElements,
@@ -867,14 +798,15 @@ class StableBrowser {
867
798
  for (let i = 0; i < locatorsGroup.length; i++) {
868
799
  let foundLocators = [];
869
800
  try {
870
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
801
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
871
802
  }
872
803
  catch (e) {
873
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
874
- 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);
875
807
  foundLocators = [];
876
808
  try {
877
- 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);
878
810
  }
879
811
  catch (e) {
880
812
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -890,11 +822,27 @@ class StableBrowser {
890
822
  }
891
823
  if (foundLocators.length > 1) {
892
824
  info.failCause.foundMultiple = true;
825
+ if (info.locatorLog) {
826
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
827
+ }
893
828
  }
894
829
  }
895
830
  return result;
896
831
  }
897
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);
898
846
  const startTime = Date.now();
899
847
  let timeout = 30000;
900
848
  if (options && options.timeout) {
@@ -917,15 +865,33 @@ class StableBrowser {
917
865
  }
918
866
  }
919
867
  catch (e) {
920
- if (Date.now() - startTime > timeout) {
868
+ if (performance.now() - startTime > timeout) {
921
869
  // throw e;
922
- 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
+ }
923
876
  }
924
877
  }
925
878
  await new Promise((resolve) => setTimeout(resolve, 3000));
926
879
  }
927
880
  }
928
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);
929
895
  const startTime = Date.now();
930
896
  let timeout = 30000;
931
897
  if (options && options.timeout) {
@@ -948,9 +914,14 @@ class StableBrowser {
948
914
  }
949
915
  }
950
916
  catch (e) {
951
- if (Date.now() - startTime > timeout) {
917
+ if (performance.now() - startTime > timeout) {
952
918
  // throw e;
953
- 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
+ }
954
925
  }
955
926
  }
956
927
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -969,9 +940,9 @@ class StableBrowser {
969
940
  };
970
941
  try {
971
942
  await _preCommand(state, this);
972
- if (state.options && state.options.context) {
973
- state.selectors.locators[0].text = state.options.context;
974
- }
943
+ // if (state.options && state.options.context) {
944
+ // state.selectors.locators[0].text = state.options.context;
945
+ // }
975
946
  try {
976
947
  await state.element.click();
977
948
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -1009,9 +980,15 @@ class StableBrowser {
1009
980
  // let element = await this._locate(selectors, info, _params);
1010
981
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1011
982
  try {
1012
- // await this._highlightElements(element);
983
+ // if (world && world.screenshot && !world.screenshotPath) {
984
+ // console.log(`Highlighting while running from recorder`);
985
+ await this._highlightElements(element);
1013
986
  await state.element.setChecked(checked);
1014
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);
1015
992
  }
1016
993
  catch (e) {
1017
994
  if (e.message && e.message.includes("did not change its state")) {
@@ -1050,6 +1027,7 @@ class StableBrowser {
1050
1027
  await _preCommand(state, this);
1051
1028
  try {
1052
1029
  await state.element.hover();
1030
+ // await _screenshot(state, this);
1053
1031
  await new Promise((resolve) => setTimeout(resolve, 1000));
1054
1032
  }
1055
1033
  catch (e) {
@@ -1057,6 +1035,7 @@ class StableBrowser {
1057
1035
  state.info.log += "hover failed, will try again" + "\n";
1058
1036
  state.element = await this._locate(selectors, state.info, _params);
1059
1037
  await state.element.hover({ timeout: 10000 });
1038
+ // await _screenshot(state, this);
1060
1039
  await new Promise((resolve) => setTimeout(resolve, 1000));
1061
1040
  }
1062
1041
  await _screenshot(state, this);
@@ -1331,7 +1310,12 @@ class StableBrowser {
1331
1310
  await this.waitForPageLoad();
1332
1311
  }
1333
1312
  else if (enter === false) {
1334
- await state.element.dispatchEvent("change");
1313
+ try {
1314
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1315
+ }
1316
+ catch (e) {
1317
+ // ignore
1318
+ }
1335
1319
  //await this.page.keyboard.press("Tab");
1336
1320
  }
1337
1321
  else {
@@ -1383,15 +1367,17 @@ class StableBrowser {
1383
1367
  return await this._getText(selectors, 0, _params, options, info, world);
1384
1368
  }
1385
1369
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1370
+ const timeout = this._getFindElementTimeout(options);
1386
1371
  _validateSelectors(selectors);
1387
1372
  let screenshotId = null;
1388
1373
  let screenshotPath = null;
1389
1374
  if (!info.log) {
1390
1375
  info.log = "";
1376
+ info.locatorLog = new LocatorLog(selectors);
1391
1377
  }
1392
1378
  info.operation = "getText";
1393
1379
  info.selectors = selectors;
1394
- let element = await this._locate(selectors, info, _params);
1380
+ let element = await this._locate(selectors, info, _params, timeout);
1395
1381
  if (climb > 0) {
1396
1382
  const climbArray = [];
1397
1383
  for (let i = 0; i < climb; i++) {
@@ -1410,6 +1396,18 @@ class StableBrowser {
1410
1396
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1411
1397
  try {
1412
1398
  await this._highlightElements(element);
1399
+ // if (world && world.screenshot && !world.screenshotPath) {
1400
+ // // console.log(`Highlighting for get text while running from recorder`);
1401
+ // this._highlightElements(element)
1402
+ // .then(async () => {
1403
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1404
+ // this._unhighlightElements(element).then(
1405
+ // () => {}
1406
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1407
+ // );
1408
+ // })
1409
+ // .catch(e);
1410
+ // }
1413
1411
  const elementText = await element.innerText();
1414
1412
  return {
1415
1413
  text: elementText,
@@ -1421,7 +1419,7 @@ class StableBrowser {
1421
1419
  }
1422
1420
  catch (e) {
1423
1421
  //await this.closeUnexpectedPopups();
1424
- this.logger.info("no innerText will use textContent");
1422
+ this.logger.info("no innerText, will use textContent");
1425
1423
  const elementText = await element.textContent();
1426
1424
  return { text: elementText, screenshotId, screenshotPath, value: value };
1427
1425
  }
@@ -1481,6 +1479,8 @@ class StableBrowser {
1481
1479
  }
1482
1480
  }
1483
1481
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1482
+ const timeout = this._getFindElementTimeout(options);
1483
+ const startTime = Date.now();
1484
1484
  const state = {
1485
1485
  selectors,
1486
1486
  _params,
@@ -1507,62 +1507,54 @@ class StableBrowser {
1507
1507
  }
1508
1508
  let foundObj = null;
1509
1509
  try {
1510
- await _preCommand(state, this);
1511
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1512
- if (foundObj && foundObj.element) {
1513
- await this.scrollIfNeeded(foundObj.element, state.info);
1514
- }
1515
- await _screenshot(state, this);
1516
- const dateAlternatives = findDateAlternatives(text);
1517
- const numberAlternatives = findNumberAlternatives(text);
1518
- if (dateAlternatives.date) {
1519
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1520
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1521
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1522
- return state.info;
1510
+ while (Date.now() - startTime < timeout) {
1511
+ try {
1512
+ await _preCommand(state, this);
1513
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1514
+ if (foundObj && foundObj.element) {
1515
+ await this.scrollIfNeeded(foundObj.element, state.info);
1523
1516
  }
1524
- }
1525
- throw new Error("element doesn't contain text " + text);
1526
- }
1527
- else if (numberAlternatives.number) {
1528
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1529
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1530
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1517
+ await _screenshot(state, this);
1518
+ const dateAlternatives = findDateAlternatives(text);
1519
+ const numberAlternatives = findNumberAlternatives(text);
1520
+ if (dateAlternatives.date) {
1521
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1522
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1523
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1524
+ return state.info;
1525
+ }
1526
+ }
1527
+ }
1528
+ else if (numberAlternatives.number) {
1529
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1530
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1531
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1532
+ return state.info;
1533
+ }
1534
+ }
1535
+ }
1536
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1531
1537
  return state.info;
1532
1538
  }
1533
1539
  }
1534
- throw new Error("element doesn't contain text " + text);
1535
- }
1536
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1537
- state.info.foundText = foundObj?.text;
1538
- state.info.value = foundObj?.value;
1539
- throw new Error("element doesn't contain text " + text);
1540
+ catch (e) {
1541
+ // Log error but continue retrying until timeout is reached
1542
+ this.logger.warn("Retrying containsText due to: " + e.message);
1543
+ }
1544
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1540
1545
  }
1541
- return state.info;
1546
+ state.info.foundText = foundObj?.text;
1547
+ state.info.value = foundObj?.value;
1548
+ throw new Error("element doesn't contain text " + text);
1542
1549
  }
1543
1550
  catch (e) {
1544
1551
  await _commandError(state, e, this);
1552
+ throw e;
1545
1553
  }
1546
1554
  finally {
1547
1555
  _commandFinally(state, this);
1548
1556
  }
1549
1557
  }
1550
- _getDataFile(world = null) {
1551
- let dataFile = null;
1552
- if (world && world.reportFolder) {
1553
- dataFile = path.join(world.reportFolder, "data.json");
1554
- }
1555
- else if (this.reportFolder) {
1556
- dataFile = path.join(this.reportFolder, "data.json");
1557
- }
1558
- else if (this.context && this.context.reportFolder) {
1559
- dataFile = path.join(this.context.reportFolder, "data.json");
1560
- }
1561
- else {
1562
- dataFile = "data.json";
1563
- }
1564
- return dataFile;
1565
- }
1566
1558
  async waitForUserInput(message, world = null) {
1567
1559
  if (!message) {
1568
1560
  message = "# Wait for user input. Press any key to continue";
@@ -1591,7 +1583,7 @@ class StableBrowser {
1591
1583
  return;
1592
1584
  }
1593
1585
  // if data file exists, load it
1594
- const dataFile = this._getDataFile(world);
1586
+ const dataFile = _getDataFile(world, this.context, this);
1595
1587
  let data = this.getTestData(world);
1596
1588
  // merge the testData with the existing data
1597
1589
  Object.assign(data, testData);
@@ -1694,7 +1686,7 @@ class StableBrowser {
1694
1686
  }
1695
1687
  }
1696
1688
  getTestData(world = null) {
1697
- const dataFile = this._getDataFile(world);
1689
+ const dataFile = _getDataFile(world, this.context, this);
1698
1690
  let data = {};
1699
1691
  if (fs.existsSync(dataFile)) {
1700
1692
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1781,6 +1773,15 @@ class StableBrowser {
1781
1773
  document.documentElement.clientWidth,
1782
1774
  ])));
1783
1775
  let screenshotBuffer = null;
1776
+ // if (focusedElement) {
1777
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1778
+ // await this._unhighlightElements(focusedElement);
1779
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1780
+ // console.log(`Unhighlighted previous element`);
1781
+ // }
1782
+ // if (focusedElement) {
1783
+ // await this._highlightElements(focusedElement);
1784
+ // }
1784
1785
  if (this.context.browserName === "chromium") {
1785
1786
  const client = await playContext.newCDPSession(this.page);
1786
1787
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1802,6 +1803,10 @@ class StableBrowser {
1802
1803
  else {
1803
1804
  screenshotBuffer = await this.page.screenshot();
1804
1805
  }
1806
+ // if (focusedElement) {
1807
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1808
+ // await this._unhighlightElements(focusedElement);
1809
+ // }
1805
1810
  let image = await Jimp.read(screenshotBuffer);
1806
1811
  // Get the image dimensions
1807
1812
  const { width, height } = image.bitmap;
@@ -1814,6 +1819,7 @@ class StableBrowser {
1814
1819
  else {
1815
1820
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1816
1821
  }
1822
+ return screenshotBuffer;
1817
1823
  }
1818
1824
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1819
1825
  const state = {
@@ -1851,6 +1857,7 @@ class StableBrowser {
1851
1857
  text: `Extract attribute from element`,
1852
1858
  operation: "extractAttribute",
1853
1859
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1860
+ allowDisabled: true,
1854
1861
  };
1855
1862
  await new Promise((resolve) => setTimeout(resolve, 2000));
1856
1863
  try {
@@ -1872,6 +1879,7 @@ class StableBrowser {
1872
1879
  state.info.value = state.value;
1873
1880
  this.setTestData({ [variable]: state.value }, world);
1874
1881
  this.logger.info("set test data: " + variable + "=" + state.value);
1882
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1875
1883
  return state.info;
1876
1884
  }
1877
1885
  catch (e) {
@@ -1890,38 +1898,58 @@ class StableBrowser {
1890
1898
  options,
1891
1899
  world,
1892
1900
  type: Types.VERIFY_ATTRIBUTE,
1901
+ highlight: true,
1902
+ screenshot: true,
1893
1903
  text: `Verify element attribute`,
1904
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1894
1905
  operation: "verifyAttribute",
1895
1906
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1907
+ allowDisabled: true,
1896
1908
  };
1897
1909
  await new Promise((resolve) => setTimeout(resolve, 2000));
1898
1910
  let val;
1911
+ let expectedValue;
1899
1912
  try {
1900
1913
  await _preCommand(state, this);
1914
+ expectedValue = state.value;
1915
+ state.info.expectedValue = expectedValue;
1901
1916
  switch (attribute) {
1902
1917
  case "innerText":
1903
- val = await state.element.innerText();
1918
+ val = String(await state.element.innerText());
1904
1919
  break;
1905
1920
  case "value":
1906
- val = await state.element.inputValue();
1921
+ val = String(await state.element.inputValue());
1922
+ break;
1923
+ case "checked":
1924
+ val = String(await state.element.isChecked());
1925
+ break;
1926
+ case "disabled":
1927
+ val = String(await state.element.isDisabled());
1928
+ break;
1929
+ case "readOnly":
1930
+ const isEditable = await state.element.isEditable();
1931
+ val = String(!isEditable);
1907
1932
  break;
1908
1933
  default:
1909
- val = await state.element.getAttribute(attribute);
1934
+ val = String(await state.element.getAttribute(attribute));
1910
1935
  break;
1911
1936
  }
1937
+ state.info.value = val;
1912
1938
  let regex;
1913
- if (value.startsWith("/") && value.endsWith("/")) {
1914
- const patternBody = value.slice(1, -1);
1939
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1940
+ const patternBody = expectedValue.slice(1, -1);
1915
1941
  regex = new RegExp(patternBody, "g");
1916
1942
  }
1917
1943
  else {
1918
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1944
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1919
1945
  regex = new RegExp(escapedPattern, "g");
1920
1946
  }
1921
1947
  if (!val.match(regex)) {
1922
- throw new Error(`Attribute ${attribute} value is ${val} but expected ${value}`);
1948
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1949
+ state.info.failCause.assertionFailed = true;
1950
+ state.info.failCause.lastError = errorMessage;
1951
+ throw new Error(errorMessage);
1923
1952
  }
1924
- state.info.value = val;
1925
1953
  return state.info;
1926
1954
  }
1927
1955
  catch (e) {
@@ -1949,7 +1977,7 @@ class StableBrowser {
1949
1977
  if (options && options.timeout) {
1950
1978
  timeout = options.timeout;
1951
1979
  }
1952
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1980
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1953
1981
  const request = {
1954
1982
  method: "POST",
1955
1983
  url: serviceUrl,
@@ -2020,27 +2048,32 @@ class StableBrowser {
2020
2048
  async _highlightElements(scope, css) {
2021
2049
  try {
2022
2050
  if (!scope) {
2051
+ // console.log(`Scope is not defined`);
2023
2052
  return;
2024
2053
  }
2025
2054
  if (!css) {
2026
2055
  scope
2027
2056
  .evaluate((node) => {
2028
2057
  if (node && node.style) {
2029
- let originalBorder = node.style.border;
2030
- node.style.border = "2px solid red";
2058
+ let originalOutline = node.style.outline;
2059
+ // console.log(`Original outline was: ${originalOutline}`);
2060
+ // node.__previousOutline = originalOutline;
2061
+ node.style.outline = "2px solid red";
2062
+ // console.log(`New outline is: ${node.style.outline}`);
2031
2063
  if (window) {
2032
2064
  window.addEventListener("beforeunload", function (e) {
2033
- node.style.border = originalBorder;
2065
+ node.style.outline = originalOutline;
2034
2066
  });
2035
2067
  }
2036
2068
  setTimeout(function () {
2037
- node.style.border = originalBorder;
2069
+ node.style.outline = originalOutline;
2038
2070
  }, 2000);
2039
2071
  }
2040
2072
  })
2041
2073
  .then(() => { })
2042
2074
  .catch((e) => {
2043
2075
  // ignore
2076
+ // console.error(`Could not highlight node : ${e}`);
2044
2077
  });
2045
2078
  }
2046
2079
  else {
@@ -2056,17 +2089,18 @@ class StableBrowser {
2056
2089
  if (!element.style) {
2057
2090
  return;
2058
2091
  }
2059
- var originalBorder = element.style.border;
2092
+ let originalOutline = element.style.outline;
2093
+ element.__previousOutline = originalOutline;
2060
2094
  // Set the new border to be red and 2px solid
2061
- element.style.border = "2px solid red";
2095
+ element.style.outline = "2px solid red";
2062
2096
  if (window) {
2063
2097
  window.addEventListener("beforeunload", function (e) {
2064
- element.style.border = originalBorder;
2098
+ element.style.outline = originalOutline;
2065
2099
  });
2066
2100
  }
2067
2101
  // Set a timeout to revert to the original border after 2 seconds
2068
2102
  setTimeout(function () {
2069
- element.style.border = originalBorder;
2103
+ element.style.outline = originalOutline;
2070
2104
  }, 2000);
2071
2105
  }
2072
2106
  return;
@@ -2074,6 +2108,7 @@ class StableBrowser {
2074
2108
  .then(() => { })
2075
2109
  .catch((e) => {
2076
2110
  // ignore
2111
+ // console.error(`Could not highlight css: ${e}`);
2077
2112
  });
2078
2113
  }
2079
2114
  }
@@ -2081,6 +2116,54 @@ class StableBrowser {
2081
2116
  console.debug(error);
2082
2117
  }
2083
2118
  }
2119
+ // async _unhighlightElements(scope, css) {
2120
+ // try {
2121
+ // if (!scope) {
2122
+ // return;
2123
+ // }
2124
+ // if (!css) {
2125
+ // scope
2126
+ // .evaluate((node) => {
2127
+ // if (node && node.style) {
2128
+ // if (!node.__previousOutline) {
2129
+ // node.style.outline = "";
2130
+ // } else {
2131
+ // node.style.outline = node.__previousOutline;
2132
+ // }
2133
+ // }
2134
+ // })
2135
+ // .then(() => {})
2136
+ // .catch((e) => {
2137
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2138
+ // });
2139
+ // } else {
2140
+ // scope
2141
+ // .evaluate(([css]) => {
2142
+ // if (!css) {
2143
+ // return;
2144
+ // }
2145
+ // let elements = Array.from(document.querySelectorAll(css));
2146
+ // for (i = 0; i < elements.length; i++) {
2147
+ // let element = elements[i];
2148
+ // if (!element.style) {
2149
+ // return;
2150
+ // }
2151
+ // if (!element.__previousOutline) {
2152
+ // element.style.outline = "";
2153
+ // } else {
2154
+ // element.style.outline = element.__previousOutline;
2155
+ // }
2156
+ // }
2157
+ // })
2158
+ // .then(() => {})
2159
+ // .catch((e) => {
2160
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2161
+ // });
2162
+ // }
2163
+ // } catch (error) {
2164
+ // // console.debug(error);
2165
+ // }
2166
+ // }
2084
2167
  async verifyPagePath(pathPart, options = {}, world = null) {
2085
2168
  const startTime = Date.now();
2086
2169
  let error = null;
@@ -2142,6 +2225,35 @@ class StableBrowser {
2142
2225
  });
2143
2226
  }
2144
2227
  }
2228
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2229
+ const frames = this.page.frames();
2230
+ let results = [];
2231
+ let ignoreCase = false;
2232
+ for (let i = 0; i < frames.length; i++) {
2233
+ if (dateAlternatives.date) {
2234
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2235
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2236
+ result.frame = frames[i];
2237
+ results.push(result);
2238
+ }
2239
+ }
2240
+ else if (numberAlternatives.number) {
2241
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2242
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2243
+ result.frame = frames[i];
2244
+ results.push(result);
2245
+ }
2246
+ }
2247
+ else {
2248
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2249
+ result.frame = frames[i];
2250
+ results.push(result);
2251
+ }
2252
+ }
2253
+ state.info.results = results;
2254
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2255
+ return resultWithElementsFound;
2256
+ }
2145
2257
  async verifyTextExistInPage(text, options = {}, world = null) {
2146
2258
  text = unEscapeString(text);
2147
2259
  const state = {
@@ -2151,12 +2263,12 @@ class StableBrowser {
2151
2263
  locate: false,
2152
2264
  scroll: false,
2153
2265
  highlight: false,
2154
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2266
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2155
2267
  text: `Verify text exists in page`,
2156
2268
  operation: "verifyTextExistInPage",
2157
2269
  log: "***** verify text " + text + " exists in page *****\n",
2158
2270
  };
2159
- const timeout = this._getLoadTimeout(options);
2271
+ const timeout = this._getFindElementTimeout(options);
2160
2272
  await new Promise((resolve) => setTimeout(resolve, 2000));
2161
2273
  const newValue = await this._replaceWithLocalData(text, world);
2162
2274
  if (newValue !== text) {
@@ -2169,31 +2281,15 @@ class StableBrowser {
2169
2281
  await _preCommand(state, this);
2170
2282
  state.info.text = text;
2171
2283
  while (true) {
2172
- const frames = this.page.frames();
2173
- let results = [];
2174
- for (let i = 0; i < frames.length; i++) {
2175
- if (dateAlternatives.date) {
2176
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2177
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2178
- result.frame = frames[i];
2179
- results.push(result);
2180
- }
2181
- }
2182
- else if (numberAlternatives.number) {
2183
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2184
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2185
- result.frame = frames[i];
2186
- results.push(result);
2187
- }
2188
- }
2189
- else {
2190
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2191
- result.frame = frames[i];
2192
- results.push(result);
2193
- }
2284
+ let resultWithElementsFound = {
2285
+ length: 0,
2286
+ };
2287
+ try {
2288
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2289
+ }
2290
+ catch (error) {
2291
+ // ignore
2194
2292
  }
2195
- state.info.results = results;
2196
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2197
2293
  if (resultWithElementsFound.length === 0) {
2198
2294
  if (Date.now() - state.startTime > timeout) {
2199
2295
  throw new Error(`Text ${text} not found in page`);
@@ -2201,18 +2297,40 @@ class StableBrowser {
2201
2297
  await new Promise((resolve) => setTimeout(resolve, 1000));
2202
2298
  continue;
2203
2299
  }
2204
- if (resultWithElementsFound[0].randomToken) {
2205
- const frame = resultWithElementsFound[0].frame;
2206
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2207
- await this._highlightElements(frame, dataAttribute);
2208
- const element = await frame.$(dataAttribute);
2209
- if (element) {
2210
- await this.scrollIfNeeded(element, state.info);
2211
- await element.dispatchEvent("bvt_verify_page_contains_text");
2300
+ try {
2301
+ if (resultWithElementsFound[0].randomToken) {
2302
+ const frame = resultWithElementsFound[0].frame;
2303
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2304
+ await this._highlightElements(frame, dataAttribute);
2305
+ // if (world && world.screenshot && !world.screenshotPath) {
2306
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2307
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2308
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2309
+ // this._unhighlightElements(frame, dataAttribute)
2310
+ // .then(async () => {
2311
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2312
+ // })
2313
+ // .catch(
2314
+ // (e) => {}
2315
+ // console.error(e)
2316
+ // );
2317
+ // });
2318
+ // }
2319
+ const element = await frame.locator(dataAttribute).first();
2320
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2321
+ // await this._unhighlightElements(frame, dataAttribute);
2322
+ if (element) {
2323
+ await this.scrollIfNeeded(element, state.info);
2324
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2325
+ // await _screenshot(state, this, element);
2326
+ }
2212
2327
  }
2328
+ await _screenshot(state, this);
2329
+ return state.info;
2330
+ }
2331
+ catch (error) {
2332
+ console.error(error);
2213
2333
  }
2214
- await _screenshot(state, this);
2215
- return state.info;
2216
2334
  }
2217
2335
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2218
2336
  }
@@ -2237,7 +2355,7 @@ class StableBrowser {
2237
2355
  operation: "verifyTextNotExistInPage",
2238
2356
  log: "***** verify text " + text + " does not exist in page *****\n",
2239
2357
  };
2240
- const timeout = this._getLoadTimeout(options);
2358
+ const timeout = this._getFindElementTimeout(options);
2241
2359
  await new Promise((resolve) => setTimeout(resolve, 2000));
2242
2360
  const newValue = await this._replaceWithLocalData(text, world);
2243
2361
  if (newValue !== text) {
@@ -2249,32 +2367,16 @@ class StableBrowser {
2249
2367
  try {
2250
2368
  await _preCommand(state, this);
2251
2369
  state.info.text = text;
2370
+ let resultWithElementsFound = {
2371
+ length: null, // initial cannot be 0
2372
+ };
2252
2373
  while (true) {
2253
- const frames = this.page.frames();
2254
- let results = [];
2255
- for (let i = 0; i < frames.length; i++) {
2256
- if (dateAlternatives.date) {
2257
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2258
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2259
- result.frame = frames[i];
2260
- results.push(result);
2261
- }
2262
- }
2263
- else if (numberAlternatives.number) {
2264
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2265
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2266
- result.frame = frames[i];
2267
- results.push(result);
2268
- }
2269
- }
2270
- else {
2271
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2272
- result.frame = frames[i];
2273
- results.push(result);
2274
- }
2374
+ try {
2375
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2376
+ }
2377
+ catch (error) {
2378
+ // ignore
2275
2379
  }
2276
- state.info.results = results;
2277
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2278
2380
  if (resultWithElementsFound.length === 0) {
2279
2381
  await _screenshot(state, this);
2280
2382
  return state.info;
@@ -2292,15 +2394,115 @@ class StableBrowser {
2292
2394
  _commandFinally(state, this);
2293
2395
  }
2294
2396
  }
2295
- _getServerUrl() {
2296
- let serviceUrl = "https://api.blinq.io";
2297
- if (process.env.NODE_ENV_BLINQ === "dev") {
2298
- serviceUrl = "https://dev.api.blinq.io";
2397
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2398
+ textAnchor = unEscapeString(textAnchor);
2399
+ textToVerify = unEscapeString(textToVerify);
2400
+ const state = {
2401
+ text_search: textToVerify,
2402
+ options,
2403
+ world,
2404
+ locate: false,
2405
+ scroll: false,
2406
+ highlight: false,
2407
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2408
+ text: `Verify text with relation to another text`,
2409
+ operation: "verify_text_with_relation",
2410
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2411
+ };
2412
+ const timeout = this._getFindElementTimeout(options);
2413
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2414
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2415
+ if (newValue !== textAnchor) {
2416
+ this.logger.info(textAnchor + "=" + newValue);
2417
+ textAnchor = newValue;
2418
+ }
2419
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2420
+ if (newValue !== textToVerify) {
2421
+ this.logger.info(textToVerify + "=" + newValue);
2422
+ textToVerify = newValue;
2423
+ }
2424
+ let dateAlternatives = findDateAlternatives(textToVerify);
2425
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2426
+ let foundAncore = false;
2427
+ try {
2428
+ await _preCommand(state, this);
2429
+ state.info.text = textToVerify;
2430
+ let resultWithElementsFound = {
2431
+ length: 0,
2432
+ };
2433
+ while (true) {
2434
+ try {
2435
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2436
+ }
2437
+ catch (error) {
2438
+ // ignore
2439
+ }
2440
+ if (resultWithElementsFound.length === 0) {
2441
+ if (Date.now() - state.startTime > timeout) {
2442
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2443
+ }
2444
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2445
+ continue;
2446
+ }
2447
+ try {
2448
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2449
+ foundAncore = true;
2450
+ const result = resultWithElementsFound[i];
2451
+ const token = result.randomToken;
2452
+ const frame = result.frame;
2453
+ let css = `[data-blinq-id-${token}]`;
2454
+ const climbArray1 = [];
2455
+ for (let i = 0; i < climb; i++) {
2456
+ climbArray1.push("..");
2457
+ }
2458
+ let climbXpath = "xpath=" + climbArray1.join("/");
2459
+ css = css + " >> " + climbXpath;
2460
+ const count = await frame.locator(css).count();
2461
+ for (let j = 0; j < count; j++) {
2462
+ const continer = await frame.locator(css).nth(j);
2463
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2464
+ if (result.elementCount > 0) {
2465
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2466
+ await this._highlightElements(frame, dataAttribute);
2467
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2468
+ // if (world && world.screenshot && !world.screenshotPath) {
2469
+ // console.log(`Highlighting for vtrt while running from recorder`);
2470
+ // this._highlightElements(frame, dataAttribute)
2471
+ // .then(async () => {
2472
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2473
+ // this._unhighlightElements(frame, dataAttribute).then(
2474
+ // () => {}
2475
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2476
+ // );
2477
+ // })
2478
+ // .catch(e);
2479
+ // }
2480
+ //await this._highlightElements(frame, cssAnchor);
2481
+ const element = await frame.locator(dataAttribute).first();
2482
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2483
+ // await this._unhighlightElements(frame, dataAttribute);
2484
+ if (element) {
2485
+ await this.scrollIfNeeded(element, state.info);
2486
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2487
+ }
2488
+ await _screenshot(state, this);
2489
+ return state.info;
2490
+ }
2491
+ }
2492
+ }
2493
+ }
2494
+ catch (error) {
2495
+ console.error(error);
2496
+ }
2497
+ }
2498
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2299
2499
  }
2300
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2301
- serviceUrl = "https://stage.api.blinq.io";
2500
+ catch (e) {
2501
+ await _commandError(state, e, this);
2502
+ }
2503
+ finally {
2504
+ _commandFinally(state, this);
2302
2505
  }
2303
- return serviceUrl;
2304
2506
  }
2305
2507
  async visualVerification(text, options = {}, world = null) {
2306
2508
  const startTime = Date.now();
@@ -2316,14 +2518,17 @@ class StableBrowser {
2316
2518
  throw new Error("TOKEN is not set");
2317
2519
  }
2318
2520
  try {
2319
- let serviceUrl = this._getServerUrl();
2521
+ let serviceUrl = _getServerUrl();
2320
2522
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2321
2523
  info.screenshotPath = screenshotPath;
2322
2524
  const screenshot = await this.takeScreenshot();
2323
- const request = {
2324
- method: "POST",
2525
+ let request = {
2526
+ method: "post",
2527
+ maxBodyLength: Infinity,
2325
2528
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2326
2529
  headers: {
2530
+ "x-bvt-project-id": path.basename(this.project_path),
2531
+ "x-source": "aaa",
2327
2532
  "Content-Type": "application/json",
2328
2533
  Authorization: `Bearer ${process.env.TOKEN}`,
2329
2534
  },
@@ -2332,7 +2537,7 @@ class StableBrowser {
2332
2537
  screenshot: screenshot,
2333
2538
  }),
2334
2539
  };
2335
- let result = await this.context.api.request(request);
2540
+ const result = await axios.request(request);
2336
2541
  if (result.data.status !== true) {
2337
2542
  throw new Error("Visual validation failed");
2338
2543
  }
@@ -2405,6 +2610,7 @@ class StableBrowser {
2405
2610
  let screenshotPath = null;
2406
2611
  const info = {};
2407
2612
  info.log = "";
2613
+ info.locatorLog = new LocatorLog(selectors);
2408
2614
  info.operation = "getTableData";
2409
2615
  info.selectors = selectors;
2410
2616
  try {
@@ -2480,7 +2686,7 @@ class StableBrowser {
2480
2686
  info.operation = "analyzeTable";
2481
2687
  info.selectors = selectors;
2482
2688
  info.query = query;
2483
- query = this._fixUsingParams(query, _params);
2689
+ query = _fixUsingParams(query, _params);
2484
2690
  info.query_fixed = query;
2485
2691
  info.operator = operator;
2486
2692
  info.value = value;
@@ -2625,6 +2831,32 @@ class StableBrowser {
2625
2831
  }
2626
2832
  return timeout;
2627
2833
  }
2834
+ _getFindElementTimeout(options) {
2835
+ if (options && options.timeout) {
2836
+ return options.timeout;
2837
+ }
2838
+ if (this.configuration.find_element_timeout) {
2839
+ return this.configuration.find_element_timeout;
2840
+ }
2841
+ return 30000;
2842
+ }
2843
+ async saveStoreState(path = null, world = null) {
2844
+ const storageState = await this.page.context().storageState();
2845
+ //const testDataFile = _getDataFile(world, this.context, this);
2846
+ if (path) {
2847
+ // save { storageState: storageState } into the path
2848
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2849
+ }
2850
+ else {
2851
+ await this.setTestData({ storageState: storageState }, world);
2852
+ }
2853
+ }
2854
+ async restoreSaveState(path = null, world = null) {
2855
+ await refreshBrowser(this, path, world);
2856
+ this.registerEventListeners(this.context);
2857
+ registerNetworkEvents(this.world, this, this.context, this.page);
2858
+ registerDownloadEvent(this.page, this.world, this.context);
2859
+ }
2628
2860
  async waitForPageLoad(options = {}, world = null) {
2629
2861
  let timeout = this._getLoadTimeout(options);
2630
2862
  const promiseArray = [];
@@ -2810,14 +3042,22 @@ class StableBrowser {
2810
3042
  }
2811
3043
  }
2812
3044
  async beforeStep(world, step) {
2813
- this.stepName = step.pickleStep.text;
2814
- this.logger.info("step: " + this.stepName);
2815
3045
  if (this.stepIndex === undefined) {
2816
3046
  this.stepIndex = 0;
2817
3047
  }
2818
3048
  else {
2819
3049
  this.stepIndex++;
2820
3050
  }
3051
+ if (step && step.pickleStep && step.pickleStep.text) {
3052
+ this.stepName = step.pickleStep.text;
3053
+ this.logger.info("step: " + this.stepName);
3054
+ }
3055
+ else if (step && step.text) {
3056
+ this.stepName = step.text;
3057
+ }
3058
+ else {
3059
+ this.stepName = "step " + this.stepIndex;
3060
+ }
2821
3061
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2822
3062
  if (this.context.browserObject.context) {
2823
3063
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2847,156 +3087,5 @@ function createTimedPromise(promise, label) {
2847
3087
  .then((result) => ({ status: "fulfilled", label, result }))
2848
3088
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2849
3089
  }
2850
- const KEYBOARD_EVENTS = [
2851
- "ALT",
2852
- "AltGraph",
2853
- "CapsLock",
2854
- "Control",
2855
- "Fn",
2856
- "FnLock",
2857
- "Hyper",
2858
- "Meta",
2859
- "NumLock",
2860
- "ScrollLock",
2861
- "Shift",
2862
- "Super",
2863
- "Symbol",
2864
- "SymbolLock",
2865
- "Enter",
2866
- "Tab",
2867
- "ArrowDown",
2868
- "ArrowLeft",
2869
- "ArrowRight",
2870
- "ArrowUp",
2871
- "End",
2872
- "Home",
2873
- "PageDown",
2874
- "PageUp",
2875
- "Backspace",
2876
- "Clear",
2877
- "Copy",
2878
- "CrSel",
2879
- "Cut",
2880
- "Delete",
2881
- "EraseEof",
2882
- "ExSel",
2883
- "Insert",
2884
- "Paste",
2885
- "Redo",
2886
- "Undo",
2887
- "Accept",
2888
- "Again",
2889
- "Attn",
2890
- "Cancel",
2891
- "ContextMenu",
2892
- "Escape",
2893
- "Execute",
2894
- "Find",
2895
- "Finish",
2896
- "Help",
2897
- "Pause",
2898
- "Play",
2899
- "Props",
2900
- "Select",
2901
- "ZoomIn",
2902
- "ZoomOut",
2903
- "BrightnessDown",
2904
- "BrightnessUp",
2905
- "Eject",
2906
- "LogOff",
2907
- "Power",
2908
- "PowerOff",
2909
- "PrintScreen",
2910
- "Hibernate",
2911
- "Standby",
2912
- "WakeUp",
2913
- "AllCandidates",
2914
- "Alphanumeric",
2915
- "CodeInput",
2916
- "Compose",
2917
- "Convert",
2918
- "Dead",
2919
- "FinalMode",
2920
- "GroupFirst",
2921
- "GroupLast",
2922
- "GroupNext",
2923
- "GroupPrevious",
2924
- "ModeChange",
2925
- "NextCandidate",
2926
- "NonConvert",
2927
- "PreviousCandidate",
2928
- "Process",
2929
- "SingleCandidate",
2930
- "HangulMode",
2931
- "HanjaMode",
2932
- "JunjaMode",
2933
- "Eisu",
2934
- "Hankaku",
2935
- "Hiragana",
2936
- "HiraganaKatakana",
2937
- "KanaMode",
2938
- "KanjiMode",
2939
- "Katakana",
2940
- "Romaji",
2941
- "Zenkaku",
2942
- "ZenkakuHanaku",
2943
- "F1",
2944
- "F2",
2945
- "F3",
2946
- "F4",
2947
- "F5",
2948
- "F6",
2949
- "F7",
2950
- "F8",
2951
- "F9",
2952
- "F10",
2953
- "F11",
2954
- "F12",
2955
- "Soft1",
2956
- "Soft2",
2957
- "Soft3",
2958
- "Soft4",
2959
- "ChannelDown",
2960
- "ChannelUp",
2961
- "Close",
2962
- "MailForward",
2963
- "MailReply",
2964
- "MailSend",
2965
- "MediaFastForward",
2966
- "MediaPause",
2967
- "MediaPlay",
2968
- "MediaPlayPause",
2969
- "MediaRecord",
2970
- "MediaRewind",
2971
- "MediaStop",
2972
- "MediaTrackNext",
2973
- "MediaTrackPrevious",
2974
- "AudioBalanceLeft",
2975
- "AudioBalanceRight",
2976
- "AudioBassBoostDown",
2977
- "AudioBassBoostToggle",
2978
- "AudioBassBoostUp",
2979
- "AudioFaderFront",
2980
- "AudioFaderRear",
2981
- "AudioSurroundModeNext",
2982
- "AudioTrebleDown",
2983
- "AudioTrebleUp",
2984
- "AudioVolumeDown",
2985
- "AudioVolumeMute",
2986
- "AudioVolumeUp",
2987
- "MicrophoneToggle",
2988
- "MicrophoneVolumeDown",
2989
- "MicrophoneVolumeMute",
2990
- "MicrophoneVolumeUp",
2991
- "TV",
2992
- "TV3DMode",
2993
- "TVAntennaCable",
2994
- "TVAudioDescription",
2995
- ];
2996
- function unEscapeString(str) {
2997
- const placeholder = "__NEWLINE__";
2998
- str = str.replace(new RegExp(placeholder, "g"), "\n");
2999
- return str;
3000
- }
3001
3090
  export { StableBrowser };
3002
3091
  //# sourceMappingURL=stable_browser.js.map