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,834 @@
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
+ });