@skyramp/mcp 0.3.0 → 0.3.1-ldw-beta
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/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/commandLibrary.test.d.ts +1 -0
- package/build/commands/commandLibrary.test.js +59 -0
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/playwright/traceRecordingPrompt.js +2 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +466 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -28
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +16 -1
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +5 -34
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestExecutionService.test.js +83 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +7 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +47 -18
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
- package/build/tools/enrichTestWithMocksTool.test.js +266 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +63 -22
- package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
- package/build/tools/executeSkyrampTestTool.test.js +36 -0
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +85 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +432 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -21
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +1 -0
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateMockRestTool.d.ts +129 -6
- package/build/tools/generate-tests/generateMockRestTool.js +234 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
- package/build/tools/generateEnrichedIntegrationTestTool.test.js +44 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +224 -0
- package/build/tools/localDevWorkerComposeTool.test.d.ts +1 -0
- package/build/tools/localDevWorkerComposeTool.test.js +129 -0
- package/build/tools/localDevWorkflowFixes.test.d.ts +1 -0
- package/build/tools/localDevWorkflowFixes.test.js +255 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
- package/build/tools/one-click/oneClickTool.test.js +172 -0
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/queryProxyMocksTool.test.d.ts +1 -0
- package/build/tools/queryProxyMocksTool.test.js +164 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +61 -38
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/RepositoryAnalysis.d.ts +117 -0
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +19 -5
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/analyze-openapi.test.d.ts +1 -0
- package/build/utils/analyze-openapi.test.js +19 -0
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +99 -14
- package/build/utils/branchDiff.test.d.ts +1 -0
- package/build/utils/branchDiff.test.js +109 -0
- package/build/utils/featureFlags.d.ts +16 -0
- package/build/utils/featureFlags.js +20 -0
- package/build/utils/featureFlags.test.js +57 -2
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/grpcMockValidation.test.d.ts +1 -0
- package/build/utils/grpcMockValidation.test.js +41 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +15 -0
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/mockCompatibility.test.d.ts +1 -0
- package/build/utils/mockCompatibility.test.js +79 -0
- package/build/utils/progress.js +10 -5
- package/build/utils/versions.d.ts +1 -0
- package/build/utils/versions.js +2 -0
- package/build/utils/workspaceAuth.d.ts +9 -0
- package/build/utils/workspaceAuth.js +51 -1
- package/build/workspace/workspace.d.ts +28 -14
- package/build/workspace/workspace.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/tools/screenshot.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +6 -22
- package/node_modules/playwright/lib/mcp/test/resultCode.test.js +48 -0
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { validateMockCompatibility, MockProtocol } from "./mockCompatibility.js";
|
|
2
|
+
describe("validateMockCompatibility", () => {
|
|
3
|
+
it("REST-only mocks produce no warnings (DNS interception works correctly)", () => {
|
|
4
|
+
const mocks = [
|
|
5
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://billing:5001/api/users", method: "GET" },
|
|
6
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://ams:4000/api/orders", method: "POST" },
|
|
7
|
+
];
|
|
8
|
+
const result = validateMockCompatibility(mocks);
|
|
9
|
+
expect(result.compatible).toBe(true);
|
|
10
|
+
expect(result.warnings).toHaveLength(0);
|
|
11
|
+
});
|
|
12
|
+
it("REST mocks using loopback URLs get a blocking REST_LOOPBACK_URL warning", () => {
|
|
13
|
+
const mocks = [
|
|
14
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://localhost:8080/api/users", method: "GET" },
|
|
15
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://host.docker.internal:4000/api/orders", method: "POST" },
|
|
16
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://[::1]:8080/api/status", method: "GET" },
|
|
17
|
+
];
|
|
18
|
+
const result = validateMockCompatibility(mocks, { dockerLocalDev: true });
|
|
19
|
+
expect(result.compatible).toBe(false);
|
|
20
|
+
expect(result.hasBlockers).toBe(true);
|
|
21
|
+
const warning = result.warnings.find((w) => w.code === "REST_LOOPBACK_URL");
|
|
22
|
+
expect(warning).toBeDefined();
|
|
23
|
+
expect(warning.blocking).toBe(true);
|
|
24
|
+
expect(warning.affectedMocks).toContain("http://localhost:8080/api/users");
|
|
25
|
+
expect(warning.affectedMocks).toContain("http://host.docker.internal:4000/api/orders");
|
|
26
|
+
expect(warning.affectedMocks).toContain("http://[::1]:8080/api/status");
|
|
27
|
+
});
|
|
28
|
+
it("does not block loopback REST URLs outside Docker local-dev preflight mode", () => {
|
|
29
|
+
const mocks = [
|
|
30
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://localhost:8080/api/users", method: "GET" },
|
|
31
|
+
];
|
|
32
|
+
const result = validateMockCompatibility(mocks);
|
|
33
|
+
expect(result.compatible).toBe(true);
|
|
34
|
+
expect(result.hasBlockers).toBe(false);
|
|
35
|
+
expect(result.warnings.find((w) => w.code === "REST_LOOPBACK_URL")).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
it("Kafka mocks produce no compatibility warning when generation payload shape is valid", () => {
|
|
38
|
+
const mocks = [
|
|
39
|
+
{ protocol: MockProtocol.Kafka, endpointURL: "redpanda:9092", kafkaTopic: "user-events" },
|
|
40
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://ams:4000/api/users", method: "GET" },
|
|
41
|
+
];
|
|
42
|
+
const result = validateMockCompatibility(mocks);
|
|
43
|
+
expect(result.compatible).toBe(true);
|
|
44
|
+
expect(result.hasBlockers).toBe(false);
|
|
45
|
+
expect(result.warnings.find((w) => w.code === "KAFKA_SDK_INCOMPATIBLE")).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
it("gRPC mocks produce GRPC_PERSISTENT_CONN warning", () => {
|
|
48
|
+
const mocks = [
|
|
49
|
+
{ protocol: MockProtocol.Grpc, endpointURL: "grpc://localhost:50051", grpcServiceName: "UserService" },
|
|
50
|
+
{ protocol: MockProtocol.Rest, endpointURL: "http://ams:4000/api/users", method: "GET" },
|
|
51
|
+
];
|
|
52
|
+
const result = validateMockCompatibility(mocks);
|
|
53
|
+
expect(result.compatible).toBe(true);
|
|
54
|
+
const grpcWarning = result.warnings.find((w) => w.code === "GRPC_PERSISTENT_CONN");
|
|
55
|
+
expect(grpcWarning).toBeDefined();
|
|
56
|
+
expect(grpcWarning.affectedMocks).toContain("UserService");
|
|
57
|
+
});
|
|
58
|
+
it("gRPC on non-8080 ports is allowed with only the persistent connection warning", () => {
|
|
59
|
+
const mocks = [
|
|
60
|
+
{ protocol: MockProtocol.Grpc, endpointURL: "partner-accounts:50051", grpcServiceName: "PaymentService" },
|
|
61
|
+
];
|
|
62
|
+
const result = validateMockCompatibility(mocks);
|
|
63
|
+
expect(result.compatible).toBe(true);
|
|
64
|
+
expect(result.hasBlockers).toBe(false);
|
|
65
|
+
expect(result.warnings).toHaveLength(1);
|
|
66
|
+
expect(result.warnings[0].code).toBe("GRPC_PERSISTENT_CONN");
|
|
67
|
+
expect(result.warnings.find((w) => w.code === "GRPC_PORT_MISMATCH")).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
it("gRPC on port 8080 also gets only GRPC_PERSISTENT_CONN", () => {
|
|
70
|
+
const mocks = [
|
|
71
|
+
{ protocol: MockProtocol.Grpc, endpointURL: "some-service:8080", grpcServiceName: "ProfileService" },
|
|
72
|
+
];
|
|
73
|
+
const result = validateMockCompatibility(mocks);
|
|
74
|
+
expect(result.compatible).toBe(true);
|
|
75
|
+
expect(result.hasBlockers).toBe(false);
|
|
76
|
+
expect(result.warnings).toHaveLength(1);
|
|
77
|
+
expect(result.warnings[0].code).toBe("GRPC_PERSISTENT_CONN");
|
|
78
|
+
});
|
|
79
|
+
});
|
package/build/utils/progress.js
CHANGED
|
@@ -10,11 +10,16 @@ export function makeProgressReporter(extra) {
|
|
|
10
10
|
return async (progress, total, message) => {
|
|
11
11
|
const progressToken = extra._meta?.progressToken;
|
|
12
12
|
if (progressToken !== undefined) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
try {
|
|
14
|
+
const notification = {
|
|
15
|
+
method: "notifications/progress",
|
|
16
|
+
params: { progressToken, progress, total, message },
|
|
17
|
+
};
|
|
18
|
+
await extra.sendNotification(notification);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Progress is best-effort; a disconnected client should not fail the tool.
|
|
22
|
+
}
|
|
18
23
|
}
|
|
19
24
|
};
|
|
20
25
|
}
|
package/build/utils/versions.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export const SKYRAMP_IMAGE_VERSION = "v1.3.34";
|
|
2
2
|
export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
|
|
3
3
|
export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
|
|
4
|
+
// Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).
|
|
5
|
+
export const WORKER_CONTROL_PORT = 35142;
|
|
@@ -125,3 +125,12 @@ export interface WorkspaceAuthConfig {
|
|
|
125
125
|
authScheme?: string;
|
|
126
126
|
}
|
|
127
127
|
export declare function getWorkspaceAuthConfig(repositoryPath: string): Promise<WorkspaceAuthConfig>;
|
|
128
|
+
/**
|
|
129
|
+
* Resolve auth header and scheme from workspace config for test generation.
|
|
130
|
+
* Returns null when auth should be skipped (authType absent or "none").
|
|
131
|
+
* Single source of truth — used by both TestGenerationService and batch scenario tool.
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveAuthFromWorkspace(repoPath: string, existingAuthHeader?: string, existingAuthScheme?: string): Promise<{
|
|
134
|
+
authHeader: string;
|
|
135
|
+
authScheme?: string;
|
|
136
|
+
} | null>;
|
|
@@ -77,7 +77,20 @@ export async function getWorkspaceBaseUrl(repositoryPath, testFile, language) {
|
|
|
77
77
|
const absTestDirectory = path.resolve(repositoryPath, s.testDirectory);
|
|
78
78
|
return absTestFile.startsWith(absTestDirectory + path.sep) || absTestFile === absTestDirectory;
|
|
79
79
|
});
|
|
80
|
-
// Step 2:
|
|
80
|
+
// Step 2: longest-prefix match — when multiple services match (e.g.
|
|
81
|
+
// tests/skyramp is a prefix of tests/skyramp/cart), pick the service
|
|
82
|
+
// whose testDirectory is the most specific (longest) prefix of the test file.
|
|
83
|
+
if (matches.length > 1) {
|
|
84
|
+
matches.sort((a, b) => {
|
|
85
|
+
const aLen = path.resolve(repositoryPath, a.testDirectory).length;
|
|
86
|
+
const bLen = path.resolve(repositoryPath, b.testDirectory).length;
|
|
87
|
+
return bLen - aLen; // longest first
|
|
88
|
+
});
|
|
89
|
+
// Keep only services with the longest matching testDirectory length.
|
|
90
|
+
const longestLen = path.resolve(repositoryPath, matches[0].testDirectory).length;
|
|
91
|
+
matches = matches.filter((s) => path.resolve(repositoryPath, s.testDirectory).length === longestLen);
|
|
92
|
+
}
|
|
93
|
+
// Step 3: narrow by language if still ambiguous, but don't eliminate all matches
|
|
81
94
|
if (matches.length > 1 && language) {
|
|
82
95
|
const byLanguage = matches.filter((s) => s.language?.toLowerCase() === language.toLowerCase());
|
|
83
96
|
if (byLanguage.length > 0)
|
|
@@ -330,3 +343,40 @@ export async function getWorkspaceAuthConfig(repositoryPath) {
|
|
|
330
343
|
const authScheme = svc?.api?.authScheme;
|
|
331
344
|
return { authHeader, authType, authScheme };
|
|
332
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Resolve auth header and scheme from workspace config for test generation.
|
|
348
|
+
* Returns null when auth should be skipped (authType absent or "none").
|
|
349
|
+
* Single source of truth — used by both TestGenerationService and batch scenario tool.
|
|
350
|
+
*/
|
|
351
|
+
export async function resolveAuthFromWorkspace(repoPath, existingAuthHeader, existingAuthScheme) {
|
|
352
|
+
if (existingAuthHeader !== undefined)
|
|
353
|
+
return null; // caller already specified
|
|
354
|
+
const wsAuth = await getWorkspaceAuthConfig(repoPath);
|
|
355
|
+
// Skip auth when explicitly none. When authType is absent, still resolve if
|
|
356
|
+
// authHeader or authScheme is set (some workspaces specify only the header).
|
|
357
|
+
if (wsAuth.authType === WorkspaceAuthType.None)
|
|
358
|
+
return null;
|
|
359
|
+
if (!wsAuth.authType && !wsAuth.authHeader && !wsAuth.authScheme)
|
|
360
|
+
return null;
|
|
361
|
+
let resolvedHeader = wsAuth.authHeader ?? getDefaultAuthHeader(wsAuth.authType);
|
|
362
|
+
if (!resolvedHeader && wsAuth.authScheme !== undefined) {
|
|
363
|
+
resolvedHeader = "Authorization";
|
|
364
|
+
}
|
|
365
|
+
if (wsAuth.authType === WorkspaceAuthType.ApiKey && !resolvedHeader) {
|
|
366
|
+
logger.warning("workspace.yml has authType: apiKey but no authHeader is set — " +
|
|
367
|
+
"requests will be sent without an auth header and will likely receive 401/403. " +
|
|
368
|
+
"Add authHeader: <X-Your-Key-Header> to .skyramp/workspace.yml.");
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
if (!resolvedHeader)
|
|
372
|
+
return null;
|
|
373
|
+
logger.info("authHeader resolved from workspace config", {
|
|
374
|
+
authHeader: resolvedHeader,
|
|
375
|
+
authType: wsAuth.authType,
|
|
376
|
+
});
|
|
377
|
+
let authScheme = existingAuthScheme;
|
|
378
|
+
if (isAuthorizationHeaderName(resolvedHeader)) {
|
|
379
|
+
authScheme = authScheme ?? wsAuth.authScheme ?? getAuthScheme(wsAuth.authType);
|
|
380
|
+
}
|
|
381
|
+
return { authHeader: resolvedHeader, authScheme };
|
|
382
|
+
}
|
|
@@ -28,22 +28,25 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
28
28
|
}>>;
|
|
29
29
|
runtimeDetails: z.ZodOptional<z.ZodObject<{
|
|
30
30
|
serverStartCommand: z.ZodOptional<z.ZodString>;
|
|
31
|
-
|
|
31
|
+
serverStopCommand: z.ZodOptional<z.ZodString>;
|
|
32
|
+
runtime: z.ZodEffects<z.ZodNativeEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, import("../types/TestTypes.js").RuntimeEnvironment, unknown>;
|
|
32
33
|
dockerNetwork: z.ZodOptional<z.ZodString>;
|
|
33
34
|
k8sNamespace: z.ZodOptional<z.ZodString>;
|
|
34
35
|
k8sContext: z.ZodOptional<z.ZodString>;
|
|
35
36
|
}, "strict", z.ZodTypeAny, {
|
|
36
|
-
runtime: "
|
|
37
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
37
38
|
dockerNetwork?: string | undefined;
|
|
38
39
|
k8sNamespace?: string | undefined;
|
|
39
40
|
k8sContext?: string | undefined;
|
|
40
41
|
serverStartCommand?: string | undefined;
|
|
42
|
+
serverStopCommand?: string | undefined;
|
|
41
43
|
}, {
|
|
42
|
-
runtime
|
|
44
|
+
runtime?: unknown;
|
|
43
45
|
dockerNetwork?: string | undefined;
|
|
44
46
|
k8sNamespace?: string | undefined;
|
|
45
47
|
k8sContext?: string | undefined;
|
|
46
48
|
serverStartCommand?: string | undefined;
|
|
49
|
+
serverStopCommand?: string | undefined;
|
|
47
50
|
}>>;
|
|
48
51
|
}, "strict", z.ZodTypeAny, {
|
|
49
52
|
serviceName: string;
|
|
@@ -59,11 +62,12 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
59
62
|
baseUrl?: string | undefined;
|
|
60
63
|
} | undefined;
|
|
61
64
|
runtimeDetails?: {
|
|
62
|
-
runtime: "
|
|
65
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
63
66
|
dockerNetwork?: string | undefined;
|
|
64
67
|
k8sNamespace?: string | undefined;
|
|
65
68
|
k8sContext?: string | undefined;
|
|
66
69
|
serverStartCommand?: string | undefined;
|
|
70
|
+
serverStopCommand?: string | undefined;
|
|
67
71
|
} | undefined;
|
|
68
72
|
}, {
|
|
69
73
|
serviceName: string;
|
|
@@ -79,11 +83,12 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
79
83
|
baseUrl?: string | undefined;
|
|
80
84
|
} | undefined;
|
|
81
85
|
runtimeDetails?: {
|
|
82
|
-
runtime
|
|
86
|
+
runtime?: unknown;
|
|
83
87
|
dockerNetwork?: string | undefined;
|
|
84
88
|
k8sNamespace?: string | undefined;
|
|
85
89
|
k8sContext?: string | undefined;
|
|
86
90
|
serverStartCommand?: string | undefined;
|
|
91
|
+
serverStopCommand?: string | undefined;
|
|
87
92
|
} | undefined;
|
|
88
93
|
}>;
|
|
89
94
|
export declare const workspaceConfigSchema: z.ZodObject<{
|
|
@@ -143,22 +148,25 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
143
148
|
}>>;
|
|
144
149
|
runtimeDetails: z.ZodOptional<z.ZodObject<{
|
|
145
150
|
serverStartCommand: z.ZodOptional<z.ZodString>;
|
|
146
|
-
|
|
151
|
+
serverStopCommand: z.ZodOptional<z.ZodString>;
|
|
152
|
+
runtime: z.ZodEffects<z.ZodNativeEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, import("../types/TestTypes.js").RuntimeEnvironment, unknown>;
|
|
147
153
|
dockerNetwork: z.ZodOptional<z.ZodString>;
|
|
148
154
|
k8sNamespace: z.ZodOptional<z.ZodString>;
|
|
149
155
|
k8sContext: z.ZodOptional<z.ZodString>;
|
|
150
156
|
}, "strict", z.ZodTypeAny, {
|
|
151
|
-
runtime: "
|
|
157
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
152
158
|
dockerNetwork?: string | undefined;
|
|
153
159
|
k8sNamespace?: string | undefined;
|
|
154
160
|
k8sContext?: string | undefined;
|
|
155
161
|
serverStartCommand?: string | undefined;
|
|
162
|
+
serverStopCommand?: string | undefined;
|
|
156
163
|
}, {
|
|
157
|
-
runtime
|
|
164
|
+
runtime?: unknown;
|
|
158
165
|
dockerNetwork?: string | undefined;
|
|
159
166
|
k8sNamespace?: string | undefined;
|
|
160
167
|
k8sContext?: string | undefined;
|
|
161
168
|
serverStartCommand?: string | undefined;
|
|
169
|
+
serverStopCommand?: string | undefined;
|
|
162
170
|
}>>;
|
|
163
171
|
}, "strict", z.ZodTypeAny, {
|
|
164
172
|
serviceName: string;
|
|
@@ -174,11 +182,12 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
174
182
|
baseUrl?: string | undefined;
|
|
175
183
|
} | undefined;
|
|
176
184
|
runtimeDetails?: {
|
|
177
|
-
runtime: "
|
|
185
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
178
186
|
dockerNetwork?: string | undefined;
|
|
179
187
|
k8sNamespace?: string | undefined;
|
|
180
188
|
k8sContext?: string | undefined;
|
|
181
189
|
serverStartCommand?: string | undefined;
|
|
190
|
+
serverStopCommand?: string | undefined;
|
|
182
191
|
} | undefined;
|
|
183
192
|
}, {
|
|
184
193
|
serviceName: string;
|
|
@@ -194,11 +203,12 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
194
203
|
baseUrl?: string | undefined;
|
|
195
204
|
} | undefined;
|
|
196
205
|
runtimeDetails?: {
|
|
197
|
-
runtime
|
|
206
|
+
runtime?: unknown;
|
|
198
207
|
dockerNetwork?: string | undefined;
|
|
199
208
|
k8sNamespace?: string | undefined;
|
|
200
209
|
k8sContext?: string | undefined;
|
|
201
210
|
serverStartCommand?: string | undefined;
|
|
211
|
+
serverStopCommand?: string | undefined;
|
|
202
212
|
} | undefined;
|
|
203
213
|
}>, "many">>;
|
|
204
214
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -227,11 +237,12 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
227
237
|
baseUrl?: string | undefined;
|
|
228
238
|
} | undefined;
|
|
229
239
|
runtimeDetails?: {
|
|
230
|
-
runtime: "
|
|
240
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
231
241
|
dockerNetwork?: string | undefined;
|
|
232
242
|
k8sNamespace?: string | undefined;
|
|
233
243
|
k8sContext?: string | undefined;
|
|
234
244
|
serverStartCommand?: string | undefined;
|
|
245
|
+
serverStopCommand?: string | undefined;
|
|
235
246
|
} | undefined;
|
|
236
247
|
}[] | undefined;
|
|
237
248
|
}, {
|
|
@@ -260,11 +271,12 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
260
271
|
baseUrl?: string | undefined;
|
|
261
272
|
} | undefined;
|
|
262
273
|
runtimeDetails?: {
|
|
263
|
-
runtime
|
|
274
|
+
runtime?: unknown;
|
|
264
275
|
dockerNetwork?: string | undefined;
|
|
265
276
|
k8sNamespace?: string | undefined;
|
|
266
277
|
k8sContext?: string | undefined;
|
|
267
278
|
serverStartCommand?: string | undefined;
|
|
279
|
+
serverStopCommand?: string | undefined;
|
|
268
280
|
} | undefined;
|
|
269
281
|
}[] | undefined;
|
|
270
282
|
}>;
|
|
@@ -301,11 +313,12 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
|
|
|
301
313
|
baseUrl?: string | undefined;
|
|
302
314
|
} | undefined;
|
|
303
315
|
runtimeDetails?: {
|
|
304
|
-
runtime
|
|
316
|
+
runtime?: unknown;
|
|
305
317
|
dockerNetwork?: string | undefined;
|
|
306
318
|
k8sNamespace?: string | undefined;
|
|
307
319
|
k8sContext?: string | undefined;
|
|
308
320
|
serverStartCommand?: string | undefined;
|
|
321
|
+
serverStopCommand?: string | undefined;
|
|
309
322
|
} | undefined;
|
|
310
323
|
}[] | undefined;
|
|
311
324
|
}, {
|
|
@@ -334,11 +347,12 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
|
|
|
334
347
|
baseUrl?: string | undefined;
|
|
335
348
|
} | undefined;
|
|
336
349
|
runtimeDetails?: {
|
|
337
|
-
runtime: "
|
|
350
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
338
351
|
dockerNetwork?: string | undefined;
|
|
339
352
|
k8sNamespace?: string | undefined;
|
|
340
353
|
k8sContext?: string | undefined;
|
|
341
354
|
serverStartCommand?: string | undefined;
|
|
355
|
+
serverStopCommand?: string | undefined;
|
|
342
356
|
} | undefined;
|
|
343
357
|
}[] | undefined;
|
|
344
358
|
}>;
|
|
@@ -14,6 +14,7 @@ import { execFile } from "child_process";
|
|
|
14
14
|
import { promisify } from "util";
|
|
15
15
|
import yaml from "js-yaml";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import { runtimeEnvironmentInputSchema } from "../types/TestTypes.js";
|
|
17
18
|
const execFileAsync = promisify(execFile);
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Constants
|
|
@@ -57,7 +58,8 @@ export const serviceSchema = z
|
|
|
57
58
|
runtimeDetails: z
|
|
58
59
|
.object({
|
|
59
60
|
serverStartCommand: z.string().optional(),
|
|
60
|
-
|
|
61
|
+
serverStopCommand: z.string().optional(),
|
|
62
|
+
runtime: runtimeEnvironmentInputSchema,
|
|
61
63
|
dockerNetwork: z.string().optional(),
|
|
62
64
|
k8sNamespace: z.string().optional(),
|
|
63
65
|
k8sContext: z.string().optional(),
|
|
@@ -40,7 +40,7 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
40
40
|
schema: {
|
|
41
41
|
name: "browser_take_screenshot",
|
|
42
42
|
title: "Take a screenshot",
|
|
43
|
-
description: `Take a screenshot of the current page. You can't perform actions based on the screenshot
|
|
43
|
+
description: `Take a diagnostic screenshot of the current page for your own inspection while recording. This is NOT a test assertion: the image is not recorded into the trace and will NOT appear in the generated test (it is dropped at export). You can't perform actions based on the screenshot \u2014 use browser_snapshot for actions. To ASSERT how a page/element/region looks (visual regression \u2014 e.g. an instruction to "take a screenshot" or capture a baseline), use browser_visual_snapshot instead, which records a toHaveScreenshot() assertion.`,
|
|
44
44
|
inputSchema: screenshotSchema,
|
|
45
45
|
type: "readOnly"
|
|
46
46
|
},
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var resultCode_exports = {};
|
|
20
|
+
__export(resultCode_exports, {
|
|
21
|
+
rewriteResultCodeText: () => rewriteResultCodeText
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(resultCode_exports);
|
|
24
|
+
function rewriteResultCodeText(text, newCode) {
|
|
25
|
+
if (!text.includes("await page."))
|
|
26
|
+
return null;
|
|
27
|
+
const fence = text.match(/```(?:js|javascript)\n/);
|
|
28
|
+
if (fence && fence.index !== void 0) {
|
|
29
|
+
const codeStart = fence.index + fence[0].length;
|
|
30
|
+
const codeEnd = text.indexOf("\n```", codeStart);
|
|
31
|
+
if (codeEnd >= 0)
|
|
32
|
+
return text.slice(0, codeStart) + newCode + text.slice(codeEnd);
|
|
33
|
+
}
|
|
34
|
+
const lines = text.split("\n");
|
|
35
|
+
const codeLineIdx = lines.findIndex((l) => l.includes("await page."));
|
|
36
|
+
if (codeLineIdx >= 0) {
|
|
37
|
+
lines[codeLineIdx] = newCode;
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
rewriteResultCodeText
|
|
45
|
+
});
|
|
@@ -48,6 +48,7 @@ var import_assertTool = require("./assertTool");
|
|
|
48
48
|
var import_assertApiRequestTool = require("./assertApiRequestTool");
|
|
49
49
|
var import_loadTraceTool = require("./loadTraceTool");
|
|
50
50
|
var import_skyRampImport = require("./skyRampImport");
|
|
51
|
+
var import_resultCode = require("./resultCode");
|
|
51
52
|
var import_mouseActionTool = require("./mouseActionTool");
|
|
52
53
|
var import_visualSnapshotTool = require("./visualSnapshotTool");
|
|
53
54
|
var import_gojsTool = require("./gojsTool");
|
|
@@ -1847,28 +1848,11 @@ Cell row ${params.row}, column ${params.column} has "${actual}", expected "${par
|
|
|
1847
1848
|
_modifyResultCode(result, newCode) {
|
|
1848
1849
|
const modified = { ...result, content: [...result.content] };
|
|
1849
1850
|
for (let i = 0; i < modified.content.length; i++) {
|
|
1850
|
-
if (modified.content[i].type
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
modified.content[i] = {
|
|
1856
|
-
...modified.content[i],
|
|
1857
|
-
text: codeBlockMatch[1] + newCode + "\n" + codeBlockMatch[3]
|
|
1858
|
-
};
|
|
1859
|
-
} else {
|
|
1860
|
-
const lines = text.split("\n");
|
|
1861
|
-
const codeLineIdx = lines.findIndex((l) => l.includes("await page."));
|
|
1862
|
-
if (codeLineIdx >= 0) {
|
|
1863
|
-
lines[codeLineIdx] = newCode;
|
|
1864
|
-
modified.content[i] = {
|
|
1865
|
-
...modified.content[i],
|
|
1866
|
-
text: lines.join("\n")
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1851
|
+
if (modified.content[i].type !== "text")
|
|
1852
|
+
continue;
|
|
1853
|
+
const rewritten = (0, import_resultCode.rewriteResultCodeText)(modified.content[i].text || "", newCode);
|
|
1854
|
+
if (rewritten !== null)
|
|
1855
|
+
modified.content[i] = { ...modified.content[i], text: rewritten };
|
|
1872
1856
|
}
|
|
1873
1857
|
return modified;
|
|
1874
1858
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_resultCode = require("../skyramp/resultCode");
|
|
4
|
+
const NEW_CODE = 'await page.locator("#viewer-cols").getByRole("button", { name: "TextBlock" }).click();';
|
|
5
|
+
function clickResult(snapshot, fence = "js") {
|
|
6
|
+
return "### Ran Playwright code\n\n```" + fence + '\nawait page.getByRole("button", { name: "TextBlock" }).click();\n```\n\n### Page state\n- Page Snapshot:\n```yaml\n' + snapshot + "\n```\n";
|
|
7
|
+
}
|
|
8
|
+
function bigSnapshot(chars) {
|
|
9
|
+
let s = "";
|
|
10
|
+
let i = 0;
|
|
11
|
+
while (s.length < chars) {
|
|
12
|
+
s += `- text "\u5024 ${i} \u5186 ${i * 7}" [ref=e${i}]
|
|
13
|
+
`;
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
return s;
|
|
17
|
+
}
|
|
18
|
+
(0, import_vitest.describe)("rewriteResultCodeText", () => {
|
|
19
|
+
(0, import_vitest.it)("replaces code inside a ```js fence and keeps the snapshot intact", () => {
|
|
20
|
+
const snapshot = bigSnapshot(2e3);
|
|
21
|
+
const out = (0, import_resultCode.rewriteResultCodeText)(clickResult(snapshot, "js"), NEW_CODE);
|
|
22
|
+
(0, import_vitest.expect)(out).toContain(NEW_CODE);
|
|
23
|
+
(0, import_vitest.expect)(out).not.toContain('await page.getByRole("button", { name: "TextBlock" }).click();');
|
|
24
|
+
(0, import_vitest.expect)(out).toContain(snapshot);
|
|
25
|
+
(0, import_vitest.expect)(out).toContain("```yaml");
|
|
26
|
+
});
|
|
27
|
+
(0, import_vitest.it)("also handles a ```javascript fence", () => {
|
|
28
|
+
const out = (0, import_resultCode.rewriteResultCodeText)(clickResult(bigSnapshot(500), "javascript"), NEW_CODE);
|
|
29
|
+
(0, import_vitest.expect)(out).toContain(NEW_CODE);
|
|
30
|
+
});
|
|
31
|
+
(0, import_vitest.it)("falls back to line replacement when there is no fenced code block", () => {
|
|
32
|
+
const text = '### Result\nawait page.getByRole("button").click();\n\n### Page state\n' + bigSnapshot(500);
|
|
33
|
+
const out = (0, import_resultCode.rewriteResultCodeText)(text, NEW_CODE);
|
|
34
|
+
(0, import_vitest.expect)(out).toContain(NEW_CODE);
|
|
35
|
+
(0, import_vitest.expect)(out).not.toContain('await page.getByRole("button").click();');
|
|
36
|
+
});
|
|
37
|
+
(0, import_vitest.it)("returns null when there is no recorded code to rewrite", () => {
|
|
38
|
+
(0, import_vitest.expect)((0, import_resultCode.rewriteResultCodeText)("### Page state\n- Page Snapshot:\n" + bigSnapshot(500), NEW_CODE)).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
(0, import_vitest.it)("is fast on a ~1.2MB result (no catastrophic backtracking)", () => {
|
|
41
|
+
const text = clickResult(bigSnapshot(12e5), "js");
|
|
42
|
+
const start = Date.now();
|
|
43
|
+
const out = (0, import_resultCode.rewriteResultCodeText)(text, NEW_CODE);
|
|
44
|
+
const elapsedMs = Date.now() - start;
|
|
45
|
+
(0, import_vitest.expect)(out).toContain(NEW_CODE);
|
|
46
|
+
(0, import_vitest.expect)(elapsedMs).toBeLessThan(2e3);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-ldw-beta",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -71,7 +71,7 @@
|
|
|
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.14.
|
|
74
|
+
"playwright": "file:vendor/skyramp-playwright-1.58.2-skyramp.8.14.2.tgz",
|
|
75
75
|
"simple-git": "^3.30.0",
|
|
76
76
|
"typescript": "^5.8.3",
|
|
77
77
|
"zod": "^3.25.3"
|