automation_model 1.0.545-dev → 1.0.545-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,17 +10,20 @@ 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, extractStepExampleParameters, 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",
26
+ WAIT_ELEMENT: "wait_element",
24
27
  NAVIGATE: "navigate",
25
28
  FILL: "fill_element",
26
29
  EXECUTE: "execute_page_method",
@@ -30,6 +33,8 @@ const Types = {
30
33
  GET_PAGE_STATUS: "get_page_status",
31
34
  CLICK_ROW_ACTION: "click_row_action",
32
35
  VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
36
+ VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
37
+ VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
33
38
  ANALYZE_TABLE: "analyze_table",
34
39
  SELECT: "select_combobox",
35
40
  VERIFY_PAGE_PATH: "verify_page_path",
@@ -47,8 +52,12 @@ const Types = {
47
52
  SET_INPUT: "set_input",
48
53
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
49
54
  VERIFY_ATTRIBUTE: "verify_element_attribute",
55
+ VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
50
56
  };
51
57
  export const apps = {};
58
+ const formatElementName = (elementName) => {
59
+ return elementName ? JSON.stringify(elementName) : "element";
60
+ };
52
61
  class StableBrowser {
53
62
  browser;
54
63
  page;
@@ -62,6 +71,7 @@ class StableBrowser {
62
71
  appName = "main";
63
72
  tags = null;
64
73
  isRecording = false;
74
+ initSnapshotTaken = false;
65
75
  constructor(browser, page, logger = null, context = null, world = null) {
66
76
  this.browser = browser;
67
77
  this.page = page;
@@ -100,27 +110,9 @@ class StableBrowser {
100
110
  registerNetworkEvents(this.world, this, this.context, this.page);
101
111
  registerDownloadEvent(this.page, this.world, this.context);
102
112
  }
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
113
  registerEventListeners(context) {
122
114
  this.registerConsoleLogListener(this.page, context);
123
- this.registerRequestListener(this.page, context, this.webLogFile);
115
+ // this.registerRequestListener(this.page, context, this.webLogFile);
124
116
  if (!context.pageLoading) {
125
117
  context.pageLoading = { status: false };
126
118
  }
@@ -176,8 +168,8 @@ class StableBrowser {
176
168
  };
177
169
  }
178
170
  const tempContext = {};
179
- this._copyContext(this, tempContext);
180
- this._copyContext(apps[appName], this);
171
+ _copyContext(this, tempContext);
172
+ _copyContext(apps[appName], this);
181
173
  apps[this.appName] = tempContext;
182
174
  this.appName = appName;
183
175
  if (newContextCreated) {
@@ -186,22 +178,6 @@ class StableBrowser {
186
178
  await this.waitForPageLoad();
187
179
  }
188
180
  }
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
181
  registerConsoleLogListener(page, context) {
206
182
  if (!this.context.webLogger) {
207
183
  this.context.webLogger = [];
@@ -265,55 +241,51 @@ class StableBrowser {
265
241
  // async closeUnexpectedPopups() {
266
242
  // await closeUnexpectedPopups(this.page);
267
243
  // }
268
- async goto(url) {
244
+ async goto(url, world = null) {
245
+ if (!url) {
246
+ throw new Error("url is null, verify that the environment file is correct");
247
+ }
269
248
  if (!url.startsWith("http")) {
270
249
  url = "https://" + url;
271
250
  }
272
- await this.page.goto(url, {
273
- timeout: 60000,
274
- });
275
- }
276
- _fixUsingParams(text, _params) {
277
- if (!_params || typeof text !== "string") {
278
- return text;
251
+ const state = {
252
+ value: url,
253
+ world: world,
254
+ type: Types.NAVIGATE,
255
+ text: `Navigate Page to: ${url}`,
256
+ operation: "goto",
257
+ log: "***** navigate page to " + url + " *****\n",
258
+ info: {},
259
+ locate: false,
260
+ scroll: false,
261
+ screenshot: false,
262
+ highlight: false,
263
+ };
264
+ try {
265
+ await _preCommand(state, this);
266
+ await this.page.goto(url, {
267
+ timeout: 60000,
268
+ });
269
+ await _screenshot(state, this);
279
270
  }
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]);
271
+ catch (error) {
272
+ console.error("Error on goto", error);
273
+ _commandError(state, error, this);
287
274
  }
288
- return text;
289
- }
290
- _fixLocatorUsingParams(locator, _params) {
291
- // check if not null
292
- if (!locator) {
293
- return locator;
275
+ finally {
276
+ _commandFinally(state, this);
294
277
  }
295
- // clone the locator
296
- locator = JSON.parse(JSON.stringify(locator));
297
- this.scanAndManipulate(locator, _params);
298
- return locator;
299
- }
300
- _isObject(value) {
301
- return value && typeof value === "object" && value.constructor === Object;
302
278
  }
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);
279
+ async _getLocator(locator, scope, _params) {
280
+ locator = _fixLocatorUsingParams(locator, _params);
281
+ // locator = await this._replaceWithLocalData(locator);
282
+ for (let key in locator) {
283
+ if (typeof locator[key] !== "string")
284
+ continue;
285
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
286
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
312
287
  }
313
288
  }
314
- }
315
- _getLocator(locator, scope, _params) {
316
- locator = this._fixLocatorUsingParams(locator, _params);
317
289
  let locatorReturn;
318
290
  if (locator.role) {
319
291
  if (locator.role[1].nameReg) {
@@ -321,7 +293,7 @@ class StableBrowser {
321
293
  delete locator.role[1].nameReg;
322
294
  }
323
295
  // if (locator.role[1].name) {
324
- // locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
296
+ // locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
325
297
  // }
326
298
  locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
327
299
  }
@@ -364,140 +336,54 @@ class StableBrowser {
364
336
  if (css && css.locator) {
365
337
  css = css.locator;
366
338
  }
367
- let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
339
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
368
340
  if (result.elementCount === 0) {
369
341
  return;
370
342
  }
371
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
343
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
372
344
  // css climb to parent element
373
345
  const climbArray = [];
374
346
  for (let i = 0; i < climb; i++) {
375
347
  climbArray.push("..");
376
348
  }
377
349
  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;
350
+ let resultCss = textElementCss + " >> " + climbXpath;
351
+ if (css) {
352
+ resultCss = resultCss + " >> " + css;
353
+ }
354
+ return resultCss;
355
+ }
356
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
357
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
358
+ const locator = scope.locator(query);
359
+ const count = await locator.count();
360
+ if (!tag1) {
361
+ tag1 = "*";
362
+ }
363
+ const randomToken = Math.random().toString(36).substring(7);
364
+ let tagCount = 0;
365
+ for (let i = 0; i < count; i++) {
366
+ const element = locator.nth(i);
367
+ // check if the tag matches
368
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
369
+ if (!tag.startsWith("*")) {
370
+ if (el.tagName.toLowerCase() !== tag) {
371
+ return false;
388
372
  }
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
373
  }
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
- }
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
- }
374
+ if (!el.setAttribute) {
375
+ el = el.parentElement;
449
376
  }
377
+ el.setAttribute("data-blinq-id-" + randomToken, "");
378
+ return true;
379
+ }, [tag1, randomToken]))) {
380
+ continue;
450
381
  }
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]);
382
+ tagCount++;
383
+ }
384
+ return { elementCount: tagCount, randomToken };
499
385
  }
500
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
386
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
501
387
  if (!info) {
502
388
  info = {};
503
389
  }
@@ -506,10 +392,13 @@ class StableBrowser {
506
392
  }
507
393
  if (!info.log) {
508
394
  info.log = "";
395
+ info.locatorLog = new LocatorLog(selectorHierarchy);
509
396
  }
510
397
  let locatorSearch = selectorHierarchy[index];
398
+ let originalLocatorSearch = "";
511
399
  try {
512
- locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
400
+ originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
401
+ locatorSearch = JSON.parse(originalLocatorSearch);
513
402
  }
514
403
  catch (e) {
515
404
  console.error(e);
@@ -517,30 +406,31 @@ class StableBrowser {
517
406
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
518
407
  let locator = null;
519
408
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
520
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
409
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
410
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
521
411
  if (!locatorString) {
522
412
  info.failCause.textNotFound = true;
523
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
413
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
524
414
  return;
525
415
  }
526
- locator = this._getLocator({ css: locatorString }, scope, _params);
416
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
527
417
  }
528
418
  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);
419
+ let text = _fixUsingParams(locatorSearch.text, _params);
420
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
531
421
  if (result.elementCount === 0) {
532
422
  info.failCause.textNotFound = true;
533
- info.failCause.lastError = "failed to locate element by text: " + text;
423
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
534
424
  return;
535
425
  }
536
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
426
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
537
427
  if (locatorSearch.childCss) {
538
428
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
539
429
  }
540
- locator = this._getLocator(locatorSearch, scope, _params);
430
+ locator = await this._getLocator(locatorSearch, scope, _params);
541
431
  }
542
432
  else {
543
- locator = this._getLocator(locatorSearch, scope, _params);
433
+ locator = await this._getLocator(locatorSearch, scope, _params);
544
434
  }
545
435
  // let cssHref = false;
546
436
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -555,16 +445,25 @@ class StableBrowser {
555
445
  let visibleLocator = null;
556
446
  if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
557
447
  foundLocators.push(locator.nth(locatorSearch.index));
448
+ if (info.locatorLog) {
449
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
450
+ }
558
451
  return;
559
452
  }
453
+ if (info.locatorLog && count === 0) {
454
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
455
+ }
560
456
  for (let j = 0; j < count; j++) {
561
457
  let visible = await locator.nth(j).isVisible();
562
458
  const enabled = await locator.nth(j).isEnabled();
563
459
  if (!visibleOnly) {
564
460
  visible = true;
565
461
  }
566
- if (visible && enabled) {
462
+ if (visible && (allowDisabled || enabled)) {
567
463
  foundLocators.push(locator.nth(j));
464
+ if (info.locatorLog) {
465
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
466
+ }
568
467
  }
569
468
  else {
570
469
  info.failCause.visible = visible;
@@ -572,8 +471,16 @@ class StableBrowser {
572
471
  if (!info.printMessages) {
573
472
  info.printMessages = {};
574
473
  }
474
+ if (info.locatorLog && !visible) {
475
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
476
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
477
+ }
478
+ if (info.locatorLog && !enabled) {
479
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
480
+ info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
481
+ }
575
482
  if (!info.printMessages[j.toString()]) {
576
- info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
483
+ //info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
577
484
  info.printMessages[j.toString()] = true;
578
485
  }
579
486
  }
@@ -589,7 +496,7 @@ class StableBrowser {
589
496
  if (!info) {
590
497
  info = {};
591
498
  }
592
- info.log += "scan for popup handlers" + "\n";
499
+ //info.log += "scan for popup handlers" + "\n";
593
500
  const handlerGroup = [];
594
501
  for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
595
502
  handlerGroup.push(this.configuration.popupHandlers[i].locator);
@@ -637,7 +544,7 @@ class StableBrowser {
637
544
  }
638
545
  return { rerun: false };
639
546
  }
640
- async _locate(selectors, info, _params, timeout) {
547
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
641
548
  if (!timeout) {
642
549
  timeout = 30000;
643
550
  }
@@ -647,9 +554,18 @@ class StableBrowser {
647
554
  let selector = selectors.locators[j];
648
555
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
649
556
  }
650
- let element = await this._locate_internal(selectors, info, _params, timeout);
557
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
651
558
  if (!element.rerun) {
652
- return element;
559
+ const randomToken = Math.random().toString(36).substring(7);
560
+ element.evaluate((el, randomToken) => {
561
+ el.setAttribute("data-blinq-id-" + randomToken, "");
562
+ }, randomToken);
563
+ if (element._frame) {
564
+ return element;
565
+ }
566
+ const scope = element.page();
567
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
568
+ return newSelector;
653
569
  }
654
570
  }
655
571
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -691,9 +607,11 @@ class StableBrowser {
691
607
  }
692
608
  return framescope;
693
609
  };
610
+ let fLocator = null;
694
611
  while (true) {
695
612
  let frameFound = false;
696
613
  if (selectors.nestFrmLoc) {
614
+ fLocator = selectors.nestFrmLoc;
697
615
  scope = await findFrame(selectors.nestFrmLoc, scope);
698
616
  frameFound = true;
699
617
  break;
@@ -702,6 +620,7 @@ class StableBrowser {
702
620
  for (let i = 0; i < selectors.frameLocators.length; i++) {
703
621
  let frameLocator = selectors.frameLocators[i];
704
622
  if (frameLocator.css) {
623
+ fLocator = frameLocator.css;
705
624
  scope = scope.frameLocator(frameLocator.css);
706
625
  frameFound = true;
707
626
  break;
@@ -709,18 +628,25 @@ class StableBrowser {
709
628
  }
710
629
  }
711
630
  if (!frameFound && selectors.iframe_src) {
631
+ fLocator = selectors.iframe_src;
712
632
  scope = this.page.frame({ url: selectors.iframe_src });
713
633
  }
714
634
  if (!scope) {
715
- info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
635
+ if (info && info.locatorLog) {
636
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
637
+ }
638
+ //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
716
639
  if (Date.now() - startTime > timeout) {
717
640
  info.failCause.iframeNotFound = true;
718
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
641
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
719
642
  throw new Error("unable to locate iframe " + selectors.iframe_src);
720
643
  }
721
644
  await new Promise((resolve) => setTimeout(resolve, 1000));
722
645
  }
723
646
  else {
647
+ if (info && info.locatorLog) {
648
+ info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
649
+ }
724
650
  break;
725
651
  }
726
652
  }
@@ -737,11 +663,12 @@ class StableBrowser {
737
663
  return bodyContent;
738
664
  });
739
665
  }
740
- async _locate_internal(selectors, info, _params, timeout = 30000) {
666
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
741
667
  if (!info) {
742
668
  info = {};
743
669
  info.failCause = {};
744
670
  info.log = "";
671
+ info.locatorLog = new LocatorLog(selectors);
745
672
  }
746
673
  let highPriorityTimeout = 5000;
747
674
  let visibleOnlyTimeout = 6000;
@@ -785,17 +712,17 @@ class StableBrowser {
785
712
  }
786
713
  // info.log += "scanning locators in priority 1" + "\n";
787
714
  let onlyPriority3 = selectorsLocators[0].priority === 3;
788
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
715
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
789
716
  if (result.foundElements.length === 0) {
790
717
  // info.log += "scanning locators in priority 2" + "\n";
791
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
718
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
792
719
  }
793
720
  if (result.foundElements.length === 0 && onlyPriority3) {
794
- 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);
795
722
  }
796
723
  else {
797
724
  if (result.foundElements.length === 0 && !highPriorityOnly) {
798
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
725
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
799
726
  }
800
727
  }
801
728
  let foundElements = result.foundElements;
@@ -840,26 +767,34 @@ class StableBrowser {
840
767
  break;
841
768
  }
842
769
  if (Date.now() - startTime > highPriorityTimeout) {
843
- info.log += "high priority timeout, will try all elements" + "\n";
770
+ //info.log += "high priority timeout, will try all elements" + "\n";
844
771
  highPriorityOnly = false;
845
772
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
846
773
  lazy_scroll = true;
847
- await this.scrollPageToLoadLazyElements();
774
+ await scrollPageToLoadLazyElements(this.page);
848
775
  }
849
776
  }
850
777
  if (Date.now() - startTime > visibleOnlyTimeout) {
851
- info.log += "visible only timeout, will try all elements" + "\n";
778
+ //info.log += "visible only timeout, will try all elements" + "\n";
852
779
  visibleOnly = false;
853
780
  }
854
781
  await new Promise((resolve) => setTimeout(resolve, 1000));
855
782
  }
856
783
  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";
784
+ // if (info.locatorLog) {
785
+ // const lines = info.locatorLog.toString().split("\n");
786
+ // for (let line of lines) {
787
+ // this.logger.debug(line);
788
+ // }
789
+ // }
790
+ //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
858
791
  info.failCause.locatorNotFound = true;
859
- info.failCause.lastError = "failed to locate unique element";
792
+ if (!info?.failCause?.lastError) {
793
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
794
+ }
860
795
  throw new Error("failed to locate first element no elements found, " + info.log);
861
796
  }
862
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
797
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
863
798
  let foundElements = [];
864
799
  const result = {
865
800
  foundElements: foundElements,
@@ -867,14 +802,15 @@ class StableBrowser {
867
802
  for (let i = 0; i < locatorsGroup.length; i++) {
868
803
  let foundLocators = [];
869
804
  try {
870
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
805
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
871
806
  }
872
807
  catch (e) {
873
- this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
874
- this.logger.debug(e);
808
+ // this call can fail it the browser is navigating
809
+ // this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
810
+ // this.logger.debug(e);
875
811
  foundLocators = [];
876
812
  try {
877
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
813
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
878
814
  }
879
815
  catch (e) {
880
816
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -890,11 +826,27 @@ class StableBrowser {
890
826
  }
891
827
  if (foundLocators.length > 1) {
892
828
  info.failCause.foundMultiple = true;
829
+ if (info.locatorLog) {
830
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
831
+ }
893
832
  }
894
833
  }
895
834
  return result;
896
835
  }
897
836
  async simpleClick(elementDescription, _params, options = {}, world = null) {
837
+ const state = {
838
+ locate: false,
839
+ scroll: false,
840
+ highlight: false,
841
+ _params,
842
+ options,
843
+ world,
844
+ type: Types.CLICK,
845
+ text: "Click element",
846
+ operation: "simpleClick",
847
+ log: "***** click on " + elementDescription + " *****\n",
848
+ };
849
+ _preCommand(state, this);
898
850
  const startTime = Date.now();
899
851
  let timeout = 30000;
900
852
  if (options && options.timeout) {
@@ -917,15 +869,33 @@ class StableBrowser {
917
869
  }
918
870
  }
919
871
  catch (e) {
920
- if (Date.now() - startTime > timeout) {
872
+ if (performance.now() - startTime > timeout) {
921
873
  // throw e;
922
- await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
874
+ try {
875
+ await _commandError(state, "timeout looking for " + elementDescription, this);
876
+ }
877
+ finally {
878
+ _commandFinally(state, this);
879
+ }
923
880
  }
924
881
  }
925
882
  await new Promise((resolve) => setTimeout(resolve, 3000));
926
883
  }
927
884
  }
928
885
  async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
886
+ const state = {
887
+ locate: false,
888
+ scroll: false,
889
+ highlight: false,
890
+ _params,
891
+ options,
892
+ world,
893
+ type: Types.FILL,
894
+ text: "Fill element",
895
+ operation: "simpleClickType",
896
+ log: "***** click type on " + elementDescription + " *****\n",
897
+ };
898
+ _preCommand(state, this);
929
899
  const startTime = Date.now();
930
900
  let timeout = 30000;
931
901
  if (options && options.timeout) {
@@ -948,9 +918,14 @@ class StableBrowser {
948
918
  }
949
919
  }
950
920
  catch (e) {
951
- if (Date.now() - startTime > timeout) {
921
+ if (performance.now() - startTime > timeout) {
952
922
  // throw e;
953
- await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
923
+ try {
924
+ await _commandError(state, "timeout looking for " + elementDescription, this);
925
+ }
926
+ finally {
927
+ _commandFinally(state, this);
928
+ }
954
929
  }
955
930
  }
956
931
  await new Promise((resolve) => setTimeout(resolve, 3000));
@@ -963,15 +938,16 @@ class StableBrowser {
963
938
  options,
964
939
  world,
965
940
  text: "Click element",
941
+ _text: "Click on " + selectors.element_name,
966
942
  type: Types.CLICK,
967
943
  operation: "click",
968
944
  log: "***** click on " + selectors.element_name + " *****\n",
969
945
  };
970
946
  try {
971
947
  await _preCommand(state, this);
972
- if (state.options && state.options.context) {
973
- state.selectors.locators[0].text = state.options.context;
974
- }
948
+ // if (state.options && state.options.context) {
949
+ // state.selectors.locators[0].text = state.options.context;
950
+ // }
975
951
  try {
976
952
  await state.element.click();
977
953
  // await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -992,6 +968,38 @@ class StableBrowser {
992
968
  _commandFinally(state, this);
993
969
  }
994
970
  }
971
+ async waitForElement(selectors, _params, options = {}, world = null) {
972
+ const timeout = this._getFindElementTimeout(options);
973
+ const state = {
974
+ selectors,
975
+ _params,
976
+ options,
977
+ world,
978
+ text: "Wait for element",
979
+ _text: "Wait for " + selectors.element_name,
980
+ type: Types.WAIT_ELEMENT,
981
+ operation: "waitForElement",
982
+ log: "***** wait for " + selectors.element_name + " *****\n",
983
+ };
984
+ let found = false;
985
+ try {
986
+ await _preCommand(state, this);
987
+ // if (state.options && state.options.context) {
988
+ // state.selectors.locators[0].text = state.options.context;
989
+ // }
990
+ await state.element.waitFor({ timeout: timeout });
991
+ found = true;
992
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
993
+ }
994
+ catch (e) {
995
+ console.error("Error on waitForElement", e);
996
+ // await _commandError(state, e, this);
997
+ }
998
+ finally {
999
+ _commandFinally(state, this);
1000
+ }
1001
+ return found;
1002
+ }
995
1003
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
996
1004
  const state = {
997
1005
  selectors,
@@ -1000,6 +1008,7 @@ class StableBrowser {
1000
1008
  world,
1001
1009
  type: checked ? Types.CHECK : Types.UNCHECK,
1002
1010
  text: checked ? `Check element` : `Uncheck element`,
1011
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
1003
1012
  operation: "setCheck",
1004
1013
  log: "***** check " + selectors.element_name + " *****\n",
1005
1014
  };
@@ -1009,9 +1018,15 @@ class StableBrowser {
1009
1018
  // let element = await this._locate(selectors, info, _params);
1010
1019
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1011
1020
  try {
1012
- // await this._highlightElements(element);
1021
+ // if (world && world.screenshot && !world.screenshotPath) {
1022
+ // console.log(`Highlighting while running from recorder`);
1023
+ await this._highlightElements(element);
1013
1024
  await state.element.setChecked(checked);
1014
1025
  await new Promise((resolve) => setTimeout(resolve, 1000));
1026
+ // await this._unHighlightElements(element);
1027
+ // }
1028
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1029
+ // await this._unHighlightElements(element);
1015
1030
  }
1016
1031
  catch (e) {
1017
1032
  if (e.message && e.message.includes("did not change its state")) {
@@ -1043,6 +1058,7 @@ class StableBrowser {
1043
1058
  world,
1044
1059
  type: Types.HOVER,
1045
1060
  text: `Hover element`,
1061
+ _text: `Hover on ${selectors.element_name}`,
1046
1062
  operation: "hover",
1047
1063
  log: "***** hover " + selectors.element_name + " *****\n",
1048
1064
  };
@@ -1050,6 +1066,7 @@ class StableBrowser {
1050
1066
  await _preCommand(state, this);
1051
1067
  try {
1052
1068
  await state.element.hover();
1069
+ // await _screenshot(state, this);
1053
1070
  await new Promise((resolve) => setTimeout(resolve, 1000));
1054
1071
  }
1055
1072
  catch (e) {
@@ -1057,6 +1074,7 @@ class StableBrowser {
1057
1074
  state.info.log += "hover failed, will try again" + "\n";
1058
1075
  state.element = await this._locate(selectors, state.info, _params);
1059
1076
  await state.element.hover({ timeout: 10000 });
1077
+ // await _screenshot(state, this);
1060
1078
  await new Promise((resolve) => setTimeout(resolve, 1000));
1061
1079
  }
1062
1080
  await _screenshot(state, this);
@@ -1082,6 +1100,7 @@ class StableBrowser {
1082
1100
  value: values.toString(),
1083
1101
  type: Types.SELECT,
1084
1102
  text: `Select option: ${values}`,
1103
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1085
1104
  operation: "selectOption",
1086
1105
  log: "***** select option " + selectors.element_name + " *****\n",
1087
1106
  };
@@ -1116,6 +1135,7 @@ class StableBrowser {
1116
1135
  highlight: false,
1117
1136
  type: Types.TYPE_PRESS,
1118
1137
  text: `Type value: ${_value}`,
1138
+ _text: `Type value: ${_value}`,
1119
1139
  operation: "type",
1120
1140
  log: "",
1121
1141
  };
@@ -1195,6 +1215,7 @@ class StableBrowser {
1195
1215
  world,
1196
1216
  type: Types.SET_DATE_TIME,
1197
1217
  text: `Set date time value: ${value}`,
1218
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1198
1219
  operation: "setDateTime",
1199
1220
  log: "***** set date time value " + selectors.element_name + " *****\n",
1200
1221
  throwError: false,
@@ -1266,6 +1287,7 @@ class StableBrowser {
1266
1287
  world,
1267
1288
  type: Types.FILL,
1268
1289
  text: `Click type input with value: ${_value}`,
1290
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1269
1291
  operation: "clickType",
1270
1292
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1271
1293
  };
@@ -1331,7 +1353,12 @@ class StableBrowser {
1331
1353
  await this.waitForPageLoad();
1332
1354
  }
1333
1355
  else if (enter === false) {
1334
- await state.element.dispatchEvent("change");
1356
+ try {
1357
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1358
+ }
1359
+ catch (e) {
1360
+ // ignore
1361
+ }
1335
1362
  //await this.page.keyboard.press("Tab");
1336
1363
  }
1337
1364
  else {
@@ -1383,15 +1410,17 @@ class StableBrowser {
1383
1410
  return await this._getText(selectors, 0, _params, options, info, world);
1384
1411
  }
1385
1412
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1413
+ const timeout = this._getFindElementTimeout(options);
1386
1414
  _validateSelectors(selectors);
1387
1415
  let screenshotId = null;
1388
1416
  let screenshotPath = null;
1389
1417
  if (!info.log) {
1390
1418
  info.log = "";
1419
+ info.locatorLog = new LocatorLog(selectors);
1391
1420
  }
1392
1421
  info.operation = "getText";
1393
1422
  info.selectors = selectors;
1394
- let element = await this._locate(selectors, info, _params);
1423
+ let element = await this._locate(selectors, info, _params, timeout);
1395
1424
  if (climb > 0) {
1396
1425
  const climbArray = [];
1397
1426
  for (let i = 0; i < climb; i++) {
@@ -1410,6 +1439,18 @@ class StableBrowser {
1410
1439
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1411
1440
  try {
1412
1441
  await this._highlightElements(element);
1442
+ // if (world && world.screenshot && !world.screenshotPath) {
1443
+ // // console.log(`Highlighting for get text while running from recorder`);
1444
+ // this._highlightElements(element)
1445
+ // .then(async () => {
1446
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1447
+ // this._unhighlightElements(element).then(
1448
+ // () => {}
1449
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1450
+ // );
1451
+ // })
1452
+ // .catch(e);
1453
+ // }
1413
1454
  const elementText = await element.innerText();
1414
1455
  return {
1415
1456
  text: elementText,
@@ -1421,7 +1462,7 @@ class StableBrowser {
1421
1462
  }
1422
1463
  catch (e) {
1423
1464
  //await this.closeUnexpectedPopups();
1424
- this.logger.info("no innerText will use textContent");
1465
+ this.logger.info("no innerText, will use textContent");
1425
1466
  const elementText = await element.textContent();
1426
1467
  return { text: elementText, screenshotId, screenshotPath, value: value };
1427
1468
  }
@@ -1446,6 +1487,7 @@ class StableBrowser {
1446
1487
  highlight: false,
1447
1488
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1448
1489
  text: `Verify element contains pattern: ${pattern}`,
1490
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1449
1491
  operation: "containsPattern",
1450
1492
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1451
1493
  };
@@ -1481,6 +1523,8 @@ class StableBrowser {
1481
1523
  }
1482
1524
  }
1483
1525
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1526
+ const timeout = this._getFindElementTimeout(options);
1527
+ const startTime = Date.now();
1484
1528
  const state = {
1485
1529
  selectors,
1486
1530
  _params,
@@ -1507,62 +1551,54 @@ class StableBrowser {
1507
1551
  }
1508
1552
  let foundObj = null;
1509
1553
  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;
1554
+ while (Date.now() - startTime < timeout) {
1555
+ try {
1556
+ await _preCommand(state, this);
1557
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1558
+ if (foundObj && foundObj.element) {
1559
+ await this.scrollIfNeeded(foundObj.element, state.info);
1523
1560
  }
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])) {
1561
+ await _screenshot(state, this);
1562
+ const dateAlternatives = findDateAlternatives(text);
1563
+ const numberAlternatives = findNumberAlternatives(text);
1564
+ if (dateAlternatives.date) {
1565
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1566
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1567
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1568
+ return state.info;
1569
+ }
1570
+ }
1571
+ }
1572
+ else if (numberAlternatives.number) {
1573
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1574
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1575
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1576
+ return state.info;
1577
+ }
1578
+ }
1579
+ }
1580
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1531
1581
  return state.info;
1532
1582
  }
1533
1583
  }
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);
1584
+ catch (e) {
1585
+ // Log error but continue retrying until timeout is reached
1586
+ this.logger.warn("Retrying containsText due to: " + e.message);
1587
+ }
1588
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1540
1589
  }
1541
- return state.info;
1590
+ state.info.foundText = foundObj?.text;
1591
+ state.info.value = foundObj?.value;
1592
+ throw new Error("element doesn't contain text " + text);
1542
1593
  }
1543
1594
  catch (e) {
1544
1595
  await _commandError(state, e, this);
1596
+ throw e;
1545
1597
  }
1546
1598
  finally {
1547
1599
  _commandFinally(state, this);
1548
1600
  }
1549
1601
  }
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
1602
  async waitForUserInput(message, world = null) {
1567
1603
  if (!message) {
1568
1604
  message = "# Wait for user input. Press any key to continue";
@@ -1591,7 +1627,7 @@ class StableBrowser {
1591
1627
  return;
1592
1628
  }
1593
1629
  // if data file exists, load it
1594
- const dataFile = this._getDataFile(world);
1630
+ const dataFile = _getDataFile(world, this.context, this);
1595
1631
  let data = this.getTestData(world);
1596
1632
  // merge the testData with the existing data
1597
1633
  Object.assign(data, testData);
@@ -1694,7 +1730,7 @@ class StableBrowser {
1694
1730
  }
1695
1731
  }
1696
1732
  getTestData(world = null) {
1697
- const dataFile = this._getDataFile(world);
1733
+ const dataFile = _getDataFile(world, this.context, this);
1698
1734
  let data = {};
1699
1735
  if (fs.existsSync(dataFile)) {
1700
1736
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1781,6 +1817,15 @@ class StableBrowser {
1781
1817
  document.documentElement.clientWidth,
1782
1818
  ])));
1783
1819
  let screenshotBuffer = null;
1820
+ // if (focusedElement) {
1821
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1822
+ // await this._unhighlightElements(focusedElement);
1823
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1824
+ // console.log(`Unhighlighted previous element`);
1825
+ // }
1826
+ // if (focusedElement) {
1827
+ // await this._highlightElements(focusedElement);
1828
+ // }
1784
1829
  if (this.context.browserName === "chromium") {
1785
1830
  const client = await playContext.newCDPSession(this.page);
1786
1831
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1802,6 +1847,10 @@ class StableBrowser {
1802
1847
  else {
1803
1848
  screenshotBuffer = await this.page.screenshot();
1804
1849
  }
1850
+ // if (focusedElement) {
1851
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1852
+ // await this._unhighlightElements(focusedElement);
1853
+ // }
1805
1854
  let image = await Jimp.read(screenshotBuffer);
1806
1855
  // Get the image dimensions
1807
1856
  const { width, height } = image.bitmap;
@@ -1814,6 +1863,7 @@ class StableBrowser {
1814
1863
  else {
1815
1864
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1816
1865
  }
1866
+ return screenshotBuffer;
1817
1867
  }
1818
1868
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1819
1869
  const state = {
@@ -1849,8 +1899,10 @@ class StableBrowser {
1849
1899
  world,
1850
1900
  type: Types.EXTRACT,
1851
1901
  text: `Extract attribute from element`,
1902
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1852
1903
  operation: "extractAttribute",
1853
1904
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1905
+ allowDisabled: true,
1854
1906
  };
1855
1907
  await new Promise((resolve) => setTimeout(resolve, 2000));
1856
1908
  try {
@@ -1872,6 +1924,7 @@ class StableBrowser {
1872
1924
  state.info.value = state.value;
1873
1925
  this.setTestData({ [variable]: state.value }, world);
1874
1926
  this.logger.info("set test data: " + variable + "=" + state.value);
1927
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1875
1928
  return state.info;
1876
1929
  }
1877
1930
  catch (e) {
@@ -1890,14 +1943,21 @@ class StableBrowser {
1890
1943
  options,
1891
1944
  world,
1892
1945
  type: Types.VERIFY_ATTRIBUTE,
1946
+ highlight: true,
1947
+ screenshot: true,
1893
1948
  text: `Verify element attribute`,
1949
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1894
1950
  operation: "verifyAttribute",
1895
1951
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1952
+ allowDisabled: true,
1896
1953
  };
1897
1954
  await new Promise((resolve) => setTimeout(resolve, 2000));
1898
1955
  let val;
1956
+ let expectedValue;
1899
1957
  try {
1900
1958
  await _preCommand(state, this);
1959
+ expectedValue = state.value;
1960
+ state.info.expectedValue = expectedValue;
1901
1961
  switch (attribute) {
1902
1962
  case "innerText":
1903
1963
  val = String(await state.element.innerText());
@@ -1911,23 +1971,30 @@ class StableBrowser {
1911
1971
  case "disabled":
1912
1972
  val = String(await state.element.isDisabled());
1913
1973
  break;
1974
+ case "readOnly":
1975
+ const isEditable = await state.element.isEditable();
1976
+ val = String(!isEditable);
1977
+ break;
1914
1978
  default:
1915
1979
  val = String(await state.element.getAttribute(attribute));
1916
1980
  break;
1917
1981
  }
1982
+ state.info.value = val;
1918
1983
  let regex;
1919
- if (value.startsWith("/") && value.endsWith("/")) {
1920
- const patternBody = value.slice(1, -1);
1984
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1985
+ const patternBody = expectedValue.slice(1, -1);
1921
1986
  regex = new RegExp(patternBody, "g");
1922
1987
  }
1923
1988
  else {
1924
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1989
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1925
1990
  regex = new RegExp(escapedPattern, "g");
1926
1991
  }
1927
1992
  if (!val.match(regex)) {
1928
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
1993
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
1994
+ state.info.failCause.assertionFailed = true;
1995
+ state.info.failCause.lastError = errorMessage;
1996
+ throw new Error(errorMessage);
1929
1997
  }
1930
- state.info.value = val;
1931
1998
  return state.info;
1932
1999
  }
1933
2000
  catch (e) {
@@ -1955,7 +2022,7 @@ class StableBrowser {
1955
2022
  if (options && options.timeout) {
1956
2023
  timeout = options.timeout;
1957
2024
  }
1958
- const serviceUrl = this._getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
2025
+ const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
1959
2026
  const request = {
1960
2027
  method: "POST",
1961
2028
  url: serviceUrl,
@@ -2026,27 +2093,32 @@ class StableBrowser {
2026
2093
  async _highlightElements(scope, css) {
2027
2094
  try {
2028
2095
  if (!scope) {
2096
+ // console.log(`Scope is not defined`);
2029
2097
  return;
2030
2098
  }
2031
2099
  if (!css) {
2032
2100
  scope
2033
2101
  .evaluate((node) => {
2034
2102
  if (node && node.style) {
2035
- let originalBorder = node.style.border;
2036
- node.style.border = "2px solid red";
2103
+ let originalOutline = node.style.outline;
2104
+ // console.log(`Original outline was: ${originalOutline}`);
2105
+ // node.__previousOutline = originalOutline;
2106
+ node.style.outline = "2px solid red";
2107
+ // console.log(`New outline is: ${node.style.outline}`);
2037
2108
  if (window) {
2038
2109
  window.addEventListener("beforeunload", function (e) {
2039
- node.style.border = originalBorder;
2110
+ node.style.outline = originalOutline;
2040
2111
  });
2041
2112
  }
2042
2113
  setTimeout(function () {
2043
- node.style.border = originalBorder;
2114
+ node.style.outline = originalOutline;
2044
2115
  }, 2000);
2045
2116
  }
2046
2117
  })
2047
2118
  .then(() => { })
2048
2119
  .catch((e) => {
2049
2120
  // ignore
2121
+ // console.error(`Could not highlight node : ${e}`);
2050
2122
  });
2051
2123
  }
2052
2124
  else {
@@ -2062,17 +2134,18 @@ class StableBrowser {
2062
2134
  if (!element.style) {
2063
2135
  return;
2064
2136
  }
2065
- var originalBorder = element.style.border;
2137
+ let originalOutline = element.style.outline;
2138
+ element.__previousOutline = originalOutline;
2066
2139
  // Set the new border to be red and 2px solid
2067
- element.style.border = "2px solid red";
2140
+ element.style.outline = "2px solid red";
2068
2141
  if (window) {
2069
2142
  window.addEventListener("beforeunload", function (e) {
2070
- element.style.border = originalBorder;
2143
+ element.style.outline = originalOutline;
2071
2144
  });
2072
2145
  }
2073
2146
  // Set a timeout to revert to the original border after 2 seconds
2074
2147
  setTimeout(function () {
2075
- element.style.border = originalBorder;
2148
+ element.style.outline = originalOutline;
2076
2149
  }, 2000);
2077
2150
  }
2078
2151
  return;
@@ -2080,6 +2153,7 @@ class StableBrowser {
2080
2153
  .then(() => { })
2081
2154
  .catch((e) => {
2082
2155
  // ignore
2156
+ // console.error(`Could not highlight css: ${e}`);
2083
2157
  });
2084
2158
  }
2085
2159
  }
@@ -2087,6 +2161,54 @@ class StableBrowser {
2087
2161
  console.debug(error);
2088
2162
  }
2089
2163
  }
2164
+ // async _unhighlightElements(scope, css) {
2165
+ // try {
2166
+ // if (!scope) {
2167
+ // return;
2168
+ // }
2169
+ // if (!css) {
2170
+ // scope
2171
+ // .evaluate((node) => {
2172
+ // if (node && node.style) {
2173
+ // if (!node.__previousOutline) {
2174
+ // node.style.outline = "";
2175
+ // } else {
2176
+ // node.style.outline = node.__previousOutline;
2177
+ // }
2178
+ // }
2179
+ // })
2180
+ // .then(() => {})
2181
+ // .catch((e) => {
2182
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2183
+ // });
2184
+ // } else {
2185
+ // scope
2186
+ // .evaluate(([css]) => {
2187
+ // if (!css) {
2188
+ // return;
2189
+ // }
2190
+ // let elements = Array.from(document.querySelectorAll(css));
2191
+ // for (i = 0; i < elements.length; i++) {
2192
+ // let element = elements[i];
2193
+ // if (!element.style) {
2194
+ // return;
2195
+ // }
2196
+ // if (!element.__previousOutline) {
2197
+ // element.style.outline = "";
2198
+ // } else {
2199
+ // element.style.outline = element.__previousOutline;
2200
+ // }
2201
+ // }
2202
+ // })
2203
+ // .then(() => {})
2204
+ // .catch((e) => {
2205
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2206
+ // });
2207
+ // }
2208
+ // } catch (error) {
2209
+ // // console.debug(error);
2210
+ // }
2211
+ // }
2090
2212
  async verifyPagePath(pathPart, options = {}, world = null) {
2091
2213
  const startTime = Date.now();
2092
2214
  let error = null;
@@ -2131,6 +2253,7 @@ class StableBrowser {
2131
2253
  _reportToWorld(world, {
2132
2254
  type: Types.VERIFY_PAGE_PATH,
2133
2255
  text: "Verify page path",
2256
+ _text: "Verify the page path contains " + pathPart,
2134
2257
  screenshotId,
2135
2258
  result: error
2136
2259
  ? {
@@ -2148,6 +2271,35 @@ class StableBrowser {
2148
2271
  });
2149
2272
  }
2150
2273
  }
2274
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2275
+ const frames = this.page.frames();
2276
+ let results = [];
2277
+ let ignoreCase = false;
2278
+ for (let i = 0; i < frames.length; i++) {
2279
+ if (dateAlternatives.date) {
2280
+ for (let j = 0; j < dateAlternatives.dates.length; j++) {
2281
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2282
+ result.frame = frames[i];
2283
+ results.push(result);
2284
+ }
2285
+ }
2286
+ else if (numberAlternatives.number) {
2287
+ for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2288
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2289
+ result.frame = frames[i];
2290
+ results.push(result);
2291
+ }
2292
+ }
2293
+ else {
2294
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2295
+ result.frame = frames[i];
2296
+ results.push(result);
2297
+ }
2298
+ }
2299
+ state.info.results = results;
2300
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2301
+ return resultWithElementsFound;
2302
+ }
2151
2303
  async verifyTextExistInPage(text, options = {}, world = null) {
2152
2304
  text = unEscapeString(text);
2153
2305
  const state = {
@@ -2157,12 +2309,13 @@ class StableBrowser {
2157
2309
  locate: false,
2158
2310
  scroll: false,
2159
2311
  highlight: false,
2160
- type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
2312
+ type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2161
2313
  text: `Verify text exists in page`,
2314
+ _text: `Verify the text '${text}' exists in page`,
2162
2315
  operation: "verifyTextExistInPage",
2163
2316
  log: "***** verify text " + text + " exists in page *****\n",
2164
2317
  };
2165
- const timeout = this._getLoadTimeout(options);
2318
+ const timeout = this._getFindElementTimeout(options);
2166
2319
  await new Promise((resolve) => setTimeout(resolve, 2000));
2167
2320
  const newValue = await this._replaceWithLocalData(text, world);
2168
2321
  if (newValue !== text) {
@@ -2175,31 +2328,15 @@ class StableBrowser {
2175
2328
  await _preCommand(state, this);
2176
2329
  state.info.text = text;
2177
2330
  while (true) {
2178
- const frames = this.page.frames();
2179
- let results = [];
2180
- for (let i = 0; i < frames.length; i++) {
2181
- if (dateAlternatives.date) {
2182
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2183
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2184
- result.frame = frames[i];
2185
- results.push(result);
2186
- }
2187
- }
2188
- else if (numberAlternatives.number) {
2189
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2190
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2191
- result.frame = frames[i];
2192
- results.push(result);
2193
- }
2194
- }
2195
- else {
2196
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2197
- result.frame = frames[i];
2198
- results.push(result);
2199
- }
2331
+ let resultWithElementsFound = {
2332
+ length: 0,
2333
+ };
2334
+ try {
2335
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2336
+ }
2337
+ catch (error) {
2338
+ // ignore
2200
2339
  }
2201
- state.info.results = results;
2202
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2203
2340
  if (resultWithElementsFound.length === 0) {
2204
2341
  if (Date.now() - state.startTime > timeout) {
2205
2342
  throw new Error(`Text ${text} not found in page`);
@@ -2207,18 +2344,40 @@ class StableBrowser {
2207
2344
  await new Promise((resolve) => setTimeout(resolve, 1000));
2208
2345
  continue;
2209
2346
  }
2210
- if (resultWithElementsFound[0].randomToken) {
2211
- const frame = resultWithElementsFound[0].frame;
2212
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2213
- await this._highlightElements(frame, dataAttribute);
2214
- const element = await frame.$(dataAttribute);
2215
- if (element) {
2216
- await this.scrollIfNeeded(element, state.info);
2217
- await element.dispatchEvent("bvt_verify_page_contains_text");
2347
+ try {
2348
+ if (resultWithElementsFound[0].randomToken) {
2349
+ const frame = resultWithElementsFound[0].frame;
2350
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2351
+ await this._highlightElements(frame, dataAttribute);
2352
+ // if (world && world.screenshot && !world.screenshotPath) {
2353
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2354
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2355
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2356
+ // this._unhighlightElements(frame, dataAttribute)
2357
+ // .then(async () => {
2358
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2359
+ // })
2360
+ // .catch(
2361
+ // (e) => {}
2362
+ // console.error(e)
2363
+ // );
2364
+ // });
2365
+ // }
2366
+ const element = await frame.locator(dataAttribute).first();
2367
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2368
+ // await this._unhighlightElements(frame, dataAttribute);
2369
+ if (element) {
2370
+ await this.scrollIfNeeded(element, state.info);
2371
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2372
+ // await _screenshot(state, this, element);
2373
+ }
2218
2374
  }
2375
+ await _screenshot(state, this);
2376
+ return state.info;
2377
+ }
2378
+ catch (error) {
2379
+ console.error(error);
2219
2380
  }
2220
- await _screenshot(state, this);
2221
- return state.info;
2222
2381
  }
2223
2382
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2224
2383
  }
@@ -2240,10 +2399,11 @@ class StableBrowser {
2240
2399
  highlight: false,
2241
2400
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2242
2401
  text: `Verify text does not exist in page`,
2402
+ _text: `Verify the text '${text}' does not exist in page`,
2243
2403
  operation: "verifyTextNotExistInPage",
2244
2404
  log: "***** verify text " + text + " does not exist in page *****\n",
2245
2405
  };
2246
- const timeout = this._getLoadTimeout(options);
2406
+ const timeout = this._getFindElementTimeout(options);
2247
2407
  await new Promise((resolve) => setTimeout(resolve, 2000));
2248
2408
  const newValue = await this._replaceWithLocalData(text, world);
2249
2409
  if (newValue !== text) {
@@ -2255,32 +2415,16 @@ class StableBrowser {
2255
2415
  try {
2256
2416
  await _preCommand(state, this);
2257
2417
  state.info.text = text;
2418
+ let resultWithElementsFound = {
2419
+ length: null, // initial cannot be 0
2420
+ };
2258
2421
  while (true) {
2259
- const frames = this.page.frames();
2260
- let results = [];
2261
- for (let i = 0; i < frames.length; i++) {
2262
- if (dateAlternatives.date) {
2263
- for (let j = 0; j < dateAlternatives.dates.length; j++) {
2264
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
2265
- result.frame = frames[i];
2266
- results.push(result);
2267
- }
2268
- }
2269
- else if (numberAlternatives.number) {
2270
- for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2271
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
2272
- result.frame = frames[i];
2273
- results.push(result);
2274
- }
2275
- }
2276
- else {
2277
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
2278
- result.frame = frames[i];
2279
- results.push(result);
2280
- }
2422
+ try {
2423
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2424
+ }
2425
+ catch (error) {
2426
+ // ignore
2281
2427
  }
2282
- state.info.results = results;
2283
- const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2284
2428
  if (resultWithElementsFound.length === 0) {
2285
2429
  await _screenshot(state, this);
2286
2430
  return state.info;
@@ -2298,15 +2442,116 @@ class StableBrowser {
2298
2442
  _commandFinally(state, this);
2299
2443
  }
2300
2444
  }
2301
- _getServerUrl() {
2302
- let serviceUrl = "https://api.blinq.io";
2303
- if (process.env.NODE_ENV_BLINQ === "dev") {
2304
- serviceUrl = "https://dev.api.blinq.io";
2445
+ async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
2446
+ textAnchor = unEscapeString(textAnchor);
2447
+ textToVerify = unEscapeString(textToVerify);
2448
+ const state = {
2449
+ text_search: textToVerify,
2450
+ options,
2451
+ world,
2452
+ locate: false,
2453
+ scroll: false,
2454
+ highlight: false,
2455
+ type: Types.VERIFY_TEXT_WITH_RELATION,
2456
+ text: `Verify text with relation to another text`,
2457
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2458
+ operation: "verify_text_with_relation",
2459
+ log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2460
+ };
2461
+ const timeout = this._getFindElementTimeout(options);
2462
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2463
+ let newValue = await this._replaceWithLocalData(textAnchor, world);
2464
+ if (newValue !== textAnchor) {
2465
+ this.logger.info(textAnchor + "=" + newValue);
2466
+ textAnchor = newValue;
2467
+ }
2468
+ newValue = await this._replaceWithLocalData(textToVerify, world);
2469
+ if (newValue !== textToVerify) {
2470
+ this.logger.info(textToVerify + "=" + newValue);
2471
+ textToVerify = newValue;
2472
+ }
2473
+ let dateAlternatives = findDateAlternatives(textToVerify);
2474
+ let numberAlternatives = findNumberAlternatives(textToVerify);
2475
+ let foundAncore = false;
2476
+ try {
2477
+ await _preCommand(state, this);
2478
+ state.info.text = textToVerify;
2479
+ let resultWithElementsFound = {
2480
+ length: 0,
2481
+ };
2482
+ while (true) {
2483
+ try {
2484
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2485
+ }
2486
+ catch (error) {
2487
+ // ignore
2488
+ }
2489
+ if (resultWithElementsFound.length === 0) {
2490
+ if (Date.now() - state.startTime > timeout) {
2491
+ throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
2492
+ }
2493
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2494
+ continue;
2495
+ }
2496
+ try {
2497
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2498
+ foundAncore = true;
2499
+ const result = resultWithElementsFound[i];
2500
+ const token = result.randomToken;
2501
+ const frame = result.frame;
2502
+ let css = `[data-blinq-id-${token}]`;
2503
+ const climbArray1 = [];
2504
+ for (let i = 0; i < climb; i++) {
2505
+ climbArray1.push("..");
2506
+ }
2507
+ let climbXpath = "xpath=" + climbArray1.join("/");
2508
+ css = css + " >> " + climbXpath;
2509
+ const count = await frame.locator(css).count();
2510
+ for (let j = 0; j < count; j++) {
2511
+ const continer = await frame.locator(css).nth(j);
2512
+ const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2513
+ if (result.elementCount > 0) {
2514
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2515
+ await this._highlightElements(frame, dataAttribute);
2516
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2517
+ // if (world && world.screenshot && !world.screenshotPath) {
2518
+ // console.log(`Highlighting for vtrt while running from recorder`);
2519
+ // this._highlightElements(frame, dataAttribute)
2520
+ // .then(async () => {
2521
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2522
+ // this._unhighlightElements(frame, dataAttribute).then(
2523
+ // () => {}
2524
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2525
+ // );
2526
+ // })
2527
+ // .catch(e);
2528
+ // }
2529
+ //await this._highlightElements(frame, cssAnchor);
2530
+ const element = await frame.locator(dataAttribute).first();
2531
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2532
+ // await this._unhighlightElements(frame, dataAttribute);
2533
+ if (element) {
2534
+ await this.scrollIfNeeded(element, state.info);
2535
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2536
+ }
2537
+ await _screenshot(state, this);
2538
+ return state.info;
2539
+ }
2540
+ }
2541
+ }
2542
+ }
2543
+ catch (error) {
2544
+ console.error(error);
2545
+ }
2546
+ }
2547
+ // await expect(element).toHaveCount(1, { timeout: 10000 });
2305
2548
  }
2306
- else if (process.env.NODE_ENV_BLINQ === "stage") {
2307
- serviceUrl = "https://stage.api.blinq.io";
2549
+ catch (e) {
2550
+ await _commandError(state, e, this);
2551
+ }
2552
+ finally {
2553
+ _commandFinally(state, this);
2308
2554
  }
2309
- return serviceUrl;
2310
2555
  }
2311
2556
  async visualVerification(text, options = {}, world = null) {
2312
2557
  const startTime = Date.now();
@@ -2322,14 +2567,17 @@ class StableBrowser {
2322
2567
  throw new Error("TOKEN is not set");
2323
2568
  }
2324
2569
  try {
2325
- let serviceUrl = this._getServerUrl();
2570
+ let serviceUrl = _getServerUrl();
2326
2571
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2327
2572
  info.screenshotPath = screenshotPath;
2328
2573
  const screenshot = await this.takeScreenshot();
2329
- const request = {
2330
- method: "POST",
2574
+ let request = {
2575
+ method: "post",
2576
+ maxBodyLength: Infinity,
2331
2577
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2332
2578
  headers: {
2579
+ "x-bvt-project-id": path.basename(this.project_path),
2580
+ "x-source": "aaa",
2333
2581
  "Content-Type": "application/json",
2334
2582
  Authorization: `Bearer ${process.env.TOKEN}`,
2335
2583
  },
@@ -2338,7 +2586,7 @@ class StableBrowser {
2338
2586
  screenshot: screenshot,
2339
2587
  }),
2340
2588
  };
2341
- let result = await this.context.api.request(request);
2589
+ const result = await axios.request(request);
2342
2590
  if (result.data.status !== true) {
2343
2591
  throw new Error("Visual validation failed");
2344
2592
  }
@@ -2366,6 +2614,7 @@ class StableBrowser {
2366
2614
  _reportToWorld(world, {
2367
2615
  type: Types.VERIFY_VISUAL,
2368
2616
  text: "Visual verification",
2617
+ _text: "Visual verification of " + text,
2369
2618
  screenshotId,
2370
2619
  result: error
2371
2620
  ? {
@@ -2411,6 +2660,7 @@ class StableBrowser {
2411
2660
  let screenshotPath = null;
2412
2661
  const info = {};
2413
2662
  info.log = "";
2663
+ info.locatorLog = new LocatorLog(selectors);
2414
2664
  info.operation = "getTableData";
2415
2665
  info.selectors = selectors;
2416
2666
  try {
@@ -2486,7 +2736,7 @@ class StableBrowser {
2486
2736
  info.operation = "analyzeTable";
2487
2737
  info.selectors = selectors;
2488
2738
  info.query = query;
2489
- query = this._fixUsingParams(query, _params);
2739
+ query = _fixUsingParams(query, _params);
2490
2740
  info.query_fixed = query;
2491
2741
  info.operator = operator;
2492
2742
  info.value = value;
@@ -2631,6 +2881,32 @@ class StableBrowser {
2631
2881
  }
2632
2882
  return timeout;
2633
2883
  }
2884
+ _getFindElementTimeout(options) {
2885
+ if (options && options.timeout) {
2886
+ return options.timeout;
2887
+ }
2888
+ if (this.configuration.find_element_timeout) {
2889
+ return this.configuration.find_element_timeout;
2890
+ }
2891
+ return 30000;
2892
+ }
2893
+ async saveStoreState(path = null, world = null) {
2894
+ const storageState = await this.page.context().storageState();
2895
+ //const testDataFile = _getDataFile(world, this.context, this);
2896
+ if (path) {
2897
+ // save { storageState: storageState } into the path
2898
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2899
+ }
2900
+ else {
2901
+ await this.setTestData({ storageState: storageState }, world);
2902
+ }
2903
+ }
2904
+ async restoreSaveState(path = null, world = null) {
2905
+ await refreshBrowser(this, path, world);
2906
+ this.registerEventListeners(this.context);
2907
+ registerNetworkEvents(this.world, this, this.context, this.page);
2908
+ registerDownloadEvent(this.page, this.world, this.context);
2909
+ }
2634
2910
  async waitForPageLoad(options = {}, world = null) {
2635
2911
  let timeout = this._getLoadTimeout(options);
2636
2912
  const promiseArray = [];
@@ -2698,6 +2974,7 @@ class StableBrowser {
2698
2974
  highlight: false,
2699
2975
  type: Types.CLOSE_PAGE,
2700
2976
  text: `Close page`,
2977
+ _text: `Close the page`,
2701
2978
  operation: "closePage",
2702
2979
  log: "***** close page *****\n",
2703
2980
  throwError: false,
@@ -2745,6 +3022,7 @@ class StableBrowser {
2745
3022
  _reportToWorld(world, {
2746
3023
  type: Types.SET_VIEWPORT,
2747
3024
  text: "set viewport size to " + width + "x" + hight,
3025
+ _text: "Set the viewport size to " + width + "x" + hight,
2748
3026
  screenshotId,
2749
3027
  result: error
2750
3028
  ? {
@@ -2816,14 +3094,25 @@ class StableBrowser {
2816
3094
  }
2817
3095
  }
2818
3096
  async beforeStep(world, step) {
2819
- this.stepName = step.pickleStep.text;
2820
- this.logger.info("step: " + this.stepName);
2821
3097
  if (this.stepIndex === undefined) {
2822
3098
  this.stepIndex = 0;
2823
3099
  }
2824
3100
  else {
2825
3101
  this.stepIndex++;
2826
3102
  }
3103
+ if (step && step.pickleStep && step.pickleStep.text) {
3104
+ this.stepName = step.pickleStep.text;
3105
+ this.logger.info("step: " + this.stepName);
3106
+ }
3107
+ else if (step && step.text) {
3108
+ this.stepName = step.text;
3109
+ }
3110
+ else {
3111
+ this.stepName = "step " + this.stepIndex;
3112
+ }
3113
+ if (this.context) {
3114
+ this.context.examplesRow = extractStepExampleParameters(step);
3115
+ }
2827
3116
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2828
3117
  if (this.context.browserObject.context) {
2829
3118
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2836,6 +3125,41 @@ class StableBrowser {
2836
3125
  this.saveTestDataAsGlobal({}, world);
2837
3126
  }
2838
3127
  }
3128
+ if (this.initSnapshotTaken === false) {
3129
+ this.initSnapshotTaken = true;
3130
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3131
+ const snapshot = await this.getAriaSnapshot();
3132
+ if (snapshot) {
3133
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3134
+ }
3135
+ }
3136
+ }
3137
+ }
3138
+ async getAriaSnapshot() {
3139
+ try {
3140
+ // find the page url
3141
+ const url = await this.page.url();
3142
+ // extract the path from the url
3143
+ const path = new URL(url).pathname;
3144
+ // get the page title
3145
+ const title = await this.page.title();
3146
+ // go over other frams
3147
+ const frames = this.page.frames();
3148
+ const snapshots = [];
3149
+ const content = [`- path: ${path}`, `- title: ${title}`];
3150
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3151
+ for (let i = 0; i < frames.length; i++) {
3152
+ content.push(`- frame: ${i}`);
3153
+ const frame = frames[i];
3154
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3155
+ content.push(snapshot);
3156
+ }
3157
+ return content.join("\n");
3158
+ }
3159
+ catch (e) {
3160
+ console.error(e);
3161
+ }
3162
+ return null;
2839
3163
  }
2840
3164
  async afterStep(world, step) {
2841
3165
  this.stepName = null;
@@ -2846,6 +3170,16 @@ class StableBrowser {
2846
3170
  });
2847
3171
  }
2848
3172
  }
3173
+ if (this.context) {
3174
+ this.context.examplesRow = null;
3175
+ }
3176
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3177
+ const snapshot = await this.getAriaSnapshot();
3178
+ if (snapshot) {
3179
+ const obj = {};
3180
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3181
+ }
3182
+ }
2849
3183
  }
2850
3184
  }
2851
3185
  function createTimedPromise(promise, label) {
@@ -2853,156 +3187,5 @@ function createTimedPromise(promise, label) {
2853
3187
  .then((result) => ({ status: "fulfilled", label, result }))
2854
3188
  .catch((error) => Promise.reject({ status: "rejected", label, error }));
2855
3189
  }
2856
- const KEYBOARD_EVENTS = [
2857
- "ALT",
2858
- "AltGraph",
2859
- "CapsLock",
2860
- "Control",
2861
- "Fn",
2862
- "FnLock",
2863
- "Hyper",
2864
- "Meta",
2865
- "NumLock",
2866
- "ScrollLock",
2867
- "Shift",
2868
- "Super",
2869
- "Symbol",
2870
- "SymbolLock",
2871
- "Enter",
2872
- "Tab",
2873
- "ArrowDown",
2874
- "ArrowLeft",
2875
- "ArrowRight",
2876
- "ArrowUp",
2877
- "End",
2878
- "Home",
2879
- "PageDown",
2880
- "PageUp",
2881
- "Backspace",
2882
- "Clear",
2883
- "Copy",
2884
- "CrSel",
2885
- "Cut",
2886
- "Delete",
2887
- "EraseEof",
2888
- "ExSel",
2889
- "Insert",
2890
- "Paste",
2891
- "Redo",
2892
- "Undo",
2893
- "Accept",
2894
- "Again",
2895
- "Attn",
2896
- "Cancel",
2897
- "ContextMenu",
2898
- "Escape",
2899
- "Execute",
2900
- "Find",
2901
- "Finish",
2902
- "Help",
2903
- "Pause",
2904
- "Play",
2905
- "Props",
2906
- "Select",
2907
- "ZoomIn",
2908
- "ZoomOut",
2909
- "BrightnessDown",
2910
- "BrightnessUp",
2911
- "Eject",
2912
- "LogOff",
2913
- "Power",
2914
- "PowerOff",
2915
- "PrintScreen",
2916
- "Hibernate",
2917
- "Standby",
2918
- "WakeUp",
2919
- "AllCandidates",
2920
- "Alphanumeric",
2921
- "CodeInput",
2922
- "Compose",
2923
- "Convert",
2924
- "Dead",
2925
- "FinalMode",
2926
- "GroupFirst",
2927
- "GroupLast",
2928
- "GroupNext",
2929
- "GroupPrevious",
2930
- "ModeChange",
2931
- "NextCandidate",
2932
- "NonConvert",
2933
- "PreviousCandidate",
2934
- "Process",
2935
- "SingleCandidate",
2936
- "HangulMode",
2937
- "HanjaMode",
2938
- "JunjaMode",
2939
- "Eisu",
2940
- "Hankaku",
2941
- "Hiragana",
2942
- "HiraganaKatakana",
2943
- "KanaMode",
2944
- "KanjiMode",
2945
- "Katakana",
2946
- "Romaji",
2947
- "Zenkaku",
2948
- "ZenkakuHanaku",
2949
- "F1",
2950
- "F2",
2951
- "F3",
2952
- "F4",
2953
- "F5",
2954
- "F6",
2955
- "F7",
2956
- "F8",
2957
- "F9",
2958
- "F10",
2959
- "F11",
2960
- "F12",
2961
- "Soft1",
2962
- "Soft2",
2963
- "Soft3",
2964
- "Soft4",
2965
- "ChannelDown",
2966
- "ChannelUp",
2967
- "Close",
2968
- "MailForward",
2969
- "MailReply",
2970
- "MailSend",
2971
- "MediaFastForward",
2972
- "MediaPause",
2973
- "MediaPlay",
2974
- "MediaPlayPause",
2975
- "MediaRecord",
2976
- "MediaRewind",
2977
- "MediaStop",
2978
- "MediaTrackNext",
2979
- "MediaTrackPrevious",
2980
- "AudioBalanceLeft",
2981
- "AudioBalanceRight",
2982
- "AudioBassBoostDown",
2983
- "AudioBassBoostToggle",
2984
- "AudioBassBoostUp",
2985
- "AudioFaderFront",
2986
- "AudioFaderRear",
2987
- "AudioSurroundModeNext",
2988
- "AudioTrebleDown",
2989
- "AudioTrebleUp",
2990
- "AudioVolumeDown",
2991
- "AudioVolumeMute",
2992
- "AudioVolumeUp",
2993
- "MicrophoneToggle",
2994
- "MicrophoneVolumeDown",
2995
- "MicrophoneVolumeMute",
2996
- "MicrophoneVolumeUp",
2997
- "TV",
2998
- "TV3DMode",
2999
- "TVAntennaCable",
3000
- "TVAudioDescription",
3001
- ];
3002
- function unEscapeString(str) {
3003
- const placeholder = "__NEWLINE__";
3004
- str = str.replace(new RegExp(placeholder, "g"), "\n");
3005
- return str;
3006
- }
3007
3190
  export { StableBrowser };
3008
3191
  //# sourceMappingURL=stable_browser.js.map