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,532 @@
1
+ /**
2
+ * Unit Tests for webtest-generate_tests tool (Phase 6.7)
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from "vitest";
6
+ import { createGenerateTestsTool } from "./generate-tests.js";
7
+ import {
8
+ createMockContext,
9
+ type MockContext,
10
+ } from "../../test-utils/index.js";
11
+
12
+ describe("webtest-generate_tests", () => {
13
+ let context: MockContext;
14
+ let tool: ReturnType<typeof createGenerateTestsTool>;
15
+
16
+ beforeEach(() => {
17
+ context = createMockContext();
18
+ tool = createGenerateTestsTool(() => context as any);
19
+
20
+ // Set up workspace with features and featureFlows
21
+ context.workspaceManager.readWorkspaceIndex = vi.fn().mockResolvedValue({
22
+ url: "https://shop.example.com",
23
+ domain: "shop.example.com",
24
+ focus: "Test checkout flow",
25
+ crawls: [],
26
+ features: {
27
+ createdAt: new Date().toISOString(),
28
+ featuresFilePath: "/test/features.md",
29
+ featuresUri: "webtest://test/features.md",
30
+ featureCount: 2,
31
+ features: [
32
+ {
33
+ slug: "product-browsing",
34
+ name: "Product Browsing",
35
+ description: "Browse and view products",
36
+ entities: ["Product"],
37
+ entryPoints: ["/products"],
38
+ },
39
+ {
40
+ slug: "checkout",
41
+ name: "Checkout",
42
+ description: "Complete purchase",
43
+ entities: ["Cart", "Order"],
44
+ entryPoints: ["/cart"],
45
+ },
46
+ ],
47
+ },
48
+ featureFlows: [
49
+ {
50
+ featureSlug: "product-browsing",
51
+ createdAt: new Date().toISOString(),
52
+ flowsFilePath: "/test/features/product-browsing/flows.md",
53
+ flowsUri: "webtest://test/features/product-browsing/flows.md",
54
+ flowCount: 1,
55
+ },
56
+ {
57
+ featureSlug: "checkout",
58
+ createdAt: new Date().toISOString(),
59
+ flowsFilePath: "/test/features/checkout/flows.md",
60
+ flowsUri: "webtest://test/features/checkout/flows.md",
61
+ flowCount: 1,
62
+ },
63
+ ],
64
+ limits: { maxSteps: 100, maxMinutes: 30, maxPages: 50 },
65
+ });
66
+
67
+ // Set up resource reading for features and flows
68
+ context.resourceManager.readResource = vi.fn().mockImplementation((uri: string) => {
69
+ if (uri.includes("features.md") && !uri.includes("features/")) {
70
+ return Promise.resolve({
71
+ text: "# Features\n\n## Product Browsing\n\nBrowse and view products\n\n## Checkout\n\nComplete purchase",
72
+ });
73
+ }
74
+ if (uri.includes("product-browsing/flows")) {
75
+ return Promise.resolve({
76
+ text: `---
77
+ flows:
78
+ - id: browse-products
79
+ name: Browse Products
80
+ entryPoint: /products
81
+ description: User views products
82
+ steps:
83
+ - Navigate to products
84
+ - View list
85
+ ---
86
+
87
+ # Product Browsing Flows
88
+
89
+ ## Browse Products
90
+ ...
91
+ `,
92
+ });
93
+ }
94
+ if (uri.includes("checkout/flows")) {
95
+ return Promise.resolve({
96
+ text: `---
97
+ flows:
98
+ - id: checkout
99
+ name: Checkout Flow
100
+ entryPoint: /cart
101
+ description: User completes purchase
102
+ steps:
103
+ - Add to cart
104
+ - Enter details
105
+ - Pay
106
+ ---
107
+
108
+ # Checkout Flows
109
+
110
+ ## Checkout Flow
111
+ ...
112
+ `,
113
+ });
114
+ }
115
+ return Promise.resolve({ text: "" });
116
+ });
117
+
118
+ // Set up sampling with mock test generation result
119
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
120
+ success: true,
121
+ data: {
122
+ tests: [
123
+ {
124
+ id: "test-browse-001",
125
+ name: "Browse products successfully",
126
+ category: "happy_path",
127
+ purpose: "Verify user can browse product list",
128
+ preconditions: ["User is on home page"],
129
+ steps: [
130
+ {
131
+ stepNumber: 1,
132
+ action: "Click",
133
+ target: "a.products-link",
134
+ expected: "Products page loads",
135
+ },
136
+ {
137
+ stepNumber: 2,
138
+ action: "Verify",
139
+ expected: "Product list is visible",
140
+ },
141
+ ],
142
+ expectedOutcomes: ["Products page is displayed", "Products are listed"],
143
+ tags: ["browse", "products"],
144
+ },
145
+ {
146
+ id: "test-checkout-001",
147
+ name: "Complete checkout",
148
+ category: "happy_path",
149
+ purpose: "Verify user can complete checkout",
150
+ preconditions: ["User has item in cart"],
151
+ steps: [
152
+ {
153
+ stepNumber: 1,
154
+ action: "Navigate to",
155
+ target: "/cart",
156
+ expected: "Cart page loads",
157
+ },
158
+ {
159
+ stepNumber: 2,
160
+ action: "Click",
161
+ target: "button.checkout",
162
+ expected: "Checkout form appears",
163
+ },
164
+ ],
165
+ expectedOutcomes: ["Order is placed", "Confirmation shown"],
166
+ tags: ["checkout", "payment"],
167
+ },
168
+ ],
169
+ coverage: {
170
+ flows: ["browse-products", "checkout"],
171
+ assertionsCovered: 5,
172
+ },
173
+ },
174
+ });
175
+ });
176
+
177
+ describe("tool metadata", () => {
178
+ it("has correct name", () => {
179
+ expect(tool.name).toBe("webtest_generate_tests");
180
+ });
181
+
182
+ it("has a description", () => {
183
+ expect(tool.description).toBeDefined();
184
+ expect(tool.description.length).toBeGreaterThan(0);
185
+ });
186
+
187
+ it("has an input schema", () => {
188
+ expect(tool.inputSchema).toBeDefined();
189
+ });
190
+ });
191
+
192
+ describe("handler - validation", () => {
193
+ it("returns error for non-existent workspace", async () => {
194
+ context.workspaceManager.workspaceExists = vi.fn().mockResolvedValue(false);
195
+
196
+ const result = await tool.handler({
197
+ analysisId: "00000000-0000-0000-0000-000000000000",
198
+ });
199
+
200
+ expect(result.isError).toBe(true);
201
+ expect(result.content[0].text).toContain("not found");
202
+ });
203
+
204
+ it("returns error when no features exist", async () => {
205
+ context.workspaceManager.readWorkspaceIndex = vi.fn().mockResolvedValue({
206
+ url: "https://example.com",
207
+ domain: "example.com",
208
+ crawls: [],
209
+ features: null,
210
+ limits: { maxSteps: 100, maxMinutes: 30, maxPages: 50 },
211
+ });
212
+
213
+ const result = await tool.handler({
214
+ analysisId: context.testAnalysisId,
215
+ });
216
+
217
+ expect(result.isError).toBe(true);
218
+ expect(result.content[0].text).toContain("No features");
219
+ });
220
+
221
+ it("returns error when features data fails to load", async () => {
222
+ context.resourceManager.readResource = vi.fn().mockRejectedValue(
223
+ new Error("File not found")
224
+ );
225
+
226
+ const result = await tool.handler({
227
+ analysisId: context.testAnalysisId,
228
+ });
229
+
230
+ expect(result.isError).toBe(true);
231
+ expect(result.content[0].text).toContain("Error loading");
232
+ });
233
+ });
234
+
235
+ describe("handler - test generation", () => {
236
+ it("calls sampling with test generation prompt", async () => {
237
+ await tool.handler({
238
+ analysisId: context.testAnalysisId,
239
+ });
240
+
241
+ expect(context.samplingClient.createMessage).toHaveBeenCalledWith(
242
+ expect.objectContaining({
243
+ systemPrompt: expect.stringContaining("QA engineer"),
244
+ userPrompt: expect.any(String),
245
+ schema: expect.any(Object),
246
+ })
247
+ );
248
+ });
249
+
250
+ it("saves generated tests to workspace", async () => {
251
+ await tool.handler({
252
+ analysisId: context.testAnalysisId,
253
+ });
254
+
255
+ expect(context.workspaceManager.saveTests).toHaveBeenCalledWith(
256
+ context.testAnalysisId,
257
+ expect.objectContaining({
258
+ markdown: expect.any(String),
259
+ frontmatter: expect.objectContaining({
260
+ tests: expect.any(Array),
261
+ }),
262
+ })
263
+ );
264
+ });
265
+
266
+ it("returns test count and URIs", async () => {
267
+ const result = await tool.handler({
268
+ analysisId: context.testAnalysisId,
269
+ });
270
+
271
+ expect(result.isError).toBeFalsy();
272
+ const content = JSON.parse(result.content[0].text!);
273
+ expect(content.testsUri).toBeDefined();
274
+ expect(content.testCount).toBeGreaterThan(0);
275
+ });
276
+
277
+ it("returns test summary with categories", async () => {
278
+ const result = await tool.handler({
279
+ analysisId: context.testAnalysisId,
280
+ });
281
+
282
+ const content = JSON.parse(result.content[0].text!);
283
+ expect(content.categories).toBeDefined();
284
+ expect(content.testSummary).toBeInstanceOf(Array);
285
+ });
286
+
287
+ it("notifies about new resources", async () => {
288
+ vi.clearAllMocks();
289
+
290
+ await tool.handler({
291
+ analysisId: context.testAnalysisId,
292
+ });
293
+
294
+ expect(context.resourceManager.notifyListChanged).toHaveBeenCalled();
295
+ });
296
+ });
297
+
298
+ describe("handler - strategies", () => {
299
+ it("accepts comprehensive strategy", async () => {
300
+ const result = await tool.handler({
301
+ analysisId: context.testAnalysisId,
302
+ testStrategy: "comprehensive",
303
+ });
304
+
305
+ expect(result.isError).toBeFalsy();
306
+ const content = JSON.parse(result.content[0].text!);
307
+ expect(content.strategy).toBe("comprehensive");
308
+ });
309
+
310
+ it("accepts happy_path strategy", async () => {
311
+ const result = await tool.handler({
312
+ analysisId: context.testAnalysisId,
313
+ testStrategy: "happy_path",
314
+ });
315
+
316
+ expect(result.isError).toBeFalsy();
317
+ const content = JSON.parse(result.content[0].text!);
318
+ expect(content.strategy).toBe("happy_path");
319
+ });
320
+
321
+ it("accepts edge_cases strategy", async () => {
322
+ const result = await tool.handler({
323
+ analysisId: context.testAnalysisId,
324
+ testStrategy: "edge_cases",
325
+ });
326
+
327
+ expect(result.isError).toBeFalsy();
328
+ });
329
+
330
+ it("accepts critical_paths strategy", async () => {
331
+ const result = await tool.handler({
332
+ analysisId: context.testAnalysisId,
333
+ testStrategy: "critical_paths",
334
+ });
335
+
336
+ expect(result.isError).toBeFalsy();
337
+ });
338
+ });
339
+
340
+ describe("handler - focus flows", () => {
341
+ it("filters flows when focusFlows provided", async () => {
342
+ await tool.handler({
343
+ analysisId: context.testAnalysisId,
344
+ focusFlows: ["checkout"],
345
+ });
346
+
347
+ expect(context.samplingClient.createMessage).toHaveBeenCalled();
348
+ // The prompt should only include the checkout flow
349
+ });
350
+
351
+ it("uses all flows when focusFlows not provided", async () => {
352
+ await tool.handler({
353
+ analysisId: context.testAnalysisId,
354
+ });
355
+
356
+ expect(context.samplingClient.createMessage).toHaveBeenCalled();
357
+ });
358
+ });
359
+
360
+ describe("handler - max tests limit", () => {
361
+ it("respects maxTests parameter", async () => {
362
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
363
+ success: true,
364
+ data: {
365
+ tests: [
366
+ { id: "test-1", name: "Test 1", category: "happy_path", purpose: "P", preconditions: [], steps: [], expectedOutcomes: [] },
367
+ { id: "test-2", name: "Test 2", category: "happy_path", purpose: "P", preconditions: [], steps: [], expectedOutcomes: [] },
368
+ { id: "test-3", name: "Test 3", category: "happy_path", purpose: "P", preconditions: [], steps: [], expectedOutcomes: [] },
369
+ ],
370
+ coverage: { flows: [], assertionsCovered: 0 },
371
+ },
372
+ });
373
+
374
+ await tool.handler({
375
+ analysisId: context.testAnalysisId,
376
+ maxTests: 2,
377
+ });
378
+
379
+ // Verify saved tests are limited
380
+ expect(context.workspaceManager.saveTests).toHaveBeenCalledWith(
381
+ context.testAnalysisId,
382
+ expect.objectContaining({
383
+ frontmatter: expect.objectContaining({
384
+ tests: expect.any(Array),
385
+ }),
386
+ })
387
+ );
388
+
389
+ const saveCall = context.workspaceManager.saveTests.mock.calls[0];
390
+ expect(saveCall[1].frontmatter.tests.length).toBeLessThanOrEqual(2);
391
+ });
392
+
393
+ it("uses default maxTests of 20 by calling sampling", async () => {
394
+ await tool.handler({
395
+ analysisId: context.testAnalysisId,
396
+ });
397
+
398
+ // The tool calls sampling with a system prompt mentioning the limit
399
+ expect(context.samplingClient.createMessage).toHaveBeenCalled();
400
+ });
401
+ });
402
+
403
+ describe("handler - fallback mode", () => {
404
+ beforeEach(() => {
405
+ context.samplingClient.hasSampling = vi.fn().mockReturnValue(false);
406
+ });
407
+
408
+ it("returns prompt when sampling unavailable", async () => {
409
+ const result = await tool.handler({
410
+ analysisId: context.testAnalysisId,
411
+ });
412
+
413
+ expect(result.isError).toBeFalsy();
414
+ const content = JSON.parse(result.content[0].text!);
415
+ expect(content.needsManualInput).toBe(true);
416
+ expect(content.prompt).toBeDefined();
417
+ });
418
+
419
+ it("includes expected schema in fallback response", async () => {
420
+ const result = await tool.handler({
421
+ analysisId: context.testAnalysisId,
422
+ });
423
+
424
+ const content = JSON.parse(result.content[0].text!);
425
+ expect(content.expectedResponseSchema).toBeDefined();
426
+ });
427
+ });
428
+
429
+ describe("handler - error handling", () => {
430
+ it("handles sampling failure", async () => {
431
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
432
+ success: false,
433
+ error: "LLM error",
434
+ });
435
+
436
+ const result = await tool.handler({
437
+ analysisId: context.testAnalysisId,
438
+ });
439
+
440
+ expect(result.isError).toBe(true);
441
+ expect(result.content[0].text).toContain("Error");
442
+ });
443
+
444
+ it("logs errors appropriately", async () => {
445
+ context.samplingClient.createMessage = vi.fn().mockRejectedValue(
446
+ new Error("Network error")
447
+ );
448
+
449
+ await tool.handler({
450
+ analysisId: context.testAnalysisId,
451
+ });
452
+
453
+ expect(context.logger.error).toHaveBeenCalled();
454
+ });
455
+ });
456
+
457
+ describe("handler - markdown generation", () => {
458
+ it("generates markdown with test cases", async () => {
459
+ await tool.handler({
460
+ analysisId: context.testAnalysisId,
461
+ });
462
+
463
+ const saveCall = context.workspaceManager.saveTests.mock.calls[0];
464
+ const markdown = saveCall[1].markdown;
465
+
466
+ expect(markdown).toContain("# Test Cases");
467
+ expect(markdown).toContain("## Overview");
468
+ expect(markdown).toContain("## Test Cases");
469
+ });
470
+
471
+ it("includes test steps in markdown", async () => {
472
+ await tool.handler({
473
+ analysisId: context.testAnalysisId,
474
+ });
475
+
476
+ const saveCall = context.workspaceManager.saveTests.mock.calls[0];
477
+ const markdown = saveCall[1].markdown;
478
+
479
+ expect(markdown).toContain("**Steps**");
480
+ });
481
+
482
+ it("includes expected outcomes in markdown", async () => {
483
+ await tool.handler({
484
+ analysisId: context.testAnalysisId,
485
+ });
486
+
487
+ const saveCall = context.workspaceManager.saveTests.mock.calls[0];
488
+ const markdown = saveCall[1].markdown;
489
+
490
+ expect(markdown).toContain("**Expected Outcomes**");
491
+ });
492
+ });
493
+
494
+ describe("handler - next steps", () => {
495
+ it("returns next steps with first test suggestion", async () => {
496
+ const result = await tool.handler({
497
+ analysisId: context.testAnalysisId,
498
+ });
499
+
500
+ const content = JSON.parse(result.content[0].text!);
501
+ expect(content.nextSteps).toBeInstanceOf(Array);
502
+ expect(content.nextSteps.length).toBeGreaterThan(0);
503
+ expect(content.nextSteps.some((s: string) => s.includes("run_tests"))).toBe(true);
504
+ });
505
+ });
506
+
507
+ describe("handler - logging", () => {
508
+ it("logs test generation start", async () => {
509
+ await tool.handler({
510
+ analysisId: context.testAnalysisId,
511
+ });
512
+
513
+ expect(context.logger.info).toHaveBeenCalledWith(
514
+ expect.stringContaining("test generation"),
515
+ expect.any(Object)
516
+ );
517
+ });
518
+
519
+ it("logs completion with test count", async () => {
520
+ await tool.handler({
521
+ analysisId: context.testAnalysisId,
522
+ });
523
+
524
+ expect(context.logger.info).toHaveBeenCalledWith(
525
+ expect.stringContaining("completed"),
526
+ expect.objectContaining({
527
+ testCount: expect.any(Number),
528
+ })
529
+ );
530
+ });
531
+ });
532
+ });