automation_model 1.0.563-dev → 1.0.563-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,18 +10,21 @@ 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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
13
+ import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } 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
22
  import { LocatorLog } from "./locator_log.js";
23
+ import axios from "axios";
24
+ import { _findCellArea, findElementsInArea } from "./table_helper.js";
23
25
  export const Types = {
24
26
  CLICK: "click_element",
27
+ WAIT_ELEMENT: "wait_element",
25
28
  NAVIGATE: "navigate",
26
29
  FILL: "fill_element",
27
30
  EXECUTE: "execute_page_method",
@@ -43,6 +46,7 @@ export const Types = {
43
46
  UNCHECK: "uncheck_element",
44
47
  EXTRACT: "extract_attribute",
45
48
  CLOSE_PAGE: "close_page",
49
+ TABLE_OPERATION: "table_operation",
46
50
  SET_DATE_TIME: "set_date_time",
47
51
  SET_VIEWPORT: "set_viewport",
48
52
  VERIFY_VISUAL: "verify_visual",
@@ -53,6 +57,9 @@ export const Types = {
53
57
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
54
58
  };
55
59
  export const apps = {};
60
+ const formatElementName = (elementName) => {
61
+ return elementName ? JSON.stringify(elementName) : "element";
62
+ };
56
63
  class StableBrowser {
57
64
  browser;
58
65
  page;
@@ -66,6 +73,7 @@ class StableBrowser {
66
73
  appName = "main";
67
74
  tags = null;
68
75
  isRecording = false;
76
+ initSnapshotTaken = false;
69
77
  constructor(browser, page, logger = null, context = null, world = null) {
70
78
  this.browser = browser;
71
79
  this.page = page;
@@ -235,16 +243,51 @@ class StableBrowser {
235
243
  // async closeUnexpectedPopups() {
236
244
  // await closeUnexpectedPopups(this.page);
237
245
  // }
238
- async goto(url) {
246
+ async goto(url, world = null) {
247
+ if (!url) {
248
+ throw new Error("url is null, verify that the environment file is correct");
249
+ }
239
250
  if (!url.startsWith("http")) {
240
251
  url = "https://" + url;
241
252
  }
242
- await this.page.goto(url, {
243
- timeout: 60000,
244
- });
253
+ const state = {
254
+ value: url,
255
+ world: world,
256
+ type: Types.NAVIGATE,
257
+ text: `Navigate Page to: ${url}`,
258
+ operation: "goto",
259
+ log: "***** navigate page to " + url + " *****\n",
260
+ info: {},
261
+ locate: false,
262
+ scroll: false,
263
+ screenshot: false,
264
+ highlight: false,
265
+ };
266
+ try {
267
+ await _preCommand(state, this);
268
+ await this.page.goto(url, {
269
+ timeout: 60000,
270
+ });
271
+ await _screenshot(state, this);
272
+ }
273
+ catch (error) {
274
+ console.error("Error on goto", error);
275
+ _commandError(state, error, this);
276
+ }
277
+ finally {
278
+ _commandFinally(state, this);
279
+ }
245
280
  }
246
- _getLocator(locator, scope, _params) {
281
+ async _getLocator(locator, scope, _params) {
247
282
  locator = _fixLocatorUsingParams(locator, _params);
283
+ // locator = await this._replaceWithLocalData(locator);
284
+ for (let key in locator) {
285
+ if (typeof locator[key] !== "string")
286
+ continue;
287
+ if (locator[key].includes("{{") && locator[key].includes("}}")) {
288
+ locator[key] = await this._replaceWithLocalData(locator[key], this.world);
289
+ }
290
+ }
248
291
  let locatorReturn;
249
292
  if (locator.role) {
250
293
  if (locator.role[1].nameReg) {
@@ -295,52 +338,60 @@ class StableBrowser {
295
338
  if (css && css.locator) {
296
339
  css = css.locator;
297
340
  }
298
- let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
341
+ let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
299
342
  if (result.elementCount === 0) {
300
343
  return;
301
344
  }
302
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
345
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
303
346
  // css climb to parent element
304
347
  const climbArray = [];
305
348
  for (let i = 0; i < climb; i++) {
306
349
  climbArray.push("..");
307
350
  }
308
351
  let climbXpath = "xpath=" + climbArray.join("/");
309
- return textElementCss + " >> " + climbXpath + " >> " + css;
310
- }
311
- async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
312
- //const stringifyText = JSON.stringify(text);
313
- return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
314
- const options = {
315
- innerText: true,
316
- };
317
- if (regex) {
318
- options.singleRegex = true;
319
- }
320
- if (tag) {
321
- options.tag = tag;
322
- }
323
- if (!(partial === true)) {
324
- options.exactMatch = true;
325
- }
326
- const elements = window.findMatchingElements(text, options);
327
- let randomToken = null;
328
- const foundElements = [];
329
- let elementCount = 0;
330
- if (elements.length > 0) {
331
- for (let i = 0; i < elements.length; i++) {
332
- if (randomToken === null) {
333
- randomToken = Math.random().toString(36).substring(7);
352
+ let resultCss = textElementCss + " >> " + climbXpath;
353
+ if (css) {
354
+ resultCss = resultCss + " >> " + css;
355
+ }
356
+ return resultCss;
357
+ }
358
+ async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
359
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
360
+ const locator = scope.locator(query);
361
+ const count = await locator.count();
362
+ if (!tag1) {
363
+ tag1 = "*";
364
+ }
365
+ const randomToken = Math.random().toString(36).substring(7);
366
+ let tagCount = 0;
367
+ for (let i = 0; i < count; i++) {
368
+ const element = locator.nth(i);
369
+ // check if the tag matches
370
+ if (!(await element.evaluate((el, [tag, randomToken]) => {
371
+ if (!tag.startsWith("*")) {
372
+ if (el.tagName.toLowerCase() !== tag) {
373
+ return false;
334
374
  }
335
- let element = elements[i];
336
- element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
337
- elementCount++;
338
375
  }
376
+ if (!el.setAttribute) {
377
+ el = el.parentElement;
378
+ }
379
+ // remove any attributes start with data-blinq-id
380
+ // for (let i = 0; i < el.attributes.length; i++) {
381
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
382
+ // el.removeAttribute(el.attributes[i].name);
383
+ // }
384
+ // }
385
+ el.setAttribute("data-blinq-id-" + randomToken, "");
386
+ return true;
387
+ }, [tag1, randomToken]))) {
388
+ continue;
339
389
  }
340
- return { elementCount: elementCount, randomToken: randomToken };
341
- }, [text1, tag1, regex1, partial1]);
390
+ tagCount++;
391
+ }
392
+ return { elementCount: tagCount, randomToken };
342
393
  }
343
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
394
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
344
395
  if (!info) {
345
396
  info = {};
346
397
  }
@@ -363,30 +414,31 @@ class StableBrowser {
363
414
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
364
415
  let locator = null;
365
416
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
366
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
417
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
418
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
367
419
  if (!locatorString) {
368
420
  info.failCause.textNotFound = true;
369
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
421
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
370
422
  return;
371
423
  }
372
- locator = this._getLocator({ css: locatorString }, scope, _params);
424
+ locator = await this._getLocator({ css: locatorString }, scope, _params);
373
425
  }
374
426
  else if (locatorSearch.text) {
375
427
  let text = _fixUsingParams(locatorSearch.text, _params);
376
- let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
428
+ let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
377
429
  if (result.elementCount === 0) {
378
430
  info.failCause.textNotFound = true;
379
- info.failCause.lastError = "failed to locate element by text: " + text;
431
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
380
432
  return;
381
433
  }
382
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
434
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
383
435
  if (locatorSearch.childCss) {
384
436
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
385
437
  }
386
- locator = this._getLocator(locatorSearch, scope, _params);
438
+ locator = await this._getLocator(locatorSearch, scope, _params);
387
439
  }
388
440
  else {
389
- locator = this._getLocator(locatorSearch, scope, _params);
441
+ locator = await this._getLocator(locatorSearch, scope, _params);
390
442
  }
391
443
  // let cssHref = false;
392
444
  // if (locatorSearch.css && locatorSearch.css.includes("href=")) {
@@ -415,7 +467,7 @@ class StableBrowser {
415
467
  if (!visibleOnly) {
416
468
  visible = true;
417
469
  }
418
- if (visible && enabled) {
470
+ if (visible && (allowDisabled || enabled)) {
419
471
  foundLocators.push(locator.nth(j));
420
472
  if (info.locatorLog) {
421
473
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
@@ -428,9 +480,11 @@ class StableBrowser {
428
480
  info.printMessages = {};
429
481
  }
430
482
  if (info.locatorLog && !visible) {
483
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
431
484
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
432
485
  }
433
486
  if (info.locatorLog && !enabled) {
487
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
434
488
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
435
489
  }
436
490
  if (!info.printMessages[j.toString()]) {
@@ -498,7 +552,7 @@ class StableBrowser {
498
552
  }
499
553
  return { rerun: false };
500
554
  }
501
- async _locate(selectors, info, _params, timeout) {
555
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
502
556
  if (!timeout) {
503
557
  timeout = 30000;
504
558
  }
@@ -508,9 +562,18 @@ class StableBrowser {
508
562
  let selector = selectors.locators[j];
509
563
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
510
564
  }
511
- let element = await this._locate_internal(selectors, info, _params, timeout);
565
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
512
566
  if (!element.rerun) {
513
- return element;
567
+ const randomToken = Math.random().toString(36).substring(7);
568
+ element.evaluate((el, randomToken) => {
569
+ el.setAttribute("data-blinq-id-" + randomToken, "");
570
+ }, randomToken);
571
+ if (element._frame) {
572
+ return element;
573
+ }
574
+ const scope = element.page();
575
+ const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
576
+ return newSelector;
514
577
  }
515
578
  }
516
579
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -583,7 +646,7 @@ class StableBrowser {
583
646
  //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
584
647
  if (Date.now() - startTime > timeout) {
585
648
  info.failCause.iframeNotFound = true;
586
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
649
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
587
650
  throw new Error("unable to locate iframe " + selectors.iframe_src);
588
651
  }
589
652
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -608,7 +671,7 @@ class StableBrowser {
608
671
  return bodyContent;
609
672
  });
610
673
  }
611
- async _locate_internal(selectors, info, _params, timeout = 30000) {
674
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
612
675
  if (!info) {
613
676
  info = {};
614
677
  info.failCause = {};
@@ -657,17 +720,17 @@ class StableBrowser {
657
720
  }
658
721
  // info.log += "scanning locators in priority 1" + "\n";
659
722
  let onlyPriority3 = selectorsLocators[0].priority === 3;
660
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
723
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
661
724
  if (result.foundElements.length === 0) {
662
725
  // info.log += "scanning locators in priority 2" + "\n";
663
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
726
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
664
727
  }
665
728
  if (result.foundElements.length === 0 && onlyPriority3) {
666
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
729
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
667
730
  }
668
731
  else {
669
732
  if (result.foundElements.length === 0 && !highPriorityOnly) {
670
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
733
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
671
734
  }
672
735
  }
673
736
  let foundElements = result.foundElements;
@@ -712,7 +775,7 @@ class StableBrowser {
712
775
  break;
713
776
  }
714
777
  if (Date.now() - startTime > highPriorityTimeout) {
715
- info.log += "high priority timeout, will try all elements" + "\n";
778
+ //info.log += "high priority timeout, will try all elements" + "\n";
716
779
  highPriorityOnly = false;
717
780
  if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
718
781
  lazy_scroll = true;
@@ -720,7 +783,7 @@ class StableBrowser {
720
783
  }
721
784
  }
722
785
  if (Date.now() - startTime > visibleOnlyTimeout) {
723
- info.log += "visible only timeout, will try all elements" + "\n";
786
+ //info.log += "visible only timeout, will try all elements" + "\n";
724
787
  visibleOnly = false;
725
788
  }
726
789
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -734,10 +797,12 @@ class StableBrowser {
734
797
  // }
735
798
  //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
736
799
  info.failCause.locatorNotFound = true;
737
- info.failCause.lastError = "failed to locate unique element";
800
+ if (!info?.failCause?.lastError) {
801
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
802
+ }
738
803
  throw new Error("failed to locate first element no elements found, " + info.log);
739
804
  }
740
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
805
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
741
806
  let foundElements = [];
742
807
  const result = {
743
808
  foundElements: foundElements,
@@ -745,7 +810,7 @@ class StableBrowser {
745
810
  for (let i = 0; i < locatorsGroup.length; i++) {
746
811
  let foundLocators = [];
747
812
  try {
748
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
813
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
749
814
  }
750
815
  catch (e) {
751
816
  // this call can fail it the browser is navigating
@@ -753,7 +818,7 @@ class StableBrowser {
753
818
  // this.logger.debug(e);
754
819
  foundLocators = [];
755
820
  try {
756
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
821
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
757
822
  }
758
823
  catch (e) {
759
824
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -768,9 +833,40 @@ class StableBrowser {
768
833
  result.locatorIndex = i;
769
834
  }
770
835
  if (foundLocators.length > 1) {
771
- info.failCause.foundMultiple = true;
772
- if (info.locatorLog) {
773
- info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
836
+ // remove elements that consume the same space with 10 pixels tolerance
837
+ const boxes = [];
838
+ for (let j = 0; j < foundLocators.length; j++) {
839
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
840
+ }
841
+ for (let j = 0; j < boxes.length; j++) {
842
+ for (let k = 0; k < boxes.length; k++) {
843
+ if (j === k) {
844
+ continue;
845
+ }
846
+ // check if x, y, width, height are the same with 10 pixels tolerance
847
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
848
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
849
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
850
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
851
+ // as the element is not unique, will remove it
852
+ boxes.splice(k, 1);
853
+ k--;
854
+ }
855
+ }
856
+ }
857
+ if (boxes.length === 1) {
858
+ result.foundElements.push({
859
+ locator: boxes[0].locator.first(),
860
+ box: boxes[0].box,
861
+ unique: true,
862
+ });
863
+ result.locatorIndex = i;
864
+ }
865
+ else {
866
+ info.failCause.foundMultiple = true;
867
+ if (info.locatorLog) {
868
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
869
+ }
774
870
  }
775
871
  }
776
872
  }
@@ -881,25 +977,14 @@ class StableBrowser {
881
977
  options,
882
978
  world,
883
979
  text: "Click element",
980
+ _text: "Click on " + selectors.element_name,
884
981
  type: Types.CLICK,
885
982
  operation: "click",
886
983
  log: "***** click on " + selectors.element_name + " *****\n",
887
984
  };
888
985
  try {
889
986
  await _preCommand(state, this);
890
- if (state.options && state.options.context) {
891
- state.selectors.locators[0].text = state.options.context;
892
- }
893
- try {
894
- await state.element.click();
895
- // await new Promise((resolve) => setTimeout(resolve, 1000));
896
- }
897
- catch (e) {
898
- // await this.closeUnexpectedPopups();
899
- state.element = await this._locate(selectors, state.info, _params);
900
- await state.element.dispatchEvent("click");
901
- // await new Promise((resolve) => setTimeout(resolve, 1000));
902
- }
987
+ await performAction("click", state.element, options, this, state, _params);
903
988
  await this.waitForPageLoad();
904
989
  return state.info;
905
990
  }
@@ -910,6 +995,38 @@ class StableBrowser {
910
995
  _commandFinally(state, this);
911
996
  }
912
997
  }
998
+ async waitForElement(selectors, _params, options = {}, world = null) {
999
+ const timeout = this._getFindElementTimeout(options);
1000
+ const state = {
1001
+ selectors,
1002
+ _params,
1003
+ options,
1004
+ world,
1005
+ text: "Wait for element",
1006
+ _text: "Wait for " + selectors.element_name,
1007
+ type: Types.WAIT_ELEMENT,
1008
+ operation: "waitForElement",
1009
+ log: "***** wait for " + selectors.element_name + " *****\n",
1010
+ };
1011
+ let found = false;
1012
+ try {
1013
+ await _preCommand(state, this);
1014
+ // if (state.options && state.options.context) {
1015
+ // state.selectors.locators[0].text = state.options.context;
1016
+ // }
1017
+ await state.element.waitFor({ timeout: timeout });
1018
+ found = true;
1019
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1020
+ }
1021
+ catch (e) {
1022
+ console.error("Error on waitForElement", e);
1023
+ // await _commandError(state, e, this);
1024
+ }
1025
+ finally {
1026
+ _commandFinally(state, this);
1027
+ }
1028
+ return found;
1029
+ }
913
1030
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
914
1031
  const state = {
915
1032
  selectors,
@@ -918,6 +1035,7 @@ class StableBrowser {
918
1035
  world,
919
1036
  type: checked ? Types.CHECK : Types.UNCHECK,
920
1037
  text: checked ? `Check element` : `Uncheck element`,
1038
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
921
1039
  operation: "setCheck",
922
1040
  log: "***** check " + selectors.element_name + " *****\n",
923
1041
  };
@@ -927,9 +1045,15 @@ class StableBrowser {
927
1045
  // let element = await this._locate(selectors, info, _params);
928
1046
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
929
1047
  try {
930
- // await this._highlightElements(element);
1048
+ // if (world && world.screenshot && !world.screenshotPath) {
1049
+ // console.log(`Highlighting while running from recorder`);
1050
+ await this._highlightElements(state.element);
931
1051
  await state.element.setChecked(checked);
932
1052
  await new Promise((resolve) => setTimeout(resolve, 1000));
1053
+ // await this._unHighlightElements(element);
1054
+ // }
1055
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1056
+ // await this._unHighlightElements(element);
933
1057
  }
934
1058
  catch (e) {
935
1059
  if (e.message && e.message.includes("did not change its state")) {
@@ -961,22 +1085,13 @@ class StableBrowser {
961
1085
  world,
962
1086
  type: Types.HOVER,
963
1087
  text: `Hover element`,
1088
+ _text: `Hover on ${selectors.element_name}`,
964
1089
  operation: "hover",
965
1090
  log: "***** hover " + selectors.element_name + " *****\n",
966
1091
  };
967
1092
  try {
968
1093
  await _preCommand(state, this);
969
- try {
970
- await state.element.hover();
971
- await new Promise((resolve) => setTimeout(resolve, 1000));
972
- }
973
- catch (e) {
974
- //await this.closeUnexpectedPopups();
975
- state.info.log += "hover failed, will try again" + "\n";
976
- state.element = await this._locate(selectors, state.info, _params);
977
- await state.element.hover({ timeout: 10000 });
978
- await new Promise((resolve) => setTimeout(resolve, 1000));
979
- }
1094
+ await performAction("hover", state.element, options, this, state, _params);
980
1095
  await _screenshot(state, this);
981
1096
  await this.waitForPageLoad();
982
1097
  return state.info;
@@ -1000,6 +1115,7 @@ class StableBrowser {
1000
1115
  value: values.toString(),
1001
1116
  type: Types.SELECT,
1002
1117
  text: `Select option: ${values}`,
1118
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1003
1119
  operation: "selectOption",
1004
1120
  log: "***** select option " + selectors.element_name + " *****\n",
1005
1121
  };
@@ -1034,6 +1150,7 @@ class StableBrowser {
1034
1150
  highlight: false,
1035
1151
  type: Types.TYPE_PRESS,
1036
1152
  text: `Type value: ${_value}`,
1153
+ _text: `Type value: ${_value}`,
1037
1154
  operation: "type",
1038
1155
  log: "",
1039
1156
  };
@@ -1113,6 +1230,7 @@ class StableBrowser {
1113
1230
  world,
1114
1231
  type: Types.SET_DATE_TIME,
1115
1232
  text: `Set date time value: ${value}`,
1233
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1116
1234
  operation: "setDateTime",
1117
1235
  log: "***** set date time value " + selectors.element_name + " *****\n",
1118
1236
  throwError: false,
@@ -1120,7 +1238,7 @@ class StableBrowser {
1120
1238
  try {
1121
1239
  await _preCommand(state, this);
1122
1240
  try {
1123
- await state.element.click();
1241
+ await performAction("click", state.element, options, this, state, _params);
1124
1242
  await new Promise((resolve) => setTimeout(resolve, 500));
1125
1243
  if (format) {
1126
1244
  state.value = dayjs(state.value).format(format);
@@ -1184,9 +1302,13 @@ class StableBrowser {
1184
1302
  world,
1185
1303
  type: Types.FILL,
1186
1304
  text: `Click type input with value: ${_value}`,
1305
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1187
1306
  operation: "clickType",
1188
1307
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1189
1308
  };
1309
+ if (!options) {
1310
+ options = {};
1311
+ }
1190
1312
  if (newValue !== _value) {
1191
1313
  //this.logger.info(_value + "=" + newValue);
1192
1314
  _value = newValue;
@@ -1194,7 +1316,7 @@ class StableBrowser {
1194
1316
  try {
1195
1317
  await _preCommand(state, this);
1196
1318
  state.info.value = _value;
1197
- if (options === null || options === undefined || !options.press) {
1319
+ if (!options.press) {
1198
1320
  try {
1199
1321
  let currentValue = await state.element.inputValue();
1200
1322
  if (currentValue) {
@@ -1205,13 +1327,9 @@ class StableBrowser {
1205
1327
  this.logger.info("unable to clear input value");
1206
1328
  }
1207
1329
  }
1208
- if (options === null || options === undefined || options.press) {
1209
- try {
1210
- await state.element.click({ timeout: 5000 });
1211
- }
1212
- catch (e) {
1213
- await state.element.dispatchEvent("click");
1214
- }
1330
+ if (options.press) {
1331
+ options.timeout = 5000;
1332
+ await performAction("click", state.element, options, this, state, _params);
1215
1333
  }
1216
1334
  else {
1217
1335
  try {
@@ -1249,7 +1367,12 @@ class StableBrowser {
1249
1367
  await this.waitForPageLoad();
1250
1368
  }
1251
1369
  else if (enter === false) {
1252
- await state.element.dispatchEvent("change");
1370
+ try {
1371
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1372
+ }
1373
+ catch (e) {
1374
+ // ignore
1375
+ }
1253
1376
  //await this.page.keyboard.press("Tab");
1254
1377
  }
1255
1378
  else {
@@ -1301,6 +1424,7 @@ class StableBrowser {
1301
1424
  return await this._getText(selectors, 0, _params, options, info, world);
1302
1425
  }
1303
1426
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1427
+ const timeout = this._getFindElementTimeout(options);
1304
1428
  _validateSelectors(selectors);
1305
1429
  let screenshotId = null;
1306
1430
  let screenshotPath = null;
@@ -1310,7 +1434,7 @@ class StableBrowser {
1310
1434
  }
1311
1435
  info.operation = "getText";
1312
1436
  info.selectors = selectors;
1313
- let element = await this._locate(selectors, info, _params);
1437
+ let element = await this._locate(selectors, info, _params, timeout);
1314
1438
  if (climb > 0) {
1315
1439
  const climbArray = [];
1316
1440
  for (let i = 0; i < climb; i++) {
@@ -1329,6 +1453,18 @@ class StableBrowser {
1329
1453
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1330
1454
  try {
1331
1455
  await this._highlightElements(element);
1456
+ // if (world && world.screenshot && !world.screenshotPath) {
1457
+ // // console.log(`Highlighting for get text while running from recorder`);
1458
+ // this._highlightElements(element)
1459
+ // .then(async () => {
1460
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1461
+ // this._unhighlightElements(element).then(
1462
+ // () => {}
1463
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1464
+ // );
1465
+ // })
1466
+ // .catch(e);
1467
+ // }
1332
1468
  const elementText = await element.innerText();
1333
1469
  return {
1334
1470
  text: elementText,
@@ -1340,7 +1476,7 @@ class StableBrowser {
1340
1476
  }
1341
1477
  catch (e) {
1342
1478
  //await this.closeUnexpectedPopups();
1343
- this.logger.info("no innerText will use textContent");
1479
+ this.logger.info("no innerText, will use textContent");
1344
1480
  const elementText = await element.textContent();
1345
1481
  return { text: elementText, screenshotId, screenshotPath, value: value };
1346
1482
  }
@@ -1365,6 +1501,7 @@ class StableBrowser {
1365
1501
  highlight: false,
1366
1502
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1367
1503
  text: `Verify element contains pattern: ${pattern}`,
1504
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1368
1505
  operation: "containsPattern",
1369
1506
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1370
1507
  };
@@ -1400,6 +1537,8 @@ class StableBrowser {
1400
1537
  }
1401
1538
  }
1402
1539
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1540
+ const timeout = this._getFindElementTimeout(options);
1541
+ const startTime = Date.now();
1403
1542
  const state = {
1404
1543
  selectors,
1405
1544
  _params,
@@ -1426,62 +1565,54 @@ class StableBrowser {
1426
1565
  }
1427
1566
  let foundObj = null;
1428
1567
  try {
1429
- await _preCommand(state, this);
1430
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1431
- if (foundObj && foundObj.element) {
1432
- await this.scrollIfNeeded(foundObj.element, state.info);
1433
- }
1434
- await _screenshot(state, this);
1435
- const dateAlternatives = findDateAlternatives(text);
1436
- const numberAlternatives = findNumberAlternatives(text);
1437
- if (dateAlternatives.date) {
1438
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1439
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1440
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1441
- return state.info;
1568
+ while (Date.now() - startTime < timeout) {
1569
+ try {
1570
+ await _preCommand(state, this);
1571
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
1572
+ if (foundObj && foundObj.element) {
1573
+ await this.scrollIfNeeded(foundObj.element, state.info);
1442
1574
  }
1443
- }
1444
- throw new Error("element doesn't contain text " + text);
1445
- }
1446
- else if (numberAlternatives.number) {
1447
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1448
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1449
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1575
+ await _screenshot(state, this);
1576
+ const dateAlternatives = findDateAlternatives(text);
1577
+ const numberAlternatives = findNumberAlternatives(text);
1578
+ if (dateAlternatives.date) {
1579
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1580
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1581
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1582
+ return state.info;
1583
+ }
1584
+ }
1585
+ }
1586
+ else if (numberAlternatives.number) {
1587
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1588
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1589
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1590
+ return state.info;
1591
+ }
1592
+ }
1593
+ }
1594
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1450
1595
  return state.info;
1451
1596
  }
1452
1597
  }
1453
- throw new Error("element doesn't contain text " + text);
1454
- }
1455
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1456
- state.info.foundText = foundObj?.text;
1457
- state.info.value = foundObj?.value;
1458
- throw new Error("element doesn't contain text " + text);
1598
+ catch (e) {
1599
+ // Log error but continue retrying until timeout is reached
1600
+ this.logger.warn("Retrying containsText due to: " + e.message);
1601
+ }
1602
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1459
1603
  }
1460
- return state.info;
1604
+ state.info.foundText = foundObj?.text;
1605
+ state.info.value = foundObj?.value;
1606
+ throw new Error("element doesn't contain text " + text);
1461
1607
  }
1462
1608
  catch (e) {
1463
1609
  await _commandError(state, e, this);
1610
+ throw e;
1464
1611
  }
1465
1612
  finally {
1466
1613
  _commandFinally(state, this);
1467
1614
  }
1468
1615
  }
1469
- _getDataFile(world = null) {
1470
- let dataFile = null;
1471
- if (world && world.reportFolder) {
1472
- dataFile = path.join(world.reportFolder, "data.json");
1473
- }
1474
- else if (this.reportFolder) {
1475
- dataFile = path.join(this.reportFolder, "data.json");
1476
- }
1477
- else if (this.context && this.context.reportFolder) {
1478
- dataFile = path.join(this.context.reportFolder, "data.json");
1479
- }
1480
- else {
1481
- dataFile = "data.json";
1482
- }
1483
- return dataFile;
1484
- }
1485
1616
  async waitForUserInput(message, world = null) {
1486
1617
  if (!message) {
1487
1618
  message = "# Wait for user input. Press any key to continue";
@@ -1510,7 +1641,7 @@ class StableBrowser {
1510
1641
  return;
1511
1642
  }
1512
1643
  // if data file exists, load it
1513
- const dataFile = this._getDataFile(world);
1644
+ const dataFile = _getDataFile(world, this.context, this);
1514
1645
  let data = this.getTestData(world);
1515
1646
  // merge the testData with the existing data
1516
1647
  Object.assign(data, testData);
@@ -1613,7 +1744,7 @@ class StableBrowser {
1613
1744
  }
1614
1745
  }
1615
1746
  getTestData(world = null) {
1616
- const dataFile = this._getDataFile(world);
1747
+ const dataFile = _getDataFile(world, this.context, this);
1617
1748
  let data = {};
1618
1749
  if (fs.existsSync(dataFile)) {
1619
1750
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1700,6 +1831,15 @@ class StableBrowser {
1700
1831
  document.documentElement.clientWidth,
1701
1832
  ])));
1702
1833
  let screenshotBuffer = null;
1834
+ // if (focusedElement) {
1835
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1836
+ // await this._unhighlightElements(focusedElement);
1837
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1838
+ // console.log(`Unhighlighted previous element`);
1839
+ // }
1840
+ // if (focusedElement) {
1841
+ // await this._highlightElements(focusedElement);
1842
+ // }
1703
1843
  if (this.context.browserName === "chromium") {
1704
1844
  const client = await playContext.newCDPSession(this.page);
1705
1845
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1721,6 +1861,10 @@ class StableBrowser {
1721
1861
  else {
1722
1862
  screenshotBuffer = await this.page.screenshot();
1723
1863
  }
1864
+ // if (focusedElement) {
1865
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1866
+ // await this._unhighlightElements(focusedElement);
1867
+ // }
1724
1868
  let image = await Jimp.read(screenshotBuffer);
1725
1869
  // Get the image dimensions
1726
1870
  const { width, height } = image.bitmap;
@@ -1733,6 +1877,7 @@ class StableBrowser {
1733
1877
  else {
1734
1878
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1735
1879
  }
1880
+ return screenshotBuffer;
1736
1881
  }
1737
1882
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1738
1883
  const state = {
@@ -1768,8 +1913,10 @@ class StableBrowser {
1768
1913
  world,
1769
1914
  type: Types.EXTRACT,
1770
1915
  text: `Extract attribute from element`,
1916
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1771
1917
  operation: "extractAttribute",
1772
1918
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1919
+ allowDisabled: true,
1773
1920
  };
1774
1921
  await new Promise((resolve) => setTimeout(resolve, 2000));
1775
1922
  try {
@@ -1791,6 +1938,7 @@ class StableBrowser {
1791
1938
  state.info.value = state.value;
1792
1939
  this.setTestData({ [variable]: state.value }, world);
1793
1940
  this.logger.info("set test data: " + variable + "=" + state.value);
1941
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1794
1942
  return state.info;
1795
1943
  }
1796
1944
  catch (e) {
@@ -1809,14 +1957,21 @@ class StableBrowser {
1809
1957
  options,
1810
1958
  world,
1811
1959
  type: Types.VERIFY_ATTRIBUTE,
1960
+ highlight: true,
1961
+ screenshot: true,
1812
1962
  text: `Verify element attribute`,
1963
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1813
1964
  operation: "verifyAttribute",
1814
1965
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1966
+ allowDisabled: true,
1815
1967
  };
1816
1968
  await new Promise((resolve) => setTimeout(resolve, 2000));
1817
1969
  let val;
1970
+ let expectedValue;
1818
1971
  try {
1819
1972
  await _preCommand(state, this);
1973
+ expectedValue = state.value;
1974
+ state.info.expectedValue = expectedValue;
1820
1975
  switch (attribute) {
1821
1976
  case "innerText":
1822
1977
  val = String(await state.element.innerText());
@@ -1830,23 +1985,30 @@ class StableBrowser {
1830
1985
  case "disabled":
1831
1986
  val = String(await state.element.isDisabled());
1832
1987
  break;
1988
+ case "readOnly":
1989
+ const isEditable = await state.element.isEditable();
1990
+ val = String(!isEditable);
1991
+ break;
1833
1992
  default:
1834
1993
  val = String(await state.element.getAttribute(attribute));
1835
1994
  break;
1836
1995
  }
1996
+ state.info.value = val;
1837
1997
  let regex;
1838
- if (value.startsWith("/") && value.endsWith("/")) {
1839
- const patternBody = value.slice(1, -1);
1998
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
1999
+ const patternBody = expectedValue.slice(1, -1);
1840
2000
  regex = new RegExp(patternBody, "g");
1841
2001
  }
1842
2002
  else {
1843
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2003
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1844
2004
  regex = new RegExp(escapedPattern, "g");
1845
2005
  }
1846
2006
  if (!val.match(regex)) {
1847
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2007
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2008
+ state.info.failCause.assertionFailed = true;
2009
+ state.info.failCause.lastError = errorMessage;
2010
+ throw new Error(errorMessage);
1848
2011
  }
1849
- state.info.value = val;
1850
2012
  return state.info;
1851
2013
  }
1852
2014
  catch (e) {
@@ -1945,27 +2107,32 @@ class StableBrowser {
1945
2107
  async _highlightElements(scope, css) {
1946
2108
  try {
1947
2109
  if (!scope) {
2110
+ // console.log(`Scope is not defined`);
1948
2111
  return;
1949
2112
  }
1950
2113
  if (!css) {
1951
2114
  scope
1952
2115
  .evaluate((node) => {
1953
2116
  if (node && node.style) {
1954
- let originalBorder = node.style.border;
1955
- node.style.border = "2px solid red";
2117
+ let originalOutline = node.style.outline;
2118
+ // console.log(`Original outline was: ${originalOutline}`);
2119
+ // node.__previousOutline = originalOutline;
2120
+ node.style.outline = "2px solid red";
2121
+ // console.log(`New outline is: ${node.style.outline}`);
1956
2122
  if (window) {
1957
2123
  window.addEventListener("beforeunload", function (e) {
1958
- node.style.border = originalBorder;
2124
+ node.style.outline = originalOutline;
1959
2125
  });
1960
2126
  }
1961
2127
  setTimeout(function () {
1962
- node.style.border = originalBorder;
2128
+ node.style.outline = originalOutline;
1963
2129
  }, 2000);
1964
2130
  }
1965
2131
  })
1966
2132
  .then(() => { })
1967
2133
  .catch((e) => {
1968
2134
  // ignore
2135
+ // console.error(`Could not highlight node : ${e}`);
1969
2136
  });
1970
2137
  }
1971
2138
  else {
@@ -1981,17 +2148,18 @@ class StableBrowser {
1981
2148
  if (!element.style) {
1982
2149
  return;
1983
2150
  }
1984
- var originalBorder = element.style.border;
2151
+ let originalOutline = element.style.outline;
2152
+ element.__previousOutline = originalOutline;
1985
2153
  // Set the new border to be red and 2px solid
1986
- element.style.border = "2px solid red";
2154
+ element.style.outline = "2px solid red";
1987
2155
  if (window) {
1988
2156
  window.addEventListener("beforeunload", function (e) {
1989
- element.style.border = originalBorder;
2157
+ element.style.outline = originalOutline;
1990
2158
  });
1991
2159
  }
1992
2160
  // Set a timeout to revert to the original border after 2 seconds
1993
2161
  setTimeout(function () {
1994
- element.style.border = originalBorder;
2162
+ element.style.outline = originalOutline;
1995
2163
  }, 2000);
1996
2164
  }
1997
2165
  return;
@@ -1999,6 +2167,7 @@ class StableBrowser {
1999
2167
  .then(() => { })
2000
2168
  .catch((e) => {
2001
2169
  // ignore
2170
+ // console.error(`Could not highlight css: ${e}`);
2002
2171
  });
2003
2172
  }
2004
2173
  }
@@ -2006,6 +2175,54 @@ class StableBrowser {
2006
2175
  console.debug(error);
2007
2176
  }
2008
2177
  }
2178
+ // async _unhighlightElements(scope, css) {
2179
+ // try {
2180
+ // if (!scope) {
2181
+ // return;
2182
+ // }
2183
+ // if (!css) {
2184
+ // scope
2185
+ // .evaluate((node) => {
2186
+ // if (node && node.style) {
2187
+ // if (!node.__previousOutline) {
2188
+ // node.style.outline = "";
2189
+ // } else {
2190
+ // node.style.outline = node.__previousOutline;
2191
+ // }
2192
+ // }
2193
+ // })
2194
+ // .then(() => {})
2195
+ // .catch((e) => {
2196
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2197
+ // });
2198
+ // } else {
2199
+ // scope
2200
+ // .evaluate(([css]) => {
2201
+ // if (!css) {
2202
+ // return;
2203
+ // }
2204
+ // let elements = Array.from(document.querySelectorAll(css));
2205
+ // for (i = 0; i < elements.length; i++) {
2206
+ // let element = elements[i];
2207
+ // if (!element.style) {
2208
+ // return;
2209
+ // }
2210
+ // if (!element.__previousOutline) {
2211
+ // element.style.outline = "";
2212
+ // } else {
2213
+ // element.style.outline = element.__previousOutline;
2214
+ // }
2215
+ // }
2216
+ // })
2217
+ // .then(() => {})
2218
+ // .catch((e) => {
2219
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2220
+ // });
2221
+ // }
2222
+ // } catch (error) {
2223
+ // // console.debug(error);
2224
+ // }
2225
+ // }
2009
2226
  async verifyPagePath(pathPart, options = {}, world = null) {
2010
2227
  const startTime = Date.now();
2011
2228
  let error = null;
@@ -2050,6 +2267,7 @@ class StableBrowser {
2050
2267
  _reportToWorld(world, {
2051
2268
  type: Types.VERIFY_PAGE_PATH,
2052
2269
  text: "Verify page path",
2270
+ _text: "Verify the page path contains " + pathPart,
2053
2271
  screenshotId,
2054
2272
  result: error
2055
2273
  ? {
@@ -2067,26 +2285,27 @@ class StableBrowser {
2067
2285
  });
2068
2286
  }
2069
2287
  }
2070
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2288
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2071
2289
  const frames = this.page.frames();
2072
2290
  let results = [];
2291
+ // let ignoreCase = false;
2073
2292
  for (let i = 0; i < frames.length; i++) {
2074
2293
  if (dateAlternatives.date) {
2075
2294
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2076
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
2295
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2077
2296
  result.frame = frames[i];
2078
2297
  results.push(result);
2079
2298
  }
2080
2299
  }
2081
2300
  else if (numberAlternatives.number) {
2082
2301
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2083
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
2302
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2084
2303
  result.frame = frames[i];
2085
2304
  results.push(result);
2086
2305
  }
2087
2306
  }
2088
2307
  else {
2089
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
2308
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2090
2309
  result.frame = frames[i];
2091
2310
  results.push(result);
2092
2311
  }
@@ -2105,11 +2324,15 @@ class StableBrowser {
2105
2324
  scroll: false,
2106
2325
  highlight: false,
2107
2326
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2108
- text: `Verify text exists in page`,
2327
+ text: `Verify the text '${text}' exists in page`,
2328
+ _text: `Verify the text '${text}' exists in page`,
2109
2329
  operation: "verifyTextExistInPage",
2110
2330
  log: "***** verify text " + text + " exists in page *****\n",
2111
2331
  };
2112
- const timeout = this._getLoadTimeout(options);
2332
+ if (testForRegex(text)) {
2333
+ text = text.replace(/\\"/g, '"');
2334
+ }
2335
+ const timeout = this._getFindElementTimeout(options);
2113
2336
  await new Promise((resolve) => setTimeout(resolve, 2000));
2114
2337
  const newValue = await this._replaceWithLocalData(text, world);
2115
2338
  if (newValue !== text) {
@@ -2122,7 +2345,15 @@ class StableBrowser {
2122
2345
  await _preCommand(state, this);
2123
2346
  state.info.text = text;
2124
2347
  while (true) {
2125
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2348
+ let resultWithElementsFound = {
2349
+ length: 0,
2350
+ };
2351
+ try {
2352
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2353
+ }
2354
+ catch (error) {
2355
+ // ignore
2356
+ }
2126
2357
  if (resultWithElementsFound.length === 0) {
2127
2358
  if (Date.now() - state.startTime > timeout) {
2128
2359
  throw new Error(`Text ${text} not found in page`);
@@ -2130,18 +2361,40 @@ class StableBrowser {
2130
2361
  await new Promise((resolve) => setTimeout(resolve, 1000));
2131
2362
  continue;
2132
2363
  }
2133
- if (resultWithElementsFound[0].randomToken) {
2134
- const frame = resultWithElementsFound[0].frame;
2135
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2136
- await this._highlightElements(frame, dataAttribute);
2137
- const element = await frame.$(dataAttribute);
2138
- if (element) {
2139
- await this.scrollIfNeeded(element, state.info);
2140
- await element.dispatchEvent("bvt_verify_page_contains_text");
2364
+ try {
2365
+ if (resultWithElementsFound[0].randomToken) {
2366
+ const frame = resultWithElementsFound[0].frame;
2367
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2368
+ await this._highlightElements(frame, dataAttribute);
2369
+ // if (world && world.screenshot && !world.screenshotPath) {
2370
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2371
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2372
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2373
+ // this._unhighlightElements(frame, dataAttribute)
2374
+ // .then(async () => {
2375
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2376
+ // })
2377
+ // .catch(
2378
+ // (e) => {}
2379
+ // console.error(e)
2380
+ // );
2381
+ // });
2382
+ // }
2383
+ const element = await frame.locator(dataAttribute).first();
2384
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2385
+ // await this._unhighlightElements(frame, dataAttribute);
2386
+ if (element) {
2387
+ await this.scrollIfNeeded(element, state.info);
2388
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2389
+ // await _screenshot(state, this, element);
2390
+ }
2141
2391
  }
2392
+ await _screenshot(state, this);
2393
+ return state.info;
2394
+ }
2395
+ catch (error) {
2396
+ console.error(error);
2142
2397
  }
2143
- await _screenshot(state, this);
2144
- return state.info;
2145
2398
  }
2146
2399
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2147
2400
  }
@@ -2162,11 +2415,15 @@ class StableBrowser {
2162
2415
  scroll: false,
2163
2416
  highlight: false,
2164
2417
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2165
- text: `Verify text does not exist in page`,
2418
+ text: `Verify the text '${text}' does not exist in page`,
2419
+ _text: `Verify the text '${text}' does not exist in page`,
2166
2420
  operation: "verifyTextNotExistInPage",
2167
2421
  log: "***** verify text " + text + " does not exist in page *****\n",
2168
2422
  };
2169
- const timeout = this._getLoadTimeout(options);
2423
+ if (testForRegex(text)) {
2424
+ text = text.replace(/\\"/g, '"');
2425
+ }
2426
+ const timeout = this._getFindElementTimeout(options);
2170
2427
  await new Promise((resolve) => setTimeout(resolve, 2000));
2171
2428
  const newValue = await this._replaceWithLocalData(text, world);
2172
2429
  if (newValue !== text) {
@@ -2178,8 +2435,16 @@ class StableBrowser {
2178
2435
  try {
2179
2436
  await _preCommand(state, this);
2180
2437
  state.info.text = text;
2438
+ let resultWithElementsFound = {
2439
+ length: null, // initial cannot be 0
2440
+ };
2181
2441
  while (true) {
2182
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2442
+ try {
2443
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2444
+ }
2445
+ catch (error) {
2446
+ // ignore
2447
+ }
2183
2448
  if (resultWithElementsFound.length === 0) {
2184
2449
  await _screenshot(state, this);
2185
2450
  return state.info;
@@ -2209,10 +2474,11 @@ class StableBrowser {
2209
2474
  highlight: false,
2210
2475
  type: Types.VERIFY_TEXT_WITH_RELATION,
2211
2476
  text: `Verify text with relation to another text`,
2477
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2212
2478
  operation: "verify_text_with_relation",
2213
2479
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2214
2480
  };
2215
- const timeout = this._getLoadTimeout(options);
2481
+ const timeout = this._getFindElementTimeout(options);
2216
2482
  await new Promise((resolve) => setTimeout(resolve, 2000));
2217
2483
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2218
2484
  if (newValue !== textAnchor) {
@@ -2230,8 +2496,16 @@ class StableBrowser {
2230
2496
  try {
2231
2497
  await _preCommand(state, this);
2232
2498
  state.info.text = textToVerify;
2499
+ let resultWithElementsFound = {
2500
+ length: 0,
2501
+ };
2233
2502
  while (true) {
2234
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2503
+ try {
2504
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2505
+ }
2506
+ catch (error) {
2507
+ // ignore
2508
+ }
2235
2509
  if (resultWithElementsFound.length === 0) {
2236
2510
  if (Date.now() - state.startTime > timeout) {
2237
2511
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2239,51 +2513,56 @@ class StableBrowser {
2239
2513
  await new Promise((resolve) => setTimeout(resolve, 1000));
2240
2514
  continue;
2241
2515
  }
2242
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2243
- foundAncore = true;
2244
- const result = resultWithElementsFound[i];
2245
- const token = result.randomToken;
2246
- const frame = result.frame;
2247
- const css = `[data-blinq-id="blinq-id-${token}"]`;
2248
- const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
2249
- const elements = Array.from(document.querySelectorAll(css));
2250
- for (let i = 0; i < elements.length; i++) {
2251
- const element = elements[i];
2252
- let climbParent = element;
2253
- for (let j = 0; j < climb; j++) {
2254
- climbParent = climbParent.parentElement;
2255
- if (!climbParent) {
2256
- break;
2516
+ try {
2517
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2518
+ foundAncore = true;
2519
+ const result = resultWithElementsFound[i];
2520
+ const token = result.randomToken;
2521
+ const frame = result.frame;
2522
+ let css = `[data-blinq-id-${token}]`;
2523
+ const climbArray1 = [];
2524
+ for (let i = 0; i < climb; i++) {
2525
+ climbArray1.push("..");
2526
+ }
2527
+ let climbXpath = "xpath=" + climbArray1.join("/");
2528
+ css = css + " >> " + climbXpath;
2529
+ const count = await frame.locator(css).count();
2530
+ for (let j = 0; j < count; j++) {
2531
+ const continer = await frame.locator(css).nth(j);
2532
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
2533
+ if (result.elementCount > 0) {
2534
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2535
+ await this._highlightElements(frame, dataAttribute);
2536
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2537
+ // if (world && world.screenshot && !world.screenshotPath) {
2538
+ // console.log(`Highlighting for vtrt while running from recorder`);
2539
+ // this._highlightElements(frame, dataAttribute)
2540
+ // .then(async () => {
2541
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2542
+ // this._unhighlightElements(frame, dataAttribute).then(
2543
+ // () => {}
2544
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2545
+ // );
2546
+ // })
2547
+ // .catch(e);
2548
+ // }
2549
+ //await this._highlightElements(frame, cssAnchor);
2550
+ const element = await frame.locator(dataAttribute).first();
2551
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2552
+ // await this._unhighlightElements(frame, dataAttribute);
2553
+ if (element) {
2554
+ await this.scrollIfNeeded(element, state.info);
2555
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2257
2556
  }
2258
- }
2259
- if (!climbParent) {
2260
- continue;
2261
- }
2262
- const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
2263
- if (foundElements.length > 0) {
2264
- // set the container element attribute
2265
- element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
2266
- climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
2267
- foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
2268
- return { found: true };
2557
+ await _screenshot(state, this);
2558
+ return state.info;
2269
2559
  }
2270
2560
  }
2271
- return { found: false };
2272
- }, [css, climb, textToVerify, result.randomToken]);
2273
- if (findResult.found === true) {
2274
- const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
2275
- const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2276
- await this._highlightElements(frame, dataAttribute);
2277
- await this._highlightElements(frame, cssAnchor);
2278
- const element = await frame.$(dataAttribute);
2279
- if (element) {
2280
- await this.scrollIfNeeded(element, state.info);
2281
- await element.dispatchEvent("bvt_verify_page_contains_text");
2282
- }
2283
- await _screenshot(state, this);
2284
- return state.info;
2285
2561
  }
2286
2562
  }
2563
+ catch (error) {
2564
+ console.error(error);
2565
+ }
2287
2566
  }
2288
2567
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2289
2568
  }
@@ -2294,6 +2573,30 @@ class StableBrowser {
2294
2573
  _commandFinally(state, this);
2295
2574
  }
2296
2575
  }
2576
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2577
+ const frames = this.page.frames();
2578
+ let results = [];
2579
+ let ignoreCase = false;
2580
+ for (let i = 0; i < frames.length; i++) {
2581
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2582
+ result.frame = frames[i];
2583
+ const climbArray = [];
2584
+ for (let i = 0; i < climb; i++) {
2585
+ climbArray.push("..");
2586
+ }
2587
+ let climbXpath = "xpath=" + climbArray.join("/");
2588
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2589
+ const count = await frames[i].locator(newLocator).count();
2590
+ if (count > 0) {
2591
+ result.elementCount = count;
2592
+ result.locator = newLocator;
2593
+ results.push(result);
2594
+ }
2595
+ }
2596
+ // state.info.results = results;
2597
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2598
+ return resultWithElementsFound;
2599
+ }
2297
2600
  async visualVerification(text, options = {}, world = null) {
2298
2601
  const startTime = Date.now();
2299
2602
  let error = null;
@@ -2312,10 +2615,13 @@ class StableBrowser {
2312
2615
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2313
2616
  info.screenshotPath = screenshotPath;
2314
2617
  const screenshot = await this.takeScreenshot();
2315
- const request = {
2316
- method: "POST",
2618
+ let request = {
2619
+ method: "post",
2620
+ maxBodyLength: Infinity,
2317
2621
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2318
2622
  headers: {
2623
+ "x-bvt-project-id": path.basename(this.project_path),
2624
+ "x-source": "aaa",
2319
2625
  "Content-Type": "application/json",
2320
2626
  Authorization: `Bearer ${process.env.TOKEN}`,
2321
2627
  },
@@ -2324,7 +2630,7 @@ class StableBrowser {
2324
2630
  screenshot: screenshot,
2325
2631
  }),
2326
2632
  };
2327
- let result = await this.context.api.request(request);
2633
+ const result = await axios.request(request);
2328
2634
  if (result.data.status !== true) {
2329
2635
  throw new Error("Visual validation failed");
2330
2636
  }
@@ -2352,6 +2658,7 @@ class StableBrowser {
2352
2658
  _reportToWorld(world, {
2353
2659
  type: Types.VERIFY_VISUAL,
2354
2660
  text: "Visual verification",
2661
+ _text: "Visual verification of " + text,
2355
2662
  screenshotId,
2356
2663
  result: error
2357
2664
  ? {
@@ -2618,6 +2925,32 @@ class StableBrowser {
2618
2925
  }
2619
2926
  return timeout;
2620
2927
  }
2928
+ _getFindElementTimeout(options) {
2929
+ if (options && options.timeout) {
2930
+ return options.timeout;
2931
+ }
2932
+ if (this.configuration.find_element_timeout) {
2933
+ return this.configuration.find_element_timeout;
2934
+ }
2935
+ return 30000;
2936
+ }
2937
+ async saveStoreState(path = null, world = null) {
2938
+ const storageState = await this.page.context().storageState();
2939
+ //const testDataFile = _getDataFile(world, this.context, this);
2940
+ if (path) {
2941
+ // save { storageState: storageState } into the path
2942
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
2943
+ }
2944
+ else {
2945
+ await this.setTestData({ storageState: storageState }, world);
2946
+ }
2947
+ }
2948
+ async restoreSaveState(path = null, world = null) {
2949
+ await refreshBrowser(this, path, world);
2950
+ this.registerEventListeners(this.context);
2951
+ registerNetworkEvents(this.world, this, this.context, this.page);
2952
+ registerDownloadEvent(this.page, this.world, this.context);
2953
+ }
2621
2954
  async waitForPageLoad(options = {}, world = null) {
2622
2955
  let timeout = this._getLoadTimeout(options);
2623
2956
  const promiseArray = [];
@@ -2685,6 +3018,7 @@ class StableBrowser {
2685
3018
  highlight: false,
2686
3019
  type: Types.CLOSE_PAGE,
2687
3020
  text: `Close page`,
3021
+ _text: `Close the page`,
2688
3022
  operation: "closePage",
2689
3023
  log: "***** close page *****\n",
2690
3024
  throwError: false,
@@ -2701,8 +3035,95 @@ class StableBrowser {
2701
3035
  _commandFinally(state, this);
2702
3036
  }
2703
3037
  }
3038
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3039
+ let operation = null;
3040
+ if (!options || !options.operation) {
3041
+ throw new Error("operation is not defined");
3042
+ }
3043
+ operation = options.operation;
3044
+ // validate operation is one of the supported operations
3045
+ if (operation != "click" && operation != "hover+click") {
3046
+ throw new Error("operation is not supported");
3047
+ }
3048
+ const state = {
3049
+ options,
3050
+ world,
3051
+ locate: false,
3052
+ scroll: false,
3053
+ highlight: false,
3054
+ type: Types.TABLE_OPERATION,
3055
+ text: `Table operation`,
3056
+ _text: `Table ${operation} operation`,
3057
+ operation: operation,
3058
+ log: "***** Table operation *****\n",
3059
+ };
3060
+ const timeout = this._getFindElementTimeout(options);
3061
+ try {
3062
+ await _preCommand(state, this);
3063
+ const start = Date.now();
3064
+ let cellArea = null;
3065
+ while (true) {
3066
+ try {
3067
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3068
+ if (cellArea) {
3069
+ break;
3070
+ }
3071
+ }
3072
+ catch (e) {
3073
+ // ignore
3074
+ }
3075
+ if (Date.now() - start > timeout) {
3076
+ throw new Error(`Cell not found in table`);
3077
+ }
3078
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3079
+ }
3080
+ switch (operation) {
3081
+ case "click":
3082
+ if (!options.css) {
3083
+ // will click in the center of the cell
3084
+ let xOffset = 0;
3085
+ let yOffset = 0;
3086
+ if (options.xOffset) {
3087
+ xOffset = options.xOffset;
3088
+ }
3089
+ if (options.yOffset) {
3090
+ yOffset = options.yOffset;
3091
+ }
3092
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3093
+ }
3094
+ else {
3095
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3096
+ if (results.length === 0) {
3097
+ throw new Error(`Element not found in cell area`);
3098
+ }
3099
+ state.element = results[0];
3100
+ await performAction("click", state.element, options, this, state, _params);
3101
+ }
3102
+ break;
3103
+ case "hover+click":
3104
+ if (!options.css) {
3105
+ throw new Error("css is not defined");
3106
+ }
3107
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3108
+ if (results.length === 0) {
3109
+ throw new Error(`Element not found in cell area`);
3110
+ }
3111
+ state.element = results[0];
3112
+ await performAction("hover+click", state.element, options, this, state, _params);
3113
+ break;
3114
+ default:
3115
+ throw new Error("operation is not supported");
3116
+ }
3117
+ }
3118
+ catch (e) {
3119
+ await _commandError(state, e, this);
3120
+ }
3121
+ finally {
3122
+ _commandFinally(state, this);
3123
+ }
3124
+ }
2704
3125
  saveTestDataAsGlobal(options, world) {
2705
- const dataFile = this._getDataFile(world);
3126
+ const dataFile = _getDataFile(world, this.context, this);
2706
3127
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2707
3128
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2708
3129
  }
@@ -2732,6 +3153,7 @@ class StableBrowser {
2732
3153
  _reportToWorld(world, {
2733
3154
  type: Types.SET_VIEWPORT,
2734
3155
  text: "set viewport size to " + width + "x" + hight,
3156
+ _text: "Set the viewport size to " + width + "x" + hight,
2735
3157
  screenshotId,
2736
3158
  result: error
2737
3159
  ? {
@@ -2803,14 +3225,25 @@ class StableBrowser {
2803
3225
  }
2804
3226
  }
2805
3227
  async beforeStep(world, step) {
2806
- this.stepName = step.pickleStep.text;
2807
- this.logger.info("step: " + this.stepName);
2808
3228
  if (this.stepIndex === undefined) {
2809
3229
  this.stepIndex = 0;
2810
3230
  }
2811
3231
  else {
2812
3232
  this.stepIndex++;
2813
3233
  }
3234
+ if (step && step.pickleStep && step.pickleStep.text) {
3235
+ this.stepName = step.pickleStep.text;
3236
+ this.logger.info("step: " + this.stepName);
3237
+ }
3238
+ else if (step && step.text) {
3239
+ this.stepName = step.text;
3240
+ }
3241
+ else {
3242
+ this.stepName = "step " + this.stepIndex;
3243
+ }
3244
+ if (this.context) {
3245
+ this.context.examplesRow = extractStepExampleParameters(step);
3246
+ }
2814
3247
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2815
3248
  if (this.context.browserObject.context) {
2816
3249
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2823,6 +3256,41 @@ class StableBrowser {
2823
3256
  this.saveTestDataAsGlobal({}, world);
2824
3257
  }
2825
3258
  }
3259
+ if (this.initSnapshotTaken === false) {
3260
+ this.initSnapshotTaken = true;
3261
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3262
+ const snapshot = await this.getAriaSnapshot();
3263
+ if (snapshot) {
3264
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3265
+ }
3266
+ }
3267
+ }
3268
+ }
3269
+ async getAriaSnapshot() {
3270
+ try {
3271
+ // find the page url
3272
+ const url = await this.page.url();
3273
+ // extract the path from the url
3274
+ const path = new URL(url).pathname;
3275
+ // get the page title
3276
+ const title = await this.page.title();
3277
+ // go over other frams
3278
+ const frames = this.page.frames();
3279
+ const snapshots = [];
3280
+ const content = [`- path: ${path}`, `- title: ${title}`];
3281
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3282
+ for (let i = 0; i < frames.length; i++) {
3283
+ content.push(`- frame: ${i}`);
3284
+ const frame = frames[i];
3285
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3286
+ content.push(snapshot);
3287
+ }
3288
+ return content.join("\n");
3289
+ }
3290
+ catch (e) {
3291
+ console.error(e);
3292
+ }
3293
+ return null;
2826
3294
  }
2827
3295
  async afterStep(world, step) {
2828
3296
  this.stepName = null;
@@ -2833,6 +3301,16 @@ class StableBrowser {
2833
3301
  });
2834
3302
  }
2835
3303
  }
3304
+ if (this.context) {
3305
+ this.context.examplesRow = null;
3306
+ }
3307
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3308
+ const snapshot = await this.getAriaSnapshot();
3309
+ if (snapshot) {
3310
+ const obj = {};
3311
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
3312
+ }
3313
+ }
2836
3314
  }
2837
3315
  }
2838
3316
  function createTimedPromise(promise, label) {