@skyramp/mcp 0.2.9 → 0.2.10-rc.1
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/index.js +1 -1
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
- package/build/prompts/personas.d.ts +2 -2
- package/build/prompts/personas.js +2 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +3 -0
- package/build/prompts/sut-setup/shared.js +1 -3
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +1 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +10 -5
- package/build/prompts/test-maintenance/driftAnalysisSections.js +1 -4
- package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +7 -1
- package/build/prompts/test-maintenance/driftAnalysisShared.js +19 -1
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +0 -6
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +0 -16
- package/build/prompts/test-recommendation/recommendationShared.js +1 -1
- package/build/prompts/testbot/testbot-prompts.js +8 -8
- package/build/resources/testbotResource.js +1 -1
- package/build/services/TestDiscoveryService.d.ts +0 -1
- package/build/services/TestDiscoveryService.js +13 -33
- package/build/services/TestDiscoveryService.test.js +136 -1
- package/build/services/TestExecutionService.d.ts +1 -1
- package/build/tools/executeSkyrampTestTool.js +40 -71
- package/build/tools/submitReportTool.d.ts +20 -20
- package/build/tools/submitReportTool.js +113 -31
- package/build/tools/submitReportTool.test.js +411 -94
- package/build/tools/test-management/actionsTool.js +135 -53
- package/build/tools/test-management/actionsTool.test.d.ts +1 -0
- package/build/tools/test-management/actionsTool.test.js +297 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
- package/build/tools/test-management/analyzeChangesTool.js +63 -152
- package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
- package/build/tools/test-management/analyzeTestHealthTool.js +24 -157
- package/build/tools/test-management/analyzeTestHealthTool.test.js +15 -215
- package/build/tools/workspace/initializeWorkspaceTool.js +35 -15
- package/build/types/FrontendIntegration.d.ts +25 -0
- package/build/types/FrontendIntegration.js +19 -0
- package/build/types/RepositoryAnalysis.d.ts +8 -8
- package/build/types/TestAnalysis.d.ts +16 -50
- package/build/types/TestAnalysis.js +0 -28
- package/build/types/TestTypes.d.ts +3 -1
- package/build/types/TestTypes.js +3 -1
- package/build/utils/AnalysisStateManager.d.ts +13 -1
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.d.ts +1 -1
- package/build/utils/featureFlags.js +1 -1
- package/build/utils/frontendIntegration.d.ts +9 -0
- package/build/utils/frontendIntegration.js +237 -0
- package/build/utils/frontendIntegration.test.d.ts +1 -0
- package/build/utils/frontendIntegration.test.js +229 -0
- package/build/utils/pr-comment-parser.d.ts +3 -3
- package/build/utils/pr-comment-parser.js +6 -6
- package/build/utils/pr-comment-parser.test.js +3 -3
- package/build/utils/repoScanner.d.ts +7 -0
- package/build/utils/repoScanner.js +14 -6
- package/build/utils/repoScanner.test.js +9 -9
- package/build/utils/routeParsers.d.ts +32 -0
- package/build/utils/routeParsers.js +205 -2
- package/build/utils/routeParsers.test.js +279 -29
- package/build/utils/sourceRouteExtractor.js +174 -5
- package/build/utils/sourceRouteExtractor.test.js +173 -0
- package/build/utils/utils.d.ts +11 -0
- package/build/utils/utils.js +19 -0
- package/build/utils/utils.test.js +23 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
- package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
|
@@ -562,4 +562,177 @@ export default function App() {
|
|
|
562
562
|
expect(routes[0].componentFile).toMatch(/src\/pages\/Cart\/index\.tsx$/);
|
|
563
563
|
cleanup(repo);
|
|
564
564
|
});
|
|
565
|
+
// ── Nested React Router routes ────────────────────────────────────────────
|
|
566
|
+
it("composes nested JSX <Route> child paths with their parent", () => {
|
|
567
|
+
// React Router v6 child paths are relative to the parent route. Without
|
|
568
|
+
// composition the extractor emits bare segments ("settings") that aren't
|
|
569
|
+
// navigable URLs.
|
|
570
|
+
const repo = makeTmpRepo([
|
|
571
|
+
{
|
|
572
|
+
path: "src/App.tsx",
|
|
573
|
+
content: `
|
|
574
|
+
import { Routes, Route } from 'react-router-dom';
|
|
575
|
+
import Dashboard from './pages/Dashboard';
|
|
576
|
+
import Settings from './pages/Settings';
|
|
577
|
+
import Users from './pages/Users';
|
|
578
|
+
|
|
579
|
+
export default function App() {
|
|
580
|
+
return (
|
|
581
|
+
<Routes>
|
|
582
|
+
<Route path="/dashboard" element={<Dashboard />}>
|
|
583
|
+
<Route path="settings" element={<Settings />} />
|
|
584
|
+
<Route path="users" element={<Users />} />
|
|
585
|
+
</Route>
|
|
586
|
+
</Routes>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
`,
|
|
590
|
+
},
|
|
591
|
+
]);
|
|
592
|
+
const routes = extractSourceRoutes(repo);
|
|
593
|
+
expect(routes.map((r) => r.path).sort()).toEqual([
|
|
594
|
+
"/dashboard",
|
|
595
|
+
"/dashboard/settings",
|
|
596
|
+
"/dashboard/users",
|
|
597
|
+
]);
|
|
598
|
+
cleanup(repo);
|
|
599
|
+
});
|
|
600
|
+
it("composes relative child paths under a root layout route", () => {
|
|
601
|
+
// Canonical RR v6 shape: a path-bearing parent that itself has children,
|
|
602
|
+
// with relative child paths. The parent must not count its own path twice.
|
|
603
|
+
const repo = makeTmpRepo([
|
|
604
|
+
{
|
|
605
|
+
path: "src/App.tsx",
|
|
606
|
+
content: `
|
|
607
|
+
import { Routes, Route } from 'react-router-dom';
|
|
608
|
+
import Layout from './Layout';
|
|
609
|
+
import Dashboard from './pages/Dashboard';
|
|
610
|
+
import Settings from './pages/Settings';
|
|
611
|
+
|
|
612
|
+
export default function App() {
|
|
613
|
+
return (
|
|
614
|
+
<Routes>
|
|
615
|
+
<Route path="/" element={<Layout />}>
|
|
616
|
+
<Route path="dashboard" element={<Dashboard />}>
|
|
617
|
+
<Route path="settings" element={<Settings />} />
|
|
618
|
+
</Route>
|
|
619
|
+
</Route>
|
|
620
|
+
</Routes>
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
`,
|
|
624
|
+
},
|
|
625
|
+
]);
|
|
626
|
+
const routes = extractSourceRoutes(repo);
|
|
627
|
+
expect(routes.find((r) => r.componentName === "Dashboard").path).toBe("/dashboard");
|
|
628
|
+
expect(routes.find((r) => r.componentName === "Settings").path).toBe("/dashboard/settings");
|
|
629
|
+
cleanup(repo);
|
|
630
|
+
});
|
|
631
|
+
it("resolves a JSX index route to its parent's path", () => {
|
|
632
|
+
// <Route index> has no path attribute; it renders at the parent's path.
|
|
633
|
+
const repo = makeTmpRepo([
|
|
634
|
+
{
|
|
635
|
+
path: "src/App.tsx",
|
|
636
|
+
content: `
|
|
637
|
+
import { Routes, Route } from 'react-router-dom';
|
|
638
|
+
import Dashboard from './pages/Dashboard';
|
|
639
|
+
import DashboardHome from './pages/DashboardHome';
|
|
640
|
+
|
|
641
|
+
export default function App() {
|
|
642
|
+
return (
|
|
643
|
+
<Routes>
|
|
644
|
+
<Route path="/dashboard" element={<Dashboard />}>
|
|
645
|
+
<Route index element={<DashboardHome />} />
|
|
646
|
+
</Route>
|
|
647
|
+
</Routes>
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
`,
|
|
651
|
+
},
|
|
652
|
+
]);
|
|
653
|
+
const routes = extractSourceRoutes(repo);
|
|
654
|
+
const home = routes.find((r) => r.componentName === "DashboardHome");
|
|
655
|
+
expect(home).toBeDefined();
|
|
656
|
+
expect(home.path).toBe("/dashboard");
|
|
657
|
+
cleanup(repo);
|
|
658
|
+
});
|
|
659
|
+
it("extracts createBrowserRouter data-router routes with composed nested paths", () => {
|
|
660
|
+
// The data-router API uses object literals with `element:` (JSX) and nests
|
|
661
|
+
// via `children:` — neither the JSX-element path nor the Vue object path
|
|
662
|
+
// handled these, so the whole tree was missed (root-only fallback).
|
|
663
|
+
const repo = makeTmpRepo([
|
|
664
|
+
{
|
|
665
|
+
path: "src/router.tsx",
|
|
666
|
+
content: `
|
|
667
|
+
import { createBrowserRouter } from 'react-router-dom';
|
|
668
|
+
import Root from './Root';
|
|
669
|
+
import Dashboard from './pages/Dashboard';
|
|
670
|
+
import Settings from './pages/Settings';
|
|
671
|
+
import Profile from './pages/Profile';
|
|
672
|
+
|
|
673
|
+
export const router = createBrowserRouter([
|
|
674
|
+
{
|
|
675
|
+
path: '/',
|
|
676
|
+
element: <Root />,
|
|
677
|
+
children: [
|
|
678
|
+
{ path: 'dashboard', element: <Dashboard /> },
|
|
679
|
+
{
|
|
680
|
+
path: 'settings',
|
|
681
|
+
element: <Settings />,
|
|
682
|
+
children: [
|
|
683
|
+
{ path: 'profile', element: <Profile /> },
|
|
684
|
+
],
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
},
|
|
688
|
+
]);
|
|
689
|
+
`,
|
|
690
|
+
},
|
|
691
|
+
]);
|
|
692
|
+
const routes = extractSourceRoutes(repo);
|
|
693
|
+
expect(routes.map((r) => r.path).sort()).toEqual([
|
|
694
|
+
"/",
|
|
695
|
+
"/dashboard",
|
|
696
|
+
"/settings",
|
|
697
|
+
"/settings/profile",
|
|
698
|
+
]);
|
|
699
|
+
expect(routes.find((r) => r.path === "/settings/profile").componentName).toBe("Profile");
|
|
700
|
+
cleanup(repo);
|
|
701
|
+
});
|
|
702
|
+
it("extracts createStaticRouter (SSR) routes with composed nested paths", () => {
|
|
703
|
+
// SSR apps build routes with createStaticRouter — same route-array shape as
|
|
704
|
+
// createBrowserRouter, so nested composition must work identically.
|
|
705
|
+
const repo = makeTmpRepo([
|
|
706
|
+
{
|
|
707
|
+
path: "src/router.tsx",
|
|
708
|
+
content: `
|
|
709
|
+
import { createStaticRouter } from 'react-router-dom/server';
|
|
710
|
+
import Root from './Root';
|
|
711
|
+
import Reports from './pages/Reports';
|
|
712
|
+
import Detail from './pages/Detail';
|
|
713
|
+
|
|
714
|
+
export const router = createStaticRouter([
|
|
715
|
+
{
|
|
716
|
+
path: '/',
|
|
717
|
+
element: <Root />,
|
|
718
|
+
children: [
|
|
719
|
+
{ path: 'reports', element: <Reports />,
|
|
720
|
+
children: [
|
|
721
|
+
{ path: ':id', element: <Detail /> },
|
|
722
|
+
],
|
|
723
|
+
},
|
|
724
|
+
],
|
|
725
|
+
},
|
|
726
|
+
]);
|
|
727
|
+
`,
|
|
728
|
+
},
|
|
729
|
+
]);
|
|
730
|
+
const routes = extractSourceRoutes(repo);
|
|
731
|
+
expect(routes.map((r) => r.path).sort()).toEqual([
|
|
732
|
+
"/",
|
|
733
|
+
"/reports",
|
|
734
|
+
"/reports/:id",
|
|
735
|
+
]);
|
|
736
|
+
cleanup(repo);
|
|
737
|
+
});
|
|
565
738
|
});
|
package/build/utils/utils.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
export declare function readDiffFile(diffFilePath: string | undefined): string | undefined;
|
|
3
3
|
export declare function toolError(message: string): CallToolResult;
|
|
4
|
+
/**
|
|
5
|
+
* Does `candidate` (an LLM- or caller-supplied name/path) identify `fullPath` (a known,
|
|
6
|
+
* absolute test file path)? Exact match first, then a real path-segment boundary match
|
|
7
|
+
* for a relative candidate (e.g. "tests/foo.py" against ".../repo/tests/foo.py") — never
|
|
8
|
+
* a bare substring/suffix check. Only falls back to basename equality when `candidate`
|
|
9
|
+
* carries no path info at all, since that's the one case where the caller genuinely gave
|
|
10
|
+
* us nothing more specific to disambiguate with (bare filenames can still collide across
|
|
11
|
+
* different directories that happen to share one — this is the caller's ambiguity, not
|
|
12
|
+
* something a smarter match here can resolve).
|
|
13
|
+
*/
|
|
14
|
+
export declare function testFileMatches(fullPath: string, candidate: string): boolean;
|
|
4
15
|
/**
|
|
5
16
|
* Happy-path counterpart to `toolError`. Wraps a single text payload in the
|
|
6
17
|
* MCP tool-result shape so handlers don't have to construct
|
package/build/utils/utils.js
CHANGED
|
@@ -18,6 +18,25 @@ export function toolError(message) {
|
|
|
18
18
|
isError: true,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Does `candidate` (an LLM- or caller-supplied name/path) identify `fullPath` (a known,
|
|
23
|
+
* absolute test file path)? Exact match first, then a real path-segment boundary match
|
|
24
|
+
* for a relative candidate (e.g. "tests/foo.py" against ".../repo/tests/foo.py") — never
|
|
25
|
+
* a bare substring/suffix check. Only falls back to basename equality when `candidate`
|
|
26
|
+
* carries no path info at all, since that's the one case where the caller genuinely gave
|
|
27
|
+
* us nothing more specific to disambiguate with (bare filenames can still collide across
|
|
28
|
+
* different directories that happen to share one — this is the caller's ambiguity, not
|
|
29
|
+
* something a smarter match here can resolve).
|
|
30
|
+
*/
|
|
31
|
+
export function testFileMatches(fullPath, candidate) {
|
|
32
|
+
if (fullPath === candidate)
|
|
33
|
+
return true;
|
|
34
|
+
if (path.isAbsolute(candidate))
|
|
35
|
+
return false;
|
|
36
|
+
if (candidate.includes("/"))
|
|
37
|
+
return fullPath.endsWith("/" + candidate);
|
|
38
|
+
return path.basename(fullPath) === candidate;
|
|
39
|
+
}
|
|
21
40
|
/**
|
|
22
41
|
* Happy-path counterpart to `toolError`. Wraps a single text payload in the
|
|
23
42
|
* MCP tool-result shape so handlers don't have to construct
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
|
-
import { validateParams, generateSkyrampHeader } from "./utils.js";
|
|
2
|
+
import { validateParams, generateSkyrampHeader, testFileMatches } from "./utils.js";
|
|
3
|
+
describe("testFileMatches", () => {
|
|
4
|
+
it("matches identical absolute paths", () => {
|
|
5
|
+
expect(testFileMatches("/repo/tests/orders_test.py", "/repo/tests/orders_test.py")).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it("does not match two different absolute paths, even with the same basename", () => {
|
|
8
|
+
expect(testFileMatches("/repo/frontend/tests/utils_test.py", "/repo/backend/tests/utils_test.py")).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
it("matches a relative candidate against a real path-segment boundary", () => {
|
|
11
|
+
expect(testFileMatches("/repo/backend/tests/orders_test.py", "tests/orders_test.py")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it("does not match a relative candidate that is only a substring, not a path-segment boundary", () => {
|
|
14
|
+
// "ers_test.py" is a suffix of the basename but not preceded by a path separator.
|
|
15
|
+
expect(testFileMatches("/repo/backend/tests/orders_test.py", "ers_test.py")).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it("falls back to basename equality only when the candidate has no path info at all", () => {
|
|
18
|
+
expect(testFileMatches("/repo/backend/tests/orders_test.py", "orders_test.py")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it("bare-basename fallback is ambiguous across two files that share a basename (documented limitation)", () => {
|
|
21
|
+
expect(testFileMatches("/repo/frontend/tests/utils_test.py", "utils_test.py")).toBe(true);
|
|
22
|
+
expect(testFileMatches("/repo/backend/tests/utils_test.py", "utils_test.py")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
3
25
|
describe("validateParams", () => {
|
|
4
26
|
it("should return null for valid comma-separated key=value pairs", () => {
|
|
5
27
|
const result = validateParams("foo=bar,baz=qux", "testField");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
3
|
-
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.
|
|
1
|
+
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.32";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.32";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.32";
|
package/build/utils/versions.js
CHANGED
|
@@ -61,9 +61,14 @@ const uploadFile = (0, import_tool.defineTool)({
|
|
|
61
61
|
const files = params.paths ?? [];
|
|
62
62
|
response.addCode(`await fileChooser.setFiles(${JSON.stringify(files)})`);
|
|
63
63
|
tab.clearModalState(fileChooserModal);
|
|
64
|
-
|
|
65
|
-
await
|
|
66
|
-
|
|
64
|
+
try {
|
|
65
|
+
await tab.waitForCompletion(async () => {
|
|
66
|
+
await fileChooserModal.fileChooser.setFiles(files);
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
tab.setModalState(fileChooserModal);
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
});
|
|
69
74
|
var files_default = [
|
|
@@ -30,7 +30,7 @@ const wait = (0, import_tool.defineTool)({
|
|
|
30
30
|
title: "Wait for",
|
|
31
31
|
description: 'Wait for text to appear, disappear, or a time to pass. REQUIRED after navigating to a folder where content was just created or renamed \u2014 file/item names update asynchronously and will not be present immediately. Always call with text: "<filename>" before attempting to hover or click a newly created item.',
|
|
32
32
|
inputSchema: import_mcpBundle.z.object({
|
|
33
|
-
time: import_mcpBundle.z.number().optional().describe("The time to wait in seconds"),
|
|
33
|
+
time: import_mcpBundle.z.number().positive().max(30).optional().describe("The time to wait, in SECONDS (not milliseconds), max 30. e.g. 3 = three seconds. Prefer text/textGone waits over a fixed time."),
|
|
34
34
|
text: import_mcpBundle.z.string().optional().describe("The text to wait for"),
|
|
35
35
|
textGone: import_mcpBundle.z.string().optional().describe("The text to wait for to disappear")
|
|
36
36
|
}),
|
|
@@ -54,6 +54,12 @@ const exportZipSchema = {
|
|
|
54
54
|
}),
|
|
55
55
|
type: "readOnly"
|
|
56
56
|
};
|
|
57
|
+
function summarizeDropped(dropped) {
|
|
58
|
+
const counts = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const d of dropped)
|
|
60
|
+
counts.set(d.toolName, (counts.get(d.toolName) ?? 0) + 1);
|
|
61
|
+
return [...counts.entries()].map(([tool, n]) => n > 1 ? `${tool} \xD7${n}` : tool).join(", ");
|
|
62
|
+
}
|
|
57
63
|
function createExportZipHandler(ctx) {
|
|
58
64
|
return async (params) => {
|
|
59
65
|
if (!ctx.trackedActions.length) {
|
|
@@ -86,6 +92,9 @@ Requested path: ${outputZip}` }],
|
|
|
86
92
|
await (0, import_skyRampExport.writeSkyrampZip)(outputZip, jsonlContent, ctx.harPath);
|
|
87
93
|
const skippedNote = skipped.length ? `
|
|
88
94
|
Skipped (not exportable): ${[...new Set(skipped)].join(", ")}` : "";
|
|
95
|
+
const dropped = ctx.droppedActions ?? [];
|
|
96
|
+
const droppedNote = dropped.length ? `
|
|
97
|
+
Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
89
98
|
return {
|
|
90
99
|
content: [{
|
|
91
100
|
type: "text",
|
|
@@ -93,7 +102,7 @@ Skipped (not exportable): ${[...new Set(skipped)].join(", ")}` : "";
|
|
|
93
102
|
Skyramp zip written to ${outputZip}
|
|
94
103
|
|
|
95
104
|
Contains:
|
|
96
|
-
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}`
|
|
105
|
+
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${droppedNote}`
|
|
97
106
|
}]
|
|
98
107
|
};
|
|
99
108
|
};
|
|
@@ -74,6 +74,7 @@ class TraceRecordingBackend {
|
|
|
74
74
|
// per-type baseline filename counter
|
|
75
75
|
constructor(options) {
|
|
76
76
|
this._trackedActions = [];
|
|
77
|
+
this._droppedActions = [];
|
|
77
78
|
this._initialized = false;
|
|
78
79
|
this._pageCount = 0;
|
|
79
80
|
// number of popup pages seen
|
|
@@ -92,6 +93,9 @@ class TraceRecordingBackend {
|
|
|
92
93
|
get trackedActions() {
|
|
93
94
|
return this._trackedActions;
|
|
94
95
|
}
|
|
96
|
+
get droppedActions() {
|
|
97
|
+
return this._droppedActions;
|
|
98
|
+
}
|
|
95
99
|
async initialize(clientInfo) {
|
|
96
100
|
const userDataDir = this._options.userDataDir || process.env.PLAYWRIGHT_USER_DATA_DIR || void 0;
|
|
97
101
|
const loadExtension = this._options.loadExtension ?? splitExtensionPaths(process.env.PLAYWRIGHT_LOAD_EXTENSION);
|
|
@@ -185,12 +189,14 @@ class TraceRecordingBackend {
|
|
|
185
189
|
this._consumeRecorderSelectors();
|
|
186
190
|
const handler = (0, import_exportTool.createExportZipHandler)({
|
|
187
191
|
trackedActions: this._trackedActions,
|
|
192
|
+
droppedActions: this._droppedActions,
|
|
188
193
|
harPath: this._harPath,
|
|
189
194
|
outputDir: this._outputDir
|
|
190
195
|
});
|
|
191
196
|
const exportResult = await handler(parsed);
|
|
192
197
|
if (!exportResult.isError) {
|
|
193
198
|
this._trackedActions = [];
|
|
199
|
+
this._droppedActions = [];
|
|
194
200
|
const ra = this._browserBackend.context?.recorderActions;
|
|
195
201
|
if (ra)
|
|
196
202
|
ra.length = 0;
|
|
@@ -1691,8 +1697,13 @@ Cell row ${params.row}, column ${params.column} has "${actual}", expected "${par
|
|
|
1691
1697
|
};
|
|
1692
1698
|
}
|
|
1693
1699
|
_maybeTrackAction(toolName, args, result, timestamp, pageAliasBeforeAction) {
|
|
1694
|
-
if (result.isError)
|
|
1700
|
+
if (result.isError) {
|
|
1701
|
+
const errParsed = (0, import_response.parseResponse)(result);
|
|
1702
|
+
const errText = (errParsed?.error ?? errParsed?.text ?? "unknown error").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
1703
|
+
this._droppedActions.push({ toolName, error: errText });
|
|
1704
|
+
traceDebug(`Dropped failed action: ${toolName} args=${JSON.stringify(args).slice(0, 200)} \u2014 ${errText} (dropped total: ${this._droppedActions.length})`);
|
|
1695
1705
|
return;
|
|
1706
|
+
}
|
|
1696
1707
|
const parsed = (0, import_response.parseResponse)(result);
|
|
1697
1708
|
const code = parsed?.code ?? "";
|
|
1698
1709
|
if (code || import_types.ARGS_ONLY_TOOLS.has(toolName)) {
|
|
@@ -1771,15 +1782,28 @@ Cell row ${params.row}, column ${params.column} has "${actual}", expected "${par
|
|
|
1771
1782
|
else
|
|
1772
1783
|
traceDebug("Flutter placeholder clicked but semantics not confirmed active");
|
|
1773
1784
|
}
|
|
1774
|
-
/**
|
|
1775
|
-
|
|
1785
|
+
/**
|
|
1786
|
+
* Clean up temp files. When `reuse` is true (between recordings in the same
|
|
1787
|
+
* session), the temp directory and `_harPath` are kept STABLE and only their
|
|
1788
|
+
* contents are cleared; when false (session end), the directory is removed.
|
|
1789
|
+
*
|
|
1790
|
+
* SKYR-3830: `recordHar.path` is baked by value into the context factory's
|
|
1791
|
+
* config at initialize(). Reassigning `_harPath` to a fresh temp dir here
|
|
1792
|
+
* desynced it from that baked path, so every recording after the first wrote
|
|
1793
|
+
* its HAR to the original (now-deleted) path while export read the new
|
|
1794
|
+
* `_harPath` — yielding an empty HAR and zero `waitForResponse` anchors.
|
|
1795
|
+
* Keeping the path stable lets the next context's recordHar overwrite the
|
|
1796
|
+
* HAR in place on close, so subsequent exports capture network again.
|
|
1797
|
+
*/
|
|
1798
|
+
_cleanupTempDir(reuse = false) {
|
|
1776
1799
|
try {
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
this._tempDir
|
|
1781
|
-
|
|
1782
|
-
|
|
1800
|
+
if (reuse) {
|
|
1801
|
+
for (const entry of import_fs.default.readdirSync(this._tempDir))
|
|
1802
|
+
import_fs.default.rmSync(import_path.default.join(this._tempDir, entry), { recursive: true, force: true });
|
|
1803
|
+
traceDebug(`Cleared temp directory for reuse: ${this._tempDir}`);
|
|
1804
|
+
} else {
|
|
1805
|
+
import_fs.default.rmSync(this._tempDir, { recursive: true, force: true });
|
|
1806
|
+
traceDebug(`Cleaned up temp directory: ${this._tempDir}`);
|
|
1783
1807
|
}
|
|
1784
1808
|
} catch (e) {
|
|
1785
1809
|
traceDebug(`Failed to clean up temp directory ${this._tempDir}: ${e}`);
|
|
@@ -251,7 +251,7 @@ function trackedActionToJsonl(action, pageGuid, timestamp) {
|
|
|
251
251
|
if (textGone)
|
|
252
252
|
return JSON.stringify({ name: "waitForSelector", selector: `text=${textGone}`, state: "hidden", ...base });
|
|
253
253
|
if (typeof time === "number")
|
|
254
|
-
return JSON.stringify({ name: "waitForTimeout", duration: time * 1e3, ...base });
|
|
254
|
+
return JSON.stringify({ name: "waitForTimeout", duration: Math.min(3e4, time * 1e3), ...base });
|
|
255
255
|
return null;
|
|
256
256
|
}
|
|
257
257
|
if (toolName === "browser_press_key")
|
|
@@ -261,6 +261,12 @@ function trackedActionToJsonl(action, pageGuid, timestamp) {
|
|
|
261
261
|
return null;
|
|
262
262
|
return JSON.stringify({ ...args, ...base });
|
|
263
263
|
}
|
|
264
|
+
if (toolName === "browser_evaluate") {
|
|
265
|
+
const fn = typeof args.function === "string" ? args.function : "";
|
|
266
|
+
if (!fn || args.ref && args.element)
|
|
267
|
+
return null;
|
|
268
|
+
return JSON.stringify({ name: "evaluate", expression: fn, ...base });
|
|
269
|
+
}
|
|
264
270
|
if (!code)
|
|
265
271
|
return null;
|
|
266
272
|
const extracted = extractLocatorFromCode(code);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_skyRampExport = require("./skyRampExport");
|
|
4
|
+
function waitForTime(time) {
|
|
5
|
+
return { toolName: "browser_wait_for", args: { time }, code: "", timestamp: 1e3 };
|
|
6
|
+
}
|
|
7
|
+
function jsonlFor(action) {
|
|
8
|
+
const line = (0, import_skyRampExport.trackedActionToJsonl)(action, "guid-1", 1e3);
|
|
9
|
+
return line ? JSON.parse(line) : null;
|
|
10
|
+
}
|
|
11
|
+
(0, import_vitest.describe)("browser_wait_for \u2192 waitForTimeout duration", () => {
|
|
12
|
+
(0, import_vitest.it)("converts a within-cap seconds value to milliseconds", () => {
|
|
13
|
+
const entry = jsonlFor(waitForTime(3));
|
|
14
|
+
(0, import_vitest.expect)(entry.name).toBe("waitForTimeout");
|
|
15
|
+
(0, import_vitest.expect)(entry.duration).toBe(3e3);
|
|
16
|
+
});
|
|
17
|
+
(0, import_vitest.it)("preserves fractional seconds", () => {
|
|
18
|
+
(0, import_vitest.expect)(jsonlFor(waitForTime(1.5)).duration).toBe(1500);
|
|
19
|
+
});
|
|
20
|
+
(0, import_vitest.it)("clamps a seconds value above the 30s live cap to 30000", () => {
|
|
21
|
+
(0, import_vitest.expect)(jsonlFor(waitForTime(45)).duration).toBe(3e4);
|
|
22
|
+
});
|
|
23
|
+
(0, import_vitest.it)("clamps a millisecond value mistakenly passed into the seconds param", () => {
|
|
24
|
+
(0, import_vitest.expect)(jsonlFor(waitForTime(1500)).duration).toBe(3e4);
|
|
25
|
+
(0, import_vitest.expect)(jsonlFor(waitForTime(3e3)).duration).toBe(3e4);
|
|
26
|
+
});
|
|
27
|
+
(0, import_vitest.it)("exactly at the cap is unchanged", () => {
|
|
28
|
+
(0, import_vitest.expect)(jsonlFor(waitForTime(30)).duration).toBe(3e4);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
(0, import_vitest.describe)("browser_wait_for \u2192 text / textGone (unaffected by the clamp)", () => {
|
|
32
|
+
(0, import_vitest.it)("emits waitForSelector for text", () => {
|
|
33
|
+
const entry = jsonlFor({ toolName: "browser_wait_for", args: { text: "Report.pdf" }, code: "", timestamp: 1e3 });
|
|
34
|
+
(0, import_vitest.expect)(entry.name).toBe("waitForSelector");
|
|
35
|
+
(0, import_vitest.expect)(entry.selector).toBe("text=Report.pdf");
|
|
36
|
+
(0, import_vitest.expect)(entry.duration).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
(0, import_vitest.it)("emits hidden-state waitForSelector for textGone", () => {
|
|
39
|
+
const entry = jsonlFor({ toolName: "browser_wait_for", args: { textGone: "Loading" }, code: "", timestamp: 1e3 });
|
|
40
|
+
(0, import_vitest.expect)(entry.name).toBe("waitForSelector");
|
|
41
|
+
(0, import_vitest.expect)(entry.selector).toBe("text=Loading");
|
|
42
|
+
(0, import_vitest.expect)(entry.state).toBe("hidden");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{T as x,r,a as C,W as F,j as e,D as z,b as I,c as P,d as A,e as O,f as V}from"./assets/defaultSettingsView-CYf9adZh.js";const $=()=>{const[o,c]=r.useState(!1),[n,l]=r.useState(),[h,u]=r.useState(),[p,b]=r.useState(N),[f,j]=r.useState({done:0,total:0}),[k,v]=r.useState(!1),[y,m]=r.useState(null),[T,R]=r.useState(null),[U,E]=r.useState(!1),g=r.useCallback(t=>{const s=new URL(window.location.href);if(!t.length)return;const i=t.item(0),a=URL.createObjectURL(i);s.searchParams.append("trace",a);const d=s.toString();window.history.pushState({},"",d),l(a),u(i.name),v(!1),m(null)},[]);r.useEffect(()=>{const t=async s=>{var a;if(!((a=s.clipboardData)!=null&&a.files.length))return;const i=["application/zip","application/x-zip-compressed"];for(const d of s.clipboardData.files)if(!i.includes(d.type))return;s.preventDefault(),g(s.clipboardData.files)};return document.addEventListener("paste",t),()=>document.removeEventListener("paste",t)}),r.useEffect(()=>{const t=s=>{const{method:i,params:a}=s.data;if(i!=="load"||!((a==null?void 0:a.trace)instanceof Blob))return;const d=new File([a.trace],"trace.zip",{type:"application/zip"}),w=new DataTransfer;w.items.add(d),g(w.files)};return window.addEventListener("message",t),()=>window.removeEventListener("message",t)});const W=r.useCallback(t=>{t.preventDefault(),g(t.dataTransfer.files)},[g]),M=r.useCallback(t=>{t.preventDefault(),t.target.files&&g(t.target.files)},[g]);r.useEffect(()=>{const t=new URL(window.location.href).searchParams,s=t.get("trace");if(c(t.has("isServer")),s!=null&&s.startsWith("file:")){R(s||null);return}if(t.has("isServer")){const i=new URLSearchParams(window.location.search).get("ws"),a=new URL(`../${i}`,window.location.toString());a.protocol=window.location.protocol==="https:"?"wss:":"ws:";const d=new C(new F(a));d.onLoadTraceRequested(async w=>{l(w.traceUrl),v(!1),m(null)}),d.initialize({}).catch(()=>{})}else s&&!s.startsWith("blob:")&&l(s)},[]);const S=r.useCallback(async t=>{const s=new URLSearchParams;s.set("trace",t);const i=await fetch(`contexts?${s.toString()}`);if(!i.ok){const{error:w}=await i.json();return m(w),w}const a=await i.json(),d=new x(t,a);j({done:0,total:0}),m(null),b(d)},[]);r.useEffect(()=>{(async()=>{if(!n){b(N);return}const t=s=>{s.data.method==="progress"&&j(s.data.params)};try{navigator.serviceWorker.addEventListener("message",t),j({done:0,total:1});let s=await S(n);s!=null&&s.includes("please grant permission for Local Network Access")&&(await fetch(n,{method:"HEAD",headers:{"x-pw-serviceworker":"skip"}}),s=await S(n)),s&&(o||l(void 0))}finally{navigator.serviceWorker.removeEventListener("message",t)}})()},[o,n,h,S]);const D=f.done!==f.total&&f.total!==0&&!y;r.useEffect(()=>{if(D){const t=setTimeout(()=>{E(!0)},200);return()=>clearTimeout(t)}else E(!1)},[D]);const L=!!(!o&&!k&&!T&&(!n||y));return e.jsxs("div",{className:"vbox workbench-loader",onDragOver:t=>{t.preventDefault(),t.dataTransfer.types.includes("Files")&&v(!0)},children:[e.jsxs("div",{className:"hbox header",...L?{inert:!0}:{},children:[e.jsx("div",{className:"logo",children:e.jsx("img",{src:"playwright-logo.svg",alt:"Playwright logo"})}),e.jsx("div",{className:"product",children:"Playwright"}),p.title&&e.jsx("div",{className:"title",children:p.title}),e.jsx("div",{className:"spacer"}),e.jsx(z,{icon:"settings-gear",title:"Settings",dialogDataTestId:"settings-toolbar-dialog",children:e.jsx(I,{location:"trace-viewer"})})]}),e.jsx(P,{model:p,inert:L}),T&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{children:"Trace Viewer uses Service Workers to show traces. To view trace:"}),e.jsxs("div",{style:{paddingTop:20},children:[e.jsxs("div",{children:["1. Click ",e.jsx("a",{href:T,children:"here"})," to put your trace into the download shelf"]}),e.jsxs("div",{children:["2. Go to ",e.jsx("a",{href:"https://trace.playwright.dev",children:"trace.playwright.dev"})]}),e.jsx("div",{children:"3. Drop the trace from the download shelf into the page"})]})]}),e.jsx(A,{open:U,isModal:!0,className:"progress-dialog",children:e.jsxs("div",{className:"progress-content",children:[e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Loading Playwright Trace..."}),e.jsx("div",{className:"progress-wrapper",children:e.jsx("div",{className:"inner-progress",style:{width:f.total?100*f.done/f.total+"%":0}})})]})}),L&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{className:"processing-error",role:"alert",children:y}),e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Drop Playwright Trace to load"}),e.jsx("div",{children:"or"}),e.jsx("button",{onClick:()=>{const t=document.createElement("input");t.type="file",t.click(),t.addEventListener("change",s=>M(s))},type:"button",children:"Select file"}),e.jsx("div",{className:"info",children:"Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, it opens it locally."}),e.jsxs("div",{className:"version",children:["Playwright v","1.58.2-skyramp.8.
|
|
1
|
+
import{T as x,r,a as C,W as F,j as e,D as z,b as I,c as P,d as A,e as O,f as V}from"./assets/defaultSettingsView-CYf9adZh.js";const $=()=>{const[o,c]=r.useState(!1),[n,l]=r.useState(),[h,u]=r.useState(),[p,b]=r.useState(N),[f,j]=r.useState({done:0,total:0}),[k,v]=r.useState(!1),[y,m]=r.useState(null),[T,R]=r.useState(null),[U,E]=r.useState(!1),g=r.useCallback(t=>{const s=new URL(window.location.href);if(!t.length)return;const i=t.item(0),a=URL.createObjectURL(i);s.searchParams.append("trace",a);const d=s.toString();window.history.pushState({},"",d),l(a),u(i.name),v(!1),m(null)},[]);r.useEffect(()=>{const t=async s=>{var a;if(!((a=s.clipboardData)!=null&&a.files.length))return;const i=["application/zip","application/x-zip-compressed"];for(const d of s.clipboardData.files)if(!i.includes(d.type))return;s.preventDefault(),g(s.clipboardData.files)};return document.addEventListener("paste",t),()=>document.removeEventListener("paste",t)}),r.useEffect(()=>{const t=s=>{const{method:i,params:a}=s.data;if(i!=="load"||!((a==null?void 0:a.trace)instanceof Blob))return;const d=new File([a.trace],"trace.zip",{type:"application/zip"}),w=new DataTransfer;w.items.add(d),g(w.files)};return window.addEventListener("message",t),()=>window.removeEventListener("message",t)});const W=r.useCallback(t=>{t.preventDefault(),g(t.dataTransfer.files)},[g]),M=r.useCallback(t=>{t.preventDefault(),t.target.files&&g(t.target.files)},[g]);r.useEffect(()=>{const t=new URL(window.location.href).searchParams,s=t.get("trace");if(c(t.has("isServer")),s!=null&&s.startsWith("file:")){R(s||null);return}if(t.has("isServer")){const i=new URLSearchParams(window.location.search).get("ws"),a=new URL(`../${i}`,window.location.toString());a.protocol=window.location.protocol==="https:"?"wss:":"ws:";const d=new C(new F(a));d.onLoadTraceRequested(async w=>{l(w.traceUrl),v(!1),m(null)}),d.initialize({}).catch(()=>{})}else s&&!s.startsWith("blob:")&&l(s)},[]);const S=r.useCallback(async t=>{const s=new URLSearchParams;s.set("trace",t);const i=await fetch(`contexts?${s.toString()}`);if(!i.ok){const{error:w}=await i.json();return m(w),w}const a=await i.json(),d=new x(t,a);j({done:0,total:0}),m(null),b(d)},[]);r.useEffect(()=>{(async()=>{if(!n){b(N);return}const t=s=>{s.data.method==="progress"&&j(s.data.params)};try{navigator.serviceWorker.addEventListener("message",t),j({done:0,total:1});let s=await S(n);s!=null&&s.includes("please grant permission for Local Network Access")&&(await fetch(n,{method:"HEAD",headers:{"x-pw-serviceworker":"skip"}}),s=await S(n)),s&&(o||l(void 0))}finally{navigator.serviceWorker.removeEventListener("message",t)}})()},[o,n,h,S]);const D=f.done!==f.total&&f.total!==0&&!y;r.useEffect(()=>{if(D){const t=setTimeout(()=>{E(!0)},200);return()=>clearTimeout(t)}else E(!1)},[D]);const L=!!(!o&&!k&&!T&&(!n||y));return e.jsxs("div",{className:"vbox workbench-loader",onDragOver:t=>{t.preventDefault(),t.dataTransfer.types.includes("Files")&&v(!0)},children:[e.jsxs("div",{className:"hbox header",...L?{inert:!0}:{},children:[e.jsx("div",{className:"logo",children:e.jsx("img",{src:"playwright-logo.svg",alt:"Playwright logo"})}),e.jsx("div",{className:"product",children:"Playwright"}),p.title&&e.jsx("div",{className:"title",children:p.title}),e.jsx("div",{className:"spacer"}),e.jsx(z,{icon:"settings-gear",title:"Settings",dialogDataTestId:"settings-toolbar-dialog",children:e.jsx(I,{location:"trace-viewer"})})]}),e.jsx(P,{model:p,inert:L}),T&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{children:"Trace Viewer uses Service Workers to show traces. To view trace:"}),e.jsxs("div",{style:{paddingTop:20},children:[e.jsxs("div",{children:["1. Click ",e.jsx("a",{href:T,children:"here"})," to put your trace into the download shelf"]}),e.jsxs("div",{children:["2. Go to ",e.jsx("a",{href:"https://trace.playwright.dev",children:"trace.playwright.dev"})]}),e.jsx("div",{children:"3. Drop the trace from the download shelf into the page"})]})]}),e.jsx(A,{open:U,isModal:!0,className:"progress-dialog",children:e.jsxs("div",{className:"progress-content",children:[e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Loading Playwright Trace..."}),e.jsx("div",{className:"progress-wrapper",children:e.jsx("div",{className:"inner-progress",style:{width:f.total?100*f.done/f.total+"%":0}})})]})}),L&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{className:"processing-error",role:"alert",children:y}),e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Drop Playwright Trace to load"}),e.jsx("div",{children:"or"}),e.jsx("button",{onClick:()=>{const t=document.createElement("input");t.type="file",t.click(),t.addEventListener("change",s=>M(s))},type:"button",children:"Select file"}),e.jsx("div",{className:"info",children:"Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, it opens it locally."}),e.jsxs("div",{className:"version",children:["Playwright v","1.58.2-skyramp.8.11.0"]})]}),o&&!n&&e.jsx("div",{className:"drop-target",children:e.jsx("div",{className:"title",children:"Select test to see the trace"})}),k&&e.jsx("div",{className:"drop-target",onDragLeave:()=>{v(!1)},onDrop:t=>W(t),children:e.jsx("div",{className:"title",children:"Release to analyse the Playwright Trace"})})]})},N=new x("",[]),q=({traceJson:o})=>{const[c,n]=r.useState(void 0),[l,h]=r.useState(0),u=r.useRef(null);return r.useEffect(()=>(u.current&&clearTimeout(u.current),u.current=setTimeout(async()=>{try{const p=await B(o);n(p)}catch{const p=new x("",[]);n(p)}finally{h(l+1)}},500),()=>{u.current&&clearTimeout(u.current)}),[o,l]),e.jsx(P,{isLive:!0,model:c})};async function B(o){const c=new URLSearchParams;c.set("trace",o);const l=await(await fetch(`contexts?${c.toString()}`)).json();return new x(o,l)}(async()=>{const o=new URLSearchParams(window.location.search);if(O(),window.location.protocol!=="file:"){if(o.get("isUnderTest")==="true"&&await new Promise(h=>setTimeout(h,1e3)),!navigator.serviceWorker)throw new Error(`Service workers are not supported.
|
|
2
2
|
Make sure to serve the Trace Viewer (${window.location}) via HTTPS or localhost.`);navigator.serviceWorker.register("sw.bundle.js"),navigator.serviceWorker.controller||await new Promise(h=>{navigator.serviceWorker.oncontrollerchange=()=>h()}),setInterval(function(){fetch("ping")},1e4)}const c=o.get("trace"),l=(c==null?void 0:c.endsWith(".json"))?e.jsx(q,{traceJson:c}):e.jsx($,{});V.createRoot(document.querySelector("#root")).render(l)})();
|
package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="icon" href="./playwright-logo.svg" type="image/svg+xml">
|
|
8
8
|
<link rel="manifest" href="./manifest.webmanifest">
|
|
9
9
|
<title>Playwright Trace Viewer</title>
|
|
10
|
-
<script type="module" crossorigin src="./index.
|
|
10
|
+
<script type="module" crossorigin src="./index.d4gkVSou.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/defaultSettingsView-CYf9adZh.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./defaultSettingsView.7ch9cixO.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./index.BVu7tZDe.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10-rc.1",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -67,11 +67,11 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
69
69
|
"@playwright/test": "^1.55.0",
|
|
70
|
-
"@skyramp/skyramp": "1.3.
|
|
70
|
+
"@skyramp/skyramp": "1.3.32",
|
|
71
71
|
"dockerode": "^5.0.0",
|
|
72
72
|
"fast-glob": "^3.3.3",
|
|
73
73
|
"js-yaml": "^4.1.1",
|
|
74
|
-
"playwright": "file:vendor/skyramp-playwright-1.58.2-skyramp.8.
|
|
74
|
+
"playwright": "file:vendor/skyramp-playwright-1.58.2-skyramp.8.11.0.tgz",
|
|
75
75
|
"simple-git": "^3.30.0",
|
|
76
76
|
"typescript": "^5.8.3",
|
|
77
77
|
"zod": "^3.25.3"
|