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,659 @@
1
+ /**
2
+ * Unit Tests for webtest_run_tests tool (Phase 7.8)
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from "vitest";
6
+ import { createRunTestCaseTool } from "./run-test-case.js";
7
+ import {
8
+ createMockContext,
9
+ type MockContext,
10
+ } from "../../test-utils/index.js";
11
+
12
+ describe("webtest_run_tests", () => {
13
+ let context: MockContext;
14
+ let tool: ReturnType<typeof createRunTestCaseTool>;
15
+
16
+ beforeEach(() => {
17
+ context = createMockContext();
18
+ tool = createRunTestCaseTool(() => context as any);
19
+
20
+ // Set up workspace with tests
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
+ analysis: {
27
+ appAnalysisUri: "webtest://test/analysis/app-analysis.md",
28
+ flowsUri: "webtest://test/analysis/flows.md",
29
+ },
30
+ tests: {
31
+ testsUri: "webtest://test/tests/tests.md",
32
+ testCount: 2,
33
+ },
34
+ runs: [],
35
+ limits: { maxSteps: 100, maxMinutes: 30, maxPages: 50 },
36
+ });
37
+
38
+ // Set up resource reading for tests (markdown with YAML frontmatter)
39
+ context.resourceManager.readResource = vi.fn().mockResolvedValue({
40
+ text: `---
41
+ tests:
42
+ - id: test-browse-001
43
+ name: Browse products successfully
44
+ category: happy_path
45
+ purpose: Verify user can browse product list
46
+ preconditions:
47
+ - User is on home page
48
+ steps:
49
+ - stepNumber: 1
50
+ action: Click
51
+ target: a.products-link
52
+ expected: Products page loads
53
+ - stepNumber: 2
54
+ action: Verify
55
+ expected: Product list is visible
56
+ expectedOutcomes:
57
+ - Products page is displayed
58
+ tags:
59
+ - browse
60
+ - id: test-checkout-001
61
+ name: Complete checkout
62
+ category: happy_path
63
+ purpose: Verify user can complete checkout
64
+ preconditions: []
65
+ steps:
66
+ - stepNumber: 1
67
+ action: Navigate to
68
+ target: /cart
69
+ expected: Cart page loads
70
+ expectedOutcomes:
71
+ - Order is placed
72
+ tags:
73
+ - checkout
74
+ ---
75
+
76
+ # Test Cases
77
+
78
+ ## Browse products successfully
79
+ ...
80
+ `,
81
+ });
82
+
83
+ // Set up evaluation result
84
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
85
+ success: true,
86
+ data: {
87
+ passed: true,
88
+ reasoning: "Expected outcome matched",
89
+ confidence: 0.95,
90
+ },
91
+ });
92
+
93
+ // Connect playwright
94
+ context.playwrightClient.isConnected = vi.fn().mockReturnValue(true);
95
+ });
96
+
97
+ describe("tool metadata", () => {
98
+ it("has correct name", () => {
99
+ expect(tool.name).toBe("webtest_run_test");
100
+ });
101
+
102
+ it("has a description", () => {
103
+ expect(tool.description).toBeDefined();
104
+ expect(tool.description.length).toBeGreaterThan(0);
105
+ });
106
+
107
+ it("has an input schema", () => {
108
+ expect(tool.inputSchema).toBeDefined();
109
+ });
110
+ });
111
+
112
+ describe("handler - validation", () => {
113
+ it("returns error for non-existent workspace", async () => {
114
+ context.workspaceManager.workspaceExists = vi.fn().mockResolvedValue(false);
115
+
116
+ const result = await tool.handler({
117
+ analysisId: "00000000-0000-0000-0000-000000000000",
118
+ testCaseId: "test-1",
119
+ });
120
+
121
+ expect(result.isError).toBe(true);
122
+ expect(result.content[0].text).toContain("not found");
123
+ });
124
+
125
+ it("returns error when no tests exist", async () => {
126
+ context.workspaceManager.readWorkspaceIndex = vi.fn().mockResolvedValue({
127
+ url: "https://example.com",
128
+ domain: "example.com",
129
+ crawls: [],
130
+ analysis: null,
131
+ tests: null,
132
+ runs: [],
133
+ limits: { maxSteps: 100, maxMinutes: 30, maxPages: 50 },
134
+ });
135
+
136
+ const result = await tool.handler({
137
+ analysisId: context.testAnalysisId,
138
+ testCaseId: "test-1",
139
+ });
140
+
141
+ expect(result.isError).toBe(true);
142
+ expect(result.content[0].text).toContain("No tests found");
143
+ });
144
+
145
+ it("returns error for non-existent test case", async () => {
146
+ const result = await tool.handler({
147
+ analysisId: context.testAnalysisId,
148
+ testCaseId: "non-existent-test",
149
+ });
150
+
151
+ expect(result.isError).toBe(true);
152
+ expect(result.content[0].text).toContain("not found");
153
+ expect(result.content[0].text).toContain("Available tests");
154
+ });
155
+
156
+ it("returns error when tests fail to load", async () => {
157
+ context.resourceManager.readResource = vi.fn().mockRejectedValue(
158
+ new Error("File not found")
159
+ );
160
+
161
+ const result = await tool.handler({
162
+ analysisId: context.testAnalysisId,
163
+ testCaseId: "test-browse-001",
164
+ });
165
+
166
+ expect(result.isError).toBe(true);
167
+ expect(result.content[0].text).toContain("Error loading");
168
+ });
169
+ });
170
+
171
+ describe("handler - test execution", () => {
172
+ it("creates test run in workspace", async () => {
173
+ await tool.handler({
174
+ analysisId: context.testAnalysisId,
175
+ testCaseId: "test-browse-001",
176
+ });
177
+
178
+ expect(context.workspaceManager.createTestRun).toHaveBeenCalledWith(
179
+ context.testAnalysisId,
180
+ expect.objectContaining({
181
+ testCaseId: "test-browse-001",
182
+ testName: "Browse products successfully",
183
+ })
184
+ );
185
+ });
186
+
187
+ it("connects to playwright if not connected", async () => {
188
+ context.playwrightClient.isConnected = vi.fn().mockReturnValue(false);
189
+
190
+ await tool.handler({
191
+ analysisId: context.testAnalysisId,
192
+ testCaseId: "test-browse-001",
193
+ });
194
+
195
+ expect(context.playwrightClient.connect).toHaveBeenCalled();
196
+ });
197
+
198
+ it("navigates to workspace URL first", async () => {
199
+ await tool.handler({
200
+ analysisId: context.testAnalysisId,
201
+ testCaseId: "test-browse-001",
202
+ });
203
+
204
+ expect(context.playwrightClient.navigate).toHaveBeenCalledWith(
205
+ "https://shop.example.com"
206
+ );
207
+ });
208
+
209
+ it("executes click actions", async () => {
210
+ await tool.handler({
211
+ analysisId: context.testAnalysisId,
212
+ testCaseId: "test-browse-001",
213
+ });
214
+
215
+ // When no element/ref provided, falls back to target as element with empty ref
216
+ expect(context.playwrightClient.click).toHaveBeenCalledWith("a.products-link", "");
217
+ });
218
+
219
+ it("returns runId in response", async () => {
220
+ const result = await tool.handler({
221
+ analysisId: context.testAnalysisId,
222
+ testCaseId: "test-browse-001",
223
+ });
224
+
225
+ expect(result.isError).toBeFalsy();
226
+ const content = JSON.parse(result.content[0].text!);
227
+ expect(content.runId).toBeDefined();
228
+ });
229
+
230
+ it("returns test status", async () => {
231
+ const result = await tool.handler({
232
+ analysisId: context.testAnalysisId,
233
+ testCaseId: "test-browse-001",
234
+ });
235
+
236
+ const content = JSON.parse(result.content[0].text!);
237
+ expect(content.status).toBeDefined();
238
+ expect(["passed", "failed", "error"]).toContain(content.status);
239
+ });
240
+
241
+ it("returns step results", async () => {
242
+ const result = await tool.handler({
243
+ analysisId: context.testAnalysisId,
244
+ testCaseId: "test-browse-001",
245
+ });
246
+
247
+ const content = JSON.parse(result.content[0].text!);
248
+ expect(content.stepResults).toBeInstanceOf(Array);
249
+ expect(content.stepResults.length).toBeGreaterThan(0);
250
+ });
251
+
252
+ it("returns summary with step counts", async () => {
253
+ const result = await tool.handler({
254
+ analysisId: context.testAnalysisId,
255
+ testCaseId: "test-browse-001",
256
+ });
257
+
258
+ const content = JSON.parse(result.content[0].text!);
259
+ expect(content.summary).toBeDefined();
260
+ expect(content.summary.totalSteps).toBeGreaterThan(0);
261
+ expect(content.summary.passed).toBeDefined();
262
+ });
263
+ });
264
+
265
+ describe("handler - evidence capture", () => {
266
+ it("captures screenshot at each step by default", async () => {
267
+ await tool.handler({
268
+ analysisId: context.testAnalysisId,
269
+ testCaseId: "test-browse-001",
270
+ });
271
+
272
+ expect(context.playwrightClient.screenshot).toHaveBeenCalled();
273
+ });
274
+
275
+ it("captures snapshot at each step by default", async () => {
276
+ await tool.handler({
277
+ analysisId: context.testAnalysisId,
278
+ testCaseId: "test-browse-001",
279
+ });
280
+
281
+ expect(context.playwrightClient.snapshot).toHaveBeenCalled();
282
+ });
283
+
284
+ it("saves evidence to workspace", async () => {
285
+ await tool.handler({
286
+ analysisId: context.testAnalysisId,
287
+ testCaseId: "test-browse-001",
288
+ });
289
+
290
+ expect(context.workspaceManager.saveTestStepEvidence).toHaveBeenCalled();
291
+ });
292
+
293
+ it("skips evidence when captureEvidence is false", async () => {
294
+ vi.clearAllMocks();
295
+
296
+ // Need to re-mock after clear
297
+ context.playwrightClient.navigate = vi.fn().mockResolvedValue(undefined);
298
+
299
+ await tool.handler({
300
+ analysisId: context.testAnalysisId,
301
+ testCaseId: "test-browse-001",
302
+ runOptions: {
303
+ captureEvidence: false,
304
+ stopOnFailure: true,
305
+ retryFailedSteps: false,
306
+ },
307
+ });
308
+
309
+ expect(context.workspaceManager.saveTestStepEvidence).not.toHaveBeenCalled();
310
+ });
311
+ });
312
+
313
+ describe("handler - assertion evaluation", () => {
314
+ it("evaluates step assertions via sampling", async () => {
315
+ await tool.handler({
316
+ analysisId: context.testAnalysisId,
317
+ testCaseId: "test-browse-001",
318
+ });
319
+
320
+ expect(context.samplingClient.createMessage).toHaveBeenCalled();
321
+ });
322
+
323
+ it("marks step as failed when assertion fails", async () => {
324
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
325
+ success: true,
326
+ data: {
327
+ passed: false,
328
+ reasoning: "Expected product list but saw error page",
329
+ confidence: 0.9,
330
+ },
331
+ });
332
+
333
+ const result = await tool.handler({
334
+ analysisId: context.testAnalysisId,
335
+ testCaseId: "test-browse-001",
336
+ });
337
+
338
+ const content = JSON.parse(result.content[0].text!);
339
+ expect(content.status).toBe("failed");
340
+ });
341
+
342
+ it("continues without sampling when unavailable", async () => {
343
+ context.samplingClient.hasSampling = vi.fn().mockReturnValue(false);
344
+
345
+ const result = await tool.handler({
346
+ analysisId: context.testAnalysisId,
347
+ testCaseId: "test-browse-001",
348
+ });
349
+
350
+ // Should still pass (no assertion evaluation)
351
+ expect(result.isError).toBeFalsy();
352
+ });
353
+ });
354
+
355
+ describe("handler - stop on failure", () => {
356
+ it("stops on first failure by default", async () => {
357
+ context.playwrightClient.click = vi.fn()
358
+ .mockRejectedValueOnce(new Error("Element not found"))
359
+ .mockResolvedValue(undefined);
360
+
361
+ const result = await tool.handler({
362
+ analysisId: context.testAnalysisId,
363
+ testCaseId: "test-browse-001",
364
+ });
365
+
366
+ const content = JSON.parse(result.content[0].text!);
367
+ expect(content.status).toBe("error");
368
+ expect(content.summary.skipped).toBeGreaterThan(0);
369
+ });
370
+
371
+ it("continues on failure when stopOnFailure is false", async () => {
372
+ context.playwrightClient.click = vi.fn()
373
+ .mockRejectedValueOnce(new Error("Element not found"))
374
+ .mockResolvedValue(undefined);
375
+
376
+ const result = await tool.handler({
377
+ analysisId: context.testAnalysisId,
378
+ testCaseId: "test-browse-001",
379
+ runOptions: {
380
+ captureEvidence: true,
381
+ stopOnFailure: false,
382
+ retryFailedSteps: false,
383
+ },
384
+ });
385
+
386
+ const content = JSON.parse(result.content[0].text!);
387
+ // All steps should have been attempted
388
+ expect(content.summary.skipped).toBe(0);
389
+ });
390
+ });
391
+
392
+ describe("handler - retry failed steps", () => {
393
+ it("retries failed steps when enabled", async () => {
394
+ let callCount = 0;
395
+ context.playwrightClient.click = vi.fn().mockImplementation(() => {
396
+ callCount++;
397
+ if (callCount === 1) {
398
+ return Promise.reject(new Error("Transient error"));
399
+ }
400
+ return Promise.resolve();
401
+ });
402
+
403
+ const result = await tool.handler({
404
+ analysisId: context.testAnalysisId,
405
+ testCaseId: "test-browse-001",
406
+ runOptions: {
407
+ captureEvidence: true,
408
+ stopOnFailure: true,
409
+ retryFailedSteps: true,
410
+ },
411
+ });
412
+
413
+ // Should have retried and passed
414
+ const content = JSON.parse(result.content[0].text!);
415
+ expect(content.status).toBe("passed");
416
+ });
417
+ });
418
+
419
+ describe("handler - cancellation", () => {
420
+ it("handles cancellation gracefully", async () => {
421
+ const { CancellationError } = await import("../../progress/index.js");
422
+ context.cancellationRegistry.checkCancelled = vi.fn().mockImplementation((id) => {
423
+ throw new CancellationError(id);
424
+ });
425
+
426
+ const result = await tool.handler({
427
+ analysisId: context.testAnalysisId,
428
+ testCaseId: "test-browse-001",
429
+ });
430
+
431
+ expect(result.isError).toBeFalsy();
432
+ const content = JSON.parse(result.content[0].text!);
433
+ expect(content.status).toBe("cancelled");
434
+ });
435
+
436
+ it("registers for cancellation on start", async () => {
437
+ await tool.handler({
438
+ analysisId: context.testAnalysisId,
439
+ testCaseId: "test-browse-001",
440
+ });
441
+
442
+ expect(context.cancellationRegistry.register).toHaveBeenCalled();
443
+ });
444
+
445
+ it("unregisters cancellation on completion", async () => {
446
+ await tool.handler({
447
+ analysisId: context.testAnalysisId,
448
+ testCaseId: "test-browse-001",
449
+ });
450
+
451
+ expect(context.cancellationRegistry.unregister).toHaveBeenCalled();
452
+ });
453
+ });
454
+
455
+ describe("handler - progress reporting", () => {
456
+ it("emits progress for each step", async () => {
457
+ await tool.handler({
458
+ analysisId: context.testAnalysisId,
459
+ testCaseId: "test-browse-001",
460
+ });
461
+
462
+ expect(context.progressEmitter.emit).toHaveBeenCalled();
463
+ });
464
+
465
+ it("includes step info in progress", async () => {
466
+ await tool.handler({
467
+ analysisId: context.testAnalysisId,
468
+ testCaseId: "test-browse-001",
469
+ });
470
+
471
+ expect(context.progressEmitter.emit).toHaveBeenCalledWith(
472
+ expect.objectContaining({
473
+ progress: expect.any(Number),
474
+ total: expect.any(Number),
475
+ message: expect.stringContaining("Step"),
476
+ })
477
+ );
478
+ });
479
+ });
480
+
481
+ describe("handler - resource notifications", () => {
482
+ it("notifies on test run creation", async () => {
483
+ vi.clearAllMocks();
484
+
485
+ await tool.handler({
486
+ analysisId: context.testAnalysisId,
487
+ testCaseId: "test-browse-001",
488
+ });
489
+
490
+ expect(context.resourceManager.notifyListChanged).toHaveBeenCalled();
491
+ });
492
+ });
493
+
494
+ describe("handler - action execution", () => {
495
+ it("executes navigate actions", async () => {
496
+ await tool.handler({
497
+ analysisId: context.testAnalysisId,
498
+ testCaseId: "test-checkout-001",
499
+ });
500
+
501
+ expect(context.playwrightClient.navigate).toHaveBeenCalledWith("/cart");
502
+ });
503
+
504
+ it("handles type actions", async () => {
505
+ context.resourceManager.readResource = vi.fn().mockResolvedValue({
506
+ text: `---
507
+ tests:
508
+ - id: test-type-001
509
+ name: Type in field
510
+ category: happy_path
511
+ purpose: Test typing
512
+ preconditions: []
513
+ steps:
514
+ - stepNumber: 1
515
+ action: Type
516
+ target: input.search
517
+ value: test query
518
+ expectedOutcomes:
519
+ - Text is entered
520
+ ---
521
+
522
+ # Test Cases
523
+ ...
524
+ `,
525
+ });
526
+
527
+ await tool.handler({
528
+ analysisId: context.testAnalysisId,
529
+ testCaseId: "test-type-001",
530
+ });
531
+
532
+ // When no element/ref provided, falls back to target as element with empty ref
533
+ expect(context.playwrightClient.type).toHaveBeenCalledWith("input.search", "", "test query");
534
+ });
535
+
536
+ it("handles fill actions", async () => {
537
+ context.resourceManager.readResource = vi.fn().mockResolvedValue({
538
+ text: `---
539
+ tests:
540
+ - id: test-fill-001
541
+ name: Fill field
542
+ category: happy_path
543
+ purpose: Test filling
544
+ preconditions: []
545
+ steps:
546
+ - stepNumber: 1
547
+ action: Fill
548
+ target: input.email
549
+ value: test@example.com
550
+ expectedOutcomes:
551
+ - Field is filled
552
+ ---
553
+
554
+ # Test Cases
555
+ ...
556
+ `,
557
+ });
558
+
559
+ await tool.handler({
560
+ analysisId: context.testAnalysisId,
561
+ testCaseId: "test-fill-001",
562
+ });
563
+
564
+ // When no element/ref provided, falls back to target as element with empty ref
565
+ expect(context.playwrightClient.fill).toHaveBeenCalledWith("input.email", "", "test@example.com");
566
+ });
567
+ });
568
+
569
+ describe("handler - error handling", () => {
570
+ it("handles playwright connection errors", async () => {
571
+ context.playwrightClient.isConnected = vi.fn().mockReturnValue(false);
572
+ context.playwrightClient.connect = vi.fn().mockRejectedValue(
573
+ new Error("Connection failed")
574
+ );
575
+
576
+ const result = await tool.handler({
577
+ analysisId: context.testAnalysisId,
578
+ testCaseId: "test-browse-001",
579
+ });
580
+
581
+ expect(result.isError).toBe(true);
582
+ });
583
+
584
+ it("handles step execution errors", async () => {
585
+ context.playwrightClient.click = vi.fn().mockRejectedValue(
586
+ new Error("Element not found")
587
+ );
588
+
589
+ const result = await tool.handler({
590
+ analysisId: context.testAnalysisId,
591
+ testCaseId: "test-browse-001",
592
+ });
593
+
594
+ const content = JSON.parse(result.content[0].text!);
595
+ expect(content.status).toBe("error");
596
+ expect(content.stepResults[0].errorMessage).toBeDefined();
597
+ });
598
+ });
599
+
600
+ describe("handler - next steps", () => {
601
+ it("returns success next steps when test passes", async () => {
602
+ const result = await tool.handler({
603
+ analysisId: context.testAnalysisId,
604
+ testCaseId: "test-browse-001",
605
+ });
606
+
607
+ const content = JSON.parse(result.content[0].text!);
608
+ expect(content.nextSteps).toBeInstanceOf(Array);
609
+ expect(content.nextSteps.some((s: string) => s.includes("passed"))).toBe(true);
610
+ });
611
+
612
+ it("returns investigation steps when test fails", async () => {
613
+ context.samplingClient.createMessage = vi.fn().mockResolvedValue({
614
+ success: true,
615
+ data: {
616
+ passed: false,
617
+ reasoning: "Element not visible",
618
+ confidence: 0.9,
619
+ },
620
+ });
621
+
622
+ const result = await tool.handler({
623
+ analysisId: context.testAnalysisId,
624
+ testCaseId: "test-browse-001",
625
+ });
626
+
627
+ const content = JSON.parse(result.content[0].text!);
628
+ expect(content.nextSteps.some((s: string) => s.includes("Investigate"))).toBe(true);
629
+ });
630
+ });
631
+
632
+ describe("handler - logging", () => {
633
+ it("logs test execution start", async () => {
634
+ await tool.handler({
635
+ analysisId: context.testAnalysisId,
636
+ testCaseId: "test-browse-001",
637
+ });
638
+
639
+ expect(context.logger.info).toHaveBeenCalledWith(
640
+ expect.stringContaining("test case"),
641
+ expect.any(Object)
642
+ );
643
+ });
644
+
645
+ it("logs completion with results", async () => {
646
+ await tool.handler({
647
+ analysisId: context.testAnalysisId,
648
+ testCaseId: "test-browse-001",
649
+ });
650
+
651
+ expect(context.logger.info).toHaveBeenCalledWith(
652
+ expect.stringContaining("completed"),
653
+ expect.objectContaining({
654
+ status: expect.any(String),
655
+ })
656
+ );
657
+ });
658
+ });
659
+ });