@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
|
@@ -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.
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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("
|
|
34
|
+
.describe("Absolute path to the test file to execute."),
|
|
60
35
|
token: z
|
|
61
36
|
.string()
|
|
62
|
-
.
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
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
|
-
|
|
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?: "
|
|
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?: "
|
|
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"]>, "
|
|
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?: "
|
|
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?: "
|
|
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: "
|
|
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?: "
|
|
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?: "
|
|
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: "
|
|
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?: "
|
|
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?: "
|
|
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"]>, "
|
|
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?: "
|
|
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?: "
|
|
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: "
|
|
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?: "
|
|
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?: "
|
|
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: "
|
|
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?: "
|
|
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?: "
|
|
384
|
+
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
385
385
|
}[] | null | undefined;
|
|
386
386
|
pageContext?: {
|
|
387
387
|
url: string;
|