@skyramp/mcp 0.4.4 → 0.5.0-rc.10

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 (144) hide show
  1. package/build/entitlements/entitlementGate.d.ts +8 -0
  2. package/build/entitlements/entitlementGate.js +57 -0
  3. package/build/entitlements/runCodes.d.ts +18 -0
  4. package/build/entitlements/runCodes.js +16 -0
  5. package/build/index.js +32 -6
  6. package/build/playwright/PlaywrightTraceService.d.ts +17 -0
  7. package/build/playwright/PlaywrightTraceService.js +29 -1
  8. package/build/playwright/dataSafetyConfig.d.ts +74 -0
  9. package/build/playwright/dataSafetyConfig.js +109 -0
  10. package/build/playwright/essentialTools.d.ts +1 -0
  11. package/build/playwright/essentialTools.js +44 -0
  12. package/build/playwright/registerPlaywrightTools.d.ts +36 -1
  13. package/build/playwright/registerPlaywrightTools.js +87 -43
  14. package/build/playwright/safeResolveDataSafety.d.ts +24 -0
  15. package/build/playwright/safeResolveDataSafety.js +33 -0
  16. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
  17. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
  18. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  19. package/build/prompts/modularization/ui-test-modularization.js +9 -2
  20. package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
  21. package/build/services/FleetExecutionService.d.ts +87 -16
  22. package/build/services/FleetExecutionService.js +487 -48
  23. package/build/services/FleetRunRegistry.d.ts +49 -0
  24. package/build/services/FleetRunRegistry.js +178 -0
  25. package/build/services/containerEnv.js +8 -2
  26. package/build/services/fleetLimitConfirmation.d.ts +19 -0
  27. package/build/services/fleetLimitConfirmation.js +126 -0
  28. package/build/tool-phases.d.ts +14 -8
  29. package/build/tool-phases.js +88 -43
  30. package/build/tools/code-refactor/assertion-state.js +3 -1
  31. package/build/tools/code-refactor/codeReuseTool.js +2 -1
  32. package/build/tools/code-refactor/utils-verify-gates.js +1 -1
  33. package/build/tools/executeFleetTool.d.ts +6 -4
  34. package/build/tools/executeFleetTool.js +67 -23
  35. package/build/tools/fixErrorTool.js +2 -1
  36. package/build/tools/fleetLifecycleTools.d.ts +20 -0
  37. package/build/tools/fleetLifecycleTools.js +124 -0
  38. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
  39. package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
  40. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
  41. package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
  42. package/build/tools/submitReportTool.js +4 -122
  43. package/build/tools/test-management/registerTestPlanTool.js +22 -6
  44. package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
  45. package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
  46. package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
  47. package/build/types/AssertionOutcome.d.ts +3 -0
  48. package/build/types/TestbotReport.d.ts +0 -37
  49. package/build/types/TestbotReport.js +0 -22
  50. package/build/types/index.d.ts +1 -1
  51. package/build/types/index.js +1 -1
  52. package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
  53. package/build/utils/assertion-verify/created-add-guard.js +173 -0
  54. package/build/utils/assertion-verify/index.d.ts +1 -0
  55. package/build/utils/assertion-verify/index.js +1 -0
  56. package/build/utils/assertion-verify/verify.d.ts +7 -3
  57. package/build/utils/assertion-verify/verify.js +9 -3
  58. package/build/utils/featureFlags.js +6 -3
  59. package/build/utils/repositorySlug.d.ts +14 -0
  60. package/build/utils/repositorySlug.js +46 -1
  61. package/build/utils/telemetry.d.ts +2 -0
  62. package/build/utils/telemetry.js +12 -0
  63. package/build/utils/utils-verify/action-key.d.ts +59 -2
  64. package/build/utils/utils-verify/action-key.js +162 -6
  65. package/build/utils/utils-verify/action-sites.d.ts +7 -0
  66. package/build/utils/utils-verify/action-sites.js +277 -35
  67. package/build/utils/utils-verify/call-sites.d.ts +8 -2
  68. package/build/utils/utils-verify/language-spec.js +1 -1
  69. package/build/utils/utils-verify/parse.d.ts +4 -0
  70. package/build/utils/utils-verify/parse.js +2 -1
  71. package/build/utils/versions.d.ts +3 -3
  72. package/build/utils/versions.js +1 -1
  73. package/build/workspace/workspace.d.ts +63 -121
  74. package/build/workspace/workspace.js +45 -48
  75. package/build/workspace/workspaceVerifier.d.ts +22 -0
  76. package/build/workspace/workspaceVerifier.js +65 -0
  77. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
  78. package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
  79. package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
  80. package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
  81. package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
  82. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
  83. package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
  84. package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
  85. package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
  86. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
  87. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
  88. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
  89. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
  90. package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
  91. package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
  92. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  93. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  94. package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
  95. package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
  96. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  97. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
  98. package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
  99. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
  100. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
  101. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
  102. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
  103. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
  104. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
  105. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
  106. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  108. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
  109. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
  112. package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
  113. package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
  114. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  115. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  116. package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
  117. package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
  118. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
  119. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
  120. package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
  121. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
  122. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
  123. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
  124. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
  125. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
  126. package/node_modules/playwright/package.json +1 -1
  127. package/package.json +4 -4
  128. package/plugin/prompts/plan-tests.md +2 -2
  129. package/plugin/prompts/testbot-task1.md +1 -1
  130. package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
  131. package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
  132. package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
  133. package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
  134. package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
  135. package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
  136. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
  137. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
  138. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
  139. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
  140. package/build/prompts/sut-setup/shared.d.ts +0 -62
  141. package/build/prompts/sut-setup/shared.js +0 -196
  142. package/build/resources/sutSetupResource.d.ts +0 -2
  143. package/build/resources/sutSetupResource.js +0 -63
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
@@ -36,6 +36,7 @@ __export(skyRampExport_exports, {
36
36
  module.exports = __toCommonJS(skyRampExport_exports);
37
37
  var import_crypto = __toESM(require("crypto"));
38
38
  var import_fs = __toESM(require("fs"));
39
+ var import_os = __toESM(require("os"));
39
40
  var import_path = __toESM(require("path"));
40
41
  var import_zipBundle = require("playwright-core/lib/zipBundle");
41
42
  var import_utils = require("playwright-core/lib/utils");
@@ -43,6 +44,12 @@ var import_visualSnapshot = require("../skyramp/common/visualSnapshot");
43
44
  var import_snapshotLocators = require("../skyramp/snapshotLocators");
44
45
  const DEFAULT_PAGE_ALIAS = "page";
45
46
  const DEFAULT_FRAME_PATH = [];
47
+ function actionProvenance(action) {
48
+ return { actionId: action.actionId, phase: action.phase };
49
+ }
50
+ function derivedActionProvenance(action, suffix) {
51
+ return { actionId: `${action.actionId}:${suffix}`, phase: action.phase };
52
+ }
46
53
  function modifiersArrayToMask(modifiers) {
47
54
  if (!modifiers || modifiers.length === 0)
48
55
  return 0;
@@ -72,8 +79,9 @@ function jsonlHeader(browserName, harPath, viewport, storageState) {
72
79
  });
73
80
  }
74
81
  function extractLocatorFromCode(code) {
82
+ code = code.split("\n").filter((line) => !/^\s*await\s+page\.(keyboard|mouse)\./.test(line)).join("\n");
75
83
  const extensionFrameMatch = code.match(
76
- /await\s+page\.frames\(\)\.find\(\s*f\s*=>\s*f\.url\(\)\.includes\(\s*(['"])([^'"]+)\1\s*\)\s*\)!?\.(.*?)\.(click|dblclick|fill|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s
84
+ /await\s+page\.frames\(\)\.find\(\s*f\s*=>\s*f\.url\(\)\.includes\(\s*(['"])([^'"]+)\1\s*\)\s*\)!?\.(.*?)\.(click|dblclick|fill|press|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s
77
85
  );
78
86
  if (extensionFrameMatch) {
79
87
  return {
@@ -83,7 +91,7 @@ function extractLocatorFromCode(code) {
83
91
  };
84
92
  }
85
93
  if (code.includes(".contentFrame()")) {
86
- const chainMatch = code.match(/await\s+page\.(.+)\.(click|dblclick|fill|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
94
+ const chainMatch = code.match(/await\s+page\.(.+)\.(click|dblclick|fill|press|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
87
95
  if (chainMatch) {
88
96
  const segments = chainMatch[1].split(".contentFrame()");
89
97
  const framePath = [];
@@ -102,15 +110,15 @@ function extractLocatorFromCode(code) {
102
110
  }
103
111
  }
104
112
  }
105
- const contentFrameMatch = code.match(/await\s+page\.locator\((['"])(iframe[^\n]*?)\1\)(?:\.contentFrame\(\))+\.(.*?)\.(click|dblclick|fill|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
113
+ const contentFrameMatch = code.match(/await\s+page\.locator\((['"])(iframe[^\n]*?)\1\)(?:\.contentFrame\(\))+\.(.*?)\.(click|dblclick|fill|press|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
106
114
  if (contentFrameMatch) {
107
115
  return { locatorExpr: contentFrameMatch[3].trim(), framePath: [contentFrameMatch[2]] };
108
116
  }
109
- const chainedIframeMatch = code.match(/await\s+page\.locator\((['"])(iframe[^\n]*?)\1\)\.(get\w+\(.*?)\.(click|dblclick|fill|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
117
+ const chainedIframeMatch = code.match(/await\s+page\.locator\((['"])(iframe[^\n]*?)\1\)\.(get\w+\(.*?)\.(click|dblclick|fill|press|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
110
118
  if (chainedIframeMatch) {
111
119
  return { locatorExpr: chainedIframeMatch[3].trim(), framePath: [chainedIframeMatch[2]] };
112
120
  }
113
- const match = code.match(/await\s+page\.(.*?)\.(click|dblclick|fill|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
121
+ const match = code.match(/await\s+page\.(.*?)\.(click|dblclick|fill|press|pressSequentially|check|uncheck|selectOption|hover|dragTo|setInputFiles)\s*\(/s);
114
122
  if (!match)
115
123
  return void 0;
116
124
  return { locatorExpr: match[1].trim(), framePath: [] };
@@ -270,6 +278,7 @@ function trackedActionToJsonl(action, pageGuid, timestamp) {
270
278
  const pageAlias = action.pageAlias ?? DEFAULT_PAGE_ALIAS;
271
279
  const signals = action.popupAlias ? [{ name: "popup", popupAlias: action.popupAlias }] : [];
272
280
  const base = {
281
+ ...actionProvenance(action),
273
282
  signals,
274
283
  timestamp: String(timestamp),
275
284
  pageGuid,
@@ -292,8 +301,20 @@ function trackedActionToJsonl(action, pageGuid, timestamp) {
292
301
  return JSON.stringify({ name: "waitForTimeout", duration: Math.min(3e4, time * 1e3), ...base });
293
302
  return null;
294
303
  }
295
- if (toolName === "browser_press_key")
296
- return JSON.stringify({ name: "press", key: args.key, modifiers: modifiersArrayToMask(args.modifiers), selector: "", ...base });
304
+ if (toolName === "browser_press_key") {
305
+ const extracted2 = extractLocatorFromCode(code);
306
+ const parsed2 = extracted2 && locatorToJsonl(extracted2.locatorExpr);
307
+ return JSON.stringify({
308
+ name: "press",
309
+ key: args.key,
310
+ modifiers: modifiersArrayToMask(args.modifiers),
311
+ selector: parsed2?.selector ?? "",
312
+ ...parsed2 ? { locator: parsed2.locator } : {},
313
+ ...base,
314
+ ...extracted2?.framePath.length ? { framePath: extracted2.framePath } : {},
315
+ ...extracted2?.frameUrlMatch ? { frameUrlMatch: extracted2.frameUrlMatch } : {}
316
+ });
317
+ }
297
318
  if (toolName === "browser_mouse_action") {
298
319
  if (!args.name)
299
320
  return null;
@@ -396,6 +417,7 @@ function gojsAddNodeActionToJsonl(action, pageGuid, timestamp, alias) {
396
417
  };
397
418
  return JSON.stringify({
398
419
  name: "diagramNodeAdd",
420
+ ...actionProvenance(action),
399
421
  ...fields,
400
422
  targetIsPalette: false,
401
423
  signals: [],
@@ -419,6 +441,7 @@ function gojsLinkActionToJsonl(action, pageGuid, timestamp, alias) {
419
441
  };
420
442
  return JSON.stringify({
421
443
  name: "diagramLinkAdd",
444
+ ...actionProvenance(action),
422
445
  ...fields,
423
446
  signals: [],
424
447
  timestamp: String(timestamp),
@@ -431,6 +454,7 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
431
454
  const { args, code } = action;
432
455
  const assertType = args.type;
433
456
  const base = {
457
+ ...actionProvenance(action),
434
458
  signals: [],
435
459
  timestamp: String(timestamp),
436
460
  pageGuid,
@@ -528,6 +552,7 @@ function visualSnapshotActionToJsonl(action, pageGuid, timestamp) {
528
552
  if (!args || !args.filename || !String(args.filename).toLowerCase().endsWith(".png"))
529
553
  return null;
530
554
  const base = {
555
+ ...actionProvenance(action),
531
556
  signals: [],
532
557
  timestamp: String(timestamp),
533
558
  pageGuid,
@@ -562,12 +587,26 @@ function visualSnapshotActionToJsonl(action, pageGuid, timestamp) {
562
587
  }
563
588
  function fileUploadActionToJsonl(action, prevClick, pageGuid, timestamp, alias) {
564
589
  const paths = action.args.paths || [];
590
+ const cwd = process.cwd();
591
+ const fromHome = import_path.default.relative(cwd, import_os.default.homedir());
592
+ const cwdIsProject = fromHome.split(import_path.default.sep)[0] === ".." || import_path.default.isAbsolute(fromHome);
565
593
  const files = paths.map((p) => {
594
+ if (paths.length === 1 && cwdIsProject) {
595
+ let real = import_path.default.resolve(p);
596
+ try {
597
+ real = import_path.default.join(import_fs.default.realpathSync(import_path.default.dirname(real)), import_path.default.basename(real));
598
+ } catch {
599
+ }
600
+ const fromCwd = import_path.default.relative(cwd, real);
601
+ if (fromCwd && fromCwd.split(import_path.default.sep)[0] !== ".." && !import_path.default.isAbsolute(fromCwd))
602
+ return real;
603
+ }
566
604
  const parts = p.split(/[\\/]/);
567
605
  return parts[parts.length - 1] || p;
568
606
  });
569
607
  const code = action.code || "";
570
608
  const base = {
609
+ ...actionProvenance(action),
571
610
  signals: [],
572
611
  timestamp: String(timestamp),
573
612
  pageGuid,
@@ -628,6 +667,7 @@ function assertTableCellActionToJsonl(action, pageGuid, timestamp, alias) {
628
667
  return null;
629
668
  const line = {
630
669
  name: "assertTableCell",
670
+ ...actionProvenance(action),
631
671
  selector,
632
672
  text: args.text ?? "",
633
673
  isInput: !!args.isInput,
@@ -669,6 +709,7 @@ function fillFormToJsonl(action, pageGuid, baseTimestamp) {
669
709
  const locatorObj = parsed?.locator;
670
710
  return JSON.stringify({
671
711
  name: field.type === "checkbox" || field.type === "radio" ? field.value === "true" ? "check" : "uncheck" : "fill",
712
+ ...i === 0 ? actionProvenance(action) : derivedActionProvenance(action, `fill-form-${i + 1}`),
672
713
  selector: selector || field.ref,
673
714
  text: field.value,
674
715
  ...locatorObj ? { locator: locatorObj } : {},
@@ -680,8 +721,10 @@ function fillFormToJsonl(action, pageGuid, baseTimestamp) {
680
721
  });
681
722
  });
682
723
  }
683
- function removeRedundantClicks(actions) {
724
+ function removeRedundantClicks(actions, protectedActionIds) {
684
725
  return actions.filter((action, i) => {
726
+ if (protectedActionIds.has(action.actionId))
727
+ return true;
685
728
  if (action.toolName !== "browser_click")
686
729
  return true;
687
730
  const prev = actions[i - 1];
@@ -699,7 +742,7 @@ function removeRedundantClicks(actions) {
699
742
  return false;
700
743
  });
701
744
  }
702
- function deduplicateRetries(actions) {
745
+ function deduplicateRetries(actions, protectedActionIds) {
703
746
  const result = [];
704
747
  let lastNavIdx = -1;
705
748
  for (const action of actions) {
@@ -708,7 +751,7 @@ function deduplicateRetries(actions) {
708
751
  continue;
709
752
  }
710
753
  const url = action.args.url;
711
- if (lastNavIdx >= 0 && result[lastNavIdx].args.url === url) {
754
+ if (lastNavIdx >= 0 && result[lastNavIdx].args.url === url && result[lastNavIdx].phase === action.phase && !result.slice(lastNavIdx).some((prior) => protectedActionIds.has(prior.actionId))) {
712
755
  result.length = lastNavIdx;
713
756
  lastNavIdx = -1;
714
757
  for (let k = result.length - 1; k >= 0; k--) {
@@ -744,15 +787,16 @@ function normalizePrimaryPageAlias(actions) {
744
787
  }
745
788
  }
746
789
  function mouseActionToJsonl(action, pageGuid, baseTs, alias) {
747
- const base = {
790
+ const base = (index) => ({
791
+ ...index === 0 ? actionProvenance(action) : derivedActionProvenance(action, `mouse-${index + 1}`),
748
792
  signals: [],
749
793
  pageGuid,
750
794
  pageAlias: alias,
751
795
  framePath: action.framePath ?? DEFAULT_FRAME_PATH
752
- };
753
- const move = (x, y, ts) => JSON.stringify({ name: "mouse.move", position: { x, y }, timestamp: String(ts), ...base });
754
- const down = (ts) => JSON.stringify({ name: "mouse.down", timestamp: String(ts), ...base });
755
- const up = (ts) => JSON.stringify({ name: "mouse.up", timestamp: String(ts), ...base });
796
+ });
797
+ const move = (x, y, ts, index = 0) => JSON.stringify({ name: "mouse.move", position: { x, y }, timestamp: String(ts), ...base(index) });
798
+ const down = (ts, index = 0) => JSON.stringify({ name: "mouse.down", timestamp: String(ts), ...base(index) });
799
+ const up = (ts, index = 0) => JSON.stringify({ name: "mouse.up", timestamp: String(ts), ...base(index) });
756
800
  switch (action.toolName) {
757
801
  case "browser_mouse_move_xy":
758
802
  return [move(action.args.x, action.args.y, baseTs)];
@@ -761,34 +805,41 @@ function mouseActionToJsonl(action, pageGuid, baseTs, alias) {
761
805
  case "browser_mouse_up":
762
806
  return [up(baseTs)];
763
807
  case "browser_mouse_click_xy":
764
- return [move(action.args.x, action.args.y, baseTs), down(baseTs + 1), up(baseTs + 2)];
808
+ return [move(action.args.x, action.args.y, baseTs), down(baseTs + 1, 1), up(baseTs + 2, 2)];
765
809
  case "browser_mouse_drag_xy":
766
810
  return [
767
811
  move(action.args.startX, action.args.startY, baseTs),
768
- down(baseTs + 1),
769
- move(action.args.endX, action.args.endY, baseTs + 2),
770
- up(baseTs + 3)
812
+ down(baseTs + 1, 1),
813
+ move(action.args.endX, action.args.endY, baseTs + 2, 2),
814
+ up(baseTs + 3, 3)
771
815
  ];
772
816
  case "browser_mouse_action":
773
817
  if (!action.args || !action.args.name)
774
818
  return [];
775
- return [JSON.stringify({ ...action.args, timestamp: String(baseTs), ...base })];
819
+ return [JSON.stringify({ ...action.args, timestamp: String(baseTs), ...base(0) })];
776
820
  default:
777
821
  return [];
778
822
  }
779
823
  }
780
- function buildJsonlContent(actions, browserName, harPath, viewport = RECORDING_VIEWPORT, storageState = process.env.PLAYWRIGHT_MCP_STORAGE_STATE?.trim() || void 0) {
824
+ function buildJsonlContent(actions, browserName, harPath, viewport = RECORDING_VIEWPORT, storageState = process.env.PLAYWRIGHT_MCP_STORAGE_STATE?.trim() || void 0, ledgerEntries = []) {
781
825
  const simplified = [];
782
826
  simplifiedLocators = simplified;
783
827
  try {
784
- return { ...buildJsonlLines(actions, browserName, harPath, viewport, storageState), simplified };
828
+ return { ...buildJsonlLines(actions, browserName, harPath, viewport, storageState, ledgerEntries), simplified };
785
829
  } finally {
786
830
  simplifiedLocators = void 0;
787
831
  }
788
832
  }
789
- function buildJsonlLines(actions, browserName, harPath, viewport, storageState) {
833
+ function buildJsonlLines(actions, browserName, harPath, viewport, storageState, ledgerEntries) {
790
834
  normalizePrimaryPageAlias(actions);
791
- const deduplicated = removeRedundantClicks(deduplicateRetries(actions));
835
+ const protectedActionIds = new Set(ledgerEntries.flatMap((entry) => entry.sourceActionId ? [entry.sourceActionId] : []));
836
+ if (ledgerEntries.length) {
837
+ for (const action of actions) {
838
+ if (action.phase === "setup")
839
+ protectedActionIds.add(action.actionId);
840
+ }
841
+ }
842
+ const deduplicated = removeRedundantClicks(deduplicateRetries(actions, protectedActionIds), protectedActionIds);
792
843
  const startTime = deduplicated[0]?.timestamp ?? Date.now();
793
844
  const pageGuids = /* @__PURE__ */ new Map();
794
845
  const getPageGuid = (alias) => {
@@ -799,7 +850,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
799
850
  const mainGuid = getPageGuid(DEFAULT_PAGE_ALIAS);
800
851
  const lines = [
801
852
  jsonlHeader(browserName, harPath, viewport, storageState),
802
- JSON.stringify({ name: "openPage", url: "about:blank", signals: [], timestamp: String(startTime - 1e3), pageGuid: mainGuid, pageAlias: DEFAULT_PAGE_ALIAS, framePath: DEFAULT_FRAME_PATH })
853
+ JSON.stringify({ name: "openPage", actionId: "export:open-page", phase: "setup", url: "about:blank", signals: [], timestamp: String(startTime - 1e3), pageGuid: mainGuid, pageAlias: DEFAULT_PAGE_ALIAS, framePath: DEFAULT_FRAME_PATH })
803
854
  ];
804
855
  let actionCount = 0;
805
856
  const skipped = [];
@@ -811,7 +862,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
811
862
  const pageGuid = getPageGuid(alias);
812
863
  if (action.popupAlias && !openedAliases.has(action.popupAlias)) {
813
864
  const popupGuid = getPageGuid(action.popupAlias);
814
- lines.push(JSON.stringify({ name: "openPage", url: "about:blank", signals: [], timestamp: String(action.timestamp), pageGuid: popupGuid, pageAlias: action.popupAlias, framePath: DEFAULT_FRAME_PATH }));
865
+ lines.push(JSON.stringify({ name: "openPage", ...derivedActionProvenance(action, `open-page-${action.popupAlias}`), url: "about:blank", signals: [], timestamp: String(action.timestamp), pageGuid: popupGuid, pageAlias: action.popupAlias, framePath: DEFAULT_FRAME_PATH }));
815
866
  openedAliases.add(action.popupAlias);
816
867
  }
817
868
  if (alias !== DEFAULT_PAGE_ALIAS && !openedAliases.has(alias)) {
@@ -832,7 +883,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
832
883
  }
833
884
  }
834
885
  const popupGuid = getPageGuid(alias);
835
- lines.push(JSON.stringify({ name: "openPage", url: "about:blank", signals: [], timestamp: String(action.timestamp), pageGuid: popupGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
886
+ lines.push(JSON.stringify({ name: "openPage", ...derivedActionProvenance(action, `open-page-${alias}`), url: "about:blank", signals: [], timestamp: String(action.timestamp), pageGuid: popupGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
836
887
  openedAliases.add(alias);
837
888
  }
838
889
  if ((action.toolName === "browser_type" || action.toolName === "browser_fill") && alias !== DEFAULT_PAGE_ALIAS)
@@ -864,7 +915,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
864
915
  continue;
865
916
  }
866
917
  if (action.toolName === "browser_assert_api_request") {
867
- lines.push(JSON.stringify({ name: "assertApiRequest", signals: [], timestamp: String(action.timestamp), pageGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
918
+ lines.push(JSON.stringify({ name: "assertApiRequest", ...actionProvenance(action), signals: [], timestamp: String(action.timestamp), pageGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
868
919
  actionCount++;
869
920
  continue;
870
921
  }
@@ -930,6 +981,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
930
981
  const { selector, locator: locatorObj } = locatorInfo;
931
982
  const pressLine = JSON.stringify({
932
983
  name: "press",
984
+ ...derivedActionProvenance(action, "submit"),
933
985
  selector,
934
986
  key: "Enter",
935
987
  locator: locatorObj,
@@ -947,7 +999,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
947
999
  continue;
948
1000
  }
949
1001
  if (action.toolName === "browser_navigate" && alias === DEFAULT_PAGE_ALIAS && hadPopupFills) {
950
- lines.push(JSON.stringify({ name: "waitForTimeout", duration: 3e4, signals: [], timestamp: String(action.timestamp - 1), pageGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
1002
+ lines.push(JSON.stringify({ name: "waitForTimeout", ...derivedActionProvenance(action, "wait-before"), duration: 3e4, signals: [], timestamp: String(action.timestamp - 1), pageGuid, pageAlias: alias, framePath: DEFAULT_FRAME_PATH }));
951
1003
  actionCount++;
952
1004
  hadPopupFills = false;
953
1005
  }
@@ -961,7 +1013,7 @@ function buildJsonlLines(actions, browserName, harPath, viewport, storageState)
961
1013
  if (action.toolName === "browser_click")
962
1014
  prevClickByAlias.set(alias, action);
963
1015
  }
964
- lines.push(JSON.stringify({ name: "closePage", signals: [], timestamp: String(Date.now()), pageGuid: mainGuid, pageAlias: DEFAULT_PAGE_ALIAS, framePath: DEFAULT_FRAME_PATH }));
1016
+ lines.push(JSON.stringify({ name: "closePage", actionId: "export:close-page", phase: deduplicated.at(-1)?.phase ?? "setup", signals: [], timestamp: String(Date.now()), pageGuid: mainGuid, pageAlias: DEFAULT_PAGE_ALIAS, framePath: DEFAULT_FRAME_PATH }));
965
1017
  return { jsonl: lines.join("\n") + "\n", actionCount, skipped };
966
1018
  }
967
1019
  const CROSS_PAGE_URL_BLOCKLIST = [
@@ -991,7 +1043,7 @@ function filterHarEntries(harContent) {
991
1043
  return harContent;
992
1044
  }
993
1045
  }
994
- async function writeSkyrampZip(outputZipPath, jsonlContent, harPath) {
1046
+ async function writeSkyrampZip(outputZipPath, jsonlContent, harPath, extraFiles) {
995
1047
  const zipFile = new import_zipBundle.yazl.ZipFile();
996
1048
  const done = new import_utils.ManualPromise();
997
1049
  zipFile.on("error", (e) => done.reject(e));
@@ -1002,6 +1054,8 @@ async function writeSkyrampZip(outputZipPath, jsonlContent, harPath) {
1002
1054
  } else {
1003
1055
  zipFile.addBuffer(Buffer.from('{"log":{"version":"1.2","creator":{"name":"Playwright","version":"1.0"},"entries":[]}}', "utf-8"), "skyramp_network.har");
1004
1056
  }
1057
+ for (const extra of extraFiles ?? [])
1058
+ zipFile.addBuffer(Buffer.from(extra.content, "utf-8"), extra.name);
1005
1059
  zipFile.end();
1006
1060
  await import_fs.default.promises.mkdir(import_path.default.dirname(outputZipPath), { recursive: true });
1007
1061
  zipFile.outputStream.pipe(import_fs.default.createWriteStream(outputZipPath)).on("close", () => done.resolve()).on("error", (e) => done.reject(e));
@@ -1,8 +1,34 @@
1
1
  "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var import_crypto = __toESM(require("crypto"));
2
25
  var import_vitest = require("vitest");
3
26
  var import_skyRampExport = require("./skyRampExport");
27
+ function trackedAction(action, actionId = "action-1", phase = "setup") {
28
+ return { ...action, actionId, phase };
29
+ }
4
30
  function waitForTime(time) {
5
- return { toolName: "browser_wait_for", args: { time }, code: "", timestamp: 1e3 };
31
+ return trackedAction({ toolName: "browser_wait_for", args: { time }, code: "", timestamp: 1e3 });
6
32
  }
7
33
  function jsonlFor(action) {
8
34
  const line = (0, import_skyRampExport.trackedActionToJsonl)(action, "guid-1", 1e3);
@@ -30,13 +56,13 @@ function jsonlFor(action) {
30
56
  });
31
57
  (0, import_vitest.describe)("browser_wait_for \u2192 text / textGone (unaffected by the clamp)", () => {
32
58
  (0, import_vitest.it)("emits waitForSelector for text", () => {
33
- const entry = jsonlFor({ toolName: "browser_wait_for", args: { text: "Report.pdf" }, code: "", timestamp: 1e3 });
59
+ const entry = jsonlFor(trackedAction({ toolName: "browser_wait_for", args: { text: "Report.pdf" }, code: "", timestamp: 1e3 }));
34
60
  (0, import_vitest.expect)(entry.name).toBe("waitForSelector");
35
61
  (0, import_vitest.expect)(entry.selector).toBe("text=Report.pdf");
36
62
  (0, import_vitest.expect)(entry.duration).toBeUndefined();
37
63
  });
38
64
  (0, import_vitest.it)("emits hidden-state waitForSelector for textGone", () => {
39
- const entry = jsonlFor({ toolName: "browser_wait_for", args: { textGone: "Loading" }, code: "", timestamp: 1e3 });
65
+ const entry = jsonlFor(trackedAction({ toolName: "browser_wait_for", args: { textGone: "Loading" }, code: "", timestamp: 1e3 }));
40
66
  (0, import_vitest.expect)(entry.name).toBe("waitForSelector");
41
67
  (0, import_vitest.expect)(entry.selector).toBe("text=Loading");
42
68
  (0, import_vitest.expect)(entry.state).toBe("hidden");
@@ -44,13 +70,13 @@ function jsonlFor(action) {
44
70
  });
45
71
  (0, import_vitest.describe)("browser_type \u2192 isPassword flag", () => {
46
72
  function typeAction(extra = {}) {
47
- return {
73
+ return trackedAction({
48
74
  toolName: "browser_type",
49
75
  args: { text: "s3cret" },
50
76
  code: `await page.locator('#login-password').fill('s3cret');`,
51
77
  timestamp: 1e3,
52
78
  ...extra
53
- };
79
+ });
54
80
  }
55
81
  (0, import_vitest.it)("marks a fill correlated to a password input", () => {
56
82
  const entry = jsonlFor(typeAction({ isPassword: true }));
@@ -72,3 +98,104 @@ function jsonlFor(action) {
72
98
  (0, import_vitest.expect)(entry.isPassword).toBe(true);
73
99
  });
74
100
  });
101
+ (0, import_vitest.describe)("export action provenance", () => {
102
+ function retryActions() {
103
+ return ["setup", "scenario"].flatMap((phase) => [
104
+ { toolName: "browser_navigate", args: { url: "https://example.com" }, code: "" },
105
+ { toolName: "browser_click", args: { ref: "e1" }, code: `await page.getByLabel('Name').click();` },
106
+ { toolName: "browser_type", args: { ref: "e1", text: "first" }, code: `await page.getByLabel('Name').fill('first');` },
107
+ { toolName: "browser_navigate", args: { url: "https://example.com" }, code: "" },
108
+ { toolName: "browser_click", args: { ref: "e1" }, code: `await page.getByLabel('Name').click();` },
109
+ { toolName: "browser_type", args: { ref: "e1", text: "second" }, code: `await page.getByLabel('Name').fill('second');` }
110
+ ].map((action) => ({ ...action, phase }))).map((action, index) => ({ ...action, actionId: `action-${index + 1}`, timestamp: 1e3 + index }));
111
+ }
112
+ (0, import_vitest.it)("retains setup and resource source actions through retry and click pruning with a non-empty ledger", () => {
113
+ const ledger = ["action-8", "action-11"].map((sourceActionId, index) => ({
114
+ entityUrl: `https://example.com/items/${index}`,
115
+ id: String(index),
116
+ method: "POST",
117
+ createdAt: 1e3,
118
+ deleteEndpoint: `https://example.com/items/${index}`,
119
+ phase: "scenario",
120
+ sourceActionId
121
+ }));
122
+ const { jsonl } = (0, import_skyRampExport.buildJsonlContent)(retryActions(), "chromium", "/tmp/test.har", void 0, "", ledger);
123
+ const entries = jsonl.trim().split("\n").slice(1).map((line) => JSON.parse(line));
124
+ const ids = entries.map((entry) => entry.actionId);
125
+ (0, import_vitest.expect)(ids).toEqual(import_vitest.expect.arrayContaining(["action-1", "action-2", "action-3", "action-4", "action-5", "action-6"]));
126
+ for (const entry of ledger)
127
+ (0, import_vitest.expect)(entries.find((action) => action.actionId === entry.sourceActionId)).toMatchObject({ phase: "scenario", name: "click" });
128
+ });
129
+ for (const ledger of [void 0, []]) {
130
+ (0, import_vitest.it)(`keeps byte-for-byte legacy retry and click pruning with ${ledger ? "zero ledger entries" : "no ledger"}`, () => {
131
+ import_vitest.vi.spyOn(Date, "now").mockReturnValue(9e3);
132
+ import_vitest.vi.spyOn(import_crypto.default, "randomBytes").mockReturnValue(Buffer.alloc(16));
133
+ try {
134
+ const { jsonl } = (0, import_skyRampExport.buildJsonlContent)(retryActions(), "chromium", "/tmp/test.har", void 0, "", ledger);
135
+ (0, import_vitest.expect)(jsonl).toMatchInlineSnapshot(`
136
+ "{"browserName":"chromium","launchOptions":{"headless":false},"contextOptions":{"viewport":{"width":1280,"height":900},"ignoreHTTPSErrors":true,"recordHar":{"path":"/tmp/test.har","mode":"minimal"},"serviceWorkers":"block"},"generateAutoExpect":false}
137
+ {"name":"openPage","actionId":"export:open-page","phase":"setup","url":"about:blank","signals":[],"timestamp":"3","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
138
+ {"name":"navigate","url":"https://example.com","actionId":"action-4","phase":"setup","signals":[],"timestamp":"1003","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
139
+ {"name":"fill","selector":"internal:label=\\"Name\\"i","text":"second","locator":{"kind":"label","body":"Name","options":{"exact":false}},"actionId":"action-6","phase":"setup","signals":[],"timestamp":"1005","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
140
+ {"name":"navigate","url":"https://example.com","actionId":"action-10","phase":"scenario","signals":[],"timestamp":"1009","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
141
+ {"name":"fill","selector":"internal:label=\\"Name\\"i","text":"second","locator":{"kind":"label","body":"Name","options":{"exact":false}},"actionId":"action-12","phase":"scenario","signals":[],"timestamp":"1011","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
142
+ {"name":"closePage","actionId":"export:close-page","phase":"scenario","signals":[],"timestamp":"9000","pageGuid":"page@00000000000000000000000000000000","pageAlias":"page","framePath":[]}
143
+ "
144
+ `);
145
+ } finally {
146
+ import_vitest.vi.restoreAllMocks();
147
+ }
148
+ });
149
+ }
150
+ (0, import_vitest.it)("keeps setup actions when the same navigation is repeated in the scenario phase", () => {
151
+ const actions = [
152
+ trackedAction({ toolName: "browser_navigate", args: { url: "https://example.com" }, code: "", timestamp: 1e3 }, "action-1", "setup"),
153
+ trackedAction({ toolName: "browser_click", args: { ref: "e1" }, code: `await page.getByRole('button', { name: 'Setup' }).click();`, timestamp: 1100 }, "action-2", "setup"),
154
+ trackedAction({ toolName: "browser_navigate", args: { url: "https://example.com" }, code: "", timestamp: 1200 }, "action-3", "scenario")
155
+ ];
156
+ const entries = (0, import_skyRampExport.buildJsonlContent)(actions, "chromium", "/tmp/test.har").jsonl.trim().split("\n").slice(1).map((line) => JSON.parse(line));
157
+ (0, import_vitest.expect)(entries.filter((entry) => entry.name === "navigate").map((entry) => entry.actionId)).toEqual(["action-1", "action-3"]);
158
+ (0, import_vitest.expect)(entries.find((entry) => entry.name === "click")).toMatchObject({ actionId: "action-2", phase: "setup" });
159
+ });
160
+ (0, import_vitest.it)("gives compound form and submit sub-actions distinct provenance", () => {
161
+ const actions = [
162
+ trackedAction({
163
+ toolName: "browser_fill_form",
164
+ args: { fields: [
165
+ { name: "First name", type: "textbox", ref: "e1", value: "Ada" },
166
+ { name: "Last name", type: "textbox", ref: "e2", value: "Lovelace" }
167
+ ] },
168
+ code: "",
169
+ timestamp: 1e3
170
+ }, "action-1", "setup"),
171
+ trackedAction({
172
+ toolName: "browser_type",
173
+ args: { text: "search", submit: true },
174
+ code: `await page.getByRole('textbox', { name: 'Search' }).fill('search');`,
175
+ timestamp: 2e3
176
+ }, "action-2", "scenario")
177
+ ];
178
+ const entries = (0, import_skyRampExport.buildJsonlContent)(actions, "chromium", "/tmp/test.har").jsonl.trim().split("\n").slice(1).map((line) => JSON.parse(line));
179
+ const compound = entries.filter((entry) => ["fill", "press"].includes(entry.name));
180
+ (0, import_vitest.expect)(new Set(compound.map((entry) => entry.actionId))).toHaveLength(compound.length);
181
+ (0, import_vitest.expect)(compound.map((entry) => entry.phase)).toEqual(["setup", "setup", "scenario", "scenario"]);
182
+ });
183
+ (0, import_vitest.it)("adds unique provenance to every emitted action, including exporter-synthesized actions", () => {
184
+ const actions = [
185
+ trackedAction({ toolName: "browser_navigate", args: { url: "https://example.com/start" }, code: "", timestamp: 1e3 }, "action-1", "setup"),
186
+ trackedAction({
187
+ toolName: "browser_type",
188
+ args: { text: "note" },
189
+ code: `await page.getByRole('textbox', { name: 'Note' }).fill('note');`,
190
+ timestamp: 2e3,
191
+ pageAlias: "popupPage"
192
+ }, "action-2", "setup"),
193
+ trackedAction({ toolName: "browser_navigate", args: { url: "https://example.com/done" }, code: "", timestamp: 3e3 }, "action-3", "scenario")
194
+ ];
195
+ const entries = (0, import_skyRampExport.buildJsonlContent)(actions, "chromium", "/tmp/test.har").jsonl.trim().split("\n").slice(1).map((line) => JSON.parse(line));
196
+ (0, import_vitest.expect)(entries.map((entry) => entry.name)).toEqual(["openPage", "navigate", "openPage", "fill", "waitForTimeout", "navigate", "closePage"]);
197
+ for (const entry of entries)
198
+ (0, import_vitest.expect)(entry).toMatchObject({ actionId: import_vitest.expect.any(String), phase: import_vitest.expect.stringMatching(/^(setup|scenario)$/) });
199
+ (0, import_vitest.expect)(new Set(entries.map((entry) => entry.actionId))).toHaveLength(entries.length);
200
+ });
201
+ });
@@ -384,6 +384,8 @@ async function launchContext(options, extraOptions) {
384
384
  contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
385
385
  if (options.saveHarGlob)
386
386
  contextOptions.recordHar.urlFilter = options.saveHarGlob;
387
+ if (options.target === "jsonl")
388
+ contextOptions.recordHar.frameRef = true;
387
389
  contextOptions.serviceWorkers = "block";
388
390
  }
389
391
  let browser;
@@ -161,7 +161,8 @@ class BrowserContext extends import_channelOwner.ChannelOwner {
161
161
  await this._recordIntoHAR(recordHar.path, null, {
162
162
  url: recordHar.urlFilter,
163
163
  updateContent: recordHar.content ?? (recordHar.omitContent ? "omit" : defaultContent),
164
- updateMode: recordHar.mode ?? "full"
164
+ updateMode: recordHar.mode ?? "full",
165
+ frameRef: recordHar.frameRef
165
166
  });
166
167
  }
167
168
  _onPage(page) {
@@ -338,7 +339,8 @@ class BrowserContext extends import_channelOwner.ChannelOwner {
338
339
  urlGlob: (0, import_rtti.isString)(options.url) ? options.url : void 0,
339
340
  urlRegexSource: (0, import_rtti.isRegExp)(options.url) ? options.url.source : void 0,
340
341
  urlRegexFlags: (0, import_rtti.isRegExp)(options.url) ? options.url.flags : void 0,
341
- mode: options.updateMode ?? "minimal"
342
+ mode: options.updateMode ?? "minimal",
343
+ frameRef: options.frameRef
342
344
  }
343
345
  });
344
346
  this._harRecorders.set(harId, { path: har, content: options.updateContent ?? "attach" });