@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.
- package/build/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +16 -0
- package/build/index.js +32 -6
- package/build/playwright/PlaywrightTraceService.d.ts +17 -0
- package/build/playwright/PlaywrightTraceService.js +29 -1
- package/build/playwright/dataSafetyConfig.d.ts +74 -0
- package/build/playwright/dataSafetyConfig.js +109 -0
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +44 -0
- package/build/playwright/registerPlaywrightTools.d.ts +36 -1
- package/build/playwright/registerPlaywrightTools.js +87 -43
- package/build/playwright/safeResolveDataSafety.d.ts +24 -0
- package/build/playwright/safeResolveDataSafety.js +33 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
- package/build/services/FleetExecutionService.d.ts +87 -16
- package/build/services/FleetExecutionService.js +487 -48
- package/build/services/FleetRunRegistry.d.ts +49 -0
- package/build/services/FleetRunRegistry.js +178 -0
- package/build/services/containerEnv.js +8 -2
- package/build/services/fleetLimitConfirmation.d.ts +19 -0
- package/build/services/fleetLimitConfirmation.js +126 -0
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +88 -43
- package/build/tools/code-refactor/assertion-state.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -1
- package/build/tools/code-refactor/utils-verify-gates.js +1 -1
- package/build/tools/executeFleetTool.d.ts +6 -4
- package/build/tools/executeFleetTool.js +67 -23
- package/build/tools/fixErrorTool.js +2 -1
- package/build/tools/fleetLifecycleTools.d.ts +20 -0
- package/build/tools/fleetLifecycleTools.js +124 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
- package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
- package/build/tools/submitReportTool.js +4 -122
- package/build/tools/test-management/registerTestPlanTool.js +22 -6
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
- package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
- package/build/types/AssertionOutcome.d.ts +3 -0
- package/build/types/TestbotReport.d.ts +0 -37
- package/build/types/TestbotReport.js +0 -22
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
- package/build/utils/assertion-verify/created-add-guard.js +173 -0
- package/build/utils/assertion-verify/index.d.ts +1 -0
- package/build/utils/assertion-verify/index.js +1 -0
- package/build/utils/assertion-verify/verify.d.ts +7 -3
- package/build/utils/assertion-verify/verify.js +9 -3
- package/build/utils/featureFlags.js +6 -3
- package/build/utils/repositorySlug.d.ts +14 -0
- package/build/utils/repositorySlug.js +46 -1
- package/build/utils/telemetry.d.ts +2 -0
- package/build/utils/telemetry.js +12 -0
- package/build/utils/utils-verify/action-key.d.ts +59 -2
- package/build/utils/utils-verify/action-key.js +162 -6
- package/build/utils/utils-verify/action-sites.d.ts +7 -0
- package/build/utils/utils-verify/action-sites.js +277 -35
- package/build/utils/utils-verify/call-sites.d.ts +8 -2
- package/build/utils/utils-verify/language-spec.js +1 -1
- package/build/utils/utils-verify/parse.d.ts +4 -0
- package/build/utils/utils-verify/parse.js +2 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +63 -121
- package/build/workspace/workspace.js +45 -48
- package/build/workspace/workspaceVerifier.d.ts +22 -0
- package/build/workspace/workspaceVerifier.js +65 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
- package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -4
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
- package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
- package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
- package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
- package/build/prompts/sut-setup/shared.d.ts +0 -62
- package/build/prompts/sut-setup/shared.js +0 -196
- package/build/resources/sutSetupResource.d.ts +0 -2
- package/build/resources/sutSetupResource.js +0 -63
- 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
|
-
|
|
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
|
|
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" });
|