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.
Files changed (327) hide show
  1. package/.claude/commands/openspec/apply.md +23 -0
  2. package/.claude/commands/openspec/archive.md +27 -0
  3. package/.claude/commands/openspec/proposal.md +28 -0
  4. package/.gemini/commands/openspec/apply.toml +21 -0
  5. package/.gemini/commands/openspec/archive.toml +25 -0
  6. package/.gemini/commands/openspec/proposal.toml +26 -0
  7. package/.github/prompts/openspec-apply.prompt.md +22 -0
  8. package/.github/prompts/openspec-archive.prompt.md +26 -0
  9. package/.github/prompts/openspec-proposal.prompt.md +27 -0
  10. package/.github/workflows/release.yml +33 -0
  11. package/.kilocode/workflows/openspec-apply.md +17 -0
  12. package/.kilocode/workflows/openspec-archive.md +21 -0
  13. package/.kilocode/workflows/openspec-proposal.md +22 -0
  14. package/.mcp.json +23 -0
  15. package/.opencode/command/openspec-apply.md +25 -0
  16. package/.opencode/command/openspec-archive.md +28 -0
  17. package/.opencode/command/openspec-proposal.md +30 -0
  18. package/.roo/commands/openspec-apply.md +20 -0
  19. package/.roo/commands/openspec-archive.md +24 -0
  20. package/.roo/commands/openspec-proposal.md +25 -0
  21. package/.vscode/mcp.json +23 -0
  22. package/AGENTS.md +18 -0
  23. package/CLAUDE.md +18 -0
  24. package/LICENSE +65 -0
  25. package/README.md +303 -0
  26. package/dist/config.d.ts +4 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +27 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/elicitation/index.d.ts +17 -0
  31. package/dist/elicitation/index.d.ts.map +1 -0
  32. package/dist/elicitation/index.js +118 -0
  33. package/dist/elicitation/index.js.map +1 -0
  34. package/dist/elicitation/types.d.ts +35 -0
  35. package/dist/elicitation/types.d.ts.map +1 -0
  36. package/dist/elicitation/types.js +39 -0
  37. package/dist/elicitation/types.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +76 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lifecycle/index.d.ts +31 -0
  43. package/dist/lifecycle/index.d.ts.map +1 -0
  44. package/dist/lifecycle/index.js +61 -0
  45. package/dist/lifecycle/index.js.map +1 -0
  46. package/dist/logger.d.ts +21 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +182 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/playwright-client/index.d.ts +29 -0
  51. package/dist/playwright-client/index.d.ts.map +1 -0
  52. package/dist/playwright-client/index.js +288 -0
  53. package/dist/playwright-client/index.js.map +1 -0
  54. package/dist/playwright-client/types.d.ts +44 -0
  55. package/dist/playwright-client/types.d.ts.map +1 -0
  56. package/dist/playwright-client/types.js +49 -0
  57. package/dist/playwright-client/types.js.map +1 -0
  58. package/dist/progress/index.d.ts +39 -0
  59. package/dist/progress/index.d.ts.map +1 -0
  60. package/dist/progress/index.js +106 -0
  61. package/dist/progress/index.js.map +1 -0
  62. package/dist/progress/types.d.ts +24 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/progress/types.js +2 -0
  65. package/dist/progress/types.js.map +1 -0
  66. package/dist/prompts/index.d.ts +19 -0
  67. package/dist/prompts/index.d.ts.map +1 -0
  68. package/dist/prompts/index.js +207 -0
  69. package/dist/prompts/index.js.map +1 -0
  70. package/dist/prompts/loader.d.ts +20 -0
  71. package/dist/prompts/loader.d.ts.map +1 -0
  72. package/dist/prompts/loader.js +47 -0
  73. package/dist/prompts/loader.js.map +1 -0
  74. package/dist/resources/index.d.ts +27 -0
  75. package/dist/resources/index.d.ts.map +1 -0
  76. package/dist/resources/index.js +186 -0
  77. package/dist/resources/index.js.map +1 -0
  78. package/dist/resources/subscriptions.d.ts +10 -0
  79. package/dist/resources/subscriptions.d.ts.map +1 -0
  80. package/dist/resources/subscriptions.js +23 -0
  81. package/dist/resources/subscriptions.js.map +1 -0
  82. package/dist/sampling/index.d.ts +11 -0
  83. package/dist/sampling/index.d.ts.map +1 -0
  84. package/dist/sampling/index.js +201 -0
  85. package/dist/sampling/index.js.map +1 -0
  86. package/dist/sampling/prompts.d.ts +56 -0
  87. package/dist/sampling/prompts.d.ts.map +1 -0
  88. package/dist/sampling/prompts.js +124 -0
  89. package/dist/sampling/prompts.js.map +1 -0
  90. package/dist/sampling/types.d.ts +57 -0
  91. package/dist/sampling/types.d.ts.map +1 -0
  92. package/dist/sampling/types.js +2 -0
  93. package/dist/sampling/types.js.map +1 -0
  94. package/dist/schemas/config.d.ts +40 -0
  95. package/dist/schemas/config.d.ts.map +1 -0
  96. package/dist/schemas/config.js +30 -0
  97. package/dist/schemas/config.js.map +1 -0
  98. package/dist/security/index.d.ts +38 -0
  99. package/dist/security/index.d.ts.map +1 -0
  100. package/dist/security/index.js +281 -0
  101. package/dist/security/index.js.map +1 -0
  102. package/dist/server.d.ts +9 -0
  103. package/dist/server.d.ts.map +1 -0
  104. package/dist/server.js +142 -0
  105. package/dist/server.js.map +1 -0
  106. package/dist/test-utils/index.d.ts +6 -0
  107. package/dist/test-utils/index.d.ts.map +1 -0
  108. package/dist/test-utils/index.js +6 -0
  109. package/dist/test-utils/index.js.map +1 -0
  110. package/dist/test-utils/mock-context.d.ts +64 -0
  111. package/dist/test-utils/mock-context.d.ts.map +1 -0
  112. package/dist/test-utils/mock-context.js +347 -0
  113. package/dist/test-utils/mock-context.js.map +1 -0
  114. package/dist/test-utils/mock-playwright-client.d.ts +62 -0
  115. package/dist/test-utils/mock-playwright-client.d.ts.map +1 -0
  116. package/dist/test-utils/mock-playwright-client.js +315 -0
  117. package/dist/test-utils/mock-playwright-client.js.map +1 -0
  118. package/dist/tools/index.d.ts +4 -0
  119. package/dist/tools/index.d.ts.map +1 -0
  120. package/dist/tools/index.js +8 -0
  121. package/dist/tools/index.js.map +1 -0
  122. package/dist/tools/webtest/crawl.d.ts +46 -0
  123. package/dist/tools/webtest/crawl.d.ts.map +1 -0
  124. package/dist/tools/webtest/crawl.js +678 -0
  125. package/dist/tools/webtest/crawl.js.map +1 -0
  126. package/dist/tools/webtest/discover-features.d.ts +30 -0
  127. package/dist/tools/webtest/discover-features.d.ts.map +1 -0
  128. package/dist/tools/webtest/discover-features.js +343 -0
  129. package/dist/tools/webtest/discover-features.js.map +1 -0
  130. package/dist/tools/webtest/discover-flows.d.ts +29 -0
  131. package/dist/tools/webtest/discover-flows.d.ts.map +1 -0
  132. package/dist/tools/webtest/discover-flows.js +341 -0
  133. package/dist/tools/webtest/discover-flows.js.map +1 -0
  134. package/dist/tools/webtest/generate-tests.d.ts +54 -0
  135. package/dist/tools/webtest/generate-tests.d.ts.map +1 -0
  136. package/dist/tools/webtest/generate-tests.js +364 -0
  137. package/dist/tools/webtest/generate-tests.js.map +1 -0
  138. package/dist/tools/webtest/index.d.ts +8 -0
  139. package/dist/tools/webtest/index.d.ts.map +1 -0
  140. package/dist/tools/webtest/index.js +8 -0
  141. package/dist/tools/webtest/index.js.map +1 -0
  142. package/dist/tools/webtest/run-test-case.d.ts +28 -0
  143. package/dist/tools/webtest/run-test-case.d.ts.map +1 -0
  144. package/dist/tools/webtest/run-test-case.js +420 -0
  145. package/dist/tools/webtest/run-test-case.js.map +1 -0
  146. package/dist/tools/webtest/schemas.d.ts +175 -0
  147. package/dist/tools/webtest/schemas.d.ts.map +1 -0
  148. package/dist/tools/webtest/schemas.js +156 -0
  149. package/dist/tools/webtest/schemas.js.map +1 -0
  150. package/dist/tools/webtest/start-analysis.d.ts +16 -0
  151. package/dist/tools/webtest/start-analysis.d.ts.map +1 -0
  152. package/dist/tools/webtest/start-analysis.js +137 -0
  153. package/dist/tools/webtest/start-analysis.js.map +1 -0
  154. package/dist/transports/http.d.ts +8 -0
  155. package/dist/transports/http.d.ts.map +1 -0
  156. package/dist/transports/http.js +9 -0
  157. package/dist/transports/http.js.map +1 -0
  158. package/dist/transports/index.d.ts +14 -0
  159. package/dist/transports/index.d.ts.map +1 -0
  160. package/dist/transports/index.js +20 -0
  161. package/dist/transports/index.js.map +1 -0
  162. package/dist/transports/stdio.d.ts +4 -0
  163. package/dist/transports/stdio.d.ts.map +1 -0
  164. package/dist/transports/stdio.js +6 -0
  165. package/dist/transports/stdio.js.map +1 -0
  166. package/dist/types/capabilities.d.ts +18 -0
  167. package/dist/types/capabilities.d.ts.map +1 -0
  168. package/dist/types/capabilities.js +35 -0
  169. package/dist/types/capabilities.js.map +1 -0
  170. package/dist/types/context.d.ts +20 -0
  171. package/dist/types/context.d.ts.map +1 -0
  172. package/dist/types/context.js +2 -0
  173. package/dist/types/context.js.map +1 -0
  174. package/dist/types/tool.d.ts +10 -0
  175. package/dist/types/tool.d.ts.map +1 -0
  176. package/dist/types/tool.js +2 -0
  177. package/dist/types/tool.js.map +1 -0
  178. package/dist/workspace/index.d.ts +99 -0
  179. package/dist/workspace/index.d.ts.map +1 -0
  180. package/dist/workspace/index.js +648 -0
  181. package/dist/workspace/index.js.map +1 -0
  182. package/dist/workspace/markdown.d.ts +50 -0
  183. package/dist/workspace/markdown.d.ts.map +1 -0
  184. package/dist/workspace/markdown.js +210 -0
  185. package/dist/workspace/markdown.js.map +1 -0
  186. package/dist/workspace/types.d.ts +173 -0
  187. package/dist/workspace/types.d.ts.map +1 -0
  188. package/dist/workspace/types.js +2 -0
  189. package/dist/workspace/types.js.map +1 -0
  190. package/openspec/AGENTS.md +456 -0
  191. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +33 -0
  192. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +27 -0
  193. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +304 -0
  194. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +43 -0
  195. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +209 -0
  196. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +41 -0
  197. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +183 -0
  198. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +112 -0
  199. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +333 -0
  200. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +66 -0
  201. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +129 -0
  202. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +138 -0
  203. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +211 -0
  204. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +157 -0
  205. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +213 -0
  206. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +257 -0
  207. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +501 -0
  208. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +264 -0
  209. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +24 -0
  210. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +80 -0
  211. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +8 -0
  212. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +90 -0
  213. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +28 -0
  214. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +90 -0
  215. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +33 -0
  216. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +558 -0
  217. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +119 -0
  218. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +109 -0
  219. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +121 -0
  220. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +133 -0
  221. package/openspec/changes/extract-prompts-to-markdown/design.md +86 -0
  222. package/openspec/changes/extract-prompts-to-markdown/proposal.md +50 -0
  223. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +74 -0
  224. package/openspec/changes/extract-prompts-to-markdown/tasks.md +40 -0
  225. package/openspec/changes/refactor-webtest-naming/design.md +95 -0
  226. package/openspec/changes/refactor-webtest-naming/proposal.md +66 -0
  227. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +79 -0
  228. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +80 -0
  229. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +122 -0
  230. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +113 -0
  231. package/openspec/changes/refactor-webtest-naming/tasks.md +119 -0
  232. package/openspec/changes/rename-package-to-retest/proposal.md +52 -0
  233. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +53 -0
  234. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +68 -0
  235. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +35 -0
  236. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +159 -0
  237. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +251 -0
  238. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +99 -0
  239. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +295 -0
  240. package/openspec/changes/rename-package-to-retest/tasks.md +71 -0
  241. package/openspec/project.md +31 -0
  242. package/openspec/specs/mcp-server-core/spec.md +178 -0
  243. package/openspec/specs/webtest-lifecycle/spec.md +136 -0
  244. package/openspec/specs/webtest-logging/spec.md +209 -0
  245. package/openspec/specs/webtest-prompts/spec.md +155 -0
  246. package/openspec/specs/webtest-resources/spec.md +248 -0
  247. package/openspec/specs/webtest-sampling/spec.md +344 -0
  248. package/openspec/specs/webtest-tools/spec.md +282 -0
  249. package/package.json +54 -0
  250. package/release.config.js +9 -0
  251. package/src/config.test.ts +96 -0
  252. package/src/config.ts +32 -0
  253. package/src/elicitation/index.test.ts +399 -0
  254. package/src/elicitation/index.ts +171 -0
  255. package/src/elicitation/types.ts +68 -0
  256. package/src/index.ts +83 -0
  257. package/src/lifecycle/index.test.ts +260 -0
  258. package/src/lifecycle/index.ts +101 -0
  259. package/src/logger.redaction.test.ts +322 -0
  260. package/src/logger.test.ts +123 -0
  261. package/src/logger.ts +229 -0
  262. package/src/playwright-client/index.ts +392 -0
  263. package/src/playwright-client/types.ts +99 -0
  264. package/src/progress/index.test.ts +327 -0
  265. package/src/progress/index.ts +170 -0
  266. package/src/progress/types.ts +25 -0
  267. package/src/prompts/index.test.ts +451 -0
  268. package/src/prompts/index.ts +246 -0
  269. package/src/prompts/loader.test.ts +100 -0
  270. package/src/prompts/loader.ts +59 -0
  271. package/src/prompts/templates/mcp/webtest-crawl.md +7 -0
  272. package/src/prompts/templates/mcp/webtest-discover-flows.md +11 -0
  273. package/src/prompts/templates/mcp/webtest-discover.md +12 -0
  274. package/src/prompts/templates/mcp/webtest-full-workflow.md +12 -0
  275. package/src/prompts/templates/mcp/webtest-generate-tests.md +11 -0
  276. package/src/prompts/templates/mcp/webtest-run-test.md +11 -0
  277. package/src/prompts/templates/mcp/webtest-start.md +8 -0
  278. package/src/prompts/templates/sampling/crawl-action.md +35 -0
  279. package/src/prompts/templates/sampling/feature-discovery.md +27 -0
  280. package/src/prompts/templates/sampling/flow-discovery.md +29 -0
  281. package/src/prompts/templates/sampling/page-content-wrapper.md +5 -0
  282. package/src/prompts/templates/sampling/system-prefix.md +12 -0
  283. package/src/prompts/templates/sampling/test-evaluation.md +17 -0
  284. package/src/prompts/templates/sampling/test-generation.md +31 -0
  285. package/src/resources/index.ts +250 -0
  286. package/src/resources/subscriptions.ts +37 -0
  287. package/src/sampling/index.test.ts +414 -0
  288. package/src/sampling/index.ts +286 -0
  289. package/src/sampling/prompts.ts +194 -0
  290. package/src/sampling/types.ts +60 -0
  291. package/src/schemas/config.ts +39 -0
  292. package/src/security/index.test.ts +441 -0
  293. package/src/security/index.ts +361 -0
  294. package/src/security/security-scenarios.test.ts +468 -0
  295. package/src/server.ts +211 -0
  296. package/src/test-utils/index.ts +6 -0
  297. package/src/test-utils/mock-context.ts +426 -0
  298. package/src/test-utils/mock-playwright-client.ts +422 -0
  299. package/src/tools/index.ts +11 -0
  300. package/src/tools/webtest/crawl.test.ts +834 -0
  301. package/src/tools/webtest/crawl.ts +901 -0
  302. package/src/tools/webtest/discover-features.ts +412 -0
  303. package/src/tools/webtest/discover-flows.ts +408 -0
  304. package/src/tools/webtest/generate-tests.test.ts +532 -0
  305. package/src/tools/webtest/generate-tests.ts +425 -0
  306. package/src/tools/webtest/index.ts +7 -0
  307. package/src/tools/webtest/integration.test.ts +536 -0
  308. package/src/tools/webtest/run-test-case.test.ts +659 -0
  309. package/src/tools/webtest/run-test-case.ts +508 -0
  310. package/src/tools/webtest/schemas.ts +201 -0
  311. package/src/tools/webtest/start-analysis.test.ts +151 -0
  312. package/src/tools/webtest/start-analysis.ts +158 -0
  313. package/src/transports/http.ts +19 -0
  314. package/src/transports/index.ts +30 -0
  315. package/src/transports/stdio.ts +7 -0
  316. package/src/types/capabilities.test.ts +193 -0
  317. package/src/types/capabilities.ts +50 -0
  318. package/src/types/context.ts +21 -0
  319. package/src/types/tool.ts +11 -0
  320. package/src/workspace/index.ts +945 -0
  321. package/src/workspace/markdown.ts +272 -0
  322. package/src/workspace/types.ts +186 -0
  323. package/tests/integration/server.test.ts +89 -0
  324. package/tests/integration/tools.test.ts +99 -0
  325. package/tsconfig.json +20 -0
  326. package/vitest.config.ts +9 -0
  327. 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
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./schemas.js";
2
+ export * from "./start-analysis.js";
3
+ export * from "./crawl.js";
4
+ export * from "./discover-features.js";
5
+ export * from "./discover-flows.js";
6
+ export * from "./generate-tests.js";
7
+ export * from "./run-test-case.js";