automation_model 1.0.787-dev → 1.0.787-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.
Files changed (44) hide show
  1. package/lib/api.js +28 -11
  2. package/lib/api.js.map +1 -1
  3. package/lib/auto_page.d.ts +1 -1
  4. package/lib/auto_page.js +59 -18
  5. package/lib/auto_page.js.map +1 -1
  6. package/lib/browser_manager.d.ts +2 -2
  7. package/lib/browser_manager.js +115 -52
  8. package/lib/browser_manager.js.map +1 -1
  9. package/lib/bruno.js.map +1 -1
  10. package/lib/check_performance.d.ts +1 -0
  11. package/lib/check_performance.js +57 -0
  12. package/lib/check_performance.js.map +1 -0
  13. package/lib/command_common.d.ts +2 -2
  14. package/lib/command_common.js +26 -25
  15. package/lib/command_common.js.map +1 -1
  16. package/lib/file_checker.js +7 -0
  17. package/lib/file_checker.js.map +1 -1
  18. package/lib/index.js +1 -0
  19. package/lib/index.js.map +1 -1
  20. package/lib/init_browser.d.ts +1 -2
  21. package/lib/init_browser.js +124 -128
  22. package/lib/init_browser.js.map +1 -1
  23. package/lib/locator_log.js.map +1 -1
  24. package/lib/network.d.ts +2 -2
  25. package/lib/network.js +341 -178
  26. package/lib/network.js.map +1 -1
  27. package/lib/route.d.ts +64 -2
  28. package/lib/route.js +493 -192
  29. package/lib/route.js.map +1 -1
  30. package/lib/scripts/axe.mini.js +23978 -1
  31. package/lib/snapshot_validation.js +3 -0
  32. package/lib/snapshot_validation.js.map +1 -1
  33. package/lib/stable_browser.d.ts +13 -8
  34. package/lib/stable_browser.js +427 -82
  35. package/lib/stable_browser.js.map +1 -1
  36. package/lib/table_helper.js +14 -0
  37. package/lib/table_helper.js.map +1 -1
  38. package/lib/test_context.d.ts +1 -0
  39. package/lib/test_context.js +1 -0
  40. package/lib/test_context.js.map +1 -1
  41. package/lib/utils.d.ts +6 -2
  42. package/lib/utils.js +121 -14
  43. package/lib/utils.js.map +1 -1
  44. package/package.json +18 -11
@@ -1,4 +1,5 @@
1
1
  // @ts-nocheck
2
+ import { check_performance } from "./check_performance.js";
2
3
  import { expect } from "@playwright/test";
3
4
  import dayjs from "dayjs";
4
5
  import fs from "fs";
@@ -10,6 +11,7 @@ import { getDateTimeValue } from "./date_time.js";
10
11
  import drawRectangle from "./drawRect.js";
11
12
  //import { closeUnexpectedPopups } from "./popups.js";
12
13
  import { getTableCells, getTableData } from "./table_analyze.js";
14
+ import errorStackParser from "error-stack-parser";
13
15
  import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, _getTestData, } from "./utils.js";
14
16
  import csv from "csv-parser";
15
17
  import { Readable } from "node:stream";
@@ -26,6 +28,7 @@ import { _findCellArea, findElementsInArea } from "./table_helper.js";
26
28
  import { highlightSnapshot, snapshotValidation } from "./snapshot_validation.js";
27
29
  import { loadBrunoParams } from "./bruno.js";
28
30
  import { registerAfterStepRoutes, registerBeforeStepRoutes } from "./route.js";
31
+ import { existsSync } from "node:fs";
29
32
  export const Types = {
30
33
  CLICK: "click_element",
31
34
  WAIT_ELEMENT: "wait_element",
@@ -44,6 +47,7 @@ export const Types = {
44
47
  VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
45
48
  ANALYZE_TABLE: "analyze_table",
46
49
  SELECT: "select_combobox", //
50
+ VERIFY_PROPERTY: "verify_element_property",
47
51
  VERIFY_PAGE_PATH: "verify_page_path",
48
52
  VERIFY_PAGE_TITLE: "verify_page_title",
49
53
  TYPE_PRESS: "type_press",
@@ -62,12 +66,11 @@ export const Types = {
62
66
  SET_INPUT: "set_input",
63
67
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
64
68
  VERIFY_ATTRIBUTE: "verify_element_attribute",
65
- VERIFY_PROPERTY: "verify_element_property",
66
69
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
67
70
  BRUNO: "bruno",
68
- SNAPSHOT_VALIDATION: "snapshot_validation",
69
71
  VERIFY_FILE_EXISTS: "verify_file_exists",
70
72
  SET_INPUT_FILES: "set_input_files",
73
+ SNAPSHOT_VALIDATION: "snapshot_validation",
71
74
  REPORT_COMMAND: "report_command",
72
75
  STEP_COMPLETE: "step_complete",
73
76
  SLEEP: "sleep",
@@ -84,6 +87,7 @@ class StableBrowser {
84
87
  context;
85
88
  world;
86
89
  fastMode;
90
+ stepTags;
87
91
  project_path = null;
88
92
  webLogFile = null;
89
93
  networkLogger = null;
@@ -92,14 +96,17 @@ class StableBrowser {
92
96
  tags = null;
93
97
  isRecording = false;
94
98
  initSnapshotTaken = false;
95
- abortedExecution = false;
96
- constructor(browser, page, logger = null, context = null, world = null, fastMode = false) {
99
+ onlyFailuresScreenshot = process.env.SCREENSHOT_ON_FAILURE_ONLY === "true";
100
+ // set to true if the step issue a report
101
+ inStepReport = false;
102
+ constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
97
103
  this.browser = browser;
98
104
  this.page = page;
99
105
  this.logger = logger;
100
106
  this.context = context;
101
107
  this.world = world;
102
108
  this.fastMode = fastMode;
109
+ this.stepTags = stepTags;
103
110
  if (!this.logger) {
104
111
  this.logger = console;
105
112
  }
@@ -132,7 +139,7 @@ class StableBrowser {
132
139
  this.fastMode = true;
133
140
  }
134
141
  if (process.env.FAST_MODE === "true") {
135
- console.log("Fast mode enabled from environment variable");
142
+ // console.log("Fast mode enabled from environment variable");
136
143
  this.fastMode = true;
137
144
  }
138
145
  if (process.env.FAST_MODE === "false") {
@@ -175,6 +182,7 @@ class StableBrowser {
175
182
  registerNetworkEvents(this.world, this, context, this.page);
176
183
  registerDownloadEvent(this.page, this.world, context);
177
184
  page.on("close", async () => {
185
+ // return if browser context is already closed
178
186
  if (this.context && this.context.pages && this.context.pages.length > 1) {
179
187
  this.context.pages.pop();
180
188
  this.page = this.context.pages[this.context.pages.length - 1];
@@ -184,7 +192,12 @@ class StableBrowser {
184
192
  console.log("Switched to page " + title);
185
193
  }
186
194
  catch (error) {
187
- console.error("Error on page close", error);
195
+ if (error?.message?.includes("Target page, context or browser has been closed")) {
196
+ // Ignore this error
197
+ }
198
+ else {
199
+ console.error("Error on page close", error);
200
+ }
188
201
  }
189
202
  }
190
203
  });
@@ -193,7 +206,12 @@ class StableBrowser {
193
206
  console.log("Switch page: " + (await page.title()));
194
207
  }
195
208
  catch (e) {
196
- this.logger.error("error on page load " + e);
209
+ if (e?.message?.includes("Target page, context or browser has been closed")) {
210
+ // Ignore this error
211
+ }
212
+ else {
213
+ this.logger.error("error on page load " + e);
214
+ }
197
215
  }
198
216
  context.pageLoading.status = false;
199
217
  }.bind(this));
@@ -205,7 +223,7 @@ class StableBrowser {
205
223
  }
206
224
  let newContextCreated = false;
207
225
  if (!apps[appName]) {
208
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
226
+ let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder, null, null, this.tags);
209
227
  newContextCreated = true;
210
228
  apps[appName] = {
211
229
  context: newContext,
@@ -221,7 +239,7 @@ class StableBrowser {
221
239
  if (newContextCreated) {
222
240
  this.registerEventListeners(this.context);
223
241
  await this.goto(this.context.environment.baseUrl);
224
- if (!this.fastMode) {
242
+ if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
225
243
  await this.waitForPageLoad();
226
244
  }
227
245
  }
@@ -313,7 +331,7 @@ class StableBrowser {
313
331
  // async closeUnexpectedPopups() {
314
332
  // await closeUnexpectedPopups(this.page);
315
333
  // }
316
- async goto(url, world = null) {
334
+ async goto(url, world = null, options = {}) {
317
335
  if (!url) {
318
336
  throw new Error("url is null, verify that the environment file is correct");
319
337
  }
@@ -334,10 +352,17 @@ class StableBrowser {
334
352
  screenshot: false,
335
353
  highlight: false,
336
354
  };
355
+ let timeout = 60000;
356
+ if (this.configuration && this.configuration.page_timeout) {
357
+ timeout = this.configuration.page_timeout;
358
+ }
359
+ if (options && options["timeout"]) {
360
+ timeout = options["timeout"];
361
+ }
337
362
  try {
338
363
  await _preCommand(state, this);
339
364
  await this.page.goto(url, {
340
- timeout: 60000,
365
+ timeout: timeout,
341
366
  });
342
367
  await _screenshot(state, this);
343
368
  }
@@ -505,12 +530,6 @@ class StableBrowser {
505
530
  if (!el.setAttribute) {
506
531
  el = el.parentElement;
507
532
  }
508
- // remove any attributes start with data-blinq-id
509
- // for (let i = 0; i < el.attributes.length; i++) {
510
- // if (el.attributes[i].name.startsWith("data-blinq-id")) {
511
- // el.removeAttribute(el.attributes[i].name);
512
- // }
513
- // }
514
533
  el.setAttribute("data-blinq-id-" + randomToken, "");
515
534
  return true;
516
535
  }, [tag1, randomToken]))) {
@@ -680,40 +699,186 @@ class StableBrowser {
680
699
  }
681
700
  return { rerun: false };
682
701
  }
702
+ getFilePath() {
703
+ const stackFrames = errorStackParser.parse(new Error());
704
+ const mjsFrames = stackFrames.filter((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
705
+ const stackFrame = mjsFrames[mjsFrames.length - 2];
706
+ const filepath = stackFrame?.fileName;
707
+ if (filepath) {
708
+ let jsonFilePath = filepath.replace(".mjs", ".json");
709
+ if (existsSync(jsonFilePath)) {
710
+ return jsonFilePath;
711
+ }
712
+ const config = this.configuration ?? {};
713
+ if (!config?.locatorsMetadataDir) {
714
+ config.locatorsMetadataDir = "features/step_definitions/locators";
715
+ }
716
+ if (config && config.locatorsMetadataDir) {
717
+ jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
718
+ }
719
+ if (existsSync(jsonFilePath)) {
720
+ return jsonFilePath;
721
+ }
722
+ return null;
723
+ }
724
+ return null;
725
+ }
726
+ getFullElementLocators(selectors, filePath) {
727
+ if (!filePath || !existsSync(filePath)) {
728
+ return null;
729
+ }
730
+ const content = fs.readFileSync(filePath, "utf8");
731
+ try {
732
+ const allElements = JSON.parse(content);
733
+ const element_key = selectors?.element_key;
734
+ if (element_key && allElements[element_key]) {
735
+ return allElements[element_key];
736
+ }
737
+ for (const elementKey in allElements) {
738
+ const element = allElements[elementKey];
739
+ let foundStrategy = null;
740
+ for (const key in element) {
741
+ if (key === "strategy") {
742
+ continue;
743
+ }
744
+ const locators = element[key];
745
+ if (!locators || !locators.length) {
746
+ continue;
747
+ }
748
+ for (const locator of locators) {
749
+ delete locator.score;
750
+ }
751
+ if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
752
+ foundStrategy = key;
753
+ break;
754
+ }
755
+ }
756
+ if (foundStrategy) {
757
+ return element;
758
+ }
759
+ }
760
+ }
761
+ catch (error) {
762
+ console.error("Error parsing locators from file: " + filePath, error);
763
+ }
764
+ return null;
765
+ }
683
766
  async _locate(selectors, info, _params, timeout, allowDisabled = false) {
684
767
  if (!timeout) {
685
768
  timeout = 30000;
686
769
  }
770
+ let element = null;
771
+ let allStrategyLocators = null;
772
+ let selectedStrategy = null;
773
+ if (this.tryAllStrategies) {
774
+ allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
775
+ selectedStrategy = allStrategyLocators?.strategy;
776
+ }
687
777
  for (let i = 0; i < 3; i++) {
688
778
  info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
689
779
  for (let j = 0; j < selectors.locators.length; j++) {
690
780
  let selector = selectors.locators[j];
691
781
  info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
692
782
  }
693
- let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
783
+ if (this.tryAllStrategies && selectedStrategy) {
784
+ const strategyLocators = allStrategyLocators[selectedStrategy];
785
+ let err;
786
+ if (strategyLocators && strategyLocators.length) {
787
+ try {
788
+ selectors.locators = strategyLocators;
789
+ element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
790
+ info.selectedStrategy = selectedStrategy;
791
+ info.log += "element found using strategy " + selectedStrategy + "\n";
792
+ }
793
+ catch (error) {
794
+ err = error;
795
+ }
796
+ }
797
+ if (!element) {
798
+ for (const key in allStrategyLocators) {
799
+ if (key === "strategy" || key === selectedStrategy) {
800
+ continue;
801
+ }
802
+ const strategyLocators = allStrategyLocators[key];
803
+ if (strategyLocators && strategyLocators.length) {
804
+ try {
805
+ info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
806
+ selectors.locators = strategyLocators;
807
+ element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
808
+ err = null;
809
+ info.selectedStrategy = key;
810
+ info.log += "element found using strategy " + key + "\n";
811
+ break;
812
+ }
813
+ catch (error) {
814
+ err = error;
815
+ }
816
+ }
817
+ }
818
+ }
819
+ if (err) {
820
+ throw err;
821
+ }
822
+ }
823
+ else {
824
+ element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
825
+ }
694
826
  if (!element.rerun) {
695
- const randomToken = Math.random().toString(36).substring(7);
696
- await element.evaluate((el, randomToken) => {
697
- el.setAttribute("data-blinq-id-" + randomToken, "");
698
- }, randomToken);
699
- // if (element._frame) {
700
- // return element;
701
- // }
827
+ let newElementSelector = "";
828
+ if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
829
+ const cssSelector = await element.evaluate((el) => {
830
+ function getCssSelector(el) {
831
+ if (!el || el.nodeType !== 1 || el === document.body)
832
+ return el.tagName.toLowerCase();
833
+ const parent = el.parentElement;
834
+ const tag = el.tagName.toLowerCase();
835
+ // Find the index of the element among its siblings of the same tag
836
+ let index = 1;
837
+ for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
838
+ if (sibling.tagName === el.tagName) {
839
+ index++;
840
+ }
841
+ }
842
+ // Use nth-child if necessary (i.e., if there's more than one of the same tag)
843
+ const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
844
+ const needsNthChild = siblings.length > 1;
845
+ const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
846
+ return getCssSelector(parent) + " > " + selector;
847
+ }
848
+ const cssSelector = getCssSelector(el);
849
+ return cssSelector;
850
+ });
851
+ newElementSelector = cssSelector;
852
+ }
853
+ else {
854
+ const randomToken = "blinq_" + Math.random().toString(36).substring(7);
855
+ if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
856
+ const dataAttribute = "data-blinq-id";
857
+ await element.evaluate((el, [dataAttribute, randomToken]) => {
858
+ el.setAttribute(dataAttribute, randomToken);
859
+ }, [dataAttribute, randomToken]);
860
+ newElementSelector = `[${dataAttribute}="${randomToken}"]`;
861
+ }
862
+ else {
863
+ // the default case just return the located element
864
+ // will not work for click and type if the locator is placeholder and the placeholder change due to the click event
865
+ return element;
866
+ }
867
+ }
702
868
  const scope = element._frame ?? element.page();
703
- let newElementSelector = "[data-blinq-id-" + randomToken + "]";
704
869
  let prefixSelector = "";
705
870
  const frameControlSelector = " >> internal:control=enter-frame";
706
871
  const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
707
872
  if (frameSelectorIndex !== -1) {
708
873
  // remove everything after the >> internal:control=enter-frame
709
874
  const frameSelector = element._selector.substring(0, frameSelectorIndex);
710
- prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
875
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
711
876
  }
712
877
  // if (element?._frame?._selector) {
713
878
  // prefixSelector = element._frame._selector + " >> " + prefixSelector;
714
879
  // }
715
880
  const newSelector = prefixSelector + newElementSelector;
716
- return scope.locator(newSelector);
881
+ return scope.locator(newSelector).first();
717
882
  }
718
883
  }
719
884
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -734,7 +899,7 @@ class StableBrowser {
734
899
  for (let i = 0; i < frame.selectors.length; i++) {
735
900
  let frameLocator = frame.selectors[i];
736
901
  if (frameLocator.css) {
737
- let testframescope = framescope.frameLocator(frameLocator.css);
902
+ let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
738
903
  if (frameLocator.index) {
739
904
  testframescope = framescope.nth(frameLocator.index);
740
905
  }
@@ -812,6 +977,15 @@ class StableBrowser {
812
977
  });
813
978
  }
814
979
  async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
980
+ if (selectors.locators && Array.isArray(selectors.locators)) {
981
+ selectors.locators.forEach((locator) => {
982
+ locator.index = locator.index ?? 0;
983
+ locator.visible = locator.visible ?? true;
984
+ if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
985
+ locator.css = locator.css + " >> visible=true";
986
+ }
987
+ });
988
+ }
815
989
  if (!info) {
816
990
  info = {};
817
991
  info.failCause = {};
@@ -824,7 +998,6 @@ class StableBrowser {
824
998
  let locatorsCount = 0;
825
999
  let lazy_scroll = false;
826
1000
  //let arrayMode = Array.isArray(selectors);
827
- let scope = await this._findFrameScope(selectors, timeout, info);
828
1001
  let selectorsLocators = null;
829
1002
  selectorsLocators = selectors.locators;
830
1003
  // group selectors by priority
@@ -852,6 +1025,7 @@ class StableBrowser {
852
1025
  let highPriorityOnly = true;
853
1026
  let visibleOnly = true;
854
1027
  while (true) {
1028
+ let scope = await this._findFrameScope(selectors, timeout, info);
855
1029
  locatorsCount = 0;
856
1030
  let result = [];
857
1031
  let popupResult = await this.closeUnexpectedPopups(info, _params);
@@ -967,9 +1141,13 @@ class StableBrowser {
967
1141
  }
968
1142
  }
969
1143
  if (foundLocators.length === 1) {
1144
+ let box = null;
1145
+ if (!this.onlyFailuresScreenshot) {
1146
+ box = await foundLocators[0].boundingBox();
1147
+ }
970
1148
  result.foundElements.push({
971
1149
  locator: foundLocators[0],
972
- box: await foundLocators[0].boundingBox(),
1150
+ box: box,
973
1151
  unique: true,
974
1152
  });
975
1153
  result.locatorIndex = i;
@@ -1124,11 +1302,22 @@ class StableBrowser {
1124
1302
  operation: "click",
1125
1303
  log: "***** click on " + selectors.element_name + " *****\n",
1126
1304
  };
1305
+ check_performance("click_all ***", this.context, true);
1306
+ let stepFastMode = this.stepTags.includes("fast-mode");
1307
+ if (stepFastMode) {
1308
+ state.onlyFailuresScreenshot = true;
1309
+ state.scroll = false;
1310
+ state.highlight = false;
1311
+ }
1127
1312
  try {
1313
+ check_performance("click_preCommand", this.context, true);
1128
1314
  await _preCommand(state, this);
1315
+ check_performance("click_preCommand", this.context, false);
1129
1316
  await performAction("click", state.element, options, this, state, _params);
1130
- if (!this.fastMode) {
1131
- await this.waitForPageLoad();
1317
+ if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
1318
+ check_performance("click_waitForPageLoad", this.context, true);
1319
+ await this.waitForPageLoad({ noSleep: true });
1320
+ check_performance("click_waitForPageLoad", this.context, false);
1132
1321
  }
1133
1322
  return state.info;
1134
1323
  }
@@ -1136,7 +1325,13 @@ class StableBrowser {
1136
1325
  await _commandError(state, e, this);
1137
1326
  }
1138
1327
  finally {
1328
+ check_performance("click_commandFinally", this.context, true);
1139
1329
  await _commandFinally(state, this);
1330
+ check_performance("click_commandFinally", this.context, false);
1331
+ check_performance("click_all ***", this.context, false);
1332
+ if (this.context.profile) {
1333
+ console.log(JSON.stringify(this.context.profile, null, 2));
1334
+ }
1140
1335
  }
1141
1336
  }
1142
1337
  async waitForElement(selectors, _params, options = {}, world = null) {
@@ -1228,7 +1423,7 @@ class StableBrowser {
1228
1423
  }
1229
1424
  }
1230
1425
  }
1231
- await this.waitForPageLoad();
1426
+ //await this.waitForPageLoad();
1232
1427
  return state.info;
1233
1428
  }
1234
1429
  catch (e) {
@@ -1254,7 +1449,7 @@ class StableBrowser {
1254
1449
  await _preCommand(state, this);
1255
1450
  await performAction("hover", state.element, options, this, state, _params);
1256
1451
  await _screenshot(state, this);
1257
- await this.waitForPageLoad();
1452
+ //await this.waitForPageLoad();
1258
1453
  return state.info;
1259
1454
  }
1260
1455
  catch (e) {
@@ -1290,7 +1485,7 @@ class StableBrowser {
1290
1485
  state.info.log += "selectOption failed, will try force" + "\n";
1291
1486
  await state.element.selectOption(values, { timeout: 10000, force: true });
1292
1487
  }
1293
- await this.waitForPageLoad();
1488
+ //await this.waitForPageLoad();
1294
1489
  return state.info;
1295
1490
  }
1296
1491
  catch (e) {
@@ -1476,6 +1671,14 @@ class StableBrowser {
1476
1671
  }
1477
1672
  try {
1478
1673
  await _preCommand(state, this);
1674
+ const randomToken = "blinq_" + Math.random().toString(36).substring(7);
1675
+ // tag the element
1676
+ let newElementSelector = await state.element.evaluate((el, token) => {
1677
+ // use attribute and not id
1678
+ const attrName = `data-blinq-id-${token}`;
1679
+ el.setAttribute(attrName, "");
1680
+ return `[${attrName}]`;
1681
+ }, randomToken);
1479
1682
  state.info.value = _value;
1480
1683
  if (!options.press) {
1481
1684
  try {
@@ -1501,6 +1704,25 @@ class StableBrowser {
1501
1704
  }
1502
1705
  }
1503
1706
  await new Promise((resolve) => setTimeout(resolve, 500));
1707
+ // check if the element exist after the click (no wait)
1708
+ const count = await state.element.count({ timeout: 0 });
1709
+ if (count === 0) {
1710
+ // the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
1711
+ const scope = state.element._frame ?? element.page();
1712
+ let prefixSelector = "";
1713
+ const frameControlSelector = " >> internal:control=enter-frame";
1714
+ const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
1715
+ if (frameSelectorIndex !== -1) {
1716
+ // remove everything after the >> internal:control=enter-frame
1717
+ const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
1718
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
1719
+ }
1720
+ // if (element?._frame?._selector) {
1721
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
1722
+ // }
1723
+ const newSelector = prefixSelector + newElementSelector;
1724
+ state.element = scope.locator(newSelector).first();
1725
+ }
1504
1726
  const valueSegment = state.value.split("&&");
1505
1727
  for (let i = 0; i < valueSegment.length; i++) {
1506
1728
  if (i > 0) {
@@ -1572,8 +1794,8 @@ class StableBrowser {
1572
1794
  if (enter) {
1573
1795
  await new Promise((resolve) => setTimeout(resolve, 2000));
1574
1796
  await this.page.keyboard.press("Enter");
1797
+ await this.waitForPageLoad();
1575
1798
  }
1576
- await this.waitForPageLoad();
1577
1799
  return state.info;
1578
1800
  }
1579
1801
  catch (e) {
@@ -1832,11 +2054,12 @@ class StableBrowser {
1832
2054
  throw new Error("referanceSnapshot is null");
1833
2055
  }
1834
2056
  let text = null;
1835
- if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
1836
- text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
2057
+ const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
2058
+ if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
2059
+ text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
1837
2060
  }
1838
- else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
1839
- text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
2061
+ else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
2062
+ text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
1840
2063
  }
1841
2064
  else if (referanceSnapshot.startsWith("yaml:")) {
1842
2065
  text = referanceSnapshot.substring(5);
@@ -1860,7 +2083,13 @@ class StableBrowser {
1860
2083
  scope = await this._findFrameScope(frameSelectors, timeout, state.info);
1861
2084
  }
1862
2085
  const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
2086
+ if (snapshot && snapshot.length <= 10) {
2087
+ console.log("Page snapshot length is suspiciously small:", snapshot);
2088
+ }
1863
2089
  matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
2090
+ if (matchResult === undefined) {
2091
+ console.log("snapshotValidation returned undefined");
2092
+ }
1864
2093
  if (matchResult.errorLine !== -1) {
1865
2094
  throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
1866
2095
  }
@@ -2031,6 +2260,9 @@ class StableBrowser {
2031
2260
  return _getTestData(world, this.context, this);
2032
2261
  }
2033
2262
  async _screenShot(options = {}, world = null, info = null) {
2263
+ if (!options) {
2264
+ options = {};
2265
+ }
2034
2266
  // collect url/path/title
2035
2267
  if (info) {
2036
2268
  if (!info.title) {
@@ -2059,7 +2291,7 @@ class StableBrowser {
2059
2291
  const uuidStr = "id_" + randomUUID();
2060
2292
  const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
2061
2293
  try {
2062
- await this.takeScreenshot(screenshotPath);
2294
+ await this.takeScreenshot(screenshotPath, options.fullPage === true);
2063
2295
  // let buffer = await this.page.screenshot({ timeout: 4000 });
2064
2296
  // // save the buffer to the screenshot path asynchrously
2065
2297
  // fs.writeFile(screenshotPath, buffer, (err) => {
@@ -2080,7 +2312,7 @@ class StableBrowser {
2080
2312
  else if (options && options.screenshot) {
2081
2313
  result.screenshotPath = options.screenshotPath;
2082
2314
  try {
2083
- await this.takeScreenshot(options.screenshotPath);
2315
+ await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
2084
2316
  // let buffer = await this.page.screenshot({ timeout: 4000 });
2085
2317
  // // save the buffer to the screenshot path asynchrously
2086
2318
  // fs.writeFile(options.screenshotPath, buffer, (err) => {
@@ -2098,7 +2330,7 @@ class StableBrowser {
2098
2330
  }
2099
2331
  return result;
2100
2332
  }
2101
- async takeScreenshot(screenshotPath) {
2333
+ async takeScreenshot(screenshotPath, fullPage = false) {
2102
2334
  const playContext = this.context.playContext;
2103
2335
  // Using CDP to capture the screenshot
2104
2336
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
@@ -2123,13 +2355,7 @@ class StableBrowser {
2123
2355
  const client = await playContext.newCDPSession(this.page);
2124
2356
  const { data } = await client.send("Page.captureScreenshot", {
2125
2357
  format: "png",
2126
- // clip: {
2127
- // x: 0,
2128
- // y: 0,
2129
- // width: viewportWidth,
2130
- // height: viewportHeight,
2131
- // scale: 1,
2132
- // },
2358
+ captureBeyondViewport: fullPage,
2133
2359
  });
2134
2360
  await client.detach();
2135
2361
  if (!screenshotPath) {
@@ -2138,7 +2364,7 @@ class StableBrowser {
2138
2364
  screenshotBuffer = Buffer.from(data, "base64");
2139
2365
  }
2140
2366
  else {
2141
- screenshotBuffer = await this.page.screenshot();
2367
+ screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
2142
2368
  }
2143
2369
  // if (focusedElement) {
2144
2370
  // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
@@ -2238,6 +2464,12 @@ class StableBrowser {
2238
2464
  state.info.value = state.value;
2239
2465
  this.setTestData({ [variable]: state.value }, world);
2240
2466
  this.logger.info("set test data: " + variable + "=" + state.value);
2467
+ if (process.env.MODE === "executions") {
2468
+ const globalDataFile = "global_test_data.json";
2469
+ if (existsSync(globalDataFile)) {
2470
+ this.saveTestDataAsGlobal({}, world);
2471
+ }
2472
+ }
2241
2473
  // await new Promise((resolve) => setTimeout(resolve, 500));
2242
2474
  return state.info;
2243
2475
  }
@@ -2309,6 +2541,12 @@ class StableBrowser {
2309
2541
  state.info.value = state.value;
2310
2542
  this.setTestData({ [variable]: state.value }, world);
2311
2543
  this.logger.info("set test data: " + variable + "=" + state.value);
2544
+ if (process.env.MODE === "executions") {
2545
+ const globalDataFile = "global_test_data.json";
2546
+ if (existsSync(globalDataFile)) {
2547
+ this.saveTestDataAsGlobal({}, world);
2548
+ }
2549
+ }
2312
2550
  // await new Promise((resolve) => setTimeout(resolve, 500));
2313
2551
  return state.info;
2314
2552
  }
@@ -2439,7 +2677,7 @@ class StableBrowser {
2439
2677
  let expectedValue;
2440
2678
  try {
2441
2679
  await _preCommand(state, this);
2442
- expectedValue = await replaceWithLocalTestData(state.value, world);
2680
+ expectedValue = await this._replaceWithLocalData(value, world);
2443
2681
  state.info.expectedValue = expectedValue;
2444
2682
  switch (property) {
2445
2683
  case "innerText":
@@ -2565,6 +2803,7 @@ class StableBrowser {
2565
2803
  allowDisabled: true,
2566
2804
  info: {},
2567
2805
  };
2806
+ state.options ??= { timeout: timeoutMs };
2568
2807
  // Initialize startTime outside try block to ensure it's always accessible
2569
2808
  const startTime = Date.now();
2570
2809
  let conditionMet = false;
@@ -2734,6 +2973,12 @@ class StableBrowser {
2734
2973
  emailUrl = url;
2735
2974
  codeOrUrlFound = true;
2736
2975
  }
2976
+ if (process.env.MODE === "executions") {
2977
+ const globalDataFile = "global_test_data.json";
2978
+ if (existsSync(globalDataFile)) {
2979
+ this.saveTestDataAsGlobal({}, world);
2980
+ }
2981
+ }
2737
2982
  if (codeOrUrlFound) {
2738
2983
  return { emailUrl, emailCode };
2739
2984
  }
@@ -3140,7 +3385,16 @@ class StableBrowser {
3140
3385
  text = text.replace(/\\"/g, '"');
3141
3386
  }
3142
3387
  const timeout = this._getFindElementTimeout(options);
3143
- await new Promise((resolve) => setTimeout(resolve, 2000));
3388
+ //if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
3389
+ let stepFastMode = this.stepTags.includes("fast-mode");
3390
+ if (!stepFastMode) {
3391
+ if (!this.fastMode) {
3392
+ await new Promise((resolve) => setTimeout(resolve, 2000));
3393
+ }
3394
+ else {
3395
+ await new Promise((resolve) => setTimeout(resolve, 500));
3396
+ }
3397
+ }
3144
3398
  const newValue = await this._replaceWithLocalData(text, world);
3145
3399
  if (newValue !== text) {
3146
3400
  this.logger.info(text + "=" + newValue);
@@ -3148,6 +3402,11 @@ class StableBrowser {
3148
3402
  }
3149
3403
  let dateAlternatives = findDateAlternatives(text);
3150
3404
  let numberAlternatives = findNumberAlternatives(text);
3405
+ if (stepFastMode) {
3406
+ state.onlyFailuresScreenshot = true;
3407
+ state.scroll = false;
3408
+ state.highlight = false;
3409
+ }
3151
3410
  try {
3152
3411
  await _preCommand(state, this);
3153
3412
  state.info.text = text;
@@ -3267,6 +3526,8 @@ class StableBrowser {
3267
3526
  operation: "verify_text_with_relation",
3268
3527
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
3269
3528
  };
3529
+ const cmdStartTime = Date.now();
3530
+ let cmdEndTime = null;
3270
3531
  const timeout = this._getFindElementTimeout(options);
3271
3532
  await new Promise((resolve) => setTimeout(resolve, 2000));
3272
3533
  let newValue = await this._replaceWithLocalData(textAnchor, world);
@@ -3302,6 +3563,17 @@ class StableBrowser {
3302
3563
  await new Promise((resolve) => setTimeout(resolve, 1000));
3303
3564
  continue;
3304
3565
  }
3566
+ else {
3567
+ cmdEndTime = Date.now();
3568
+ if (cmdEndTime - cmdStartTime > 55000) {
3569
+ if (foundAncore) {
3570
+ throw new Error(`Text ${textToVerify} not found in page`);
3571
+ }
3572
+ else {
3573
+ throw new Error(`Text ${textAnchor} not found in page`);
3574
+ }
3575
+ }
3576
+ }
3305
3577
  try {
3306
3578
  for (let i = 0; i < resultWithElementsFound.length; i++) {
3307
3579
  foundAncore = true;
@@ -3440,7 +3712,7 @@ class StableBrowser {
3440
3712
  Object.assign(e, { info: info });
3441
3713
  error = e;
3442
3714
  // throw e;
3443
- await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
3715
+ await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
3444
3716
  }
3445
3717
  finally {
3446
3718
  const endTime = Date.now();
@@ -3789,6 +4061,22 @@ class StableBrowser {
3789
4061
  }
3790
4062
  }
3791
4063
  async waitForPageLoad(options = {}, world = null) {
4064
+ // try {
4065
+ // let currentPagePath = null;
4066
+ // currentPagePath = new URL(this.page.url()).pathname;
4067
+ // if (this.latestPagePath) {
4068
+ // // get the currect page path and compare with the latest page path
4069
+ // if (this.latestPagePath === currentPagePath) {
4070
+ // // if the page path is the same, do not wait for page load
4071
+ // console.log("No page change: " + currentPagePath);
4072
+ // return;
4073
+ // }
4074
+ // }
4075
+ // this.latestPagePath = currentPagePath;
4076
+ // } catch (e) {
4077
+ // console.debug("Error getting current page path: ", e);
4078
+ // }
4079
+ //console.log("Waiting for page load");
3792
4080
  let timeout = this._getLoadTimeout(options);
3793
4081
  const promiseArray = [];
3794
4082
  // let waitForNetworkIdle = true;
@@ -3823,7 +4111,10 @@ class StableBrowser {
3823
4111
  }
3824
4112
  }
3825
4113
  finally {
3826
- await new Promise((resolve) => setTimeout(resolve, 2000));
4114
+ await new Promise((resolve) => setTimeout(resolve, 500));
4115
+ if (options && !options.noSleep) {
4116
+ await new Promise((resolve) => setTimeout(resolve, 1500));
4117
+ }
3827
4118
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
3828
4119
  const endTime = Date.now();
3829
4120
  _reportToWorld(world, {
@@ -3877,7 +4168,7 @@ class StableBrowser {
3877
4168
  }
3878
4169
  operation = options.operation;
3879
4170
  // validate operation is one of the supported operations
3880
- if (operation != "click" && operation != "hover+click") {
4171
+ if (operation != "click" && operation != "hover+click" && operation != "hover") {
3881
4172
  throw new Error("operation is not supported");
3882
4173
  }
3883
4174
  const state = {
@@ -3946,6 +4237,17 @@ class StableBrowser {
3946
4237
  state.element = results[0];
3947
4238
  await performAction("hover+click", state.element, options, this, state, _params);
3948
4239
  break;
4240
+ case "hover":
4241
+ if (!options.css) {
4242
+ throw new Error("css is not defined");
4243
+ }
4244
+ const result1 = await findElementsInArea(options.css, cellArea, this, options);
4245
+ if (result1.length === 0) {
4246
+ throw new Error(`Element not found in cell area`);
4247
+ }
4248
+ state.element = result1[0];
4249
+ await performAction("hover", state.element, options, this, state, _params);
4250
+ break;
3949
4251
  default:
3950
4252
  throw new Error("operation is not supported");
3951
4253
  }
@@ -3959,6 +4261,12 @@ class StableBrowser {
3959
4261
  }
3960
4262
  saveTestDataAsGlobal(options, world) {
3961
4263
  const dataFile = _getDataFile(world, this.context, this);
4264
+ if (process.env.MODE === "executions") {
4265
+ const globalDataFile = path.join(this.project_path, "global_test_data.json");
4266
+ fs.copyFileSync(dataFile, globalDataFile);
4267
+ this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
4268
+ return;
4269
+ }
3962
4270
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
3963
4271
  this.logger.info("Save the scenario test data as global for the following scenarios.");
3964
4272
  }
@@ -4061,6 +4369,7 @@ class StableBrowser {
4061
4369
  if (world && world.attach) {
4062
4370
  world.attach(this.context.reportFolder, { mediaType: "text/plain" });
4063
4371
  }
4372
+ this.context.loadedRoutes = null;
4064
4373
  this.beforeScenarioCalled = true;
4065
4374
  if (scenario && scenario.pickle && scenario.pickle.name) {
4066
4375
  this.scenarioName = scenario.pickle.name;
@@ -4087,14 +4396,32 @@ class StableBrowser {
4087
4396
  await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
4088
4397
  }
4089
4398
  await loadBrunoParams(this.context, this.context.environment.name);
4399
+ if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
4400
+ this.trace = true;
4401
+ const traceFolder = path.join(this.context.reportFolder, "trace");
4402
+ if (!fs.existsSync(traceFolder)) {
4403
+ fs.mkdirSync(traceFolder, { recursive: true });
4404
+ }
4405
+ this.traceFolder = traceFolder;
4406
+ await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
4407
+ }
4408
+ }
4409
+ async afterScenario(world, scenario) {
4410
+ const id = scenario.testCaseStartedId;
4411
+ if (this.trace) {
4412
+ await this.context.playContext.tracing.stop({
4413
+ path: path.join(this.traceFolder, `trace-${id}.zip`),
4414
+ });
4415
+ }
4090
4416
  }
4091
- async afterScenario(world, scenario) { }
4092
4417
  async beforeStep(world, step) {
4093
- if (this.abortedExecution) {
4094
- throw new Error("Aborted");
4418
+ if (step?.pickleStep && this.trace) {
4419
+ await this.context.playContext.tracing.group(`Step: ${step.pickleStep.text}`);
4095
4420
  }
4421
+ this.stepTags = [];
4096
4422
  if (!this.beforeScenarioCalled) {
4097
4423
  this.beforeScenario(world, step);
4424
+ this.context.loadedRoutes = null;
4098
4425
  }
4099
4426
  if (this.stepIndex === undefined) {
4100
4427
  this.stepIndex = 0;
@@ -4104,7 +4431,12 @@ class StableBrowser {
4104
4431
  }
4105
4432
  if (step && step.pickleStep && step.pickleStep.text) {
4106
4433
  this.stepName = step.pickleStep.text;
4107
- this.logger.info("step: " + this.stepName);
4434
+ let printableStepName = this.stepName;
4435
+ // take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
4436
+ printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
4437
+ return `\x1b[33m"${p1}"\x1b[0m`;
4438
+ });
4439
+ this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
4108
4440
  }
4109
4441
  else if (step && step.text) {
4110
4442
  this.stepName = step.text;
@@ -4119,7 +4451,10 @@ class StableBrowser {
4119
4451
  }
4120
4452
  if (this.initSnapshotTaken === false) {
4121
4453
  this.initSnapshotTaken = true;
4122
- if (world && world.attach && !process.env.DISABLE_SNAPSHOT && !this.fastMode) {
4454
+ if (world &&
4455
+ world.attach &&
4456
+ !process.env.DISABLE_SNAPSHOT &&
4457
+ (!this.fastMode || this.stepTags.includes("fast-mode"))) {
4123
4458
  const snapshot = await this.getAriaSnapshot();
4124
4459
  if (snapshot) {
4125
4460
  await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
@@ -4129,7 +4464,11 @@ class StableBrowser {
4129
4464
  this.context.routeResults = null;
4130
4465
  this.context.loadedRoutes = null;
4131
4466
  await registerBeforeStepRoutes(this.context, this.stepName, world);
4132
- networkBeforeStep(this.stepName);
4467
+ networkBeforeStep(this.stepName, this.context);
4468
+ this.inStepReport = false;
4469
+ }
4470
+ setStepTags(tags) {
4471
+ this.stepTags = tags;
4133
4472
  }
4134
4473
  async getAriaSnapshot() {
4135
4474
  try {
@@ -4203,7 +4542,7 @@ class StableBrowser {
4203
4542
  state.payload = payload;
4204
4543
  if (commandStatus === "FAILED") {
4205
4544
  state.throwError = true;
4206
- throw new Error("Command failed");
4545
+ throw new Error(commandText);
4207
4546
  }
4208
4547
  }
4209
4548
  catch (e) {
@@ -4213,26 +4552,22 @@ class StableBrowser {
4213
4552
  await _commandFinally(state, this);
4214
4553
  }
4215
4554
  }
4216
- async afterStep(world, step) {
4555
+ async afterStep(world, step, result) {
4217
4556
  this.stepName = null;
4218
- if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
4219
- if (this.context.browserObject.context) {
4220
- await this.context.browserObject.context.tracing.stopChunk({
4221
- path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
4222
- });
4223
- if (world && world.attach) {
4224
- await world.attach(JSON.stringify({
4225
- type: "trace",
4226
- traceFilePath: `trace-${this.stepIndex}.zip`,
4227
- }), "application/json+trace");
4228
- }
4229
- // console.log("trace file created", `trace-${this.stepIndex}.zip`);
4230
- }
4231
- }
4232
4557
  if (this.context) {
4233
4558
  this.context.examplesRow = null;
4234
4559
  }
4235
- if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
4560
+ if (!this.inStepReport) {
4561
+ // check the step result
4562
+ if (result && result.status === "FAILED" && world && world.attach) {
4563
+ await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
4564
+ }
4565
+ }
4566
+ if (world &&
4567
+ world.attach &&
4568
+ !process.env.DISABLE_SNAPSHOT &&
4569
+ !this.fastMode &&
4570
+ !this.stepTags.includes("fast-mode")) {
4236
4571
  const snapshot = await this.getAriaSnapshot();
4237
4572
  if (snapshot) {
4238
4573
  const obj = {};
@@ -4240,6 +4575,11 @@ class StableBrowser {
4240
4575
  }
4241
4576
  }
4242
4577
  this.context.routeResults = await registerAfterStepRoutes(this.context, world);
4578
+ if (this.context.routeResults) {
4579
+ if (world && world.attach) {
4580
+ await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
4581
+ }
4582
+ }
4243
4583
  if (!process.env.TEMP_RUN) {
4244
4584
  const state = {
4245
4585
  world,
@@ -4263,10 +4603,15 @@ class StableBrowser {
4263
4603
  await _commandFinally(state, this);
4264
4604
  }
4265
4605
  }
4266
- networkAfterStep(this.stepName);
4606
+ networkAfterStep(this.stepName, this.context);
4267
4607
  if (process.env.TEMP_RUN === "true") {
4268
4608
  // Put a sleep for some time to allow the browser to finish processing
4269
- await new Promise((resolve) => setTimeout(resolve, 3000));
4609
+ if (!this.stepTags.includes("fast-mode")) {
4610
+ await new Promise((resolve) => setTimeout(resolve, 3000));
4611
+ }
4612
+ }
4613
+ if (this.trace) {
4614
+ await this.context.playContext.tracing.groupEnd();
4270
4615
  }
4271
4616
  }
4272
4617
  }