@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.
Files changed (76) hide show
  1. package/build/index.js +1 -1
  2. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
  3. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
  4. package/build/prompts/personas.d.ts +2 -2
  5. package/build/prompts/personas.js +2 -2
  6. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +3 -0
  7. package/build/prompts/sut-setup/shared.js +1 -3
  8. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +1 -1
  9. package/build/prompts/test-maintenance/drift-analysis-prompt.js +10 -5
  10. package/build/prompts/test-maintenance/driftAnalysisSections.js +1 -4
  11. package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +7 -1
  12. package/build/prompts/test-maintenance/driftAnalysisShared.js +19 -1
  13. package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +0 -6
  14. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +0 -16
  15. package/build/prompts/test-recommendation/recommendationShared.js +1 -1
  16. package/build/prompts/testbot/testbot-prompts.js +8 -8
  17. package/build/resources/testbotResource.js +1 -1
  18. package/build/services/TestDiscoveryService.d.ts +0 -1
  19. package/build/services/TestDiscoveryService.js +13 -33
  20. package/build/services/TestDiscoveryService.test.js +136 -1
  21. package/build/services/TestExecutionService.d.ts +1 -1
  22. package/build/tools/executeSkyrampTestTool.js +40 -71
  23. package/build/tools/submitReportTool.d.ts +20 -20
  24. package/build/tools/submitReportTool.js +113 -31
  25. package/build/tools/submitReportTool.test.js +411 -94
  26. package/build/tools/test-management/actionsTool.js +135 -53
  27. package/build/tools/test-management/actionsTool.test.d.ts +1 -0
  28. package/build/tools/test-management/actionsTool.test.js +297 -0
  29. package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
  30. package/build/tools/test-management/analyzeChangesTool.js +63 -152
  31. package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
  32. package/build/tools/test-management/analyzeTestHealthTool.js +24 -157
  33. package/build/tools/test-management/analyzeTestHealthTool.test.js +15 -215
  34. package/build/tools/workspace/initializeWorkspaceTool.js +35 -15
  35. package/build/types/FrontendIntegration.d.ts +25 -0
  36. package/build/types/FrontendIntegration.js +19 -0
  37. package/build/types/RepositoryAnalysis.d.ts +8 -8
  38. package/build/types/TestAnalysis.d.ts +16 -50
  39. package/build/types/TestAnalysis.js +0 -28
  40. package/build/types/TestTypes.d.ts +3 -1
  41. package/build/types/TestTypes.js +3 -1
  42. package/build/utils/AnalysisStateManager.d.ts +13 -1
  43. package/build/utils/docker.test.js +1 -1
  44. package/build/utils/featureFlags.d.ts +1 -1
  45. package/build/utils/featureFlags.js +1 -1
  46. package/build/utils/frontendIntegration.d.ts +9 -0
  47. package/build/utils/frontendIntegration.js +237 -0
  48. package/build/utils/frontendIntegration.test.d.ts +1 -0
  49. package/build/utils/frontendIntegration.test.js +229 -0
  50. package/build/utils/pr-comment-parser.d.ts +3 -3
  51. package/build/utils/pr-comment-parser.js +6 -6
  52. package/build/utils/pr-comment-parser.test.js +3 -3
  53. package/build/utils/repoScanner.d.ts +7 -0
  54. package/build/utils/repoScanner.js +14 -6
  55. package/build/utils/repoScanner.test.js +9 -9
  56. package/build/utils/routeParsers.d.ts +32 -0
  57. package/build/utils/routeParsers.js +205 -2
  58. package/build/utils/routeParsers.test.js +279 -29
  59. package/build/utils/sourceRouteExtractor.js +174 -5
  60. package/build/utils/sourceRouteExtractor.test.js +173 -0
  61. package/build/utils/utils.d.ts +11 -0
  62. package/build/utils/utils.js +19 -0
  63. package/build/utils/utils.test.js +23 -1
  64. package/build/utils/versions.d.ts +3 -3
  65. package/build/utils/versions.js +1 -1
  66. package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
  67. package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
  68. package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
  69. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
  70. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
  71. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
  72. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
  73. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  74. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  75. package/node_modules/playwright/package.json +1 -1
  76. package/package.json +3 -3
@@ -578,7 +578,6 @@ describe("TestDiscoveryService — scoreRelevanceByContent regex safety", () =>
578
578
  writeFile("tests/test_order.py", `import requests\nrequests.get("/api/orderXitems")`);
579
579
  // "order.items" in changedResources — dot must be escaped
580
580
  const result = await service.discoverTests(tmpDir, { changedResources: ["order.items"] });
581
- const t = result.tests.find(f => f.testFile.includes("test_order.py"));
582
581
  // should NOT match (orderXitems ≠ order.items)
583
582
  expect(result.relevantExternalTestPaths.some(p => p.includes("test_order.py"))).toBe(false);
584
583
  });
@@ -623,4 +622,140 @@ describe("TestDiscoveryService — hasFrontendChanges=false guard", () => {
623
622
  // .e2e.ts should now be promoted (UI_TEST_EXT includes .e2e.ts)
624
623
  expect(result.tests.some(t => t.testFile.includes("onboarding.e2e.ts"))).toBe(true);
625
624
  });
625
+ // ── Step 3: API test promotion ─────────────────────────────────────────────
626
+ // Uses a minimal mirror of the prefect-testb file structure to validate that
627
+ // the content-scored promotion finds test_deployments.py (which imports from
628
+ // the changed module) but not unrelated schedule/events test files.
629
+ describe("Step 3 API promotion (hasApiChanges)", () => {
630
+ // Realistic import lines taken from prefect-testb actual files.
631
+ // test_deployments.py imports from schemas.actions — matches the import-context
632
+ // pattern "schemas[./]actions" built when schemas/actions.py changes.
633
+ const TEST_DEPLOYMENTS_CONTENT = `\
634
+ import pytest
635
+ from httpx import AsyncClient
636
+ from prefect.server.schemas.actions import DeploymentCreate, DeploymentUpdate
637
+ from prefect.server import models, schemas
638
+
639
+ class TestCreateDeployment:
640
+ async def test_create_deployment(self, client):
641
+ response = await client.post("/api/deployments/", json={"name": "test"})
642
+ assert response.status_code == 201
643
+
644
+ class TestCreateFlowRunFromDeployment:
645
+ async def test_create_flow_run(self, client, deployment):
646
+ response = await client.post(f"/api/deployments/{deployment.id}/create_flow_run", json={})
647
+ assert response.status_code == 201
648
+ `;
649
+ // test_deployment_schedules.py imports from schedules, NOT schemas.actions
650
+ const TEST_DEPLOYMENT_SCHEDULES_CONTENT = `\
651
+ import pytest
652
+ from prefect.server import models, schemas
653
+ from prefect.server.schemas.core import DeploymentSchedule
654
+
655
+ class TestReadDeploymentSchedules:
656
+ async def test_read_schedules(self, client, deployment):
657
+ response = await client.get(f"/api/deployments/{deployment.id}/schedules")
658
+ assert response.status_code == 200
659
+ `;
660
+ // events/actions file has no deployment schema imports at all
661
+ const TEST_EVENTS_ACTIONS_CONTENT = `\
662
+ import pytest
663
+ from prefect.server.events import actions
664
+ from prefect.server.events.clients import AssertingEventsClient
665
+
666
+ class TestPausingWorkQueue:
667
+ async def test_pausing(self, session):
668
+ action = actions.PauseWorkQueue(work_queue_id="test")
669
+ await action.act(event=None)
670
+ `;
671
+ // test_schedules.py — no deployment imports
672
+ const TEST_SCHEDULES_CONTENT = `\
673
+ import pytest
674
+ from prefect.server.schemas.schedules import CronSchedule, IntervalSchedule
675
+
676
+ class TestCronSchedule:
677
+ def test_cron_parse(self):
678
+ s = CronSchedule(cron="0 * * * *")
679
+ assert s.cron == "0 * * * *"
680
+ `;
681
+ it("content-matches test_deployments.py when schemas/actions.py changed", async () => {
682
+ // Mirror the Prefect directory structure
683
+ writeFile("tests/server/orchestration/api/test_deployments.py", TEST_DEPLOYMENTS_CONTENT);
684
+ writeFile("tests/server/orchestration/api/test_deployment_schedules.py", TEST_DEPLOYMENT_SCHEDULES_CONTENT);
685
+ writeFile("tests/events/server/actions/test_pausing_resuming_work_queue.py", TEST_EVENTS_ACTIONS_CONTENT);
686
+ writeFile("tests/test_schedules.py", TEST_SCHEDULES_CONTENT);
687
+ // changedResources = ["unknown"] because "actions" is filtered by GENERIC_SCHEMA_NAMES
688
+ // in analyzeChangesTool. Step 3 is skipped for ["unknown"] sentinel.
689
+ // So we use the import-context pattern directly via hasApiChanges + a non-unknown resource.
690
+ // Simulate: schemas/actions.py changed, but it produced a non-generic parent resource.
691
+ // For this test, use changedResources derived from the parent path "schemas":
692
+ // "schemas" is in GENERIC_SCHEMA_NAMES so would be filtered → ["unknown"].
693
+ // Step 3 should be skipped → test_deployments.py not promoted via Step 3.
694
+ const resultUnknown = await service.discoverTests(tmpDir, {
695
+ changedResources: ["unknown"],
696
+ });
697
+ // Step 3 skipped for ["unknown"] — no content-matched files
698
+ const promotedUnknown = resultUnknown.relevantExternalTestPaths;
699
+ expect(promotedUnknown.some(p => p.includes("test_deployments.py"))).toBe(false);
700
+ });
701
+ it("content-matches test_deployments.py when api/deployments.py changed (TM17 scenario)", async () => {
702
+ writeFile("tests/server/orchestration/api/test_deployments.py", TEST_DEPLOYMENTS_CONTENT);
703
+ writeFile("tests/server/orchestration/api/test_deployment_schedules.py", TEST_DEPLOYMENT_SCHEDULES_CONTENT);
704
+ writeFile("tests/events/server/actions/test_pausing_resuming_work_queue.py", TEST_EVENTS_ACTIONS_CONTENT);
705
+ writeFile("tests/test_schedules.py", TEST_SCHEDULES_CONTENT);
706
+ // changedResources = ["deployments"] (from api/deployments.py route file)
707
+ // Content pattern: /import\s+[^\n]*["']\/api\/deployments/i or /deployments/ prefix
708
+ // test_deployments.py calls POST /api/deployments/ → content-matched in Step 3
709
+ const result = await service.discoverTests(tmpDir, {
710
+ changedResources: ["deployments"],
711
+ });
712
+ const promoted = result.relevantExternalTestPaths;
713
+ // test_deployments.py: calls /api/deployments/ → content-matched → promoted unconditionally
714
+ expect(promoted.some(p => p.includes("test_deployments.py"))).toBe(true);
715
+ // test_deployment_schedules.py: contains /api/deployments/ in URL calls → also content-matched
716
+ // (the word "deployments" appears in its URL strings even though it tests schedules)
717
+ // This is expected — it IS in scope for a deployment endpoint change.
718
+ // test_schedules.py: no /deployments/ URL → scores 0 → fallback only (may or may not be in promoted)
719
+ // test_pausing_resuming: no /deployments/ URL → fallback only
720
+ });
721
+ it("API external tests no longer pre-loaded (agent greps instead)", async () => {
722
+ // External API tests are discovered by the agent via the grep instruction in the
723
+ // drift analysis prompt. discoverTests in PR mode (changedResources=[]) returns
724
+ // only Skyramp tests; external tests are excluded entirely.
725
+ writeFile("tests/server/orchestration/api/test_deployments.py", [
726
+ "from prefect.server import utils",
727
+ "",
728
+ "async def test_max_active_runs(client, deployment):",
729
+ " count = utils.calculate_max_runs(deployment.id, db)",
730
+ " assert count >= 0",
731
+ ].join("\n"));
732
+ const result = await service.discoverTests(tmpDir, {
733
+ changedResources: [], // PR mode, no pre-loading of external API tests
734
+ });
735
+ // No external tests pre-loaded — agent discovers via grep
736
+ expect(result.relevantExternalTestPaths.some(p => p.includes("test_deployments.py"))).toBe(false);
737
+ });
738
+ it("Step 3 does not run when changedResources is empty (no API changes)", async () => {
739
+ writeFile("tests/server/orchestration/api/test_deployments.py", TEST_DEPLOYMENTS_CONTENT);
740
+ const result = await service.discoverTests(tmpDir, {
741
+ changedResources: [],
742
+ });
743
+ // changedResources=[] means PR mode with no endpoints — external tests excluded entirely
744
+ expect(result.relevantExternalTestPaths).toHaveLength(0);
745
+ });
746
+ it("Step 1 path-token scoring finds test_work_queues.py for changedResources=[work_queues]", async () => {
747
+ // Pattern Q scenario: work_queues.py changed → changedResources=["work_queues"]
748
+ // test_work_queues.py scores >0 by name (has "work_queues" token)
749
+ writeFile("tests/server/orchestration/api/test_work_queues.py", 'import pytest\nfrom prefect.server.api import work_queues\n\nasync def test_read_work_queue_status(client, work_queue):\n r = await client.get(f"/api/work_queues/{work_queue.id}/status")\n assert r.status_code == 200\n');
750
+ writeFile("tests/server/orchestration/api/test_flows.py", "import pytest\nfrom prefect.server import schemas\n\nasync def test_create_flow(client): pass\n");
751
+ const result = await service.discoverTests(tmpDir, {
752
+ changedResources: ["work_queues"],
753
+ });
754
+ const promoted = result.relevantExternalTestPaths;
755
+ // test_work_queues.py → Step 1 name-scores it → in relevantExternal before Step 3
756
+ expect(promoted.some(p => p.includes("test_work_queues.py"))).toBe(true);
757
+ // test_flows.py → scores 0 → not promoted via Step 1
758
+ // (may appear in Step 3 fallback but is not guaranteed)
759
+ });
760
+ });
626
761
  });
@@ -1,5 +1,5 @@
1
1
  import { TestExecutionResult, BatchExecutionResult, TestExecutionOptions, ProgressCallback } from "../types/TestExecution.js";
2
- export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.30";
2
+ export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.32";
3
3
  export declare const PLAYWRIGHT_CONFIG_FILES: string[];
4
4
  export declare const EXCLUDED_MOUNT_ITEMS: string[];
5
5
  export declare const MOUNT_NULL_ITEMS: string[];
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import path from "path";
2
3
  import { stripVTControlCharacters } from "util";
3
4
  import { TestExecutionService } from "../services/TestExecutionService.js";
4
5
  import { AnalyticsService } from "../services/AnalyticsService.js";
@@ -7,43 +8,17 @@ import { getWorkspaceBaseUrl } from "../utils/workspaceAuth.js";
7
8
  import { ProgrammingLanguage, TestType } from "../types/TestTypes.js";
8
9
  import { StateManager } from "../utils/AnalysisStateManager.js";
9
10
  import { logger } from "../utils/logger.js";
11
+ import { toolError } from "../utils/utils.js";
10
12
  const TOOL_NAME = "skyramp_execute_test";
11
13
  export function registerExecuteSkyrampTestTool(server) {
12
14
  server.registerTool(TOOL_NAME, {
13
- description: `Execute a Skyramp-generated test in isolated containerized environments for reliable, deterministic testing.
14
-
15
- Skyramp is a comprehensive testing platform that generates reliable, out-of-the-box functional and performance tests with AI-powered scaffolding. The test execution engine runs your generated tests in controlled, isolated environments to ensure consistent results.
16
-
17
- KEY FEATURES:
18
- • Isolated Execution: Tests run in containerized environments for consistency
19
- • Multi-Language Support: Execute tests written in Python, Java, JavaScript, or TypeScript
20
- • Out-of-the-Box Execution: Generated tests work immediately without modification
21
- • StateFile Integration: Optionally write execution results back to stateFile for health analysis
22
-
23
- REQUIRED PARAMETERS:
24
- - language: Programming language of your test file (python, javascript, typescript, java)
25
- - testType: Type of test to execute (smoke, contract, fuzz, integration, load, ui, e2e)
26
- - testFile: Absolute path to the generated test file to execute
27
- - token: Authentication token for your service (use empty string if no authentication required)
28
-
29
- OPTIONAL PARAMETERS:
30
- - stateFile: Path to state file from skyramp_analyze_changes. When provided, execution results (passed/failed, errors, duration) will be written back to enrich test health analysis.
31
-
32
- AUTHENTICATION:
33
- Pass an empty string as the token parameter — the server automatically injects SKYRAMP_TEST_TOKEN from the environment. The executor applies it with the correct header defined in workspace.yml:
34
- - bearer / oauth / token (or custom via authScheme) → Authorization: <scheme> <token>
35
- - cookie / session → Cookie: <token>
36
- - apiKey → <authHeader>: <token> (header name from workspace.yml)
37
- - basic → Authorization: Basic <token>
38
- Use an empty string only if the endpoint is confirmed unauthenticated.
39
- IMPORTANT NOTES:
40
- - First-time usage may take longer as Docker images are downloaded
41
- - Tests run in isolated containers for maximum reliability
42
- - Generated tests are designed to work out-of-the-box without modification
43
- - Results include detailed execution logs and test outcomes
44
- - When stateFile is provided, results are merged back for use by skyramp_actions
45
-
46
- For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
15
+ description: `Execute a Skyramp-generated test in isolated containerized environments for reliable, deterministic testing. Call this once a test file exists on disk (from a skyramp_*_test_generation tool). First-time execution may take longer while Docker images download — this is expected, not a failure.`,
16
+ annotations: {
17
+ readOnlyHint: false,
18
+ destructiveHint: false,
19
+ idempotentHint: false,
20
+ openWorldHint: true,
21
+ },
47
22
  inputSchema: {
48
23
  workspacePath: z
49
24
  .string()
@@ -53,13 +28,18 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
53
28
  .describe("Programming language of the test file to execute (e.g., python, javascript, typescript, java)"),
54
29
  testType: z
55
30
  .nativeEnum(TestType)
56
- .describe("Type of the test to execute."),
31
+ .describe("Type of the test to execute (e.g., smoke, contract, fuzz, integration, load, ui, e2e)."),
57
32
  testFile: z
58
33
  .string()
59
- .describe("ALWAYS USE ABSOLUTE PATH to the test file to execute"),
34
+ .describe("Absolute path to the test file to execute."),
60
35
  token: z
61
36
  .string()
62
- .describe("Skyramp authentication token for test execution. USE EMPTY STRING WHEN USER CONFIRMS 'No token required'"),
37
+ .optional()
38
+ .describe("Explicit Skyramp authentication token for test execution. Omit this parameter to use SKYRAMP_TEST_TOKEN from the environment. An empty string is passed through as a literal token value, not a 'no auth' signal — use `unauthenticated: true` for tests that must run without credentials."),
39
+ unauthenticated: z
40
+ .boolean()
41
+ .optional()
42
+ .describe("Set true to force this test execution to carry NO auth token, even if SKYRAMP_TEST_TOKEN is set in the environment or a token was passed. Use for tests that must assert 401/403 unauthenticated behavior."),
63
43
  playwrightSaveStoragePath: z
64
44
  .string()
65
45
  .optional()
@@ -106,6 +86,10 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
106
86
  let didSetSkyrampBaseUrl = false;
107
87
  let useHostNetwork = false;
108
88
  try {
89
+ if (!path.isAbsolute(params.testFile)) {
90
+ errorResult = toolError(`testFile must be an absolute path, got: ${params.testFile}`);
91
+ return errorResult;
92
+ }
109
93
  // Send initial progress
110
94
  await sendProgress(0, 100, "Starting test execution...");
111
95
  // Always resolve workspace config for dockerNetwork (host networking)
@@ -119,25 +103,25 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
119
103
  didSetSkyrampBaseUrl = true;
120
104
  }
121
105
  else if (candidates.length > 0) {
122
- return {
123
- content: [{
124
- type: "text",
125
- text: [
126
- `Cannot determine SKYRAMP_TEST_BASE_URL test file matches multiple services:`,
127
- ...candidates.map((c) => ` • ${c.serviceName}: ${c.baseUrl}`),
128
- ``,
129
- `Re-invoke with SKYRAMP_TEST_BASE_URL set to the correct service URL, or make each service's testDirectory unique in .skyramp/workspace.yml.`,
130
- ].join("\n"),
131
- }],
132
- isError: true,
133
- };
106
+ errorResult = toolError([
107
+ `Cannot determine SKYRAMP_TEST_BASE_URL — test file matches multiple services:`,
108
+ ...candidates.map((c) => ` • ${c.serviceName}: ${c.baseUrl}`),
109
+ ``,
110
+ `Re-invoke with SKYRAMP_TEST_BASE_URL set to the correct service URL, or make each service's testDirectory unique in .skyramp/workspace.yml.`,
111
+ ].join("\n"));
112
+ return errorResult;
134
113
  }
135
114
  }
136
115
  }
137
116
  const executionService = new TestExecutionService();
138
- // Fall back to SKYRAMP_TEST_TOKEN when caller passes empty string or omits the token.
139
- // This keeps secrets server-side and avoids embedding credential values in prompts.
140
- const effectiveToken = params.token || process.env.SKYRAMP_TEST_TOKEN || "";
117
+ // `unauthenticated: true` forces no auth token regardless of what's passed or
118
+ // configured, so tests asserting 401/403 unauthenticated behavior can't be broken
119
+ // by an ambient SKYRAMP_TEST_TOKEN — see SKYR-3874. Otherwise fall back to the
120
+ // environment token when the caller omits `token`; this keeps secrets server-side
121
+ // and avoids embedding credential values in prompts.
122
+ const effectiveToken = params.unauthenticated
123
+ ? ""
124
+ : (params.token ?? process.env.SKYRAMP_TEST_TOKEN ?? "");
141
125
  // Execute test with progress callback - reports Docker cache/pull status
142
126
  const result = await executionService.executeTest({
143
127
  testFile: params.testFile,
@@ -180,16 +164,9 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
180
164
  // Progress is already reported by TestExecutionService
181
165
  // Only report final status if not already at 100%
182
166
  if (result.status !== TestExecutionStatus.Pass) {
183
- errorResult = {
184
- content: [
185
- {
186
- type: "text",
187
- text: `Test execution failed: ${stripVTControlCharacters(result.output || "")}
188
- **IMPORTANT: IF THE EXECUTION FAILED BECAUSE OF A 401 STATUS CODE, ASK THE USER TO PROVIDE THE AUTHENTICATION TOKEN TO THE TOOL OR A PLACE FROM WHERE WE CAN READ THE TOKEN. DO NOT UPDATE THE GENERATED TEST FILE.**`,
189
- },
190
- ],
191
- isError: true,
192
- };
167
+ errorResult = toolError(`Test execution failed: ${stripVTControlCharacters(result.output || "")}
168
+
169
+ If this failed with a 401 status code, ask the user for the authentication token (or where to read it from) and retry — leave the generated test file unchanged.`);
193
170
  return errorResult;
194
171
  }
195
172
  // Success - progress already reported by TestExecutionService
@@ -206,15 +183,7 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
206
183
  };
207
184
  }
208
185
  catch (err) {
209
- errorResult = {
210
- content: [
211
- {
212
- type: "text",
213
- text: `Test execution failed: ${err.message}`,
214
- },
215
- ],
216
- isError: true,
217
- };
186
+ errorResult = toolError(`Test execution failed: ${err.message}`);
218
187
  return errorResult;
219
188
  }
220
189
  finally {
@@ -16,7 +16,7 @@ export declare const targetElementSchema: z.ZodObject<{
16
16
  stableId: string | null;
17
17
  contextText: string[] | null;
18
18
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
19
- widgetType?: "custom" | "unknown" | "native" | undefined;
19
+ widgetType?: "unknown" | "custom" | "native" | undefined;
20
20
  }, {
21
21
  role: string;
22
22
  accessibleName: string;
@@ -24,7 +24,7 @@ export declare const targetElementSchema: z.ZodObject<{
24
24
  stableId: string | null;
25
25
  contextText: string[] | null;
26
26
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
27
- widgetType?: "custom" | "unknown" | "native" | undefined;
27
+ widgetType?: "unknown" | "custom" | "native" | undefined;
28
28
  }>;
29
29
  export declare const pageContextSchema: z.ZodObject<{
30
30
  url: z.ZodString;
@@ -39,7 +39,7 @@ export declare const pageContextSchema: z.ZodObject<{
39
39
  export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
40
40
  testId: z.ZodString;
41
41
  testType: z.ZodNativeEnum<typeof TestType>;
42
- category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
42
+ category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
43
43
  endpoint: z.ZodString;
44
44
  fileName: z.ZodString;
45
45
  description: z.ZodString;
@@ -63,7 +63,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
63
63
  stableId: string | null;
64
64
  contextText: string[] | null;
65
65
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
66
- widgetType?: "custom" | "unknown" | "native" | undefined;
66
+ widgetType?: "unknown" | "custom" | "native" | undefined;
67
67
  }, {
68
68
  role: string;
69
69
  accessibleName: string;
@@ -71,7 +71,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
71
71
  stableId: string | null;
72
72
  contextText: string[] | null;
73
73
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
74
- widgetType?: "custom" | "unknown" | "native" | undefined;
74
+ widgetType?: "unknown" | "custom" | "native" | undefined;
75
75
  }>, "many">>>;
76
76
  pageContext: z.ZodOptional<z.ZodObject<{
77
77
  url: z.ZodString;
@@ -86,7 +86,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
86
86
  }, "strip", z.ZodTypeAny, {
87
87
  description: string;
88
88
  testType: TestType;
89
- category: "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
89
+ category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
90
90
  fileName: string;
91
91
  endpoint: string;
92
92
  testId: string;
@@ -102,7 +102,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
102
102
  stableId: string | null;
103
103
  contextText: string[] | null;
104
104
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
105
- widgetType?: "custom" | "unknown" | "native" | undefined;
105
+ widgetType?: "unknown" | "custom" | "native" | undefined;
106
106
  }[] | null | undefined;
107
107
  pageContext?: {
108
108
  url: string;
@@ -127,7 +127,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
127
127
  stableId: string | null;
128
128
  contextText: string[] | null;
129
129
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
130
- widgetType?: "custom" | "unknown" | "native" | undefined;
130
+ widgetType?: "unknown" | "custom" | "native" | undefined;
131
131
  }[] | null | undefined;
132
132
  pageContext?: {
133
133
  url: string;
@@ -136,7 +136,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
136
136
  }>, {
137
137
  description: string;
138
138
  testType: TestType;
139
- category: "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
139
+ category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
140
140
  fileName: string;
141
141
  endpoint: string;
142
142
  testId: string;
@@ -152,7 +152,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
152
152
  stableId: string | null;
153
153
  contextText: string[] | null;
154
154
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
155
- widgetType?: "custom" | "unknown" | "native" | undefined;
155
+ widgetType?: "unknown" | "custom" | "native" | undefined;
156
156
  }[] | null | undefined;
157
157
  pageContext?: {
158
158
  url: string;
@@ -177,7 +177,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
177
177
  stableId: string | null;
178
178
  contextText: string[] | null;
179
179
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
180
- widgetType?: "custom" | "unknown" | "native" | undefined;
180
+ widgetType?: "unknown" | "custom" | "native" | undefined;
181
181
  }[] | null | undefined;
182
182
  pageContext?: {
183
183
  url: string;
@@ -187,7 +187,7 @@ export declare const newTestSchema: z.ZodEffects<z.ZodObject<{
187
187
  export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
188
188
  testId: z.ZodString;
189
189
  testType: z.ZodNativeEnum<typeof TestType>;
190
- category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
190
+ category: z.ZodEffects<z.ZodEnum<["business_rule", "security_boundary", "data_integrity", "breaking_change", "auth", "error_handling", "workflow", "data_validation", "crud"]>, "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud", unknown>;
191
191
  primaryEndpoint: z.ZodOptional<z.ZodString>;
192
192
  scenarioName: z.ZodOptional<z.ZodString>;
193
193
  steps: z.ZodArray<z.ZodObject<{
@@ -234,7 +234,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
234
234
  stableId: string | null;
235
235
  contextText: string[] | null;
236
236
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
237
- widgetType?: "custom" | "unknown" | "native" | undefined;
237
+ widgetType?: "unknown" | "custom" | "native" | undefined;
238
238
  }, {
239
239
  role: string;
240
240
  accessibleName: string;
@@ -242,7 +242,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
242
242
  stableId: string | null;
243
243
  contextText: string[] | null;
244
244
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
245
- widgetType?: "custom" | "unknown" | "native" | undefined;
245
+ widgetType?: "unknown" | "custom" | "native" | undefined;
246
246
  }>, "many">>>;
247
247
  pageContext: z.ZodOptional<z.ZodObject<{
248
248
  url: z.ZodString;
@@ -258,7 +258,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
258
258
  description: string;
259
259
  priority: "high" | "medium" | "low";
260
260
  testType: TestType;
261
- category: "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
261
+ category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
262
262
  steps: {
263
263
  description: string;
264
264
  path?: string | undefined;
@@ -279,7 +279,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
279
279
  stableId: string | null;
280
280
  contextText: string[] | null;
281
281
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
282
- widgetType?: "custom" | "unknown" | "native" | undefined;
282
+ widgetType?: "unknown" | "custom" | "native" | undefined;
283
283
  }[] | null | undefined;
284
284
  pageContext?: {
285
285
  url: string;
@@ -313,7 +313,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
313
313
  stableId: string | null;
314
314
  contextText: string[] | null;
315
315
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
316
- widgetType?: "custom" | "unknown" | "native" | undefined;
316
+ widgetType?: "unknown" | "custom" | "native" | undefined;
317
317
  }[] | null | undefined;
318
318
  pageContext?: {
319
319
  url: string;
@@ -326,7 +326,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
326
326
  description: string;
327
327
  priority: "high" | "medium" | "low";
328
328
  testType: TestType;
329
- category: "breaking_change" | "business_rule" | "security_boundary" | "data_integrity" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
329
+ category: "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
330
330
  steps: {
331
331
  description: string;
332
332
  path?: string | undefined;
@@ -347,7 +347,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
347
347
  stableId: string | null;
348
348
  contextText: string[] | null;
349
349
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
350
- widgetType?: "custom" | "unknown" | "native" | undefined;
350
+ widgetType?: "unknown" | "custom" | "native" | undefined;
351
351
  }[] | null | undefined;
352
352
  pageContext?: {
353
353
  url: string;
@@ -381,7 +381,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodObject<{
381
381
  stableId: string | null;
382
382
  contextText: string[] | null;
383
383
  mutability?: "unknown" | "mutable" | "immutable" | undefined;
384
- widgetType?: "custom" | "unknown" | "native" | undefined;
384
+ widgetType?: "unknown" | "custom" | "native" | undefined;
385
385
  }[] | null | undefined;
386
386
  pageContext?: {
387
387
  url: string;