automation_model 1.0.806-dev → 1.0.806-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 (47) hide show
  1. package/lib/api.js +40 -12
  2. package/lib/api.js.map +1 -1
  3. package/lib/auto_page.d.ts +1 -1
  4. package/lib/auto_page.js +104 -58
  5. package/lib/auto_page.js.map +1 -1
  6. package/lib/browser_manager.d.ts +2 -7
  7. package/lib/browser_manager.js +116 -102
  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 +42 -24
  15. package/lib/command_common.js.map +1 -1
  16. package/lib/constants.d.ts +4 -0
  17. package/lib/constants.js +2 -0
  18. package/lib/constants.js.map +1 -0
  19. package/lib/file_checker.js +7 -0
  20. package/lib/file_checker.js.map +1 -1
  21. package/lib/index.js +1 -0
  22. package/lib/index.js.map +1 -1
  23. package/lib/init_browser.d.ts +1 -2
  24. package/lib/init_browser.js +137 -128
  25. package/lib/init_browser.js.map +1 -1
  26. package/lib/locator_log.js.map +1 -1
  27. package/lib/network.d.ts +2 -2
  28. package/lib/network.js +183 -120
  29. package/lib/network.js.map +1 -1
  30. package/lib/route.d.ts +64 -2
  31. package/lib/route.js +496 -251
  32. package/lib/route.js.map +1 -1
  33. package/lib/scripts/axe.mini.js +23978 -1
  34. package/lib/snapshot_validation.js +3 -0
  35. package/lib/snapshot_validation.js.map +1 -1
  36. package/lib/stable_browser.d.ts +12 -8
  37. package/lib/stable_browser.js +355 -96
  38. package/lib/stable_browser.js.map +1 -1
  39. package/lib/table_helper.js +14 -0
  40. package/lib/table_helper.js.map +1 -1
  41. package/lib/test_context.d.ts +1 -0
  42. package/lib/test_context.js +1 -0
  43. package/lib/test_context.js.map +1 -1
  44. package/lib/utils.d.ts +7 -4
  45. package/lib/utils.js +157 -34
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +20 -12
@@ -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";
@@ -28,6 +29,7 @@ import { highlightSnapshot, snapshotValidation } from "./snapshot_validation.js"
28
29
  import { loadBrunoParams } from "./bruno.js";
29
30
  import { registerAfterStepRoutes, registerBeforeStepRoutes } from "./route.js";
30
31
  import { existsSync } from "node:fs";
32
+ import _ from "lodash";
31
33
  export const Types = {
32
34
  CLICK: "click_element",
33
35
  WAIT_ELEMENT: "wait_element",
@@ -46,6 +48,7 @@ export const Types = {
46
48
  VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
47
49
  ANALYZE_TABLE: "analyze_table",
48
50
  SELECT: "select_combobox", //
51
+ VERIFY_PROPERTY: "verify_element_property",
49
52
  VERIFY_PAGE_PATH: "verify_page_path",
50
53
  VERIFY_PAGE_TITLE: "verify_page_title",
51
54
  TYPE_PRESS: "type_press",
@@ -64,12 +67,11 @@ export const Types = {
64
67
  SET_INPUT: "set_input",
65
68
  WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
66
69
  VERIFY_ATTRIBUTE: "verify_element_attribute",
67
- VERIFY_PROPERTY: "verify_element_property",
68
70
  VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
69
71
  BRUNO: "bruno",
70
- SNAPSHOT_VALIDATION: "snapshot_validation",
71
72
  VERIFY_FILE_EXISTS: "verify_file_exists",
72
73
  SET_INPUT_FILES: "set_input_files",
74
+ SNAPSHOT_VALIDATION: "snapshot_validation",
73
75
  REPORT_COMMAND: "report_command",
74
76
  STEP_COMPLETE: "step_complete",
75
77
  SLEEP: "sleep",
@@ -86,6 +88,7 @@ class StableBrowser {
86
88
  context;
87
89
  world;
88
90
  fastMode;
91
+ stepTags;
89
92
  project_path = null;
90
93
  webLogFile = null;
91
94
  networkLogger = null;
@@ -94,14 +97,17 @@ class StableBrowser {
94
97
  tags = null;
95
98
  isRecording = false;
96
99
  initSnapshotTaken = false;
97
- abortedExecution = false;
98
- constructor(browser, page, logger = null, context = null, world = null, fastMode = false) {
100
+ onlyFailuresScreenshot = process.env.SCREENSHOT_ON_FAILURE_ONLY === "true";
101
+ // set to true if the step issue a report
102
+ inStepReport = false;
103
+ constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
99
104
  this.browser = browser;
100
105
  this.page = page;
101
106
  this.logger = logger;
102
107
  this.context = context;
103
108
  this.world = world;
104
109
  this.fastMode = fastMode;
110
+ this.stepTags = stepTags;
105
111
  if (!this.logger) {
106
112
  this.logger = console;
107
113
  }
@@ -134,7 +140,7 @@ class StableBrowser {
134
140
  this.fastMode = true;
135
141
  }
136
142
  if (process.env.FAST_MODE === "true") {
137
- console.log("Fast mode enabled from environment variable");
143
+ // console.log("Fast mode enabled from environment variable");
138
144
  this.fastMode = true;
139
145
  }
140
146
  if (process.env.FAST_MODE === "false") {
@@ -177,6 +183,7 @@ class StableBrowser {
177
183
  registerNetworkEvents(this.world, this, context, this.page);
178
184
  registerDownloadEvent(this.page, this.world, context);
179
185
  page.on("close", async () => {
186
+ // return if browser context is already closed
180
187
  if (this.context && this.context.pages && this.context.pages.length > 1) {
181
188
  this.context.pages.pop();
182
189
  this.page = this.context.pages[this.context.pages.length - 1];
@@ -186,7 +193,12 @@ class StableBrowser {
186
193
  console.log("Switched to page " + title);
187
194
  }
188
195
  catch (error) {
189
- console.error("Error on page close", error);
196
+ if (error?.message?.includes("Target page, context or browser has been closed")) {
197
+ // Ignore this error
198
+ }
199
+ else {
200
+ console.error("Error on page close", error);
201
+ }
190
202
  }
191
203
  }
192
204
  });
@@ -195,7 +207,12 @@ class StableBrowser {
195
207
  console.log("Switch page: " + (await page.title()));
196
208
  }
197
209
  catch (e) {
198
- this.logger.error("error on page load " + e);
210
+ if (e?.message?.includes("Target page, context or browser has been closed")) {
211
+ // Ignore this error
212
+ }
213
+ else {
214
+ this.logger.error("error on page load " + e);
215
+ }
199
216
  }
200
217
  context.pageLoading.status = false;
201
218
  }.bind(this));
@@ -207,7 +224,7 @@ class StableBrowser {
207
224
  }
208
225
  let newContextCreated = false;
209
226
  if (!apps[appName]) {
210
- let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
227
+ 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);
211
228
  newContextCreated = true;
212
229
  apps[appName] = {
213
230
  context: newContext,
@@ -223,7 +240,7 @@ class StableBrowser {
223
240
  if (newContextCreated) {
224
241
  this.registerEventListeners(this.context);
225
242
  await this.goto(this.context.environment.baseUrl);
226
- if (!this.fastMode) {
243
+ if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
227
244
  await this.waitForPageLoad();
228
245
  }
229
246
  }
@@ -315,7 +332,7 @@ class StableBrowser {
315
332
  // async closeUnexpectedPopups() {
316
333
  // await closeUnexpectedPopups(this.page);
317
334
  // }
318
- async goto(url, world = null) {
335
+ async goto(url, world = null, options = {}) {
319
336
  if (!url) {
320
337
  throw new Error("url is null, verify that the environment file is correct");
321
338
  }
@@ -336,16 +353,23 @@ class StableBrowser {
336
353
  screenshot: false,
337
354
  highlight: false,
338
355
  };
356
+ let timeout = 60000;
357
+ if (this.configuration && this.configuration.page_timeout) {
358
+ timeout = this.configuration.page_timeout;
359
+ }
360
+ if (options && options["timeout"]) {
361
+ timeout = options["timeout"];
362
+ }
339
363
  try {
340
364
  await _preCommand(state, this);
341
365
  await this.page.goto(url, {
342
- timeout: 60000,
366
+ timeout: timeout,
343
367
  });
344
368
  await _screenshot(state, this);
345
369
  }
346
370
  catch (error) {
347
371
  console.error("Error on goto", error);
348
- _commandError(state, error, this);
372
+ await _commandError(state, error, this);
349
373
  }
350
374
  finally {
351
375
  await _commandFinally(state, this);
@@ -374,7 +398,7 @@ class StableBrowser {
374
398
  }
375
399
  catch (error) {
376
400
  console.error("Error on goBack", error);
377
- _commandError(state, error, this);
401
+ await _commandError(state, error, this);
378
402
  }
379
403
  finally {
380
404
  await _commandFinally(state, this);
@@ -403,7 +427,7 @@ class StableBrowser {
403
427
  }
404
428
  catch (error) {
405
429
  console.error("Error on goForward", error);
406
- _commandError(state, error, this);
430
+ await _commandError(state, error, this);
407
431
  }
408
432
  finally {
409
433
  await _commandFinally(state, this);
@@ -507,12 +531,6 @@ class StableBrowser {
507
531
  if (!el.setAttribute) {
508
532
  el = el.parentElement;
509
533
  }
510
- // remove any attributes start with data-blinq-id
511
- // for (let i = 0; i < el.attributes.length; i++) {
512
- // if (el.attributes[i].name.startsWith("data-blinq-id")) {
513
- // el.removeAttribute(el.attributes[i].name);
514
- // }
515
- // }
516
534
  el.setAttribute("data-blinq-id-" + randomToken, "");
517
535
  return true;
518
536
  }, [tag1, randomToken]))) {
@@ -684,8 +702,8 @@ class StableBrowser {
684
702
  }
685
703
  getFilePath() {
686
704
  const stackFrames = errorStackParser.parse(new Error());
687
- const stackFrame = stackFrames.findLast((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
688
- // return stackFrame?.fileName || null;
705
+ const mjsFrames = stackFrames.filter((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
706
+ const stackFrame = mjsFrames[mjsFrames.length - 2];
689
707
  const filepath = stackFrame?.fileName;
690
708
  if (filepath) {
691
709
  let jsonFilePath = filepath.replace(".mjs", ".json");
@@ -807,29 +825,61 @@ class StableBrowser {
807
825
  element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
808
826
  }
809
827
  if (!element.rerun) {
810
- const randomToken = Math.random().toString(36).substring(7);
811
- await element.evaluate((el, randomToken) => {
812
- el.setAttribute("data-blinq-id-" + randomToken, "");
813
- console.log("set data-blinq-id-" + randomToken + " on element", el);
814
- }, randomToken);
815
- // if (element._frame) {
816
- // return element;
817
- // }
828
+ let newElementSelector = "";
829
+ if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
830
+ const cssSelector = await element.evaluate((el) => {
831
+ function getCssSelector(el) {
832
+ if (!el || el.nodeType !== 1 || el === document.body)
833
+ return el.tagName.toLowerCase();
834
+ const parent = el.parentElement;
835
+ const tag = el.tagName.toLowerCase();
836
+ // Find the index of the element among its siblings of the same tag
837
+ let index = 1;
838
+ for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
839
+ if (sibling.tagName === el.tagName) {
840
+ index++;
841
+ }
842
+ }
843
+ // Use nth-child if necessary (i.e., if there's more than one of the same tag)
844
+ const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
845
+ const needsNthChild = siblings.length > 1;
846
+ const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
847
+ return getCssSelector(parent) + " > " + selector;
848
+ }
849
+ const cssSelector = getCssSelector(el);
850
+ return cssSelector;
851
+ });
852
+ newElementSelector = cssSelector;
853
+ }
854
+ else {
855
+ const randomToken = "blinq_" + Math.random().toString(36).substring(7);
856
+ if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
857
+ const dataAttribute = "data-blinq-id";
858
+ await element.evaluate((el, [dataAttribute, randomToken]) => {
859
+ el.setAttribute(dataAttribute, randomToken);
860
+ }, [dataAttribute, randomToken]);
861
+ newElementSelector = `[${dataAttribute}="${randomToken}"]`;
862
+ }
863
+ else {
864
+ // the default case just return the located element
865
+ // will not work for click and type if the locator is placeholder and the placeholder change due to the click event
866
+ return element;
867
+ }
868
+ }
818
869
  const scope = element._frame ?? element.page();
819
- let newElementSelector = "[data-blinq-id-" + randomToken + "]";
820
870
  let prefixSelector = "";
821
871
  const frameControlSelector = " >> internal:control=enter-frame";
822
872
  const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
823
873
  if (frameSelectorIndex !== -1) {
824
874
  // remove everything after the >> internal:control=enter-frame
825
875
  const frameSelector = element._selector.substring(0, frameSelectorIndex);
826
- prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
876
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
827
877
  }
828
878
  // if (element?._frame?._selector) {
829
879
  // prefixSelector = element._frame._selector + " >> " + prefixSelector;
830
880
  // }
831
881
  const newSelector = prefixSelector + newElementSelector;
832
- return scope.locator(newSelector);
882
+ return scope.locator(newSelector).first();
833
883
  }
834
884
  }
835
885
  throw new Error("unable to locate element " + JSON.stringify(selectors));
@@ -850,7 +900,7 @@ class StableBrowser {
850
900
  for (let i = 0; i < frame.selectors.length; i++) {
851
901
  let frameLocator = frame.selectors[i];
852
902
  if (frameLocator.css) {
853
- let testframescope = framescope.frameLocator(frameLocator.css);
903
+ let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
854
904
  if (frameLocator.index) {
855
905
  testframescope = framescope.nth(frameLocator.index);
856
906
  }
@@ -928,6 +978,15 @@ class StableBrowser {
928
978
  });
929
979
  }
930
980
  async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
981
+ if (selectors.locators && Array.isArray(selectors.locators)) {
982
+ selectors.locators.forEach((locator) => {
983
+ locator.index = locator.index ?? 0;
984
+ locator.visible = locator.visible ?? true;
985
+ if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
986
+ locator.css = locator.css + " >> visible=true";
987
+ }
988
+ });
989
+ }
931
990
  if (!info) {
932
991
  info = {};
933
992
  info.failCause = {};
@@ -1083,9 +1142,13 @@ class StableBrowser {
1083
1142
  }
1084
1143
  }
1085
1144
  if (foundLocators.length === 1) {
1145
+ let box = null;
1146
+ if (!this.onlyFailuresScreenshot) {
1147
+ box = await foundLocators[0].boundingBox();
1148
+ }
1086
1149
  result.foundElements.push({
1087
1150
  locator: foundLocators[0],
1088
- box: await foundLocators[0].boundingBox(),
1151
+ box: box,
1089
1152
  unique: true,
1090
1153
  });
1091
1154
  result.locatorIndex = i;
@@ -1143,7 +1206,7 @@ class StableBrowser {
1143
1206
  operation: "simpleClick",
1144
1207
  log: "***** click on " + elementDescription + " *****\n",
1145
1208
  };
1146
- _preCommand(state, this);
1209
+ await _preCommand(state, this);
1147
1210
  const startTime = Date.now();
1148
1211
  let timeout = 30000;
1149
1212
  if (options && options.timeout) {
@@ -1192,7 +1255,7 @@ class StableBrowser {
1192
1255
  operation: "simpleClickType",
1193
1256
  log: "***** click type on " + elementDescription + " *****\n",
1194
1257
  };
1195
- _preCommand(state, this);
1258
+ await _preCommand(state, this);
1196
1259
  const startTime = Date.now();
1197
1260
  let timeout = 30000;
1198
1261
  if (options && options.timeout) {
@@ -1240,11 +1303,22 @@ class StableBrowser {
1240
1303
  operation: "click",
1241
1304
  log: "***** click on " + selectors.element_name + " *****\n",
1242
1305
  };
1306
+ check_performance("click_all ***", this.context, true);
1307
+ let stepFastMode = this.stepTags.includes("fast-mode");
1308
+ if (stepFastMode) {
1309
+ state.onlyFailuresScreenshot = true;
1310
+ state.scroll = false;
1311
+ state.highlight = false;
1312
+ }
1243
1313
  try {
1314
+ check_performance("click_preCommand", this.context, true);
1244
1315
  await _preCommand(state, this);
1316
+ check_performance("click_preCommand", this.context, false);
1245
1317
  await performAction("click", state.element, options, this, state, _params);
1246
- if (!this.fastMode) {
1247
- await this.waitForPageLoad();
1318
+ if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
1319
+ check_performance("click_waitForPageLoad", this.context, true);
1320
+ await this.waitForPageLoad({ noSleep: true });
1321
+ check_performance("click_waitForPageLoad", this.context, false);
1248
1322
  }
1249
1323
  return state.info;
1250
1324
  }
@@ -1252,7 +1326,13 @@ class StableBrowser {
1252
1326
  await _commandError(state, e, this);
1253
1327
  }
1254
1328
  finally {
1329
+ check_performance("click_commandFinally", this.context, true);
1255
1330
  await _commandFinally(state, this);
1331
+ check_performance("click_commandFinally", this.context, false);
1332
+ check_performance("click_all ***", this.context, false);
1333
+ if (this.context.profile) {
1334
+ console.log(JSON.stringify(this.context.profile, null, 2));
1335
+ }
1256
1336
  }
1257
1337
  }
1258
1338
  async waitForElement(selectors, _params, options = {}, world = null) {
@@ -1344,7 +1424,7 @@ class StableBrowser {
1344
1424
  }
1345
1425
  }
1346
1426
  }
1347
- await this.waitForPageLoad();
1427
+ //await this.waitForPageLoad();
1348
1428
  return state.info;
1349
1429
  }
1350
1430
  catch (e) {
@@ -1370,7 +1450,7 @@ class StableBrowser {
1370
1450
  await _preCommand(state, this);
1371
1451
  await performAction("hover", state.element, options, this, state, _params);
1372
1452
  await _screenshot(state, this);
1373
- await this.waitForPageLoad();
1453
+ //await this.waitForPageLoad();
1374
1454
  return state.info;
1375
1455
  }
1376
1456
  catch (e) {
@@ -1381,9 +1461,10 @@ class StableBrowser {
1381
1461
  }
1382
1462
  }
1383
1463
  async selectOption(selectors, values, _params = null, options = {}, world = null) {
1384
- if (!values) {
1385
- throw new Error("values is null");
1386
- }
1464
+ // commented out this condition as some select elements have NULL values by default.
1465
+ // if (!values) {
1466
+ // throw new Error("values is null");
1467
+ // }
1387
1468
  const state = {
1388
1469
  selectors,
1389
1470
  _params,
@@ -1406,7 +1487,7 @@ class StableBrowser {
1406
1487
  state.info.log += "selectOption failed, will try force" + "\n";
1407
1488
  await state.element.selectOption(values, { timeout: 10000, force: true });
1408
1489
  }
1409
- await this.waitForPageLoad();
1490
+ //await this.waitForPageLoad();
1410
1491
  return state.info;
1411
1492
  }
1412
1493
  catch (e) {
@@ -1510,7 +1591,7 @@ class StableBrowser {
1510
1591
  _text: `Set date time value: ${value} on ${selectors.element_name}`,
1511
1592
  operation: "setDateTime",
1512
1593
  log: "***** set date time value " + selectors.element_name + " *****\n",
1513
- throwError: false,
1594
+ // throwError: false,
1514
1595
  };
1515
1596
  try {
1516
1597
  await _preCommand(state, this);
@@ -1592,6 +1673,14 @@ class StableBrowser {
1592
1673
  }
1593
1674
  try {
1594
1675
  await _preCommand(state, this);
1676
+ const randomToken = "blinq_" + Math.random().toString(36).substring(7);
1677
+ // tag the element
1678
+ let newElementSelector = await state.element.evaluate((el, token) => {
1679
+ // use attribute and not id
1680
+ const attrName = `data-blinq-id-${token}`;
1681
+ el.setAttribute(attrName, "");
1682
+ return `[${attrName}]`;
1683
+ }, randomToken);
1595
1684
  state.info.value = _value;
1596
1685
  if (!options.press) {
1597
1686
  try {
@@ -1617,6 +1706,25 @@ class StableBrowser {
1617
1706
  }
1618
1707
  }
1619
1708
  await new Promise((resolve) => setTimeout(resolve, 500));
1709
+ // check if the element exist after the click (no wait)
1710
+ const count = await state.element.count({ timeout: 0 });
1711
+ if (count === 0) {
1712
+ // the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
1713
+ const scope = state.element._frame ?? element.page();
1714
+ let prefixSelector = "";
1715
+ const frameControlSelector = " >> internal:control=enter-frame";
1716
+ const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
1717
+ if (frameSelectorIndex !== -1) {
1718
+ // remove everything after the >> internal:control=enter-frame
1719
+ const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
1720
+ prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
1721
+ }
1722
+ // if (element?._frame?._selector) {
1723
+ // prefixSelector = element._frame._selector + " >> " + prefixSelector;
1724
+ // }
1725
+ const newSelector = prefixSelector + newElementSelector;
1726
+ state.element = scope.locator(newSelector).first();
1727
+ }
1620
1728
  const valueSegment = state.value.split("&&");
1621
1729
  for (let i = 0; i < valueSegment.length; i++) {
1622
1730
  if (i > 0) {
@@ -1688,8 +1796,8 @@ class StableBrowser {
1688
1796
  if (enter) {
1689
1797
  await new Promise((resolve) => setTimeout(resolve, 2000));
1690
1798
  await this.page.keyboard.press("Enter");
1799
+ await this.waitForPageLoad();
1691
1800
  }
1692
- await this.waitForPageLoad();
1693
1801
  return state.info;
1694
1802
  }
1695
1803
  catch (e) {
@@ -1948,11 +2056,12 @@ class StableBrowser {
1948
2056
  throw new Error("referanceSnapshot is null");
1949
2057
  }
1950
2058
  let text = null;
1951
- if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
1952
- text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
2059
+ const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
2060
+ if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
2061
+ text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
1953
2062
  }
1954
- else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
1955
- text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
2063
+ else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
2064
+ text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
1956
2065
  }
1957
2066
  else if (referanceSnapshot.startsWith("yaml:")) {
1958
2067
  text = referanceSnapshot.substring(5);
@@ -1976,7 +2085,13 @@ class StableBrowser {
1976
2085
  scope = await this._findFrameScope(frameSelectors, timeout, state.info);
1977
2086
  }
1978
2087
  const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
2088
+ if (snapshot && snapshot.length <= 10) {
2089
+ console.log("Page snapshot length is suspiciously small:", snapshot);
2090
+ }
1979
2091
  matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
2092
+ if (matchResult === undefined) {
2093
+ console.log("snapshotValidation returned undefined");
2094
+ }
1980
2095
  if (matchResult.errorLine !== -1) {
1981
2096
  throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
1982
2097
  }
@@ -2147,6 +2262,9 @@ class StableBrowser {
2147
2262
  return _getTestData(world, this.context, this);
2148
2263
  }
2149
2264
  async _screenShot(options = {}, world = null, info = null) {
2265
+ if (!options) {
2266
+ options = {};
2267
+ }
2150
2268
  // collect url/path/title
2151
2269
  if (info) {
2152
2270
  if (!info.title) {
@@ -2175,7 +2293,7 @@ class StableBrowser {
2175
2293
  const uuidStr = "id_" + randomUUID();
2176
2294
  const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
2177
2295
  try {
2178
- await this.takeScreenshot(screenshotPath);
2296
+ await this.takeScreenshot(screenshotPath, options.fullPage === true);
2179
2297
  // let buffer = await this.page.screenshot({ timeout: 4000 });
2180
2298
  // // save the buffer to the screenshot path asynchrously
2181
2299
  // fs.writeFile(screenshotPath, buffer, (err) => {
@@ -2196,7 +2314,7 @@ class StableBrowser {
2196
2314
  else if (options && options.screenshot) {
2197
2315
  result.screenshotPath = options.screenshotPath;
2198
2316
  try {
2199
- await this.takeScreenshot(options.screenshotPath);
2317
+ await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
2200
2318
  // let buffer = await this.page.screenshot({ timeout: 4000 });
2201
2319
  // // save the buffer to the screenshot path asynchrously
2202
2320
  // fs.writeFile(options.screenshotPath, buffer, (err) => {
@@ -2214,7 +2332,7 @@ class StableBrowser {
2214
2332
  }
2215
2333
  return result;
2216
2334
  }
2217
- async takeScreenshot(screenshotPath) {
2335
+ async takeScreenshot(screenshotPath, fullPage = false) {
2218
2336
  const playContext = this.context.playContext;
2219
2337
  // Using CDP to capture the screenshot
2220
2338
  const viewportWidth = Math.max(...(await this.page.evaluate(() => [
@@ -2239,13 +2357,7 @@ class StableBrowser {
2239
2357
  const client = await playContext.newCDPSession(this.page);
2240
2358
  const { data } = await client.send("Page.captureScreenshot", {
2241
2359
  format: "png",
2242
- // clip: {
2243
- // x: 0,
2244
- // y: 0,
2245
- // width: viewportWidth,
2246
- // height: viewportHeight,
2247
- // scale: 1,
2248
- // },
2360
+ captureBeyondViewport: fullPage,
2249
2361
  });
2250
2362
  await client.detach();
2251
2363
  if (!screenshotPath) {
@@ -2254,7 +2366,7 @@ class StableBrowser {
2254
2366
  screenshotBuffer = Buffer.from(data, "base64");
2255
2367
  }
2256
2368
  else {
2257
- screenshotBuffer = await this.page.screenshot();
2369
+ screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
2258
2370
  }
2259
2371
  // if (focusedElement) {
2260
2372
  // // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
@@ -2354,6 +2466,12 @@ class StableBrowser {
2354
2466
  state.info.value = state.value;
2355
2467
  this.setTestData({ [variable]: state.value }, world);
2356
2468
  this.logger.info("set test data: " + variable + "=" + state.value);
2469
+ if (process.env.MODE === "executions") {
2470
+ const globalDataFile = "global_test_data.json";
2471
+ if (existsSync(globalDataFile)) {
2472
+ this.saveTestDataAsGlobal({}, world);
2473
+ }
2474
+ }
2357
2475
  // await new Promise((resolve) => setTimeout(resolve, 500));
2358
2476
  return state.info;
2359
2477
  }
@@ -2425,6 +2543,12 @@ class StableBrowser {
2425
2543
  state.info.value = state.value;
2426
2544
  this.setTestData({ [variable]: state.value }, world);
2427
2545
  this.logger.info("set test data: " + variable + "=" + state.value);
2546
+ if (process.env.MODE === "executions") {
2547
+ const globalDataFile = "global_test_data.json";
2548
+ if (existsSync(globalDataFile)) {
2549
+ this.saveTestDataAsGlobal({}, world);
2550
+ }
2551
+ }
2428
2552
  // await new Promise((resolve) => setTimeout(resolve, 500));
2429
2553
  return state.info;
2430
2554
  }
@@ -2555,7 +2679,7 @@ class StableBrowser {
2555
2679
  let expectedValue;
2556
2680
  try {
2557
2681
  await _preCommand(state, this);
2558
- expectedValue = await replaceWithLocalTestData(state.value, world);
2682
+ expectedValue = await this._replaceWithLocalData(value, world);
2559
2683
  state.info.expectedValue = expectedValue;
2560
2684
  switch (property) {
2561
2685
  case "innerText":
@@ -2681,6 +2805,7 @@ class StableBrowser {
2681
2805
  allowDisabled: true,
2682
2806
  info: {},
2683
2807
  };
2808
+ state.options ??= { timeout: timeoutMs };
2684
2809
  // Initialize startTime outside try block to ensure it's always accessible
2685
2810
  const startTime = Date.now();
2686
2811
  let conditionMet = false;
@@ -2850,6 +2975,12 @@ class StableBrowser {
2850
2975
  emailUrl = url;
2851
2976
  codeOrUrlFound = true;
2852
2977
  }
2978
+ if (process.env.MODE === "executions") {
2979
+ const globalDataFile = "global_test_data.json";
2980
+ if (existsSync(globalDataFile)) {
2981
+ this.saveTestDataAsGlobal({}, world);
2982
+ }
2983
+ }
2853
2984
  if (codeOrUrlFound) {
2854
2985
  return { emailUrl, emailCode };
2855
2986
  }
@@ -3256,7 +3387,16 @@ class StableBrowser {
3256
3387
  text = text.replace(/\\"/g, '"');
3257
3388
  }
3258
3389
  const timeout = this._getFindElementTimeout(options);
3259
- await new Promise((resolve) => setTimeout(resolve, 2000));
3390
+ //if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
3391
+ let stepFastMode = this.stepTags.includes("fast-mode");
3392
+ if (!stepFastMode) {
3393
+ if (!this.fastMode) {
3394
+ await new Promise((resolve) => setTimeout(resolve, 2000));
3395
+ }
3396
+ else {
3397
+ await new Promise((resolve) => setTimeout(resolve, 500));
3398
+ }
3399
+ }
3260
3400
  const newValue = await this._replaceWithLocalData(text, world);
3261
3401
  if (newValue !== text) {
3262
3402
  this.logger.info(text + "=" + newValue);
@@ -3264,6 +3404,11 @@ class StableBrowser {
3264
3404
  }
3265
3405
  let dateAlternatives = findDateAlternatives(text);
3266
3406
  let numberAlternatives = findNumberAlternatives(text);
3407
+ if (stepFastMode) {
3408
+ state.onlyFailuresScreenshot = true;
3409
+ state.scroll = false;
3410
+ state.highlight = false;
3411
+ }
3267
3412
  try {
3268
3413
  await _preCommand(state, this);
3269
3414
  state.info.text = text;
@@ -3383,6 +3528,8 @@ class StableBrowser {
3383
3528
  operation: "verify_text_with_relation",
3384
3529
  log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
3385
3530
  };
3531
+ const cmdStartTime = Date.now();
3532
+ let cmdEndTime = null;
3386
3533
  const timeout = this._getFindElementTimeout(options);
3387
3534
  await new Promise((resolve) => setTimeout(resolve, 2000));
3388
3535
  let newValue = await this._replaceWithLocalData(textAnchor, world);
@@ -3418,6 +3565,17 @@ class StableBrowser {
3418
3565
  await new Promise((resolve) => setTimeout(resolve, 1000));
3419
3566
  continue;
3420
3567
  }
3568
+ else {
3569
+ cmdEndTime = Date.now();
3570
+ if (cmdEndTime - cmdStartTime > 55000) {
3571
+ if (foundAncore) {
3572
+ throw new Error(`Text ${textToVerify} not found in page`);
3573
+ }
3574
+ else {
3575
+ throw new Error(`Text ${textAnchor} not found in page`);
3576
+ }
3577
+ }
3578
+ }
3421
3579
  try {
3422
3580
  for (let i = 0; i < resultWithElementsFound.length; i++) {
3423
3581
  foundAncore = true;
@@ -3430,7 +3588,9 @@ class StableBrowser {
3430
3588
  climbArray1.push("..");
3431
3589
  }
3432
3590
  let climbXpath = "xpath=" + climbArray1.join("/");
3433
- css = css + " >> " + climbXpath;
3591
+ if (Number(climb) > 0) {
3592
+ css = css + " >> " + climbXpath;
3593
+ }
3434
3594
  const count = await frame.locator(css).count();
3435
3595
  for (let j = 0; j < count; j++) {
3436
3596
  const continer = await frame.locator(css).nth(j);
@@ -3556,7 +3716,7 @@ class StableBrowser {
3556
3716
  Object.assign(e, { info: info });
3557
3717
  error = e;
3558
3718
  // throw e;
3559
- await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
3719
+ await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
3560
3720
  }
3561
3721
  finally {
3562
3722
  const endTime = Date.now();
@@ -3901,10 +4061,26 @@ class StableBrowser {
3901
4061
  registerNetworkEvents(this.world, this, this.context, this.page);
3902
4062
  registerDownloadEvent(this.page, this.world, this.context);
3903
4063
  if (this.onRestoreSaveState) {
3904
- this.onRestoreSaveState(path);
4064
+ await this.onRestoreSaveState(path);
3905
4065
  }
3906
4066
  }
3907
4067
  async waitForPageLoad(options = {}, world = null) {
4068
+ // try {
4069
+ // let currentPagePath = null;
4070
+ // currentPagePath = new URL(this.page.url()).pathname;
4071
+ // if (this.latestPagePath) {
4072
+ // // get the currect page path and compare with the latest page path
4073
+ // if (this.latestPagePath === currentPagePath) {
4074
+ // // if the page path is the same, do not wait for page load
4075
+ // console.log("No page change: " + currentPagePath);
4076
+ // return;
4077
+ // }
4078
+ // }
4079
+ // this.latestPagePath = currentPagePath;
4080
+ // } catch (e) {
4081
+ // console.debug("Error getting current page path: ", e);
4082
+ // }
4083
+ //console.log("Waiting for page load");
3908
4084
  let timeout = this._getLoadTimeout(options);
3909
4085
  const promiseArray = [];
3910
4086
  // let waitForNetworkIdle = true;
@@ -3939,7 +4115,10 @@ class StableBrowser {
3939
4115
  }
3940
4116
  }
3941
4117
  finally {
3942
- await new Promise((resolve) => setTimeout(resolve, 2000));
4118
+ await new Promise((resolve) => setTimeout(resolve, 500));
4119
+ if (options && !options.noSleep) {
4120
+ await new Promise((resolve) => setTimeout(resolve, 1500));
4121
+ }
3943
4122
  ({ screenshotId, screenshotPath } = await this._screenShot(options, world));
3944
4123
  const endTime = Date.now();
3945
4124
  _reportToWorld(world, {
@@ -3973,7 +4152,7 @@ class StableBrowser {
3973
4152
  _text: `Close the page`,
3974
4153
  operation: "closePage",
3975
4154
  log: "***** close page *****\n",
3976
- throwError: false,
4155
+ // throwError: false,
3977
4156
  };
3978
4157
  try {
3979
4158
  await _preCommand(state, this);
@@ -3993,7 +4172,7 @@ class StableBrowser {
3993
4172
  }
3994
4173
  operation = options.operation;
3995
4174
  // validate operation is one of the supported operations
3996
- if (operation != "click" && operation != "hover+click") {
4175
+ if (operation != "click" && operation != "hover+click" && operation != "hover") {
3997
4176
  throw new Error("operation is not supported");
3998
4177
  }
3999
4178
  const state = {
@@ -4062,6 +4241,17 @@ class StableBrowser {
4062
4241
  state.element = results[0];
4063
4242
  await performAction("hover+click", state.element, options, this, state, _params);
4064
4243
  break;
4244
+ case "hover":
4245
+ if (!options.css) {
4246
+ throw new Error("css is not defined");
4247
+ }
4248
+ const result1 = await findElementsInArea(options.css, cellArea, this, options);
4249
+ if (result1.length === 0) {
4250
+ throw new Error(`Element not found in cell area`);
4251
+ }
4252
+ state.element = result1[0];
4253
+ await performAction("hover", state.element, options, this, state, _params);
4254
+ break;
4065
4255
  default:
4066
4256
  throw new Error("operation is not supported");
4067
4257
  }
@@ -4075,6 +4265,16 @@ class StableBrowser {
4075
4265
  }
4076
4266
  saveTestDataAsGlobal(options, world) {
4077
4267
  const dataFile = _getDataFile(world, this.context, this);
4268
+ if (process.env.MODE === "executions") {
4269
+ const globalDataFile = path.join(this.project_path, "global_test_data.json");
4270
+ const dataFileContents = fs.existsSync(dataFile) ? JSON.parse(fs.readFileSync(dataFile)) : {};
4271
+ const globalDataFileContents = fs.existsSync(globalDataFile) ? JSON.parse(fs.readFileSync(globalDataFile)) : {};
4272
+ const mergedData = JSON.stringify(_.merge({}, dataFileContents, globalDataFileContents), null, 2);
4273
+ fs.writeFileSync(dataFile, mergedData);
4274
+ fs.writeFileSync(globalDataFile, mergedData);
4275
+ this.logger.info("Save the scenario test data to " + dataFile + " as global for the following scenarios.");
4276
+ return;
4277
+ }
4078
4278
  process.env.GLOBAL_TEST_DATA_FILE = dataFile;
4079
4279
  this.logger.info("Save the scenario test data as global for the following scenarios.");
4080
4280
  }
@@ -4177,6 +4377,7 @@ class StableBrowser {
4177
4377
  if (world && world.attach) {
4178
4378
  world.attach(this.context.reportFolder, { mediaType: "text/plain" });
4179
4379
  }
4380
+ this.context.loadedRoutes = null;
4180
4381
  this.beforeScenarioCalled = true;
4181
4382
  if (scenario && scenario.pickle && scenario.pickle.name) {
4182
4383
  this.scenarioName = scenario.pickle.name;
@@ -4190,9 +4391,12 @@ class StableBrowser {
4190
4391
  if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
4191
4392
  this.tags = scenario.pickle.tags.map((tag) => tag.name);
4192
4393
  // check if @global_test_data tag is present
4193
- if (this.tags.includes("@global_test_data")) {
4394
+ if (this.tags.includes("@global_test_data" /* TAG_CONSTANTS.GLOBAL_TEST_DATA */)) {
4194
4395
  this.saveTestDataAsGlobal({}, world);
4195
4396
  }
4397
+ if (this.tags.includes("@fast_mode" /* TAG_CONSTANTS.FAST_MODE */)) {
4398
+ this.fastMode = true;
4399
+ }
4196
4400
  }
4197
4401
  // update test data based on feature/scenario
4198
4402
  let envName = null;
@@ -4203,14 +4407,51 @@ class StableBrowser {
4203
4407
  await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
4204
4408
  }
4205
4409
  await loadBrunoParams(this.context, this.context.environment.name);
4410
+ if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
4411
+ this.trace = true;
4412
+ const traceFolder = path.join(this.context.reportFolder, "trace");
4413
+ if (!fs.existsSync(traceFolder)) {
4414
+ fs.mkdirSync(traceFolder, { recursive: true });
4415
+ }
4416
+ this.traceFolder = traceFolder;
4417
+ await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
4418
+ }
4419
+ }
4420
+ async afterScenario(world, scenario) {
4421
+ const id = scenario.testCaseStartedId;
4422
+ if (this.trace) {
4423
+ await this.context.playContext.tracing.stop({
4424
+ path: path.join(this.traceFolder, `trace-${id}.zip`),
4425
+ });
4426
+ }
4427
+ }
4428
+ getGherkinKeyword(step) {
4429
+ if (!step?.type) {
4430
+ return "";
4431
+ }
4432
+ switch (step.type) {
4433
+ case "Context":
4434
+ return "Given";
4435
+ case "Action":
4436
+ return "When";
4437
+ case "Outcome":
4438
+ return "Then";
4439
+ case "Conjunction":
4440
+ return "And";
4441
+ default:
4442
+ return "";
4443
+ }
4206
4444
  }
4207
- async afterScenario(world, scenario) { }
4208
4445
  async beforeStep(world, step) {
4209
- if (this.abortedExecution) {
4210
- throw new Error("Aborted");
4446
+ if (step?.pickleStep && this.trace) {
4447
+ const keyword = this.getGherkinKeyword(step.pickleStep);
4448
+ this.traceGroupName = `${keyword} ${step.pickleStep.text}`;
4449
+ await this.context.playContext.tracing.group(this.traceGroupName);
4211
4450
  }
4451
+ this.stepTags = [];
4212
4452
  if (!this.beforeScenarioCalled) {
4213
4453
  this.beforeScenario(world, step);
4454
+ this.context.loadedRoutes = null;
4214
4455
  }
4215
4456
  if (this.stepIndex === undefined) {
4216
4457
  this.stepIndex = 0;
@@ -4220,7 +4461,12 @@ class StableBrowser {
4220
4461
  }
4221
4462
  if (step && step.pickleStep && step.pickleStep.text) {
4222
4463
  this.stepName = step.pickleStep.text;
4223
- this.logger.info("step: " + this.stepName);
4464
+ let printableStepName = this.stepName;
4465
+ // take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
4466
+ printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
4467
+ return `\x1b[33m"${p1}"\x1b[0m`;
4468
+ });
4469
+ this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
4224
4470
  }
4225
4471
  else if (step && step.text) {
4226
4472
  this.stepName = step.text;
@@ -4235,7 +4481,10 @@ class StableBrowser {
4235
4481
  }
4236
4482
  if (this.initSnapshotTaken === false) {
4237
4483
  this.initSnapshotTaken = true;
4238
- if (world && world.attach && !process.env.DISABLE_SNAPSHOT && !this.fastMode) {
4484
+ if (world &&
4485
+ world.attach &&
4486
+ !process.env.DISABLE_SNAPSHOT &&
4487
+ (!this.fastMode || this.stepTags.includes("fast-mode"))) {
4239
4488
  const snapshot = await this.getAriaSnapshot();
4240
4489
  if (snapshot) {
4241
4490
  await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
@@ -4245,7 +4494,11 @@ class StableBrowser {
4245
4494
  this.context.routeResults = null;
4246
4495
  this.context.loadedRoutes = null;
4247
4496
  await registerBeforeStepRoutes(this.context, this.stepName, world);
4248
- networkBeforeStep(this.stepName);
4497
+ networkBeforeStep(this.stepName, this.context);
4498
+ this.inStepReport = false;
4499
+ }
4500
+ setStepTags(tags) {
4501
+ this.stepTags = tags;
4249
4502
  }
4250
4503
  async getAriaSnapshot() {
4251
4504
  try {
@@ -4319,7 +4572,7 @@ class StableBrowser {
4319
4572
  state.payload = payload;
4320
4573
  if (commandStatus === "FAILED") {
4321
4574
  state.throwError = true;
4322
- throw new Error("Command failed");
4575
+ throw new Error(commandText);
4323
4576
  }
4324
4577
  }
4325
4578
  catch (e) {
@@ -4329,26 +4582,22 @@ class StableBrowser {
4329
4582
  await _commandFinally(state, this);
4330
4583
  }
4331
4584
  }
4332
- async afterStep(world, step) {
4585
+ async afterStep(world, step, result) {
4333
4586
  this.stepName = null;
4334
- if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
4335
- if (this.context.browserObject.context) {
4336
- await this.context.browserObject.context.tracing.stopChunk({
4337
- path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
4338
- });
4339
- if (world && world.attach) {
4340
- await world.attach(JSON.stringify({
4341
- type: "trace",
4342
- traceFilePath: `trace-${this.stepIndex}.zip`,
4343
- }), "application/json+trace");
4344
- }
4345
- // console.log("trace file created", `trace-${this.stepIndex}.zip`);
4346
- }
4347
- }
4348
4587
  if (this.context) {
4349
4588
  this.context.examplesRow = null;
4350
4589
  }
4351
- if (world && world.attach && !process.env.DISABLE_SNAPSHOT && !this.fastMode) {
4590
+ if (!this.inStepReport) {
4591
+ // check the step result
4592
+ if (result && result.status === "FAILED" && world && world.attach) {
4593
+ await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
4594
+ }
4595
+ }
4596
+ if (world &&
4597
+ world.attach &&
4598
+ !process.env.DISABLE_SNAPSHOT &&
4599
+ !this.fastMode &&
4600
+ !this.stepTags.includes("fast-mode")) {
4352
4601
  const snapshot = await this.getAriaSnapshot();
4353
4602
  if (snapshot) {
4354
4603
  const obj = {};
@@ -4356,6 +4605,11 @@ class StableBrowser {
4356
4605
  }
4357
4606
  }
4358
4607
  this.context.routeResults = await registerAfterStepRoutes(this.context, world);
4608
+ if (this.context.routeResults) {
4609
+ if (world && world.attach) {
4610
+ await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
4611
+ }
4612
+ }
4359
4613
  if (!process.env.TEMP_RUN) {
4360
4614
  const state = {
4361
4615
  world,
@@ -4379,10 +4633,15 @@ class StableBrowser {
4379
4633
  await _commandFinally(state, this);
4380
4634
  }
4381
4635
  }
4382
- networkAfterStep(this.stepName);
4636
+ networkAfterStep(this.stepName, this.context);
4383
4637
  if (process.env.TEMP_RUN === "true") {
4384
4638
  // Put a sleep for some time to allow the browser to finish processing
4385
- await new Promise((resolve) => setTimeout(resolve, 3000));
4639
+ if (!this.stepTags.includes("fast-mode")) {
4640
+ await new Promise((resolve) => setTimeout(resolve, 3000));
4641
+ }
4642
+ }
4643
+ if (this.trace) {
4644
+ await this.context.playContext.tracing.groupEnd();
4386
4645
  }
4387
4646
  }
4388
4647
  }