automation_model 1.0.581-dev → 1.0.581-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 { _convertToRegexQuery, _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",
@@ -51,8 +55,12 @@ export const Types = {
51
55
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
52
56
  VERIFY_ATTRIBUTE: "verify_element_attribute",
53
57
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
58
+ BRUNO: "bruno",
54
59
  };
55
60
  export const apps = {};
61
+ const formatElementName = (elementName) => {
62
+ return elementName ? JSON.stringify(elementName) : "element";
63
+ };
56
64
  class StableBrowser {
57
65
  browser;
58
66
  page;
@@ -66,6 +74,7 @@ class StableBrowser {
66
74
  appName = "main";
67
75
  tags = null;
68
76
  isRecording = false;
77
+ initSnapshotTaken = false;
69
78
  constructor(browser, page, logger = null, context = null, world = null) {
70
79
  this.browser = browser;
71
80
  this.page = page;
@@ -236,6 +245,9 @@ class StableBrowser {
236
245
  // await closeUnexpectedPopups(this.page);
237
246
  // }
238
247
  async goto(url, world = null) {
248
+ if (!url) {
249
+ throw new Error("url is null, verify that the environment file is correct");
250
+ }
239
251
  if (!url.startsWith("http")) {
240
252
  url = "https://" + url;
241
253
  }
@@ -331,7 +343,7 @@ class StableBrowser {
331
343
  if (result.elementCount === 0) {
332
344
  return;
333
345
  }
334
- let textElementCss = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
346
+ let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
335
347
  // css climb to parent element
336
348
  const climbArray = [];
337
349
  for (let i = 0; i < climb; i++) {
@@ -345,9 +357,12 @@ class StableBrowser {
345
357
  return resultCss;
346
358
  }
347
359
  async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
348
- const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
360
+ const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
349
361
  const locator = scope.locator(query);
350
362
  const count = await locator.count();
363
+ if (!tag1) {
364
+ tag1 = "*";
365
+ }
351
366
  const randomToken = Math.random().toString(36).substring(7);
352
367
  let tagCount = 0;
353
368
  for (let i = 0; i < count; i++) {
@@ -362,7 +377,13 @@ class StableBrowser {
362
377
  if (!el.setAttribute) {
363
378
  el = el.parentElement;
364
379
  }
365
- el.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
380
+ // remove any attributes start with data-blinq-id
381
+ // for (let i = 0; i < el.attributes.length; i++) {
382
+ // if (el.attributes[i].name.startsWith("data-blinq-id")) {
383
+ // el.removeAttribute(el.attributes[i].name);
384
+ // }
385
+ // }
386
+ el.setAttribute("data-blinq-id-" + randomToken, "");
366
387
  return true;
367
388
  }, [tag1, randomToken]))) {
368
389
  continue;
@@ -371,7 +392,7 @@ class StableBrowser {
371
392
  }
372
393
  return { elementCount: tagCount, randomToken };
373
394
  }
374
- async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
395
+ async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
375
396
  if (!info) {
376
397
  info = {};
377
398
  }
@@ -394,10 +415,11 @@ class StableBrowser {
394
415
  //info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
395
416
  let locator = null;
396
417
  if (locatorSearch.climb && locatorSearch.climb >= 0) {
397
- let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
418
+ const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
419
+ let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
398
420
  if (!locatorString) {
399
421
  info.failCause.textNotFound = true;
400
- info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
422
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
401
423
  return;
402
424
  }
403
425
  locator = await this._getLocator({ css: locatorString }, scope, _params);
@@ -407,10 +429,10 @@ class StableBrowser {
407
429
  let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
408
430
  if (result.elementCount === 0) {
409
431
  info.failCause.textNotFound = true;
410
- info.failCause.lastError = "failed to locate element by text: " + text;
432
+ info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
411
433
  return;
412
434
  }
413
- locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
435
+ locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
414
436
  if (locatorSearch.childCss) {
415
437
  locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
416
438
  }
@@ -446,7 +468,7 @@ class StableBrowser {
446
468
  if (!visibleOnly) {
447
469
  visible = true;
448
470
  }
449
- if (visible && enabled) {
471
+ if (visible && (allowDisabled || enabled)) {
450
472
  foundLocators.push(locator.nth(j));
451
473
  if (info.locatorLog) {
452
474
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
@@ -459,9 +481,11 @@ class StableBrowser {
459
481
  info.printMessages = {};
460
482
  }
461
483
  if (info.locatorLog && !visible) {
484
+ info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
462
485
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
463
486
  }
464
487
  if (info.locatorLog && !enabled) {
488
+ info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
465
489
  info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
466
490
  }
467
491
  if (!info.printMessages[j.toString()]) {
@@ -529,7 +553,7 @@ class StableBrowser {
529
553
  }
530
554
  return { rerun: false };
531
555
  }
532
- async _locate(selectors, info, _params, timeout) {
556
+ async _locate(selectors, info, _params, timeout, allowDisabled = false) {
533
557
  if (!timeout) {
534
558
  timeout = 30000;
535
559
  }
@@ -539,9 +563,30 @@ class StableBrowser {
539
563
  let selector = selectors.locators[j];
540
564
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
541
565
  }
542
- let element = await this._locate_internal(selectors, info, _params, timeout);
566
+ let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
543
567
  if (!element.rerun) {
544
- return element;
568
+ const randomToken = Math.random().toString(36).substring(7);
569
+ element.evaluate((el, randomToken) => {
570
+ el.setAttribute("data-blinq-id-" + randomToken, "");
571
+ }, randomToken);
572
+ // if (element._frame) {
573
+ // return element;
574
+ // }
575
+ const scope = element._frame ?? element.page();
576
+ let newElementSelector = "[data-blinq-id-" + randomToken + "]";
577
+ let prefixSelector = "";
578
+ const frameControlSelector = " >> internal:control=enter-frame";
579
+ const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
580
+ if (frameSelectorIndex !== -1) {
581
+ // remove everything after the >> internal:control=enter-frame
582
+ const frameSelector = element._selector.substring(0, frameSelectorIndex);
583
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
584
+ }
585
+ // if (element?._frame?._selector) {
586
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
587
+ // }
588
+ const newSelector = prefixSelector + newElementSelector;
589
+ return scope.locator(newSelector);
545
590
  }
546
591
  }
547
592
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -614,7 +659,7 @@ class StableBrowser {
614
659
  //info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
615
660
  if (Date.now() - startTime > timeout) {
616
661
  info.failCause.iframeNotFound = true;
617
- info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
662
+ info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
618
663
  throw new Error("unable to locate iframe " + selectors.iframe_src);
619
664
  }
620
665
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -639,7 +684,7 @@ class StableBrowser {
639
684
  return bodyContent;
640
685
  });
641
686
  }
642
- async _locate_internal(selectors, info, _params, timeout = 30000) {
687
+ async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
643
688
  if (!info) {
644
689
  info = {};
645
690
  info.failCause = {};
@@ -688,18 +733,13 @@ class StableBrowser {
688
733
  }
689
734
  // info.log += "scanning locators in priority 1" + "\n";
690
735
  let onlyPriority3 = selectorsLocators[0].priority === 3;
691
- result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
736
+ result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
692
737
  if (result.foundElements.length === 0) {
693
738
  // info.log += "scanning locators in priority 2" + "\n";
694
- result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
739
+ result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
695
740
  }
696
- if (result.foundElements.length === 0 && onlyPriority3) {
697
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
698
- }
699
- else {
700
- if (result.foundElements.length === 0 && !highPriorityOnly) {
701
- result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
702
- }
741
+ if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
742
+ result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
703
743
  }
704
744
  let foundElements = result.foundElements;
705
745
  if (foundElements.length === 1 && foundElements[0].unique) {
@@ -755,6 +795,11 @@ class StableBrowser {
755
795
  visibleOnly = false;
756
796
  }
757
797
  await new Promise((resolve) => setTimeout(resolve, 1000));
798
+ // sheck of more of half of the timeout has passed
799
+ if (Date.now() - startTime > timeout / 2) {
800
+ highPriorityOnly = false;
801
+ visibleOnly = false;
802
+ }
758
803
  }
759
804
  this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
760
805
  // if (info.locatorLog) {
@@ -765,10 +810,12 @@ class StableBrowser {
765
810
  // }
766
811
  //info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
767
812
  info.failCause.locatorNotFound = true;
768
- info.failCause.lastError = "failed to locate unique element";
813
+ if (!info?.failCause?.lastError) {
814
+ info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
815
+ }
769
816
  throw new Error("failed to locate first element no elements found, " + info.log);
770
817
  }
771
- async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
818
+ async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
772
819
  let foundElements = [];
773
820
  const result = {
774
821
  foundElements: foundElements,
@@ -776,7 +823,7 @@ class StableBrowser {
776
823
  for (let i = 0; i < locatorsGroup.length; i++) {
777
824
  let foundLocators = [];
778
825
  try {
779
- await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
826
+ await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
780
827
  }
781
828
  catch (e) {
782
829
  // this call can fail it the browser is navigating
@@ -784,7 +831,7 @@ class StableBrowser {
784
831
  // this.logger.debug(e);
785
832
  foundLocators = [];
786
833
  try {
787
- await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
834
+ await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
788
835
  }
789
836
  catch (e) {
790
837
  this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
@@ -799,9 +846,40 @@ class StableBrowser {
799
846
  result.locatorIndex = i;
800
847
  }
801
848
  if (foundLocators.length > 1) {
802
- info.failCause.foundMultiple = true;
803
- if (info.locatorLog) {
804
- info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
849
+ // remove elements that consume the same space with 10 pixels tolerance
850
+ const boxes = [];
851
+ for (let j = 0; j < foundLocators.length; j++) {
852
+ boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
853
+ }
854
+ for (let j = 0; j < boxes.length; j++) {
855
+ for (let k = 0; k < boxes.length; k++) {
856
+ if (j === k) {
857
+ continue;
858
+ }
859
+ // check if x, y, width, height are the same with 10 pixels tolerance
860
+ if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
861
+ Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
862
+ Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
863
+ Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
864
+ // as the element is not unique, will remove it
865
+ boxes.splice(k, 1);
866
+ k--;
867
+ }
868
+ }
869
+ }
870
+ if (boxes.length === 1) {
871
+ result.foundElements.push({
872
+ locator: boxes[0].locator.first(),
873
+ box: boxes[0].box,
874
+ unique: true,
875
+ });
876
+ result.locatorIndex = i;
877
+ }
878
+ else {
879
+ info.failCause.foundMultiple = true;
880
+ if (info.locatorLog) {
881
+ info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
882
+ }
805
883
  }
806
884
  }
807
885
  }
@@ -912,25 +990,14 @@ class StableBrowser {
912
990
  options,
913
991
  world,
914
992
  text: "Click element",
993
+ _text: "Click on " + selectors.element_name,
915
994
  type: Types.CLICK,
916
995
  operation: "click",
917
996
  log: "***** click on " + selectors.element_name + " *****\n",
918
997
  };
919
998
  try {
920
999
  await _preCommand(state, this);
921
- // if (state.options && state.options.context) {
922
- // state.selectors.locators[0].text = state.options.context;
923
- // }
924
- try {
925
- await state.element.click();
926
- // await new Promise((resolve) => setTimeout(resolve, 1000));
927
- }
928
- catch (e) {
929
- // await this.closeUnexpectedPopups();
930
- state.element = await this._locate(selectors, state.info, _params);
931
- await state.element.dispatchEvent("click");
932
- // await new Promise((resolve) => setTimeout(resolve, 1000));
933
- }
1000
+ await performAction("click", state.element, options, this, state, _params);
934
1001
  await this.waitForPageLoad();
935
1002
  return state.info;
936
1003
  }
@@ -941,6 +1008,38 @@ class StableBrowser {
941
1008
  _commandFinally(state, this);
942
1009
  }
943
1010
  }
1011
+ async waitForElement(selectors, _params, options = {}, world = null) {
1012
+ const timeout = this._getFindElementTimeout(options);
1013
+ const state = {
1014
+ selectors,
1015
+ _params,
1016
+ options,
1017
+ world,
1018
+ text: "Wait for element",
1019
+ _text: "Wait for " + selectors.element_name,
1020
+ type: Types.WAIT_ELEMENT,
1021
+ operation: "waitForElement",
1022
+ log: "***** wait for " + selectors.element_name + " *****\n",
1023
+ };
1024
+ let found = false;
1025
+ try {
1026
+ await _preCommand(state, this);
1027
+ // if (state.options && state.options.context) {
1028
+ // state.selectors.locators[0].text = state.options.context;
1029
+ // }
1030
+ await state.element.waitFor({ timeout: timeout });
1031
+ found = true;
1032
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1033
+ }
1034
+ catch (e) {
1035
+ console.error("Error on waitForElement", e);
1036
+ // await _commandError(state, e, this);
1037
+ }
1038
+ finally {
1039
+ _commandFinally(state, this);
1040
+ }
1041
+ return found;
1042
+ }
944
1043
  async setCheck(selectors, checked = true, _params, options = {}, world = null) {
945
1044
  const state = {
946
1045
  selectors,
@@ -949,6 +1048,7 @@ class StableBrowser {
949
1048
  world,
950
1049
  type: checked ? Types.CHECK : Types.UNCHECK,
951
1050
  text: checked ? `Check element` : `Uncheck element`,
1051
+ _text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
952
1052
  operation: "setCheck",
953
1053
  log: "***** check " + selectors.element_name + " *****\n",
954
1054
  };
@@ -958,9 +1058,15 @@ class StableBrowser {
958
1058
  // let element = await this._locate(selectors, info, _params);
959
1059
  // ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
960
1060
  try {
961
- // await this._highlightElements(element);
1061
+ // if (world && world.screenshot && !world.screenshotPath) {
1062
+ // console.log(`Highlighting while running from recorder`);
1063
+ await this._highlightElements(state.element);
962
1064
  await state.element.setChecked(checked);
963
1065
  await new Promise((resolve) => setTimeout(resolve, 1000));
1066
+ // await this._unHighlightElements(element);
1067
+ // }
1068
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1069
+ // await this._unHighlightElements(element);
964
1070
  }
965
1071
  catch (e) {
966
1072
  if (e.message && e.message.includes("did not change its state")) {
@@ -992,22 +1098,13 @@ class StableBrowser {
992
1098
  world,
993
1099
  type: Types.HOVER,
994
1100
  text: `Hover element`,
1101
+ _text: `Hover on ${selectors.element_name}`,
995
1102
  operation: "hover",
996
1103
  log: "***** hover " + selectors.element_name + " *****\n",
997
1104
  };
998
1105
  try {
999
1106
  await _preCommand(state, this);
1000
- try {
1001
- await state.element.hover();
1002
- await new Promise((resolve) => setTimeout(resolve, 1000));
1003
- }
1004
- catch (e) {
1005
- //await this.closeUnexpectedPopups();
1006
- state.info.log += "hover failed, will try again" + "\n";
1007
- state.element = await this._locate(selectors, state.info, _params);
1008
- await state.element.hover({ timeout: 10000 });
1009
- await new Promise((resolve) => setTimeout(resolve, 1000));
1010
- }
1107
+ await performAction("hover", state.element, options, this, state, _params);
1011
1108
  await _screenshot(state, this);
1012
1109
  await this.waitForPageLoad();
1013
1110
  return state.info;
@@ -1031,6 +1128,7 @@ class StableBrowser {
1031
1128
  value: values.toString(),
1032
1129
  type: Types.SELECT,
1033
1130
  text: `Select option: ${values}`,
1131
+ _text: `Select option: ${values} on ${selectors.element_name}`,
1034
1132
  operation: "selectOption",
1035
1133
  log: "***** select option " + selectors.element_name + " *****\n",
1036
1134
  };
@@ -1065,6 +1163,7 @@ class StableBrowser {
1065
1163
  highlight: false,
1066
1164
  type: Types.TYPE_PRESS,
1067
1165
  text: `Type value: ${_value}`,
1166
+ _text: `Type value: ${_value}`,
1068
1167
  operation: "type",
1069
1168
  log: "",
1070
1169
  };
@@ -1144,6 +1243,7 @@ class StableBrowser {
1144
1243
  world,
1145
1244
  type: Types.SET_DATE_TIME,
1146
1245
  text: `Set date time value: ${value}`,
1246
+ _text: `Set date time value: ${value} on ${selectors.element_name}`,
1147
1247
  operation: "setDateTime",
1148
1248
  log: "***** set date time value " + selectors.element_name + " *****\n",
1149
1249
  throwError: false,
@@ -1151,7 +1251,7 @@ class StableBrowser {
1151
1251
  try {
1152
1252
  await _preCommand(state, this);
1153
1253
  try {
1154
- await state.element.click();
1254
+ await performAction("click", state.element, options, this, state, _params);
1155
1255
  await new Promise((resolve) => setTimeout(resolve, 500));
1156
1256
  if (format) {
1157
1257
  state.value = dayjs(state.value).format(format);
@@ -1215,9 +1315,13 @@ class StableBrowser {
1215
1315
  world,
1216
1316
  type: Types.FILL,
1217
1317
  text: `Click type input with value: ${_value}`,
1318
+ _text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
1218
1319
  operation: "clickType",
1219
1320
  log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
1220
1321
  };
1322
+ if (!options) {
1323
+ options = {};
1324
+ }
1221
1325
  if (newValue !== _value) {
1222
1326
  //this.logger.info(_value + "=" + newValue);
1223
1327
  _value = newValue;
@@ -1225,7 +1329,7 @@ class StableBrowser {
1225
1329
  try {
1226
1330
  await _preCommand(state, this);
1227
1331
  state.info.value = _value;
1228
- if (options === null || options === undefined || !options.press) {
1332
+ if (!options.press) {
1229
1333
  try {
1230
1334
  let currentValue = await state.element.inputValue();
1231
1335
  if (currentValue) {
@@ -1236,13 +1340,9 @@ class StableBrowser {
1236
1340
  this.logger.info("unable to clear input value");
1237
1341
  }
1238
1342
  }
1239
- if (options === null || options === undefined || options.press) {
1240
- try {
1241
- await state.element.click({ timeout: 5000 });
1242
- }
1243
- catch (e) {
1244
- await state.element.dispatchEvent("click");
1245
- }
1343
+ if (options.press) {
1344
+ options.timeout = 5000;
1345
+ await performAction("click", state.element, options, this, state, _params);
1246
1346
  }
1247
1347
  else {
1248
1348
  try {
@@ -1280,7 +1380,12 @@ class StableBrowser {
1280
1380
  await this.waitForPageLoad();
1281
1381
  }
1282
1382
  else if (enter === false) {
1283
- await state.element.dispatchEvent("change");
1383
+ try {
1384
+ await state.element.dispatchEvent("change", null, { timeout: 5000 });
1385
+ }
1386
+ catch (e) {
1387
+ // ignore
1388
+ }
1284
1389
  //await this.page.keyboard.press("Tab");
1285
1390
  }
1286
1391
  else {
@@ -1332,6 +1437,7 @@ class StableBrowser {
1332
1437
  return await this._getText(selectors, 0, _params, options, info, world);
1333
1438
  }
1334
1439
  async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
1440
+ const timeout = this._getFindElementTimeout(options);
1335
1441
  _validateSelectors(selectors);
1336
1442
  let screenshotId = null;
1337
1443
  let screenshotPath = null;
@@ -1341,7 +1447,7 @@ class StableBrowser {
1341
1447
  }
1342
1448
  info.operation = "getText";
1343
1449
  info.selectors = selectors;
1344
- let element = await this._locate(selectors, info, _params);
1450
+ let element = await this._locate(selectors, info, _params, timeout);
1345
1451
  if (climb > 0) {
1346
1452
  const climbArray = [];
1347
1453
  for (let i = 0; i < climb; i++) {
@@ -1360,6 +1466,18 @@ class StableBrowser {
1360
1466
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
1361
1467
  try {
1362
1468
  await this._highlightElements(element);
1469
+ // if (world && world.screenshot && !world.screenshotPath) {
1470
+ // // console.log(`Highlighting for get text while running from recorder`);
1471
+ // this._highlightElements(element)
1472
+ // .then(async () => {
1473
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
1474
+ // this._unhighlightElements(element).then(
1475
+ // () => {}
1476
+ // // console.log(`Unhighlighting vrtr in recorder is successful`)
1477
+ // );
1478
+ // })
1479
+ // .catch(e);
1480
+ // }
1363
1481
  const elementText = await element.innerText();
1364
1482
  return {
1365
1483
  text: elementText,
@@ -1371,7 +1489,7 @@ class StableBrowser {
1371
1489
  }
1372
1490
  catch (e) {
1373
1491
  //await this.closeUnexpectedPopups();
1374
- this.logger.info("no innerText will use textContent");
1492
+ this.logger.info("no innerText, will use textContent");
1375
1493
  const elementText = await element.textContent();
1376
1494
  return { text: elementText, screenshotId, screenshotPath, value: value };
1377
1495
  }
@@ -1396,6 +1514,7 @@ class StableBrowser {
1396
1514
  highlight: false,
1397
1515
  type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
1398
1516
  text: `Verify element contains pattern: ${pattern}`,
1517
+ _text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
1399
1518
  operation: "containsPattern",
1400
1519
  log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
1401
1520
  };
@@ -1431,6 +1550,8 @@ class StableBrowser {
1431
1550
  }
1432
1551
  }
1433
1552
  async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
1553
+ const timeout = this._getFindElementTimeout(options);
1554
+ const startTime = Date.now();
1434
1555
  const state = {
1435
1556
  selectors,
1436
1557
  _params,
@@ -1457,62 +1578,54 @@ class StableBrowser {
1457
1578
  }
1458
1579
  let foundObj = null;
1459
1580
  try {
1460
- await _preCommand(state, this);
1461
- foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
1462
- if (foundObj && foundObj.element) {
1463
- await this.scrollIfNeeded(foundObj.element, state.info);
1464
- }
1465
- await _screenshot(state, this);
1466
- const dateAlternatives = findDateAlternatives(text);
1467
- const numberAlternatives = findNumberAlternatives(text);
1468
- if (dateAlternatives.date) {
1469
- for (let i = 0; i < dateAlternatives.dates.length; i++) {
1470
- if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1471
- foundObj?.value?.includes(dateAlternatives.dates[i])) {
1472
- return state.info;
1581
+ while (Date.now() - startTime < timeout) {
1582
+ try {
1583
+ await _preCommand(state, this);
1584
+ foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
1585
+ if (foundObj && foundObj.element) {
1586
+ await this.scrollIfNeeded(foundObj.element, state.info);
1473
1587
  }
1474
- }
1475
- throw new Error("element doesn't contain text " + text);
1476
- }
1477
- else if (numberAlternatives.number) {
1478
- for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1479
- if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1480
- foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1588
+ await _screenshot(state, this);
1589
+ const dateAlternatives = findDateAlternatives(text);
1590
+ const numberAlternatives = findNumberAlternatives(text);
1591
+ if (dateAlternatives.date) {
1592
+ for (let i = 0; i < dateAlternatives.dates.length; i++) {
1593
+ if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
1594
+ foundObj?.value?.includes(dateAlternatives.dates[i])) {
1595
+ return state.info;
1596
+ }
1597
+ }
1598
+ }
1599
+ else if (numberAlternatives.number) {
1600
+ for (let i = 0; i < numberAlternatives.numbers.length; i++) {
1601
+ if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
1602
+ foundObj?.value?.includes(numberAlternatives.numbers[i])) {
1603
+ return state.info;
1604
+ }
1605
+ }
1606
+ }
1607
+ else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
1481
1608
  return state.info;
1482
1609
  }
1483
1610
  }
1484
- throw new Error("element doesn't contain text " + text);
1485
- }
1486
- else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
1487
- state.info.foundText = foundObj?.text;
1488
- state.info.value = foundObj?.value;
1489
- throw new Error("element doesn't contain text " + text);
1611
+ catch (e) {
1612
+ // Log error but continue retrying until timeout is reached
1613
+ this.logger.warn("Retrying containsText due to: " + e.message);
1614
+ }
1615
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
1490
1616
  }
1491
- return state.info;
1617
+ state.info.foundText = foundObj?.text;
1618
+ state.info.value = foundObj?.value;
1619
+ throw new Error("element doesn't contain text " + text);
1492
1620
  }
1493
1621
  catch (e) {
1494
1622
  await _commandError(state, e, this);
1623
+ throw e;
1495
1624
  }
1496
1625
  finally {
1497
1626
  _commandFinally(state, this);
1498
1627
  }
1499
1628
  }
1500
- _getDataFile(world = null) {
1501
- let dataFile = null;
1502
- if (world && world.reportFolder) {
1503
- dataFile = path.join(world.reportFolder, "data.json");
1504
- }
1505
- else if (this.reportFolder) {
1506
- dataFile = path.join(this.reportFolder, "data.json");
1507
- }
1508
- else if (this.context && this.context.reportFolder) {
1509
- dataFile = path.join(this.context.reportFolder, "data.json");
1510
- }
1511
- else {
1512
- dataFile = "data.json";
1513
- }
1514
- return dataFile;
1515
- }
1516
1629
  async waitForUserInput(message, world = null) {
1517
1630
  if (!message) {
1518
1631
  message = "# Wait for user input. Press any key to continue";
@@ -1541,13 +1654,22 @@ class StableBrowser {
1541
1654
  return;
1542
1655
  }
1543
1656
  // if data file exists, load it
1544
- const dataFile = this._getDataFile(world);
1657
+ const dataFile = _getDataFile(world, this.context, this);
1545
1658
  let data = this.getTestData(world);
1546
1659
  // merge the testData with the existing data
1547
1660
  Object.assign(data, testData);
1548
1661
  // save the data to the file
1549
1662
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
1550
1663
  }
1664
+ overwriteTestData(testData, world = null) {
1665
+ if (!testData) {
1666
+ return;
1667
+ }
1668
+ // if data file exists, load it
1669
+ const dataFile = _getDataFile(world, this.context, this);
1670
+ // save the data to the file
1671
+ fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
1672
+ }
1551
1673
  _getDataFilePath(fileName) {
1552
1674
  let dataFile = path.join(this.project_path, "data", fileName);
1553
1675
  if (fs.existsSync(dataFile)) {
@@ -1644,7 +1766,7 @@ class StableBrowser {
1644
1766
  }
1645
1767
  }
1646
1768
  getTestData(world = null) {
1647
- const dataFile = this._getDataFile(world);
1769
+ const dataFile = _getDataFile(world, this.context, this);
1648
1770
  let data = {};
1649
1771
  if (fs.existsSync(dataFile)) {
1650
1772
  data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
@@ -1731,6 +1853,15 @@ class StableBrowser {
1731
1853
  document.documentElement.clientWidth,
1732
1854
  ])));
1733
1855
  let screenshotBuffer = null;
1856
+ // if (focusedElement) {
1857
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1858
+ // await this._unhighlightElements(focusedElement);
1859
+ // await new Promise((resolve) => setTimeout(resolve, 100));
1860
+ // console.log(`Unhighlighted previous element`);
1861
+ // }
1862
+ // if (focusedElement) {
1863
+ // await this._highlightElements(focusedElement);
1864
+ // }
1734
1865
  if (this.context.browserName === "chromium") {
1735
1866
  const client = await playContext.newCDPSession(this.page);
1736
1867
  const { data } = await client.send("Page.captureScreenshot", {
@@ -1752,6 +1883,10 @@ class StableBrowser {
1752
1883
  else {
1753
1884
  screenshotBuffer = await this.page.screenshot();
1754
1885
  }
1886
+ // if (focusedElement) {
1887
+ // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
1888
+ // await this._unhighlightElements(focusedElement);
1889
+ // }
1755
1890
  let image = await Jimp.read(screenshotBuffer);
1756
1891
  // Get the image dimensions
1757
1892
  const { width, height } = image.bitmap;
@@ -1764,6 +1899,7 @@ class StableBrowser {
1764
1899
  else {
1765
1900
  fs.writeFileSync(screenshotPath, screenshotBuffer);
1766
1901
  }
1902
+ return screenshotBuffer;
1767
1903
  }
1768
1904
  async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
1769
1905
  const state = {
@@ -1799,8 +1935,10 @@ class StableBrowser {
1799
1935
  world,
1800
1936
  type: Types.EXTRACT,
1801
1937
  text: `Extract attribute from element`,
1938
+ _text: `Extract attribute ${attribute} from ${selectors.element_name}`,
1802
1939
  operation: "extractAttribute",
1803
1940
  log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
1941
+ allowDisabled: true,
1804
1942
  };
1805
1943
  await new Promise((resolve) => setTimeout(resolve, 2000));
1806
1944
  try {
@@ -1815,6 +1953,9 @@ class StableBrowser {
1815
1953
  case "value":
1816
1954
  state.value = await state.element.inputValue();
1817
1955
  break;
1956
+ case "text":
1957
+ state.value = await state.element.textContent();
1958
+ break;
1818
1959
  default:
1819
1960
  state.value = await state.element.getAttribute(attribute);
1820
1961
  break;
@@ -1822,6 +1963,7 @@ class StableBrowser {
1822
1963
  state.info.value = state.value;
1823
1964
  this.setTestData({ [variable]: state.value }, world);
1824
1965
  this.logger.info("set test data: " + variable + "=" + state.value);
1966
+ // await new Promise((resolve) => setTimeout(resolve, 500));
1825
1967
  return state.info;
1826
1968
  }
1827
1969
  catch (e) {
@@ -1840,18 +1982,28 @@ class StableBrowser {
1840
1982
  options,
1841
1983
  world,
1842
1984
  type: Types.VERIFY_ATTRIBUTE,
1985
+ highlight: true,
1986
+ screenshot: true,
1843
1987
  text: `Verify element attribute`,
1988
+ _text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
1844
1989
  operation: "verifyAttribute",
1845
1990
  log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
1991
+ allowDisabled: true,
1846
1992
  };
1847
1993
  await new Promise((resolve) => setTimeout(resolve, 2000));
1848
1994
  let val;
1995
+ let expectedValue;
1849
1996
  try {
1850
1997
  await _preCommand(state, this);
1998
+ expectedValue = await replaceWithLocalTestData(state.value, world);
1999
+ state.info.expectedValue = expectedValue;
1851
2000
  switch (attribute) {
1852
2001
  case "innerText":
1853
2002
  val = String(await state.element.innerText());
1854
2003
  break;
2004
+ case "text":
2005
+ val = String(await state.element.textContent());
2006
+ break;
1855
2007
  case "value":
1856
2008
  val = String(await state.element.inputValue());
1857
2009
  break;
@@ -1869,19 +2021,22 @@ class StableBrowser {
1869
2021
  val = String(await state.element.getAttribute(attribute));
1870
2022
  break;
1871
2023
  }
2024
+ state.info.value = val;
1872
2025
  let regex;
1873
- if (value.startsWith("/") && value.endsWith("/")) {
1874
- const patternBody = value.slice(1, -1);
2026
+ if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
2027
+ const patternBody = expectedValue.slice(1, -1);
1875
2028
  regex = new RegExp(patternBody, "g");
1876
2029
  }
1877
2030
  else {
1878
- const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2031
+ const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1879
2032
  regex = new RegExp(escapedPattern, "g");
1880
2033
  }
1881
2034
  if (!val.match(regex)) {
1882
- throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
2035
+ let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
2036
+ state.info.failCause.assertionFailed = true;
2037
+ state.info.failCause.lastError = errorMessage;
2038
+ throw new Error(errorMessage);
1883
2039
  }
1884
- state.info.expectedValue = val;
1885
2040
  return state.info;
1886
2041
  }
1887
2042
  catch (e) {
@@ -1980,27 +2135,32 @@ class StableBrowser {
1980
2135
  async _highlightElements(scope, css) {
1981
2136
  try {
1982
2137
  if (!scope) {
2138
+ // console.log(`Scope is not defined`);
1983
2139
  return;
1984
2140
  }
1985
2141
  if (!css) {
1986
2142
  scope
1987
2143
  .evaluate((node) => {
1988
2144
  if (node && node.style) {
1989
- let originalBorder = node.style.border;
1990
- node.style.border = "2px solid red";
2145
+ let originalOutline = node.style.outline;
2146
+ // console.log(`Original outline was: ${originalOutline}`);
2147
+ // node.__previousOutline = originalOutline;
2148
+ node.style.outline = "2px solid red";
2149
+ // console.log(`New outline is: ${node.style.outline}`);
1991
2150
  if (window) {
1992
2151
  window.addEventListener("beforeunload", function (e) {
1993
- node.style.border = originalBorder;
2152
+ node.style.outline = originalOutline;
1994
2153
  });
1995
2154
  }
1996
2155
  setTimeout(function () {
1997
- node.style.border = originalBorder;
2156
+ node.style.outline = originalOutline;
1998
2157
  }, 2000);
1999
2158
  }
2000
2159
  })
2001
2160
  .then(() => { })
2002
2161
  .catch((e) => {
2003
2162
  // ignore
2163
+ // console.error(`Could not highlight node : ${e}`);
2004
2164
  });
2005
2165
  }
2006
2166
  else {
@@ -2016,17 +2176,18 @@ class StableBrowser {
2016
2176
  if (!element.style) {
2017
2177
  return;
2018
2178
  }
2019
- var originalBorder = element.style.border;
2179
+ let originalOutline = element.style.outline;
2180
+ element.__previousOutline = originalOutline;
2020
2181
  // Set the new border to be red and 2px solid
2021
- element.style.border = "2px solid red";
2182
+ element.style.outline = "2px solid red";
2022
2183
  if (window) {
2023
2184
  window.addEventListener("beforeunload", function (e) {
2024
- element.style.border = originalBorder;
2185
+ element.style.outline = originalOutline;
2025
2186
  });
2026
2187
  }
2027
2188
  // Set a timeout to revert to the original border after 2 seconds
2028
2189
  setTimeout(function () {
2029
- element.style.border = originalBorder;
2190
+ element.style.outline = originalOutline;
2030
2191
  }, 2000);
2031
2192
  }
2032
2193
  return;
@@ -2034,6 +2195,7 @@ class StableBrowser {
2034
2195
  .then(() => { })
2035
2196
  .catch((e) => {
2036
2197
  // ignore
2198
+ // console.error(`Could not highlight css: ${e}`);
2037
2199
  });
2038
2200
  }
2039
2201
  }
@@ -2041,6 +2203,54 @@ class StableBrowser {
2041
2203
  console.debug(error);
2042
2204
  }
2043
2205
  }
2206
+ // async _unhighlightElements(scope, css) {
2207
+ // try {
2208
+ // if (!scope) {
2209
+ // return;
2210
+ // }
2211
+ // if (!css) {
2212
+ // scope
2213
+ // .evaluate((node) => {
2214
+ // if (node && node.style) {
2215
+ // if (!node.__previousOutline) {
2216
+ // node.style.outline = "";
2217
+ // } else {
2218
+ // node.style.outline = node.__previousOutline;
2219
+ // }
2220
+ // }
2221
+ // })
2222
+ // .then(() => {})
2223
+ // .catch((e) => {
2224
+ // // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
2225
+ // });
2226
+ // } else {
2227
+ // scope
2228
+ // .evaluate(([css]) => {
2229
+ // if (!css) {
2230
+ // return;
2231
+ // }
2232
+ // let elements = Array.from(document.querySelectorAll(css));
2233
+ // for (i = 0; i < elements.length; i++) {
2234
+ // let element = elements[i];
2235
+ // if (!element.style) {
2236
+ // return;
2237
+ // }
2238
+ // if (!element.__previousOutline) {
2239
+ // element.style.outline = "";
2240
+ // } else {
2241
+ // element.style.outline = element.__previousOutline;
2242
+ // }
2243
+ // }
2244
+ // })
2245
+ // .then(() => {})
2246
+ // .catch((e) => {
2247
+ // // console.error(`Error while unhighlighting element in css: ${e}`);
2248
+ // });
2249
+ // }
2250
+ // } catch (error) {
2251
+ // // console.debug(error);
2252
+ // }
2253
+ // }
2044
2254
  async verifyPagePath(pathPart, options = {}, world = null) {
2045
2255
  const startTime = Date.now();
2046
2256
  let error = null;
@@ -2085,6 +2295,69 @@ class StableBrowser {
2085
2295
  _reportToWorld(world, {
2086
2296
  type: Types.VERIFY_PAGE_PATH,
2087
2297
  text: "Verify page path",
2298
+ _text: "Verify the page path contains " + pathPart,
2299
+ screenshotId,
2300
+ result: error
2301
+ ? {
2302
+ status: "FAILED",
2303
+ startTime,
2304
+ endTime,
2305
+ message: error?.message,
2306
+ }
2307
+ : {
2308
+ status: "PASSED",
2309
+ startTime,
2310
+ endTime,
2311
+ },
2312
+ info: info,
2313
+ });
2314
+ }
2315
+ }
2316
+ async verifyPageTitle(title, options = {}, world = null) {
2317
+ const startTime = Date.now();
2318
+ let error = null;
2319
+ let screenshotId = null;
2320
+ let screenshotPath = null;
2321
+ await new Promise((resolve) => setTimeout(resolve, 2000));
2322
+ const info = {};
2323
+ info.log = "***** verify page title " + title + " *****\n";
2324
+ info.operation = "verifyPageTitle";
2325
+ const newValue = await this._replaceWithLocalData(title, world);
2326
+ if (newValue !== title) {
2327
+ this.logger.info(title + "=" + newValue);
2328
+ title = newValue;
2329
+ }
2330
+ info.title = title;
2331
+ try {
2332
+ for (let i = 0; i < 30; i++) {
2333
+ const foundTitle = await this.page.title();
2334
+ if (!foundTitle.includes(title)) {
2335
+ if (i === 29) {
2336
+ throw new Error(`url ${foundTitle} doesn't contain ${title}`);
2337
+ }
2338
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2339
+ continue;
2340
+ }
2341
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2342
+ return info;
2343
+ }
2344
+ }
2345
+ catch (e) {
2346
+ //await this.closeUnexpectedPopups();
2347
+ this.logger.error("verify page title failed " + info.log);
2348
+ ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2349
+ info.screenshotPath = screenshotPath;
2350
+ Object.assign(e, { info: info });
2351
+ error = e;
2352
+ // throw e;
2353
+ await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
2354
+ }
2355
+ finally {
2356
+ const endTime = Date.now();
2357
+ _reportToWorld(world, {
2358
+ type: Types.VERIFY_PAGE_PATH,
2359
+ text: "Verify page title",
2360
+ _text: "Verify the page title contains " + title,
2088
2361
  screenshotId,
2089
2362
  result: error
2090
2363
  ? {
@@ -2102,27 +2375,27 @@ class StableBrowser {
2102
2375
  });
2103
2376
  }
2104
2377
  }
2105
- async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
2378
+ async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
2106
2379
  const frames = this.page.frames();
2107
2380
  let results = [];
2108
- let ignoreCase = !(text.startsWith("/") && text.endsWith("/"));
2381
+ // let ignoreCase = false;
2109
2382
  for (let i = 0; i < frames.length; i++) {
2110
2383
  if (dateAlternatives.date) {
2111
2384
  for (let j = 0; j < dateAlternatives.dates.length; j++) {
2112
- const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2385
+ const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2113
2386
  result.frame = frames[i];
2114
2387
  results.push(result);
2115
2388
  }
2116
2389
  }
2117
2390
  else if (numberAlternatives.number) {
2118
2391
  for (let j = 0; j < numberAlternatives.numbers.length; j++) {
2119
- const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
2392
+ const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
2120
2393
  result.frame = frames[i];
2121
2394
  results.push(result);
2122
2395
  }
2123
2396
  }
2124
2397
  else {
2125
- const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
2398
+ const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
2126
2399
  result.frame = frames[i];
2127
2400
  results.push(result);
2128
2401
  }
@@ -2141,11 +2414,15 @@ class StableBrowser {
2141
2414
  scroll: false,
2142
2415
  highlight: false,
2143
2416
  type: Types.VERIFY_PAGE_CONTAINS_TEXT,
2144
- text: `Verify text exists in page`,
2417
+ text: `Verify the text '${text}' exists in page`,
2418
+ _text: `Verify the text '${text}' exists in page`,
2145
2419
  operation: "verifyTextExistInPage",
2146
2420
  log: "***** verify text " + text + " exists in page *****\n",
2147
2421
  };
2148
- const timeout = this._getLoadTimeout(options);
2422
+ if (testForRegex(text)) {
2423
+ text = text.replace(/\\"/g, '"');
2424
+ }
2425
+ const timeout = this._getFindElementTimeout(options);
2149
2426
  await new Promise((resolve) => setTimeout(resolve, 2000));
2150
2427
  const newValue = await this._replaceWithLocalData(text, world);
2151
2428
  if (newValue !== text) {
@@ -2158,7 +2435,15 @@ class StableBrowser {
2158
2435
  await _preCommand(state, this);
2159
2436
  state.info.text = text;
2160
2437
  while (true) {
2161
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2438
+ let resultWithElementsFound = {
2439
+ length: 0,
2440
+ };
2441
+ try {
2442
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2443
+ }
2444
+ catch (error) {
2445
+ // ignore
2446
+ }
2162
2447
  if (resultWithElementsFound.length === 0) {
2163
2448
  if (Date.now() - state.startTime > timeout) {
2164
2449
  throw new Error(`Text ${text} not found in page`);
@@ -2166,18 +2451,40 @@ class StableBrowser {
2166
2451
  await new Promise((resolve) => setTimeout(resolve, 1000));
2167
2452
  continue;
2168
2453
  }
2169
- if (resultWithElementsFound[0].randomToken) {
2170
- const frame = resultWithElementsFound[0].frame;
2171
- const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
2172
- await this._highlightElements(frame, dataAttribute);
2173
- const element = await frame.locator(dataAttribute).first();
2174
- if (element) {
2175
- await this.scrollIfNeeded(element, state.info);
2176
- await element.dispatchEvent("bvt_verify_page_contains_text");
2454
+ try {
2455
+ if (resultWithElementsFound[0].randomToken) {
2456
+ const frame = resultWithElementsFound[0].frame;
2457
+ const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
2458
+ await this._highlightElements(frame, dataAttribute);
2459
+ // if (world && world.screenshot && !world.screenshotPath) {
2460
+ // console.log(`Highlighting for verify text is found while running from recorder`);
2461
+ // this._highlightElements(frame, dataAttribute).then(async () => {
2462
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2463
+ // this._unhighlightElements(frame, dataAttribute)
2464
+ // .then(async () => {
2465
+ // console.log(`Unhighlighted frame dataAttribute successfully`);
2466
+ // })
2467
+ // .catch(
2468
+ // (e) => {}
2469
+ // console.error(e)
2470
+ // );
2471
+ // });
2472
+ // }
2473
+ const element = await frame.locator(dataAttribute).first();
2474
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2475
+ // await this._unhighlightElements(frame, dataAttribute);
2476
+ if (element) {
2477
+ await this.scrollIfNeeded(element, state.info);
2478
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2479
+ // await _screenshot(state, this, element);
2480
+ }
2177
2481
  }
2482
+ await _screenshot(state, this);
2483
+ return state.info;
2484
+ }
2485
+ catch (error) {
2486
+ console.error(error);
2178
2487
  }
2179
- await _screenshot(state, this);
2180
- return state.info;
2181
2488
  }
2182
2489
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2183
2490
  }
@@ -2198,11 +2505,15 @@ class StableBrowser {
2198
2505
  scroll: false,
2199
2506
  highlight: false,
2200
2507
  type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
2201
- text: `Verify text does not exist in page`,
2508
+ text: `Verify the text '${text}' does not exist in page`,
2509
+ _text: `Verify the text '${text}' does not exist in page`,
2202
2510
  operation: "verifyTextNotExistInPage",
2203
2511
  log: "***** verify text " + text + " does not exist in page *****\n",
2204
2512
  };
2205
- const timeout = this._getLoadTimeout(options);
2513
+ if (testForRegex(text)) {
2514
+ text = text.replace(/\\"/g, '"');
2515
+ }
2516
+ const timeout = this._getFindElementTimeout(options);
2206
2517
  await new Promise((resolve) => setTimeout(resolve, 2000));
2207
2518
  const newValue = await this._replaceWithLocalData(text, world);
2208
2519
  if (newValue !== text) {
@@ -2214,8 +2525,16 @@ class StableBrowser {
2214
2525
  try {
2215
2526
  await _preCommand(state, this);
2216
2527
  state.info.text = text;
2528
+ let resultWithElementsFound = {
2529
+ length: null, // initial cannot be 0
2530
+ };
2217
2531
  while (true) {
2218
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2532
+ try {
2533
+ resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
2534
+ }
2535
+ catch (error) {
2536
+ // ignore
2537
+ }
2219
2538
  if (resultWithElementsFound.length === 0) {
2220
2539
  await _screenshot(state, this);
2221
2540
  return state.info;
@@ -2245,10 +2564,11 @@ class StableBrowser {
2245
2564
  highlight: false,
2246
2565
  type: Types.VERIFY_TEXT_WITH_RELATION,
2247
2566
  text: `Verify text with relation to another text`,
2567
+ _text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
2248
2568
  operation: "verify_text_with_relation",
2249
2569
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
2250
2570
  };
2251
- const timeout = this._getLoadTimeout(options);
2571
+ const timeout = this._getFindElementTimeout(options);
2252
2572
  await new Promise((resolve) => setTimeout(resolve, 2000));
2253
2573
  let newValue = await this._replaceWithLocalData(textAnchor, world);
2254
2574
  if (newValue !== textAnchor) {
@@ -2266,8 +2586,16 @@ class StableBrowser {
2266
2586
  try {
2267
2587
  await _preCommand(state, this);
2268
2588
  state.info.text = textToVerify;
2589
+ let resultWithElementsFound = {
2590
+ length: 0,
2591
+ };
2269
2592
  while (true) {
2270
- const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
2593
+ try {
2594
+ resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
2595
+ }
2596
+ catch (error) {
2597
+ // ignore
2598
+ }
2271
2599
  if (resultWithElementsFound.length === 0) {
2272
2600
  if (Date.now() - state.startTime > timeout) {
2273
2601
  throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
@@ -2275,37 +2603,56 @@ class StableBrowser {
2275
2603
  await new Promise((resolve) => setTimeout(resolve, 1000));
2276
2604
  continue;
2277
2605
  }
2278
- for (let i = 0; i < resultWithElementsFound.length; i++) {
2279
- foundAncore = true;
2280
- const result = resultWithElementsFound[i];
2281
- const token = result.randomToken;
2282
- const frame = result.frame;
2283
- let css = `[data-blinq-id="blinq-id-${token}"]`;
2284
- const climbArray1 = [];
2285
- for (let i = 0; i < climb; i++) {
2286
- climbArray1.push("..");
2287
- }
2288
- let climbXpath = "xpath=" + climbArray1.join("/");
2289
- css = css + " >> " + climbXpath;
2290
- const count = await frame.locator(css).count();
2291
- for (let j = 0; j < count; j++) {
2292
- const continer = await frame.locator(css).nth(j);
2293
- const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
2294
- if (result.elementCount > 0) {
2295
- const dataAttribute = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
2296
- //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2297
- await this._highlightElements(frame, dataAttribute);
2298
- //await this._highlightElements(frame, cssAnchor);
2299
- const element = await frame.locator(dataAttribute).first();
2300
- if (element) {
2301
- await this.scrollIfNeeded(element, state.info);
2302
- await element.dispatchEvent("bvt_verify_page_contains_text");
2606
+ try {
2607
+ for (let i = 0; i < resultWithElementsFound.length; i++) {
2608
+ foundAncore = true;
2609
+ const result = resultWithElementsFound[i];
2610
+ const token = result.randomToken;
2611
+ const frame = result.frame;
2612
+ let css = `[data-blinq-id-${token}]`;
2613
+ const climbArray1 = [];
2614
+ for (let i = 0; i < climb; i++) {
2615
+ climbArray1.push("..");
2616
+ }
2617
+ let climbXpath = "xpath=" + climbArray1.join("/");
2618
+ css = css + " >> " + climbXpath;
2619
+ const count = await frame.locator(css).count();
2620
+ for (let j = 0; j < count; j++) {
2621
+ const continer = await frame.locator(css).nth(j);
2622
+ const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
2623
+ if (result.elementCount > 0) {
2624
+ const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
2625
+ await this._highlightElements(frame, dataAttribute);
2626
+ //const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
2627
+ // if (world && world.screenshot && !world.screenshotPath) {
2628
+ // console.log(`Highlighting for vtrt while running from recorder`);
2629
+ // this._highlightElements(frame, dataAttribute)
2630
+ // .then(async () => {
2631
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
2632
+ // this._unhighlightElements(frame, dataAttribute).then(
2633
+ // () => {}
2634
+ // console.log(`Unhighlighting vrtr in recorder is successful`)
2635
+ // );
2636
+ // })
2637
+ // .catch(e);
2638
+ // }
2639
+ //await this._highlightElements(frame, cssAnchor);
2640
+ const element = await frame.locator(dataAttribute).first();
2641
+ // await new Promise((resolve) => setTimeout(resolve, 100));
2642
+ // await this._unhighlightElements(frame, dataAttribute);
2643
+ if (element) {
2644
+ await this.scrollIfNeeded(element, state.info);
2645
+ await element.dispatchEvent("bvt_verify_page_contains_text");
2646
+ }
2647
+ await _screenshot(state, this);
2648
+ return state.info;
2303
2649
  }
2304
- await _screenshot(state, this);
2305
- return state.info;
2306
2650
  }
2307
2651
  }
2308
2652
  }
2653
+ catch (error) {
2654
+ console.error(error);
2655
+ }
2309
2656
  }
2310
2657
  // await expect(element).toHaveCount(1, { timeout: 10000 });
2311
2658
  }
@@ -2316,6 +2663,30 @@ class StableBrowser {
2316
2663
  _commandFinally(state, this);
2317
2664
  }
2318
2665
  }
2666
+ async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
2667
+ const frames = this.page.frames();
2668
+ let results = [];
2669
+ let ignoreCase = false;
2670
+ for (let i = 0; i < frames.length; i++) {
2671
+ const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
2672
+ result.frame = frames[i];
2673
+ const climbArray = [];
2674
+ for (let i = 0; i < climb; i++) {
2675
+ climbArray.push("..");
2676
+ }
2677
+ let climbXpath = "xpath=" + climbArray.join("/");
2678
+ const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
2679
+ const count = await frames[i].locator(newLocator).count();
2680
+ if (count > 0) {
2681
+ result.elementCount = count;
2682
+ result.locator = newLocator;
2683
+ results.push(result);
2684
+ }
2685
+ }
2686
+ // state.info.results = results;
2687
+ const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
2688
+ return resultWithElementsFound;
2689
+ }
2319
2690
  async visualVerification(text, options = {}, world = null) {
2320
2691
  const startTime = Date.now();
2321
2692
  let error = null;
@@ -2334,10 +2705,13 @@ class StableBrowser {
2334
2705
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
2335
2706
  info.screenshotPath = screenshotPath;
2336
2707
  const screenshot = await this.takeScreenshot();
2337
- const request = {
2338
- method: "POST",
2708
+ let request = {
2709
+ method: "post",
2710
+ maxBodyLength: Infinity,
2339
2711
  url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
2340
2712
  headers: {
2713
+ "x-bvt-project-id": path.basename(this.project_path),
2714
+ "x-source": "aaa",
2341
2715
  "Content-Type": "application/json",
2342
2716
  Authorization: `Bearer ${process.env.TOKEN}`,
2343
2717
  },
@@ -2346,7 +2720,7 @@ class StableBrowser {
2346
2720
  screenshot: screenshot,
2347
2721
  }),
2348
2722
  };
2349
- let result = await this.context.api.request(request);
2723
+ const result = await axios.request(request);
2350
2724
  if (result.data.status !== true) {
2351
2725
  throw new Error("Visual validation failed");
2352
2726
  }
@@ -2374,6 +2748,7 @@ class StableBrowser {
2374
2748
  _reportToWorld(world, {
2375
2749
  type: Types.VERIFY_VISUAL,
2376
2750
  text: "Visual verification",
2751
+ _text: "Visual verification of " + text,
2377
2752
  screenshotId,
2378
2753
  result: error
2379
2754
  ? {
@@ -2640,6 +3015,32 @@ class StableBrowser {
2640
3015
  }
2641
3016
  return timeout;
2642
3017
  }
3018
+ _getFindElementTimeout(options) {
3019
+ if (options && options.timeout) {
3020
+ return options.timeout;
3021
+ }
3022
+ if (this.configuration.find_element_timeout) {
3023
+ return this.configuration.find_element_timeout;
3024
+ }
3025
+ return 30000;
3026
+ }
3027
+ async saveStoreState(path = null, world = null) {
3028
+ const storageState = await this.page.context().storageState();
3029
+ //const testDataFile = _getDataFile(world, this.context, this);
3030
+ if (path) {
3031
+ // save { storageState: storageState } into the path
3032
+ fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
3033
+ }
3034
+ else {
3035
+ await this.setTestData({ storageState: storageState }, world);
3036
+ }
3037
+ }
3038
+ async restoreSaveState(path = null, world = null) {
3039
+ await refreshBrowser(this, path, world);
3040
+ this.registerEventListeners(this.context);
3041
+ registerNetworkEvents(this.world, this, this.context, this.page);
3042
+ registerDownloadEvent(this.page, this.world, this.context);
3043
+ }
2643
3044
  async waitForPageLoad(options = {}, world = null) {
2644
3045
  let timeout = this._getLoadTimeout(options);
2645
3046
  const promiseArray = [];
@@ -2707,6 +3108,7 @@ class StableBrowser {
2707
3108
  highlight: false,
2708
3109
  type: Types.CLOSE_PAGE,
2709
3110
  text: `Close page`,
3111
+ _text: `Close the page`,
2710
3112
  operation: "closePage",
2711
3113
  log: "***** close page *****\n",
2712
3114
  throwError: false,
@@ -2723,8 +3125,95 @@ class StableBrowser {
2723
3125
  _commandFinally(state, this);
2724
3126
  }
2725
3127
  }
3128
+ async tableCellOperation(headerText, rowText, options, _params, world = null) {
3129
+ let operation = null;
3130
+ if (!options || !options.operation) {
3131
+ throw new Error("operation is not defined");
3132
+ }
3133
+ operation = options.operation;
3134
+ // validate operation is one of the supported operations
3135
+ if (operation != "click" && operation != "hover+click") {
3136
+ throw new Error("operation is not supported");
3137
+ }
3138
+ const state = {
3139
+ options,
3140
+ world,
3141
+ locate: false,
3142
+ scroll: false,
3143
+ highlight: false,
3144
+ type: Types.TABLE_OPERATION,
3145
+ text: `Table operation`,
3146
+ _text: `Table ${operation} operation`,
3147
+ operation: operation,
3148
+ log: "***** Table operation *****\n",
3149
+ };
3150
+ const timeout = this._getFindElementTimeout(options);
3151
+ try {
3152
+ await _preCommand(state, this);
3153
+ const start = Date.now();
3154
+ let cellArea = null;
3155
+ while (true) {
3156
+ try {
3157
+ cellArea = await _findCellArea(headerText, rowText, this, state);
3158
+ if (cellArea) {
3159
+ break;
3160
+ }
3161
+ }
3162
+ catch (e) {
3163
+ // ignore
3164
+ }
3165
+ if (Date.now() - start > timeout) {
3166
+ throw new Error(`Cell not found in table`);
3167
+ }
3168
+ await new Promise((resolve) => setTimeout(resolve, 1000));
3169
+ }
3170
+ switch (operation) {
3171
+ case "click":
3172
+ if (!options.css) {
3173
+ // will click in the center of the cell
3174
+ let xOffset = 0;
3175
+ let yOffset = 0;
3176
+ if (options.xOffset) {
3177
+ xOffset = options.xOffset;
3178
+ }
3179
+ if (options.yOffset) {
3180
+ yOffset = options.yOffset;
3181
+ }
3182
+ await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
3183
+ }
3184
+ else {
3185
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3186
+ if (results.length === 0) {
3187
+ throw new Error(`Element not found in cell area`);
3188
+ }
3189
+ state.element = results[0];
3190
+ await performAction("click", state.element, options, this, state, _params);
3191
+ }
3192
+ break;
3193
+ case "hover+click":
3194
+ if (!options.css) {
3195
+ throw new Error("css is not defined");
3196
+ }
3197
+ const results = await findElementsInArea(options.css, cellArea, this, options);
3198
+ if (results.length === 0) {
3199
+ throw new Error(`Element not found in cell area`);
3200
+ }
3201
+ state.element = results[0];
3202
+ await performAction("hover+click", state.element, options, this, state, _params);
3203
+ break;
3204
+ default:
3205
+ throw new Error("operation is not supported");
3206
+ }
3207
+ }
3208
+ catch (e) {
3209
+ await _commandError(state, e, this);
3210
+ }
3211
+ finally {
3212
+ _commandFinally(state, this);
3213
+ }
3214
+ }
2726
3215
  saveTestDataAsGlobal(options, world) {
2727
- const dataFile = this._getDataFile(world);
3216
+ const dataFile = _getDataFile(world, this.context, this);
2728
3217
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
2729
3218
  this.logger.info("Save the scenario test data as global for the following scenarios.");
2730
3219
  }
@@ -2754,6 +3243,7 @@ class StableBrowser {
2754
3243
  _reportToWorld(world, {
2755
3244
  type: Types.SET_VIEWPORT,
2756
3245
  text: "set viewport size to " + width + "x" + hight,
3246
+ _text: "Set the viewport size to " + width + "x" + hight,
2757
3247
  screenshotId,
2758
3248
  result: error
2759
3249
  ? {
@@ -2841,6 +3331,9 @@ class StableBrowser {
2841
3331
  else {
2842
3332
  this.stepName = "step " + this.stepIndex;
2843
3333
  }
3334
+ if (this.context) {
3335
+ this.context.examplesRow = extractStepExampleParameters(step);
3336
+ }
2844
3337
  if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
2845
3338
  if (this.context.browserObject.context) {
2846
3339
  await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
@@ -2853,6 +3346,41 @@ class StableBrowser {
2853
3346
  this.saveTestDataAsGlobal({}, world);
2854
3347
  }
2855
3348
  }
3349
+ if (this.initSnapshotTaken === false) {
3350
+ this.initSnapshotTaken = true;
3351
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3352
+ const snapshot = await this.getAriaSnapshot();
3353
+ if (snapshot) {
3354
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
3355
+ }
3356
+ }
3357
+ }
3358
+ }
3359
+ async getAriaSnapshot() {
3360
+ try {
3361
+ // find the page url
3362
+ const url = await this.page.url();
3363
+ // extract the path from the url
3364
+ const path = new URL(url).pathname;
3365
+ // get the page title
3366
+ const title = await this.page.title();
3367
+ // go over other frams
3368
+ const frames = this.page.frames();
3369
+ const snapshots = [];
3370
+ const content = [`- path: ${path}`, `- title: ${title}`];
3371
+ const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
3372
+ for (let i = 0; i < frames.length; i++) {
3373
+ content.push(`- frame: ${i}`);
3374
+ const frame = frames[i];
3375
+ const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
3376
+ content.push(snapshot);
3377
+ }
3378
+ return content.join("\n");
3379
+ }
3380
+ catch (e) {
3381
+ console.error(e);
3382
+ }
3383
+ return null;
2856
3384
  }
2857
3385
  async afterStep(world, step) {
2858
3386
  this.stepName = null;
@@ -2861,6 +3389,23 @@ class StableBrowser {
2861
3389
  await this.context.browserObject.context.tracing.stopChunk({
2862
3390
  path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
2863
3391
  });
3392
+ if (world && world.attach) {
3393
+ await world.attach(JSON.stringify({
3394
+ type: "trace",
3395
+ traceFilePath: `trace-${this.stepIndex}.zip`,
3396
+ }), "application/json+trace");
3397
+ }
3398
+ // console.log("trace file created", `trace-${this.stepIndex}.zip`);
3399
+ }
3400
+ }
3401
+ if (this.context) {
3402
+ this.context.examplesRow = null;
3403
+ }
3404
+ if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
3405
+ const snapshot = await this.getAriaSnapshot();
3406
+ if (snapshot) {
3407
+ const obj = {};
3408
+ await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
2864
3409
  }
2865
3410
  }
2866
3411
  }