retestkit 1.4.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/.claude/commands/openspec/apply.md +23 -0
- package/.claude/commands/openspec/archive.md +27 -0
- package/.claude/commands/openspec/proposal.md +28 -0
- package/.gemini/commands/openspec/apply.toml +21 -0
- package/.gemini/commands/openspec/archive.toml +25 -0
- package/.gemini/commands/openspec/proposal.toml +26 -0
- package/.github/prompts/openspec-apply.prompt.md +22 -0
- package/.github/prompts/openspec-archive.prompt.md +26 -0
- package/.github/prompts/openspec-proposal.prompt.md +27 -0
- package/.github/workflows/release.yml +33 -0
- package/.kilocode/workflows/openspec-apply.md +17 -0
- package/.kilocode/workflows/openspec-archive.md +21 -0
- package/.kilocode/workflows/openspec-proposal.md +22 -0
- package/.mcp.json +23 -0
- package/.opencode/command/openspec-apply.md +25 -0
- package/.opencode/command/openspec-archive.md +28 -0
- package/.opencode/command/openspec-proposal.md +30 -0
- package/.roo/commands/openspec-apply.md +20 -0
- package/.roo/commands/openspec-archive.md +24 -0
- package/.roo/commands/openspec-proposal.md +25 -0
- package/.vscode/mcp.json +23 -0
- package/AGENTS.md +18 -0
- package/CLAUDE.md +18 -0
- package/LICENSE +65 -0
- package/README.md +303 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/elicitation/index.d.ts +17 -0
- package/dist/elicitation/index.d.ts.map +1 -0
- package/dist/elicitation/index.js +118 -0
- package/dist/elicitation/index.js.map +1 -0
- package/dist/elicitation/types.d.ts +35 -0
- package/dist/elicitation/types.d.ts.map +1 -0
- package/dist/elicitation/types.js +39 -0
- package/dist/elicitation/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/index.d.ts +31 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +61 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/logger.d.ts +21 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +182 -0
- package/dist/logger.js.map +1 -0
- package/dist/playwright-client/index.d.ts +29 -0
- package/dist/playwright-client/index.d.ts.map +1 -0
- package/dist/playwright-client/index.js +288 -0
- package/dist/playwright-client/index.js.map +1 -0
- package/dist/playwright-client/types.d.ts +44 -0
- package/dist/playwright-client/types.d.ts.map +1 -0
- package/dist/playwright-client/types.js +49 -0
- package/dist/playwright-client/types.js.map +1 -0
- package/dist/progress/index.d.ts +39 -0
- package/dist/progress/index.d.ts.map +1 -0
- package/dist/progress/index.js +106 -0
- package/dist/progress/index.js.map +1 -0
- package/dist/progress/types.d.ts +24 -0
- package/dist/progress/types.d.ts.map +1 -0
- package/dist/progress/types.js +2 -0
- package/dist/progress/types.js.map +1 -0
- package/dist/prompts/index.d.ts +19 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +207 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/loader.d.ts +20 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +47 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/resources/index.d.ts +27 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +186 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/subscriptions.d.ts +10 -0
- package/dist/resources/subscriptions.d.ts.map +1 -0
- package/dist/resources/subscriptions.js +23 -0
- package/dist/resources/subscriptions.js.map +1 -0
- package/dist/sampling/index.d.ts +11 -0
- package/dist/sampling/index.d.ts.map +1 -0
- package/dist/sampling/index.js +201 -0
- package/dist/sampling/index.js.map +1 -0
- package/dist/sampling/prompts.d.ts +56 -0
- package/dist/sampling/prompts.d.ts.map +1 -0
- package/dist/sampling/prompts.js +124 -0
- package/dist/sampling/prompts.js.map +1 -0
- package/dist/sampling/types.d.ts +57 -0
- package/dist/sampling/types.d.ts.map +1 -0
- package/dist/sampling/types.js +2 -0
- package/dist/sampling/types.js.map +1 -0
- package/dist/schemas/config.d.ts +40 -0
- package/dist/schemas/config.d.ts.map +1 -0
- package/dist/schemas/config.js +30 -0
- package/dist/schemas/config.js.map +1 -0
- package/dist/security/index.d.ts +38 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +281 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +142 -0
- package/dist/server.js.map +1 -0
- package/dist/test-utils/index.d.ts +6 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +6 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/mock-context.d.ts +64 -0
- package/dist/test-utils/mock-context.d.ts.map +1 -0
- package/dist/test-utils/mock-context.js +347 -0
- package/dist/test-utils/mock-context.js.map +1 -0
- package/dist/test-utils/mock-playwright-client.d.ts +62 -0
- package/dist/test-utils/mock-playwright-client.d.ts.map +1 -0
- package/dist/test-utils/mock-playwright-client.js +315 -0
- package/dist/test-utils/mock-playwright-client.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/webtest/crawl.d.ts +46 -0
- package/dist/tools/webtest/crawl.d.ts.map +1 -0
- package/dist/tools/webtest/crawl.js +678 -0
- package/dist/tools/webtest/crawl.js.map +1 -0
- package/dist/tools/webtest/discover-features.d.ts +30 -0
- package/dist/tools/webtest/discover-features.d.ts.map +1 -0
- package/dist/tools/webtest/discover-features.js +343 -0
- package/dist/tools/webtest/discover-features.js.map +1 -0
- package/dist/tools/webtest/discover-flows.d.ts +29 -0
- package/dist/tools/webtest/discover-flows.d.ts.map +1 -0
- package/dist/tools/webtest/discover-flows.js +341 -0
- package/dist/tools/webtest/discover-flows.js.map +1 -0
- package/dist/tools/webtest/generate-tests.d.ts +54 -0
- package/dist/tools/webtest/generate-tests.d.ts.map +1 -0
- package/dist/tools/webtest/generate-tests.js +364 -0
- package/dist/tools/webtest/generate-tests.js.map +1 -0
- package/dist/tools/webtest/index.d.ts +8 -0
- package/dist/tools/webtest/index.d.ts.map +1 -0
- package/dist/tools/webtest/index.js +8 -0
- package/dist/tools/webtest/index.js.map +1 -0
- package/dist/tools/webtest/run-test-case.d.ts +28 -0
- package/dist/tools/webtest/run-test-case.d.ts.map +1 -0
- package/dist/tools/webtest/run-test-case.js +420 -0
- package/dist/tools/webtest/run-test-case.js.map +1 -0
- package/dist/tools/webtest/schemas.d.ts +175 -0
- package/dist/tools/webtest/schemas.d.ts.map +1 -0
- package/dist/tools/webtest/schemas.js +156 -0
- package/dist/tools/webtest/schemas.js.map +1 -0
- package/dist/tools/webtest/start-analysis.d.ts +16 -0
- package/dist/tools/webtest/start-analysis.d.ts.map +1 -0
- package/dist/tools/webtest/start-analysis.js +137 -0
- package/dist/tools/webtest/start-analysis.js.map +1 -0
- package/dist/transports/http.d.ts +8 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +9 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/index.d.ts +14 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/transports/index.js +20 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/transports/stdio.d.ts +4 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +6 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/types/capabilities.d.ts +18 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +35 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/context.d.ts +20 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/context.js +2 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +2 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/workspace/index.d.ts +99 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +648 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/markdown.d.ts +50 -0
- package/dist/workspace/markdown.d.ts.map +1 -0
- package/dist/workspace/markdown.js +210 -0
- package/dist/workspace/markdown.js.map +1 -0
- package/dist/workspace/types.d.ts +173 -0
- package/dist/workspace/types.d.ts.map +1 -0
- package/dist/workspace/types.js +2 -0
- package/dist/workspace/types.js.map +1 -0
- package/openspec/AGENTS.md +456 -0
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +33 -0
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +27 -0
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +304 -0
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +43 -0
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +209 -0
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +41 -0
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +183 -0
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +112 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +333 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +66 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +129 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +138 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +211 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +157 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +213 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +257 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +501 -0
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +264 -0
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +24 -0
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +80 -0
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +8 -0
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +90 -0
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +28 -0
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +90 -0
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +33 -0
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +558 -0
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +119 -0
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +109 -0
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +121 -0
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +133 -0
- package/openspec/changes/extract-prompts-to-markdown/design.md +86 -0
- package/openspec/changes/extract-prompts-to-markdown/proposal.md +50 -0
- package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +74 -0
- package/openspec/changes/extract-prompts-to-markdown/tasks.md +40 -0
- package/openspec/changes/refactor-webtest-naming/design.md +95 -0
- package/openspec/changes/refactor-webtest-naming/proposal.md +66 -0
- package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +79 -0
- package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +80 -0
- package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +122 -0
- package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +113 -0
- package/openspec/changes/refactor-webtest-naming/tasks.md +119 -0
- package/openspec/changes/rename-package-to-retest/proposal.md +52 -0
- package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +53 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +68 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +35 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +159 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +251 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +99 -0
- package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +295 -0
- package/openspec/changes/rename-package-to-retest/tasks.md +71 -0
- package/openspec/project.md +31 -0
- package/openspec/specs/mcp-server-core/spec.md +178 -0
- package/openspec/specs/webtest-lifecycle/spec.md +136 -0
- package/openspec/specs/webtest-logging/spec.md +209 -0
- package/openspec/specs/webtest-prompts/spec.md +155 -0
- package/openspec/specs/webtest-resources/spec.md +248 -0
- package/openspec/specs/webtest-sampling/spec.md +344 -0
- package/openspec/specs/webtest-tools/spec.md +282 -0
- package/package.json +54 -0
- package/release.config.js +9 -0
- package/src/config.test.ts +96 -0
- package/src/config.ts +32 -0
- package/src/elicitation/index.test.ts +399 -0
- package/src/elicitation/index.ts +171 -0
- package/src/elicitation/types.ts +68 -0
- package/src/index.ts +83 -0
- package/src/lifecycle/index.test.ts +260 -0
- package/src/lifecycle/index.ts +101 -0
- package/src/logger.redaction.test.ts +322 -0
- package/src/logger.test.ts +123 -0
- package/src/logger.ts +229 -0
- package/src/playwright-client/index.ts +392 -0
- package/src/playwright-client/types.ts +99 -0
- package/src/progress/index.test.ts +327 -0
- package/src/progress/index.ts +170 -0
- package/src/progress/types.ts +25 -0
- package/src/prompts/index.test.ts +451 -0
- package/src/prompts/index.ts +246 -0
- package/src/prompts/loader.test.ts +100 -0
- package/src/prompts/loader.ts +59 -0
- package/src/prompts/templates/mcp/webtest-crawl.md +7 -0
- package/src/prompts/templates/mcp/webtest-discover-flows.md +11 -0
- package/src/prompts/templates/mcp/webtest-discover.md +12 -0
- package/src/prompts/templates/mcp/webtest-full-workflow.md +12 -0
- package/src/prompts/templates/mcp/webtest-generate-tests.md +11 -0
- package/src/prompts/templates/mcp/webtest-run-test.md +11 -0
- package/src/prompts/templates/mcp/webtest-start.md +8 -0
- package/src/prompts/templates/sampling/crawl-action.md +35 -0
- package/src/prompts/templates/sampling/feature-discovery.md +27 -0
- package/src/prompts/templates/sampling/flow-discovery.md +29 -0
- package/src/prompts/templates/sampling/page-content-wrapper.md +5 -0
- package/src/prompts/templates/sampling/system-prefix.md +12 -0
- package/src/prompts/templates/sampling/test-evaluation.md +17 -0
- package/src/prompts/templates/sampling/test-generation.md +31 -0
- package/src/resources/index.ts +250 -0
- package/src/resources/subscriptions.ts +37 -0
- package/src/sampling/index.test.ts +414 -0
- package/src/sampling/index.ts +286 -0
- package/src/sampling/prompts.ts +194 -0
- package/src/sampling/types.ts +60 -0
- package/src/schemas/config.ts +39 -0
- package/src/security/index.test.ts +441 -0
- package/src/security/index.ts +361 -0
- package/src/security/security-scenarios.test.ts +468 -0
- package/src/server.ts +211 -0
- package/src/test-utils/index.ts +6 -0
- package/src/test-utils/mock-context.ts +426 -0
- package/src/test-utils/mock-playwright-client.ts +422 -0
- package/src/tools/index.ts +11 -0
- package/src/tools/webtest/crawl.test.ts +834 -0
- package/src/tools/webtest/crawl.ts +901 -0
- package/src/tools/webtest/discover-features.ts +412 -0
- package/src/tools/webtest/discover-flows.ts +408 -0
- package/src/tools/webtest/generate-tests.test.ts +532 -0
- package/src/tools/webtest/generate-tests.ts +425 -0
- package/src/tools/webtest/index.ts +7 -0
- package/src/tools/webtest/integration.test.ts +536 -0
- package/src/tools/webtest/run-test-case.test.ts +659 -0
- package/src/tools/webtest/run-test-case.ts +508 -0
- package/src/tools/webtest/schemas.ts +201 -0
- package/src/tools/webtest/start-analysis.test.ts +151 -0
- package/src/tools/webtest/start-analysis.ts +158 -0
- package/src/transports/http.ts +19 -0
- package/src/transports/index.ts +30 -0
- package/src/transports/stdio.ts +7 -0
- package/src/types/capabilities.test.ts +193 -0
- package/src/types/capabilities.ts +50 -0
- package/src/types/context.ts +21 -0
- package/src/types/tool.ts +11 -0
- package/src/workspace/index.ts +945 -0
- package/src/workspace/markdown.ts +272 -0
- package/src/workspace/types.ts +186 -0
- package/tests/integration/server.test.ts +89 -0
- package/tests/integration/tools.test.ts +99 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
- package/vitest.integration.config.ts +10 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
import type { McpTool, ToolResult } from "../../types/tool.js";
|
|
4
|
+
import type { ServerContext } from "../../types/context.js";
|
|
5
|
+
import type { WorkspaceManager } from "../../workspace/index.js";
|
|
6
|
+
import type { SamplingClient } from "../../sampling/index.js";
|
|
7
|
+
import type { ResourceManager } from "../../resources/index.js";
|
|
8
|
+
import { buildTestGenerationPrompt } from "../../sampling/prompts.js";
|
|
9
|
+
import { TestGenerationSchema, AnalysisIdSchema, type TestGeneration, type TestCase } from "./schemas.js";
|
|
10
|
+
|
|
11
|
+
export const generateTestsInputSchema = z.object({
|
|
12
|
+
analysisId: AnalysisIdSchema,
|
|
13
|
+
appAnalysisUri: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("URI of the app analysis resource. If not provided, uses the latest analysis."),
|
|
17
|
+
testStrategy: z
|
|
18
|
+
.enum(["comprehensive", "happy_path", "edge_cases", "critical_paths"])
|
|
19
|
+
.default("comprehensive")
|
|
20
|
+
.describe("Test generation strategy"),
|
|
21
|
+
focusFlows: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Specific flow IDs to focus test generation on"),
|
|
25
|
+
maxTests: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(50)
|
|
30
|
+
.default(20)
|
|
31
|
+
.describe("Maximum number of test cases to generate"),
|
|
32
|
+
manualTests: z
|
|
33
|
+
.object({
|
|
34
|
+
tests: z.array(z.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
name: z.string(),
|
|
37
|
+
purpose: z.string(),
|
|
38
|
+
category: z.enum(["happy_path", "edge_case", "error_handling", "boundary"]),
|
|
39
|
+
preconditions: z.array(z.string()),
|
|
40
|
+
steps: z.array(z.object({
|
|
41
|
+
stepNumber: z.number().int(),
|
|
42
|
+
action: z.string(),
|
|
43
|
+
target: z.string().optional(),
|
|
44
|
+
value: z.string().optional(),
|
|
45
|
+
expected: z.string().optional(),
|
|
46
|
+
element: z.string().optional(),
|
|
47
|
+
ref: z.string().optional(),
|
|
48
|
+
})),
|
|
49
|
+
expectedOutcomes: z.array(z.string()),
|
|
50
|
+
tags: z.array(z.string()).optional(),
|
|
51
|
+
})),
|
|
52
|
+
coverage: z.object({
|
|
53
|
+
flowsCovered: z.array(z.string()),
|
|
54
|
+
estimatedCoverage: z.string(),
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Manual test generation result when sampling is unavailable. Provide this after executing the returned prompt."),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export type GenerateTestsInput = z.infer<typeof generateTestsInputSchema>;
|
|
62
|
+
|
|
63
|
+
export function createGenerateTestsTool(
|
|
64
|
+
getContext: () => ServerContext & {
|
|
65
|
+
workspaceManager: WorkspaceManager;
|
|
66
|
+
samplingClient: SamplingClient;
|
|
67
|
+
resourceManager: ResourceManager;
|
|
68
|
+
}
|
|
69
|
+
): McpTool<GenerateTestsInput> {
|
|
70
|
+
return {
|
|
71
|
+
name: "webtest_generate_tests",
|
|
72
|
+
description: `Generate test cases from application analysis.
|
|
73
|
+
|
|
74
|
+
This tool creates structured test cases based on the app analysis:
|
|
75
|
+
- Generates tests for identified user flows
|
|
76
|
+
- Covers happy paths, edge cases, and error conditions
|
|
77
|
+
- Creates step-by-step test instructions
|
|
78
|
+
- Defines expected outcomes for each test
|
|
79
|
+
|
|
80
|
+
Requires a completed app analysis. If sampling is unavailable, returns a prompt for manual execution.`,
|
|
81
|
+
|
|
82
|
+
inputSchema: generateTestsInputSchema,
|
|
83
|
+
|
|
84
|
+
async handler(input: GenerateTestsInput): Promise<ToolResult> {
|
|
85
|
+
const ctx = getContext();
|
|
86
|
+
const { logger, workspaceManager, samplingClient, resourceManager } = ctx;
|
|
87
|
+
|
|
88
|
+
const testLogger = logger.withCorrelation({
|
|
89
|
+
analysisId: input.analysisId,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
testLogger.info("Starting test generation", {
|
|
93
|
+
strategy: input.testStrategy,
|
|
94
|
+
maxTests: input.maxTests,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Validate workspace exists
|
|
99
|
+
if (!(await workspaceManager.workspaceExists(input.analysisId))) {
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Error: Analysis workspace "${input.analysisId}" not found.`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const workspace = await workspaceManager.readWorkspaceIndex(input.analysisId);
|
|
112
|
+
|
|
113
|
+
// Check for features
|
|
114
|
+
if (!workspace.features) {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: "Error: No features found. Run webtest_discover_features first.",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Load features data
|
|
127
|
+
const featuresUri = input.appAnalysisUri || workspace.features.featuresUri;
|
|
128
|
+
|
|
129
|
+
let featuresContent: string;
|
|
130
|
+
let allFlows: Array<{ featureSlug: string; flows: unknown[] }> = [];
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const featuresData = await resourceManager.readResource(featuresUri);
|
|
134
|
+
featuresContent = featuresData.text || "";
|
|
135
|
+
|
|
136
|
+
// Load flows from all feature flow files
|
|
137
|
+
if (workspace.featureFlows) {
|
|
138
|
+
for (const flowRef of workspace.featureFlows) {
|
|
139
|
+
try {
|
|
140
|
+
const flowsContent = await resourceManager.readResource(flowRef.flowsUri);
|
|
141
|
+
const { data } = matter(flowsContent.text || "");
|
|
142
|
+
allFlows.push({
|
|
143
|
+
featureSlug: flowRef.featureSlug,
|
|
144
|
+
flows: (data as { flows?: unknown[] }).flows || [],
|
|
145
|
+
});
|
|
146
|
+
} catch {
|
|
147
|
+
// Flow file might not exist, skip
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `Error loading features data: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
isError: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Flatten flows for backward compatibility
|
|
164
|
+
const flowsData = {
|
|
165
|
+
flows: allFlows.flatMap(f => f.flows),
|
|
166
|
+
entities: workspace.features.features.flatMap(f => f.entities),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Filter flows if specific ones requested
|
|
170
|
+
let targetFlows = flowsData.flows;
|
|
171
|
+
if (input.focusFlows && input.focusFlows.length > 0) {
|
|
172
|
+
targetFlows = flowsData.flows.filter((f: any) =>
|
|
173
|
+
input.focusFlows!.includes(f.id)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
testLogger.info("Generating tests", {
|
|
178
|
+
flowCount: targetFlows.length,
|
|
179
|
+
strategy: input.testStrategy,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Build strategy description
|
|
183
|
+
let strategyDescription: string;
|
|
184
|
+
switch (input.testStrategy) {
|
|
185
|
+
case "happy_path":
|
|
186
|
+
strategyDescription =
|
|
187
|
+
"Focus on successful user journeys. Test the main flows with valid inputs.";
|
|
188
|
+
break;
|
|
189
|
+
case "edge_cases":
|
|
190
|
+
strategyDescription =
|
|
191
|
+
"Focus on boundary conditions, unusual inputs, and corner cases.";
|
|
192
|
+
break;
|
|
193
|
+
case "critical_paths":
|
|
194
|
+
strategyDescription =
|
|
195
|
+
"Focus on business-critical functionality and high-risk areas.";
|
|
196
|
+
break;
|
|
197
|
+
case "comprehensive":
|
|
198
|
+
default:
|
|
199
|
+
strategyDescription =
|
|
200
|
+
"Generate a comprehensive test suite covering happy paths, edge cases, and error handling.";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Determine test source: manual input, sampling, or fallback prompt
|
|
204
|
+
let testGeneration: TestGeneration;
|
|
205
|
+
|
|
206
|
+
if (input.manualTests) {
|
|
207
|
+
// Use manually provided tests (from fallback mode)
|
|
208
|
+
testLogger.info("Using manual tests input");
|
|
209
|
+
testGeneration = input.manualTests as TestGeneration;
|
|
210
|
+
} else if (!samplingClient.hasSampling()) {
|
|
211
|
+
// No sampling available and no manual input - return prompt for manual execution
|
|
212
|
+
const prompt = buildTestGenerationPrompt({
|
|
213
|
+
appAnalysis: featuresContent,
|
|
214
|
+
flows: JSON.stringify(targetFlows, null, 2),
|
|
215
|
+
strategy: strategyDescription,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
221
|
+
type: "text",
|
|
222
|
+
text: JSON.stringify(
|
|
223
|
+
{
|
|
224
|
+
needsManualInput: true,
|
|
225
|
+
analysisId: input.analysisId,
|
|
226
|
+
prompt,
|
|
227
|
+
expectedResponseSchema: TestGenerationSchema._def,
|
|
228
|
+
instructions:
|
|
229
|
+
"Execute this prompt with your LLM, then call webtest_generate_tests again with the result in the 'manualTests' parameter",
|
|
230
|
+
},
|
|
231
|
+
null,
|
|
232
|
+
2
|
|
233
|
+
),
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
} else {
|
|
238
|
+
// Request test generation via sampling
|
|
239
|
+
const samplingResult = await samplingClient.createMessage({
|
|
240
|
+
systemPrompt: `You are a QA engineer generating test cases for a web application.
|
|
241
|
+
Generate ${input.maxTests} or fewer high-quality test cases based on the analysis.
|
|
242
|
+
Each test case should be actionable and specific.`,
|
|
243
|
+
userPrompt: buildTestGenerationPrompt({
|
|
244
|
+
appAnalysis: featuresContent,
|
|
245
|
+
flows: JSON.stringify(targetFlows, null, 2),
|
|
246
|
+
strategy: strategyDescription,
|
|
247
|
+
}),
|
|
248
|
+
schema: TestGenerationSchema,
|
|
249
|
+
maxTokens: 8192,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (!samplingResult.success || !samplingResult.data) {
|
|
253
|
+
testLogger.error("Test generation sampling failed", {
|
|
254
|
+
error: samplingResult.error,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `Error during test generation: ${samplingResult.error}`,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
isError: true,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
testGeneration = samplingResult.data;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Limit to maxTests
|
|
272
|
+
const tests = testGeneration.tests.slice(0, input.maxTests);
|
|
273
|
+
|
|
274
|
+
// Generate markdown report
|
|
275
|
+
const markdownReport = generateTestsMarkdown(tests, workspace, input.testStrategy);
|
|
276
|
+
|
|
277
|
+
// Create frontmatter with test data
|
|
278
|
+
const testsFrontmatter = {
|
|
279
|
+
tests,
|
|
280
|
+
coverage: testGeneration.coverage,
|
|
281
|
+
strategy: input.testStrategy,
|
|
282
|
+
generatedAt: new Date().toISOString(),
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Save tests as single markdown file with frontmatter
|
|
286
|
+
const { testsFilePath, testsUri, testCount } =
|
|
287
|
+
await workspaceManager.saveTests(input.analysisId, {
|
|
288
|
+
markdown: markdownReport,
|
|
289
|
+
frontmatter: testsFrontmatter,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await resourceManager.notifyListChanged();
|
|
293
|
+
|
|
294
|
+
// Categorize tests
|
|
295
|
+
const categories = {
|
|
296
|
+
happy_path: tests.filter((t) => t.category === "happy_path").length,
|
|
297
|
+
edge_case: tests.filter((t) => t.category === "edge_case").length,
|
|
298
|
+
error_handling: tests.filter((t) => t.category === "error_handling").length,
|
|
299
|
+
boundary: tests.filter((t) => t.category === "boundary").length,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const result = {
|
|
303
|
+
analysisId: input.analysisId,
|
|
304
|
+
testsFilePath,
|
|
305
|
+
testsUri,
|
|
306
|
+
testCount,
|
|
307
|
+
strategy: input.testStrategy,
|
|
308
|
+
categories,
|
|
309
|
+
coverage: testGeneration.coverage,
|
|
310
|
+
testSummary: tests.map((t) => ({
|
|
311
|
+
id: t.id,
|
|
312
|
+
name: t.name,
|
|
313
|
+
category: t.category,
|
|
314
|
+
stepCount: t.steps.length,
|
|
315
|
+
})),
|
|
316
|
+
nextSteps: [
|
|
317
|
+
`Use webtest_run_tests to execute individual tests`,
|
|
318
|
+
`Review generated tests at ${testsUri}`,
|
|
319
|
+
tests.length > 0
|
|
320
|
+
? `First test to run: ${tests[0].id} - ${tests[0].name}`
|
|
321
|
+
: "",
|
|
322
|
+
].filter(Boolean),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
testLogger.info("Test generation completed", {
|
|
326
|
+
testCount,
|
|
327
|
+
categories,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify(result, null, 2),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
340
|
+
testLogger.error("Test generation failed", { error: message });
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
{
|
|
345
|
+
type: "text",
|
|
346
|
+
text: `Error during test generation: ${message}`,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
isError: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function generateTestsMarkdown(
|
|
357
|
+
tests: TestCase[],
|
|
358
|
+
workspace: { url: string; domain: string },
|
|
359
|
+
strategy: string
|
|
360
|
+
): string {
|
|
361
|
+
let md = `# Test Cases: ${workspace.domain}
|
|
362
|
+
|
|
363
|
+
## Overview
|
|
364
|
+
|
|
365
|
+
- **Target URL**: ${workspace.url}
|
|
366
|
+
- **Strategy**: ${strategy}
|
|
367
|
+
- **Total Tests**: ${tests.length}
|
|
368
|
+
|
|
369
|
+
## Test Cases
|
|
370
|
+
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
for (const test of tests) {
|
|
374
|
+
md += `### ${test.id}: ${test.name}
|
|
375
|
+
|
|
376
|
+
**Category**: ${test.category}
|
|
377
|
+
**Purpose**: ${test.purpose}
|
|
378
|
+
|
|
379
|
+
`;
|
|
380
|
+
|
|
381
|
+
if (test.preconditions.length > 0) {
|
|
382
|
+
md += `**Preconditions**:
|
|
383
|
+
`;
|
|
384
|
+
for (const pre of test.preconditions) {
|
|
385
|
+
md += `- ${pre}\n`;
|
|
386
|
+
}
|
|
387
|
+
md += "\n";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
md += `**Steps**:
|
|
391
|
+
`;
|
|
392
|
+
for (const step of test.steps) {
|
|
393
|
+
let stepText = `${step.stepNumber}. ${step.action}`;
|
|
394
|
+
if (step.target) {
|
|
395
|
+
stepText += ` on "${step.target}"`;
|
|
396
|
+
}
|
|
397
|
+
if (step.value) {
|
|
398
|
+
stepText += ` with value "${step.value}"`;
|
|
399
|
+
}
|
|
400
|
+
md += stepText + "\n";
|
|
401
|
+
if (step.expected) {
|
|
402
|
+
md += ` - *Expected*: ${step.expected}\n`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
md += `
|
|
407
|
+
**Expected Outcomes**:
|
|
408
|
+
`;
|
|
409
|
+
for (const outcome of test.expectedOutcomes) {
|
|
410
|
+
md += `- ${outcome}\n`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (test.tags && test.tags.length > 0) {
|
|
414
|
+
md += `\n**Tags**: ${test.tags.join(", ")}\n`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
md += "\n---\n\n";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
md += `
|
|
421
|
+
*Generated by testing-mcp*
|
|
422
|
+
`;
|
|
423
|
+
|
|
424
|
+
return md;
|
|
425
|
+
}
|