explorbot 0.0.1 → 0.1.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 (423) hide show
  1. package/README.md +80 -26
  2. package/bin/explorbot-cli.ts +680 -0
  3. package/boat/api-tester/src/ai/chief/styles.ts +15 -0
  4. package/boat/api-tester/src/ai/chief.ts +335 -0
  5. package/boat/api-tester/src/ai/curler-tools.ts +278 -0
  6. package/boat/api-tester/src/ai/curler.ts +306 -0
  7. package/boat/api-tester/src/api-client.ts +28 -0
  8. package/boat/api-tester/src/apibot.ts +203 -0
  9. package/boat/api-tester/src/cli.ts +301 -0
  10. package/boat/api-tester/src/config.ts +190 -0
  11. package/dist/bin/explorbot-cli.js +23 -101
  12. package/dist/boat/api-tester/bin/apibot-cli.js +0 -1
  13. package/dist/boat/api-tester/src/ai/chief/styles.js +0 -1
  14. package/dist/boat/api-tester/src/ai/chief.js +0 -1
  15. package/dist/boat/api-tester/src/ai/curler-tools.js +0 -1
  16. package/dist/boat/api-tester/src/ai/curler.js +0 -1
  17. package/dist/boat/api-tester/src/api-client.js +0 -1
  18. package/dist/boat/api-tester/src/apibot.js +0 -1
  19. package/dist/boat/api-tester/src/cli.js +0 -1
  20. package/dist/boat/api-tester/src/config.js +0 -1
  21. package/dist/src/action-result.js +0 -1
  22. package/dist/src/action.js +14 -12
  23. package/dist/src/activity.js +0 -1
  24. package/dist/src/ai/agent.js +0 -1
  25. package/dist/src/ai/bosun.js +0 -1
  26. package/dist/src/ai/captain/idle-mode.js +0 -1
  27. package/dist/src/ai/captain/mixin.js +0 -1
  28. package/dist/src/ai/captain/test-mode.js +0 -1
  29. package/dist/src/ai/captain/web-mode.js +0 -1
  30. package/dist/src/ai/captain.js +0 -1
  31. package/dist/src/ai/conversation.js +0 -1
  32. package/dist/src/ai/experience-compactor.js +0 -1
  33. package/dist/src/ai/fisherman-tools.js +0 -1
  34. package/dist/src/ai/fisherman.js +0 -1
  35. package/dist/src/ai/historian.js +0 -1
  36. package/dist/src/ai/navigator.js +0 -1
  37. package/dist/src/ai/pilot.js +0 -1
  38. package/dist/src/ai/planner/session-dedup.js +0 -1
  39. package/dist/src/ai/planner/styles.js +0 -1
  40. package/dist/src/ai/planner/subpages.js +42 -7
  41. package/dist/src/ai/planner.js +15 -4
  42. package/dist/src/ai/provider.js +0 -1
  43. package/dist/src/ai/quartermaster.js +0 -1
  44. package/dist/src/ai/researcher/cache.js +13 -9
  45. package/dist/src/ai/researcher/coordinates.js +4 -3
  46. package/dist/src/ai/researcher/deep-analysis.js +16 -20
  47. package/dist/src/ai/researcher/fingerprint-worker.js +0 -1
  48. package/dist/src/ai/researcher/focus.js +0 -1
  49. package/dist/src/ai/researcher/locators.js +1 -2
  50. package/dist/src/ai/researcher/mixin.js +0 -1
  51. package/dist/src/ai/researcher/parser.js +4 -4
  52. package/dist/src/ai/researcher/research-result.js +2 -1
  53. package/dist/src/ai/researcher.js +6 -6
  54. package/dist/src/ai/rules.js +0 -1
  55. package/dist/src/ai/task-agent.js +0 -1
  56. package/dist/src/ai/tester.js +0 -1
  57. package/dist/src/ai/tools.js +4 -1
  58. package/dist/src/api/api-client.js +0 -1
  59. package/dist/src/api/request-result.js +0 -1
  60. package/dist/src/api/request-store.js +0 -1
  61. package/dist/src/api/spec-reader.js +0 -1
  62. package/dist/src/api/xhr-capture.js +0 -1
  63. package/dist/src/browser-server.js +0 -1
  64. package/dist/src/command-handler.js +0 -1
  65. package/dist/src/commands/add-rule-command.js +0 -1
  66. package/dist/src/commands/base-command.js +0 -1
  67. package/dist/src/commands/clean-command.js +0 -1
  68. package/dist/src/commands/context-aria-command.js +0 -1
  69. package/dist/src/commands/context-command.js +2 -3
  70. package/dist/src/commands/context-data-command.js +0 -1
  71. package/dist/src/commands/context-experience-command.js +0 -1
  72. package/dist/src/commands/context-html-command.js +0 -1
  73. package/dist/src/commands/context-knowledge-command.js +0 -1
  74. package/dist/src/commands/debug-command.js +0 -1
  75. package/dist/src/commands/drill-command.js +0 -1
  76. package/dist/src/commands/exit-command.js +0 -1
  77. package/dist/src/commands/explore-command.js +3 -3
  78. package/dist/src/commands/freesail-command.js +0 -1
  79. package/dist/src/commands/help-command.js +0 -1
  80. package/dist/src/commands/index.js +0 -1
  81. package/dist/src/commands/init-command.js +117 -0
  82. package/dist/src/commands/knows-command.js +0 -1
  83. package/dist/src/commands/learn-command.js +0 -1
  84. package/dist/src/commands/navigate-command.js +0 -1
  85. package/dist/src/commands/path-command.js +0 -1
  86. package/dist/src/commands/plan-clear-command.js +0 -1
  87. package/dist/src/commands/plan-command.js +6 -2
  88. package/dist/src/commands/plan-edit-command.js +0 -1
  89. package/dist/src/commands/plan-load-command.js +0 -1
  90. package/dist/src/commands/plan-reload-command.js +0 -1
  91. package/dist/src/commands/plan-save-command.js +0 -1
  92. package/dist/src/commands/research-command.js +0 -1
  93. package/dist/src/commands/start-command.js +0 -1
  94. package/dist/src/commands/status-command.js +0 -1
  95. package/dist/src/commands/test-command.js +0 -1
  96. package/dist/src/components/ActivityPane.js +0 -1
  97. package/dist/src/components/AddKnowledge.js +0 -1
  98. package/dist/src/components/AddRule.js +0 -1
  99. package/dist/src/components/App.js +0 -1
  100. package/dist/src/components/Autocomplete.js +0 -1
  101. package/dist/src/components/InputPane.js +0 -1
  102. package/dist/src/components/InputReadline.js +0 -1
  103. package/dist/src/components/LogPane.js +0 -1
  104. package/dist/src/components/PlanEditor.js +0 -1
  105. package/dist/src/components/PlanPane.js +0 -1
  106. package/dist/src/components/SessionTimer.js +0 -1
  107. package/dist/src/components/StateTransitionPane.js +0 -1
  108. package/dist/src/components/StatusPane.js +0 -1
  109. package/dist/src/components/TaskPane.js +0 -1
  110. package/dist/src/components/Welcome.js +0 -1
  111. package/dist/src/components/WelcomeChecklist.js +0 -1
  112. package/dist/src/components/WelcomeCommands.js +0 -1
  113. package/dist/src/components/autocomplete-store.js +0 -1
  114. package/dist/src/components/parse-keypress.js +0 -1
  115. package/dist/src/config.js +0 -1
  116. package/dist/src/execution-controller.js +0 -1
  117. package/dist/src/experience-tracker.js +0 -1
  118. package/dist/src/explorbot.js +1 -2
  119. package/dist/src/explorer.js +58 -17
  120. package/dist/src/index.js +0 -1
  121. package/dist/src/knowledge-tracker.js +2 -2
  122. package/dist/src/observability.js +0 -1
  123. package/dist/src/reporter.js +0 -1
  124. package/dist/src/state-manager.js +0 -1
  125. package/dist/src/stats.js +0 -1
  126. package/dist/src/test-plan.js +0 -1
  127. package/dist/src/utils/aria.js +0 -1
  128. package/dist/src/utils/cli-name.js +16 -0
  129. package/dist/src/utils/code-extractor.js +0 -1
  130. package/dist/src/utils/context-formatter.js +0 -1
  131. package/dist/src/utils/error-page.js +0 -1
  132. package/dist/src/utils/expandable.js +0 -1
  133. package/dist/src/utils/hooks-runner.js +0 -1
  134. package/dist/src/utils/html-diff.js +0 -1
  135. package/dist/src/utils/html.js +0 -1
  136. package/dist/src/utils/logger.js +0 -1
  137. package/dist/src/utils/loop.js +0 -1
  138. package/dist/src/utils/markdown-parser.js +0 -1
  139. package/dist/src/utils/markdown-query.js +0 -1
  140. package/dist/src/utils/markdown-terminal.js +0 -1
  141. package/dist/src/utils/research-parser.js +0 -1
  142. package/dist/src/utils/retry.js +0 -1
  143. package/dist/src/utils/rules-loader.js +0 -1
  144. package/dist/src/utils/strings.js +0 -1
  145. package/dist/src/utils/test-plan-markdown.js +0 -1
  146. package/dist/src/utils/throttle.js +0 -1
  147. package/dist/src/utils/unique-names.js +0 -1
  148. package/dist/src/utils/url-matcher.js +0 -1
  149. package/dist/src/utils/web-element.js +6 -5
  150. package/dist/src/utils/xpath.js +0 -1
  151. package/package.json +28 -4
  152. package/src/action-result.ts +694 -0
  153. package/src/action.ts +449 -0
  154. package/src/activity.ts +111 -0
  155. package/src/ai/agent.ts +3 -0
  156. package/src/ai/bosun.ts +557 -0
  157. package/src/ai/captain/idle-mode.ts +116 -0
  158. package/src/ai/captain/mixin.ts +22 -0
  159. package/src/ai/captain/test-mode.ts +262 -0
  160. package/src/ai/captain/web-mode.ts +136 -0
  161. package/src/ai/captain.ts +504 -0
  162. package/src/ai/conversation.ts +205 -0
  163. package/src/ai/experience-compactor.ts +284 -0
  164. package/src/ai/fisherman-tools.ts +181 -0
  165. package/src/ai/fisherman.ts +223 -0
  166. package/src/ai/historian.ts +457 -0
  167. package/src/ai/navigator.ts +572 -0
  168. package/src/ai/pilot.ts +776 -0
  169. package/src/ai/planner/session-dedup.ts +35 -0
  170. package/src/ai/planner/styles.ts +17 -0
  171. package/src/ai/planner/subpages.ts +171 -0
  172. package/src/ai/planner.ts +549 -0
  173. package/src/ai/provider.ts +613 -0
  174. package/src/ai/quartermaster.ts +286 -0
  175. package/src/ai/researcher/cache.ts +109 -0
  176. package/src/ai/researcher/coordinates.ts +239 -0
  177. package/src/ai/researcher/deep-analysis.ts +412 -0
  178. package/src/ai/researcher/fingerprint-worker.ts +59 -0
  179. package/src/ai/researcher/focus.ts +42 -0
  180. package/src/ai/researcher/locators.ts +282 -0
  181. package/src/ai/researcher/mixin.ts +4 -0
  182. package/src/ai/researcher/parser.ts +186 -0
  183. package/src/ai/researcher/research-result.ts +116 -0
  184. package/src/ai/researcher.ts +858 -0
  185. package/src/ai/rules.ts +376 -0
  186. package/src/ai/task-agent.ts +141 -0
  187. package/src/ai/tester.ts +939 -0
  188. package/src/ai/tools.ts +1122 -0
  189. package/src/api/api-client.ts +109 -0
  190. package/src/api/request-result.ts +212 -0
  191. package/src/api/request-store.ts +130 -0
  192. package/src/api/spec-reader.ts +174 -0
  193. package/src/api/xhr-capture.ts +100 -0
  194. package/src/browser-server.ts +74 -0
  195. package/src/command-handler.ts +454 -0
  196. package/src/commands/add-rule-command.ts +63 -0
  197. package/src/commands/base-command.ts +27 -0
  198. package/src/commands/clean-command.ts +73 -0
  199. package/src/commands/context-aria-command.ts +22 -0
  200. package/src/commands/context-command.ts +67 -0
  201. package/src/commands/context-data-command.ts +30 -0
  202. package/src/commands/context-experience-command.ts +48 -0
  203. package/src/commands/context-html-command.ts +33 -0
  204. package/src/commands/context-knowledge-command.ts +43 -0
  205. package/src/commands/debug-command.ts +13 -0
  206. package/src/commands/drill-command.ts +34 -0
  207. package/src/commands/exit-command.ts +32 -0
  208. package/src/commands/explore-command.ts +129 -0
  209. package/src/commands/freesail-command.ts +95 -0
  210. package/src/commands/help-command.ts +8 -0
  211. package/src/commands/index.ts +69 -0
  212. package/src/commands/init-command.ts +131 -0
  213. package/src/commands/knows-command.ts +68 -0
  214. package/src/commands/learn-command.ts +44 -0
  215. package/src/commands/navigate-command.ts +18 -0
  216. package/src/commands/path-command.ts +83 -0
  217. package/src/commands/plan-clear-command.ts +14 -0
  218. package/src/commands/plan-command.ts +46 -0
  219. package/src/commands/plan-edit-command.ts +9 -0
  220. package/src/commands/plan-load-command.ts +18 -0
  221. package/src/commands/plan-reload-command.ts +28 -0
  222. package/src/commands/plan-save-command.ts +25 -0
  223. package/src/commands/research-command.ts +45 -0
  224. package/src/commands/start-command.ts +13 -0
  225. package/src/commands/status-command.tsx +23 -0
  226. package/src/commands/test-command.ts +84 -0
  227. package/src/components/ActivityPane.tsx +80 -0
  228. package/src/components/AddKnowledge.tsx +169 -0
  229. package/src/components/AddRule.tsx +174 -0
  230. package/src/components/App.tsx +377 -0
  231. package/src/components/Autocomplete.tsx +63 -0
  232. package/src/components/InputPane.tsx +259 -0
  233. package/src/components/InputReadline.tsx +704 -0
  234. package/src/components/LogPane.tsx +187 -0
  235. package/src/components/PlanEditor.tsx +150 -0
  236. package/src/components/PlanPane.tsx +71 -0
  237. package/src/components/SessionTimer.tsx +35 -0
  238. package/src/components/StateTransitionPane.tsx +149 -0
  239. package/src/components/StatusPane.tsx +62 -0
  240. package/src/components/TaskPane.tsx +119 -0
  241. package/src/components/Welcome.tsx +83 -0
  242. package/src/components/WelcomeChecklist.tsx +118 -0
  243. package/src/components/WelcomeCommands.tsx +102 -0
  244. package/src/components/autocomplete-store.ts +35 -0
  245. package/src/components/parse-keypress.ts +170 -0
  246. package/src/config.ts +491 -0
  247. package/src/execution-controller.ts +109 -0
  248. package/src/experience-tracker.ts +350 -0
  249. package/src/explorbot.ts +405 -0
  250. package/src/explorer.ts +760 -0
  251. package/src/index.tsx +62 -0
  252. package/src/knowledge-tracker.ts +230 -0
  253. package/src/observability.ts +150 -0
  254. package/src/reporter.ts +224 -0
  255. package/src/state-manager.ts +556 -0
  256. package/src/stats.ts +53 -0
  257. package/src/test-plan.ts +432 -0
  258. package/src/utils/aria.ts +629 -0
  259. package/src/utils/cli-name.ts +13 -0
  260. package/src/utils/code-extractor.ts +22 -0
  261. package/src/utils/context-formatter.ts +239 -0
  262. package/src/utils/error-page.ts +23 -0
  263. package/src/utils/expandable.ts +38 -0
  264. package/src/utils/hooks-runner.ts +79 -0
  265. package/src/utils/html-diff.ts +918 -0
  266. package/src/utils/html.ts +1316 -0
  267. package/src/utils/logger.ts +534 -0
  268. package/src/utils/loop.ts +176 -0
  269. package/src/utils/markdown-parser.ts +127 -0
  270. package/src/utils/markdown-query.ts +466 -0
  271. package/src/utils/markdown-terminal.ts +43 -0
  272. package/src/utils/research-parser.ts +11 -0
  273. package/src/utils/retry.ts +73 -0
  274. package/src/utils/rules-loader.ts +118 -0
  275. package/src/utils/strings.ts +13 -0
  276. package/src/utils/test-plan-markdown.ts +332 -0
  277. package/src/utils/throttle.ts +18 -0
  278. package/src/utils/unique-names.ts +14 -0
  279. package/src/utils/url-matcher.ts +45 -0
  280. package/src/utils/web-element.ts +147 -0
  281. package/src/utils/xpath.ts +129 -0
  282. package/dist/bin/explorbot-cli.js.map +0 -1
  283. package/dist/boat/api-tester/bin/apibot-cli.js.map +0 -1
  284. package/dist/boat/api-tester/example/apibot.config.js +0 -31
  285. package/dist/boat/api-tester/example/apibot.config.js.map +0 -1
  286. package/dist/boat/api-tester/src/ai/chief/styles.js.map +0 -1
  287. package/dist/boat/api-tester/src/ai/chief.js.map +0 -1
  288. package/dist/boat/api-tester/src/ai/curler-tools.js.map +0 -1
  289. package/dist/boat/api-tester/src/ai/curler.js.map +0 -1
  290. package/dist/boat/api-tester/src/api-client.js.map +0 -1
  291. package/dist/boat/api-tester/src/apibot.js.map +0 -1
  292. package/dist/boat/api-tester/src/cli.js.map +0 -1
  293. package/dist/boat/api-tester/src/config.js.map +0 -1
  294. package/dist/prompts/audit-rules.md +0 -124
  295. package/dist/src/action-result.js.map +0 -1
  296. package/dist/src/action.js.map +0 -1
  297. package/dist/src/activity.js.map +0 -1
  298. package/dist/src/ai/agent.js.map +0 -1
  299. package/dist/src/ai/bosun.js.map +0 -1
  300. package/dist/src/ai/captain/idle-mode.js.map +0 -1
  301. package/dist/src/ai/captain/mixin.js.map +0 -1
  302. package/dist/src/ai/captain/test-mode.js.map +0 -1
  303. package/dist/src/ai/captain/web-mode.js.map +0 -1
  304. package/dist/src/ai/captain.js.map +0 -1
  305. package/dist/src/ai/conversation.js.map +0 -1
  306. package/dist/src/ai/experience-compactor.js.map +0 -1
  307. package/dist/src/ai/fisherman-tools.js.map +0 -1
  308. package/dist/src/ai/fisherman.js.map +0 -1
  309. package/dist/src/ai/historian.js.map +0 -1
  310. package/dist/src/ai/navigator.js.map +0 -1
  311. package/dist/src/ai/pilot.js.map +0 -1
  312. package/dist/src/ai/planner/session-dedup.js.map +0 -1
  313. package/dist/src/ai/planner/styles.js.map +0 -1
  314. package/dist/src/ai/planner/subpages.js.map +0 -1
  315. package/dist/src/ai/planner.js.map +0 -1
  316. package/dist/src/ai/provider.js.map +0 -1
  317. package/dist/src/ai/quartermaster.js.map +0 -1
  318. package/dist/src/ai/researcher/cache.js.map +0 -1
  319. package/dist/src/ai/researcher/coordinates.js.map +0 -1
  320. package/dist/src/ai/researcher/deep-analysis.js.map +0 -1
  321. package/dist/src/ai/researcher/fingerprint-worker.js.map +0 -1
  322. package/dist/src/ai/researcher/focus.js.map +0 -1
  323. package/dist/src/ai/researcher/locators.js.map +0 -1
  324. package/dist/src/ai/researcher/mixin.js.map +0 -1
  325. package/dist/src/ai/researcher/parser.js.map +0 -1
  326. package/dist/src/ai/researcher/research-result.js.map +0 -1
  327. package/dist/src/ai/researcher.js.map +0 -1
  328. package/dist/src/ai/rules.js.map +0 -1
  329. package/dist/src/ai/task-agent.js.map +0 -1
  330. package/dist/src/ai/tester.js.map +0 -1
  331. package/dist/src/ai/tools.js.map +0 -1
  332. package/dist/src/api/api-client.js.map +0 -1
  333. package/dist/src/api/request-result.js.map +0 -1
  334. package/dist/src/api/request-store.js.map +0 -1
  335. package/dist/src/api/spec-reader.js.map +0 -1
  336. package/dist/src/api/xhr-capture.js.map +0 -1
  337. package/dist/src/browser-server.js.map +0 -1
  338. package/dist/src/command-handler.js.map +0 -1
  339. package/dist/src/commands/add-rule-command.js.map +0 -1
  340. package/dist/src/commands/base-command.js.map +0 -1
  341. package/dist/src/commands/clean-command.js.map +0 -1
  342. package/dist/src/commands/context-aria-command.js.map +0 -1
  343. package/dist/src/commands/context-command.js.map +0 -1
  344. package/dist/src/commands/context-data-command.js.map +0 -1
  345. package/dist/src/commands/context-experience-command.js.map +0 -1
  346. package/dist/src/commands/context-html-command.js.map +0 -1
  347. package/dist/src/commands/context-knowledge-command.js.map +0 -1
  348. package/dist/src/commands/debug-command.js.map +0 -1
  349. package/dist/src/commands/drill-command.js.map +0 -1
  350. package/dist/src/commands/exit-command.js.map +0 -1
  351. package/dist/src/commands/explore-command.js.map +0 -1
  352. package/dist/src/commands/freesail-command.js.map +0 -1
  353. package/dist/src/commands/help-command.js.map +0 -1
  354. package/dist/src/commands/index.js.map +0 -1
  355. package/dist/src/commands/knows-command.js.map +0 -1
  356. package/dist/src/commands/learn-command.js.map +0 -1
  357. package/dist/src/commands/navigate-command.js.map +0 -1
  358. package/dist/src/commands/path-command.js.map +0 -1
  359. package/dist/src/commands/plan-clear-command.js.map +0 -1
  360. package/dist/src/commands/plan-command.js.map +0 -1
  361. package/dist/src/commands/plan-edit-command.js.map +0 -1
  362. package/dist/src/commands/plan-load-command.js.map +0 -1
  363. package/dist/src/commands/plan-reload-command.js.map +0 -1
  364. package/dist/src/commands/plan-save-command.js.map +0 -1
  365. package/dist/src/commands/research-command.js.map +0 -1
  366. package/dist/src/commands/start-command.js.map +0 -1
  367. package/dist/src/commands/status-command.js.map +0 -1
  368. package/dist/src/commands/test-command.js.map +0 -1
  369. package/dist/src/components/ActivityPane.js.map +0 -1
  370. package/dist/src/components/AddKnowledge.js.map +0 -1
  371. package/dist/src/components/AddRule.js.map +0 -1
  372. package/dist/src/components/App.js.map +0 -1
  373. package/dist/src/components/Autocomplete.js.map +0 -1
  374. package/dist/src/components/InputPane.js.map +0 -1
  375. package/dist/src/components/InputReadline.js.map +0 -1
  376. package/dist/src/components/LogPane.js.map +0 -1
  377. package/dist/src/components/PlanEditor.js.map +0 -1
  378. package/dist/src/components/PlanPane.js.map +0 -1
  379. package/dist/src/components/SessionTimer.js.map +0 -1
  380. package/dist/src/components/StateTransitionPane.js.map +0 -1
  381. package/dist/src/components/StatusPane.js.map +0 -1
  382. package/dist/src/components/TaskPane.js.map +0 -1
  383. package/dist/src/components/Welcome.js.map +0 -1
  384. package/dist/src/components/WelcomeChecklist.js.map +0 -1
  385. package/dist/src/components/WelcomeCommands.js.map +0 -1
  386. package/dist/src/components/autocomplete-store.js.map +0 -1
  387. package/dist/src/components/parse-keypress.js.map +0 -1
  388. package/dist/src/config.js.map +0 -1
  389. package/dist/src/execution-controller.js.map +0 -1
  390. package/dist/src/experience-tracker.js.map +0 -1
  391. package/dist/src/explorbot.js.map +0 -1
  392. package/dist/src/explorer.js.map +0 -1
  393. package/dist/src/index.js.map +0 -1
  394. package/dist/src/knowledge-tracker.js.map +0 -1
  395. package/dist/src/observability.js.map +0 -1
  396. package/dist/src/reporter.js.map +0 -1
  397. package/dist/src/state-manager.js.map +0 -1
  398. package/dist/src/stats.js.map +0 -1
  399. package/dist/src/test-plan.js.map +0 -1
  400. package/dist/src/utils/aria.js.map +0 -1
  401. package/dist/src/utils/code-extractor.js.map +0 -1
  402. package/dist/src/utils/context-formatter.js.map +0 -1
  403. package/dist/src/utils/error-page.js.map +0 -1
  404. package/dist/src/utils/expandable.js.map +0 -1
  405. package/dist/src/utils/hooks-runner.js.map +0 -1
  406. package/dist/src/utils/html-diff.js.map +0 -1
  407. package/dist/src/utils/html.js.map +0 -1
  408. package/dist/src/utils/logger.js.map +0 -1
  409. package/dist/src/utils/loop.js.map +0 -1
  410. package/dist/src/utils/markdown-parser.js.map +0 -1
  411. package/dist/src/utils/markdown-query.js.map +0 -1
  412. package/dist/src/utils/markdown-terminal.js.map +0 -1
  413. package/dist/src/utils/research-parser.js.map +0 -1
  414. package/dist/src/utils/retry.js.map +0 -1
  415. package/dist/src/utils/rules-loader.js.map +0 -1
  416. package/dist/src/utils/strings.js.map +0 -1
  417. package/dist/src/utils/test-plan-markdown.js.map +0 -1
  418. package/dist/src/utils/throttle.js.map +0 -1
  419. package/dist/src/utils/unique-names.js.map +0 -1
  420. package/dist/src/utils/url-matcher.js.map +0 -1
  421. package/dist/src/utils/web-element.js.map +0 -1
  422. package/dist/src/utils/xpath.js.map +0 -1
  423. package/prompts/audit-rules.md +0 -124
@@ -0,0 +1,939 @@
1
+ import { dirname, join, relative, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { tool } from 'ai';
4
+ import dedent from 'dedent';
5
+ import { z } from 'zod';
6
+ import { ActionResult } from '../action-result.ts';
7
+ import { setActivity } from '../activity.ts';
8
+ import { ConfigParser } from '../config.ts';
9
+ import type { ExperienceTracker } from '../experience-tracker.ts';
10
+ import type Explorer from '../explorer.ts';
11
+ import type { StateTransition, WebPageState } from '../state-manager.ts';
12
+ import { Stats } from '../stats.ts';
13
+ import { type Note, type Test, TestResult, type TestResultType } from '../test-plan.ts';
14
+ import { extractFocusedElement } from '../utils/aria.ts';
15
+ import { HooksRunner } from '../utils/hooks-runner.ts';
16
+ import { codeToMarkdown } from '../utils/html.ts';
17
+ import { createDebug, tag } from '../utils/logger.ts';
18
+ import { loop } from '../utils/loop.ts';
19
+ import type { Agent } from './agent.ts';
20
+ import type { Conversation } from './conversation.ts';
21
+ import { Navigator } from './navigator.ts';
22
+ import type { Captain } from './captain.ts';
23
+ import type { Pilot } from './pilot.ts';
24
+ import { Provider } from './provider.ts';
25
+ import { Researcher } from './researcher.ts';
26
+ import { actionRule, focusedElementRule, locatorRule, multipleTabsRule, sectionContextRule } from './rules.ts';
27
+ import { TaskAgent } from './task-agent.ts';
28
+ import { createCodeceptJSTools, createSpecialContextTools } from './tools.ts';
29
+
30
+ const debugLog = createDebug('explorbot:tester');
31
+
32
+ const SAMPLE_FILES_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../assets/sample-files');
33
+ const SAMPLE_FILES: Record<string, string> = {
34
+ 'PNG image': 'sample.png',
35
+ 'PDF document': 'sample.pdf',
36
+ 'Word document (DOCX)': 'sample.docx',
37
+ 'Excel spreadsheet (XLSX)': 'sample.xlsx',
38
+ 'ZIP archive': 'sample.zip',
39
+ 'MP4 video': 'sample.mp4',
40
+ 'MP3 audio': 'sample.mp3',
41
+ };
42
+
43
+ export class Tester extends TaskAgent implements Agent {
44
+ protected readonly ACTION_TOOLS = ['click', 'pressKey', 'form'];
45
+ protected readonly SPECIAL_CONTEXT_ACTION_TOOLS = ['exitIframe'];
46
+ emoji = '🧪';
47
+ private explorer: Explorer;
48
+ private provider: Provider;
49
+ private currentConversation: Conversation | null = null;
50
+ private pilot: Pilot | null = null;
51
+ private captain: Captain | null = null;
52
+
53
+ MAX_ITERATIONS = 30;
54
+ MAX_EXTENSIONS = 2;
55
+ ASSERTION_TOOLS = ['verify'];
56
+ researcher: Researcher;
57
+ navigator: Navigator;
58
+ agentTools: any;
59
+ executionLogFile: string | null = null;
60
+ private previousUrl: string | null = null;
61
+ private previousStateHash: string | null = null;
62
+ private hooksRunner: HooksRunner;
63
+
64
+ constructor(explorer: Explorer, provider: Provider, researcher: Researcher, navigator: Navigator, agentTools?: any) {
65
+ super();
66
+ this.explorer = explorer;
67
+ this.provider = provider;
68
+ this.researcher = researcher;
69
+ this.navigator = navigator;
70
+ this.agentTools = agentTools;
71
+ this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
72
+ }
73
+
74
+ protected getNavigator(): Navigator {
75
+ return this.navigator;
76
+ }
77
+
78
+ protected getExperienceTracker(): ExperienceTracker {
79
+ return this.explorer.getStateManager().getExperienceTracker();
80
+ }
81
+
82
+ protected getKnowledgeTracker() {
83
+ return this.explorer.getKnowledgeTracker();
84
+ }
85
+
86
+ protected getProvider(): Provider {
87
+ return this.provider;
88
+ }
89
+
90
+ setPilot(pilot: Pilot): void {
91
+ this.pilot = pilot;
92
+ }
93
+
94
+ setCaptain(captain: Captain): void {
95
+ this.captain = captain;
96
+ }
97
+
98
+ private getCurrentState(): ActionResult {
99
+ return ActionResult.fromState(this.explorer.getStateManager().getCurrentState()!);
100
+ }
101
+
102
+ private get progressCheckInterval(): number {
103
+ return (this.explorer.getConfig().ai?.agents?.tester as any)?.progressCheckInterval ?? 5;
104
+ }
105
+
106
+ getConversation(): Conversation | null {
107
+ return this.currentConversation;
108
+ }
109
+
110
+ async test(task: Test): Promise<{ success: boolean }> {
111
+ Stats.tests++;
112
+ const state = this.explorer.getStateManager().getCurrentState();
113
+ if (!state) throw new Error('No state found');
114
+
115
+ tag('info').log(`Testing scenario: ${task.scenario}`);
116
+ setActivity(`🧪 Testing: ${task.scenario}`, 'action');
117
+
118
+ this.previousUrl = null;
119
+ this.previousStateHash = null;
120
+ this.explorer.getStateManager().clearHistory();
121
+ this.resetFailureCount();
122
+ this.pilot?.reset();
123
+
124
+ const initialState = ActionResult.fromState(state);
125
+
126
+ const conversation = this.provider.startConversation(this.getSystemMessage(), 'tester');
127
+ this.currentConversation = conversation;
128
+
129
+ const outputDir = ConfigParser.getInstance().getOutputDir();
130
+ this.executionLogFile = join(outputDir, `tester_${task.sessionName}.md`);
131
+ // Note: Markdown saving functionality removed from Conversation class
132
+
133
+ const initialPrompt = await this.buildTestPrompt(task, initialState);
134
+ conversation.addUserText(initialPrompt);
135
+
136
+ if (this.pilot) {
137
+ const plan = await this.pilot.planTest(task, initialState);
138
+ if (plan) {
139
+ conversation.addUserText(`Pilot's test plan:\n${plan}\n\nFollow this plan while executing the test.`);
140
+ }
141
+ }
142
+
143
+ debugLog('Starting test execution with tools');
144
+
145
+ task.start();
146
+ await this.explorer.startTest(task);
147
+
148
+ debugLog(`Navigating to ${task.startUrl}`);
149
+ await this.explorer.visit(task.startUrl!);
150
+
151
+ const currentUrl = this.explorer.getStateManager().getCurrentState()?.url || task.startUrl || '';
152
+ await this.hooksRunner.runBeforeHook('tester', currentUrl);
153
+
154
+ const offStateChange = this.explorer.getStateManager().onStateChange((event: StateTransition) => {
155
+ if (event.toState?.url === event.fromState?.url) return;
156
+ task.addNote(`Navigated to ${event.toState?.url}`, TestResult.PASSED);
157
+ task.states.push(event.toState);
158
+ });
159
+
160
+ const codeceptjsTools = createCodeceptJSTools(this.explorer, task);
161
+ let assertionPerformed = false;
162
+ let extensions = 0;
163
+ let shouldContinue = true;
164
+
165
+ while (shouldContinue) {
166
+ shouldContinue = false;
167
+
168
+ await loop(
169
+ async ({ stop, pause, iteration, userInput }) => {
170
+ debugLog('iteration', iteration);
171
+ const currentState = this.getCurrentState();
172
+
173
+ const tools = {
174
+ ...codeceptjsTools,
175
+ ...(currentState.isInsideIframe ? createSpecialContextTools(this.explorer, 'iframe') : {}),
176
+ ...this.createTestFlowTools(task, currentState, conversation),
177
+ ...this.agentTools,
178
+ };
179
+
180
+ debugLog(`Test ${task.scenario} iteration ${iteration}`);
181
+
182
+ if (this.explorer.getStateManager().isInDeadLoop()) {
183
+ task.addNote('Dead loop detected. Stopped');
184
+ stop();
185
+ return;
186
+ }
187
+
188
+ if (userInput) {
189
+ conversation.addUserText(dedent`
190
+ <page>
191
+ CURRENT URL: ${currentState.url}
192
+ CURRENT TITLE: ${currentState.title}
193
+ </page>
194
+
195
+ <user_redirect>
196
+ ${userInput}
197
+ </user_redirect>
198
+
199
+ The user has interrupted and wants to change direction. Follow the new instruction.
200
+ `);
201
+ }
202
+
203
+ conversation.cleanupTag('page_aria', '...cleaned aria snapshot...', 2);
204
+ conversation.cleanupTag('page_html', '...cleaned HTML snapshot...', 1);
205
+ conversation.cleanupTag('experience', '...cleaned experience...', 1);
206
+
207
+ if (iteration > 1) {
208
+ const isNewPage = this.previousUrl !== null && this.previousUrl !== currentState.url;
209
+ let nextStep = '';
210
+ nextStep += await this.reinjectContextIfNeeded(iteration, currentState);
211
+ nextStep += await this.prepareInstructionsForNextStep(task);
212
+
213
+ if (isNewPage && this.pilot) {
214
+ const guidance = await this.pilot.reviewNewPage(task, currentState);
215
+ if (guidance) nextStep += `\n\n${guidance}`;
216
+ } else if ((iteration % this.progressCheckInterval === 0 || this.consecutiveFailures >= 3 || this.consecutiveEmptyResults >= 2) && this.pilot) {
217
+ const guidance = await this.pilot.analyzeProgress(task, currentState, conversation);
218
+ if (guidance) nextStep += `\n\n${guidance}`;
219
+ this.consecutiveFailures = 0;
220
+ }
221
+ conversation.addUserText(nextStep);
222
+ }
223
+
224
+ const result = await this.provider.invokeConversation(conversation, tools, {
225
+ maxToolRoundtrips: 5,
226
+ toolChoice: 'required',
227
+ });
228
+
229
+ if (!result) throw new Error('Failed to get response from provider');
230
+
231
+ if (result.response?.text && result.toolExecutions?.length === 0) {
232
+ task.addNote(result.response.text.substring(0, 200));
233
+ }
234
+
235
+ debugLog('tool executions:', result?.toolExecutions?.map((execution: any) => execution.toolName).join(', '));
236
+
237
+ const allToolNames = result?.toolExecutions?.map((execution: any) => execution.toolName) || [];
238
+ const successfulToolNames = result?.toolExecutions?.filter((execution: any) => execution.wasSuccessful)?.map((execution: any) => execution.toolName) || [];
239
+ const actionPerformed = !!allToolNames.find((toolName: string) => this.ACTION_TOOLS.includes(toolName));
240
+ assertionPerformed = !!successfulToolNames.find((toolName: string) => this.ASSERTION_TOOLS.includes(toolName));
241
+ const wasSuccessful = result?.toolExecutions?.every((execution: any) => execution.wasSuccessful);
242
+
243
+ this.trackToolExecutions(result?.toolExecutions || []);
244
+
245
+ if (this.consecutiveEmptyResults >= 5) {
246
+ task.addNote('AI model is not responding with actions. Stopped');
247
+ stop();
248
+ return;
249
+ }
250
+
251
+ if (actionPerformed && !wasSuccessful) {
252
+ result?.toolExecutions
253
+ ?.filter((execution: any) => !execution.wasSuccessful && execution.input?.explanation)
254
+ .forEach((execution: any) => {
255
+ task.addNote(`Failed to ${execution.input.explanation} (${execution.toolName})`, TestResult.FAILED);
256
+ });
257
+ }
258
+
259
+ if (assertionPerformed) {
260
+ const message = result?.toolExecutions?.find((execution: any) => execution.toolName === 'verify')?.output?.message || '';
261
+ task.addNote(message, wasSuccessful ? TestResult.PASSED : TestResult.FAILED);
262
+ if (wasSuccessful) {
263
+ conversation.addUserText(dedent`
264
+ Assertion "${message}" successfully passed!
265
+
266
+ If the scenario goal is achieved, call finish() now to complete the test.
267
+ If there are remaining expected outcomes that require NEW ACTIONS, proceed with those actions.
268
+ Do not call verify() again until you perform a new action that changes the page.
269
+
270
+ Expected outcomes to check:
271
+ ${task.expected.map((expectation) => `- ${expectation}`).join('\n')}
272
+ `);
273
+ }
274
+ }
275
+
276
+ if (task.hasFinished) {
277
+ stop();
278
+ return;
279
+ }
280
+
281
+ if (iteration >= this.MAX_ITERATIONS) {
282
+ task.addNote('Max iterations reached. Stopped');
283
+ stop();
284
+ return;
285
+ }
286
+ },
287
+ {
288
+ maxAttempts: this.MAX_ITERATIONS,
289
+ interruptPrompt: 'Test interrupted. Enter new instruction (or "stop" to cancel):',
290
+ onInterrupt: this.captain
291
+ ? async (userInput, context) => {
292
+ if (!userInput) return;
293
+ const result = await this.captain!.processSupervisorInterrupt(userInput, task);
294
+ tag('info').log(`🧑‍✈️ Supervisor: ${result.action} — ${result.message}`);
295
+
296
+ const terminalResults: Record<string, (typeof TestResult)[keyof typeof TestResult]> = {
297
+ stop: TestResult.FAILED,
298
+ pass: TestResult.PASSED,
299
+ skip: TestResult.SKIPPED,
300
+ };
301
+ const terminalResult = terminalResults[result.action];
302
+ if (terminalResult) {
303
+ task.addNote(result.message, terminalResult);
304
+ task.finish(terminalResult);
305
+ context.stop();
306
+ return;
307
+ }
308
+ context.setUserInput(result.message);
309
+ }
310
+ : undefined,
311
+ observability: {
312
+ name: `test: ${task.scenario}`,
313
+ agent: 'tester',
314
+ sessionId: task.sessionName,
315
+ metadata: {
316
+ input: {
317
+ scenario: task.scenario,
318
+ startUrl: task.startUrl,
319
+ expected: task.expected,
320
+ },
321
+ },
322
+ },
323
+ catch: async ({ error, stop }) => {
324
+ tag('error').log(`Test execution error: ${error}`);
325
+ task.addNote(`Execution error: ${error instanceof Error ? error.message : String(error)}`);
326
+ stop();
327
+ },
328
+ }
329
+ );
330
+
331
+ if (task.hasFinished) break;
332
+
333
+ const finalState = this.getCurrentState();
334
+ const wantsContinue = await this.pilot!.finalReview(task, finalState, conversation);
335
+
336
+ if (!wantsContinue || task.hasFinished) break;
337
+ if (extensions >= this.MAX_EXTENSIONS) break;
338
+
339
+ extensions++;
340
+ tag('info').log(`Pilot extending test (${extensions}/${this.MAX_EXTENSIONS})`);
341
+ shouldContinue = true;
342
+ }
343
+
344
+ const finalUrl = this.explorer.getStateManager().getCurrentState()?.url || currentUrl;
345
+ await this.hooksRunner.runAfterHook('tester', finalUrl);
346
+
347
+ await this.getHistorian().saveSession(task, initialState, conversation);
348
+ if (task.plan) {
349
+ this.getHistorian().savePlanToFile(task.plan);
350
+ }
351
+ await this.getQuartermaster().analyzeSession(task, initialState, conversation);
352
+
353
+ offStateChange();
354
+ await this.finishTest(task);
355
+ await this.explorer.stopTest(task, {
356
+ startUrl: task.startUrl,
357
+ style: task.style,
358
+ sessionName: task.sessionName,
359
+ });
360
+
361
+ return {
362
+ success: task.isSuccessful,
363
+ ...task,
364
+ };
365
+ }
366
+
367
+ private async prepareInstructionsForNextStep(task: Test): Promise<string> {
368
+ let outcomeStatus = dedent`
369
+ <task>
370
+ Continue testing to achieve the scenario goal or expected outcomes.
371
+ </task>
372
+
373
+ <rules>
374
+ Use tools ${this.ACTION_TOOLS.join(', ')} to interact with the page.
375
+ Do not do unsuccesful clicks again.
376
+ Do not run same tool calls with same parameters again.
377
+ </rules>
378
+ `;
379
+
380
+ if (task.getPrintableNotes()) {
381
+ outcomeStatus = dedent`
382
+ Your current log:
383
+ <notes>
384
+ ${task.notesToString()}
385
+ </notes>
386
+ `;
387
+ }
388
+
389
+ const remaining = task.getRemainingExpectations();
390
+ if (remaining.length > 0) {
391
+ outcomeStatus += `\nExpected steps to check: ${remaining.join(', ')}`;
392
+ }
393
+
394
+ return outcomeStatus;
395
+ }
396
+
397
+ private async reinjectContextIfNeeded(iteration: number, currentState: ActionResult): Promise<string> {
398
+ const currentUrl = currentState.url;
399
+ const currentStateHash = currentState.hash;
400
+
401
+ const isNewUrl = this.previousUrl !== currentUrl;
402
+ const isStateChanged = !isNewUrl && this.previousStateHash !== currentStateHash;
403
+
404
+ this.previousUrl = currentUrl;
405
+ this.previousStateHash = currentStateHash;
406
+
407
+ let context = '';
408
+
409
+ const focusedElement = extractFocusedElement(currentState.ariaSnapshot);
410
+ if (focusedElement) {
411
+ const isTextInput = ['textbox', 'combobox', 'searchbox'].includes(focusedElement.role);
412
+ context += dedent`
413
+ <current_focus>
414
+ FOCUSED: ${focusedElement.role} "${focusedElement.name}"${focusedElement.value ? ` (current value: "${focusedElement.value}")` : ''}
415
+ ${isTextInput ? focusedElementRule : ''}
416
+ </current_focus>
417
+ `;
418
+ } else {
419
+ context += dedent`
420
+ <no_focus>
421
+ No element is focused
422
+ </no_focus>
423
+ `;
424
+ }
425
+
426
+ if (currentState.isInsideIframe) {
427
+ const iframeInfo = currentState.iframeURL || this.explorer.getCurrentIframeInfo() || 'iframe context active';
428
+ context += dedent`
429
+ <iframe_context>
430
+ INSIDE IFRAME: ${iframeInfo}
431
+ You are currently inside an iframe. Use exitIframe() before interacting with elements outside the iframe.
432
+ </iframe_context>
433
+ `;
434
+ }
435
+
436
+ if (this.explorer.hasOtherTabs()) {
437
+ const otherTabs = this.explorer.getOtherTabsInfo();
438
+ context += multipleTabsRule(otherTabs);
439
+ this.explorer.clearOtherTabsInfo();
440
+ }
441
+
442
+ if (isNewUrl) {
443
+ const research = await this.researcher.research(currentState);
444
+ const experience = this.getExperience(currentState);
445
+ let uiMapSection = '';
446
+ if (research) {
447
+ uiMapSection = dedent`
448
+
449
+ Page UI Map
450
+ The complete UI map of a page (can be oudated)
451
+ <page_ui_map>
452
+ ${research}
453
+ </page_ui_map>
454
+ `;
455
+ }
456
+
457
+ context += dedent`
458
+ Context:
459
+
460
+ <page>
461
+ CURRENT URL: ${currentState.url}
462
+ CURRENT TITLE: ${currentState.title}
463
+ </page>
464
+
465
+ <page_aria>
466
+ ${currentState.getInteractiveARIA()}
467
+ </page_aria>
468
+ ${uiMapSection}
469
+
470
+ ${experience}
471
+
472
+ Use <page_ui_map> to understand the page structure and its main elements.
473
+ However, <page_ui_map> is not always up to date, use <page_aria> and <page_html> to understand the ACTUAL state of the page
474
+ Do not interact with elements that are not listed in <page_aria> and <page_html>
475
+ Refer to information on page sections in <page_ui_map> and use container CSS locators to interact with elements inside sections
476
+ `;
477
+ return context;
478
+ }
479
+
480
+ // if (isStateChanged) {
481
+ // const combinedHtml = await currentState.combinedHtml();
482
+ // context += dedent`
483
+ // Context (state changed):
484
+
485
+ // <page>
486
+ // CURRENT URL: ${currentState.url}
487
+ // CURRENT TITLE: ${currentState.title}
488
+ // </page>
489
+
490
+ // <page_html>
491
+ // ${combinedHtml}
492
+ // </page_html>
493
+
494
+ // <page_aria>
495
+ // ${currentState.ariaSnapshot}
496
+ // </page_aria>
497
+ // `;
498
+ // return context;
499
+ // }
500
+
501
+ if (context) return context;
502
+
503
+ if (iteration % 5) return '';
504
+
505
+ return dedent`
506
+ Context:
507
+
508
+ <page>
509
+ CURRENT URL: ${currentState.url}
510
+ CURRENT TITLE: ${currentState.title}
511
+ </page>
512
+
513
+ <page_aria>
514
+ ${currentState.getInteractiveARIA()}
515
+ </page_aria>
516
+ `;
517
+ }
518
+
519
+ private async promptLogStep(task: Test): Promise<string> {
520
+ let logPrompt = dedent`
521
+ <task>
522
+ Add a note explaining what you achieved with previous action.
523
+ Use tools to interact with the page to achieve the scenario goal or expected outcomes.
524
+ Call record tool to explain the last action
525
+ Format: record([<action performed>, <what has changed>, <what you expect to do next>])
526
+ </task>
527
+ `;
528
+
529
+ if (task.getPrintableNotes()) {
530
+ logPrompt = dedent`
531
+ Your interaction log notes:
532
+ <notes>
533
+ ${task.getPrintableNotes()}
534
+ </notes>
535
+
536
+ <rules>
537
+ Use your previous interaction notes to guide your next actions.
538
+ Do not perform the same checks.
539
+ </rules>
540
+ `;
541
+ }
542
+
543
+ const remaining = task.getRemainingExpectations();
544
+ if (remaining.length > 0) {
545
+ logPrompt += `\nExpected steps to check: ${remaining.join(', ')}`;
546
+ }
547
+
548
+ return logPrompt;
549
+ }
550
+
551
+ private finishTest(task: Test): void {
552
+ if (!task.hasFinished) {
553
+ task.finish(TestResult.FAILED);
554
+ }
555
+ tag('info').log(`Finished: ${task.scenario}`);
556
+
557
+ if (task.isSuccessful) {
558
+ tag('success').log(`Successful test: ${task.scenario}`);
559
+ } else if (task.isSkipped) {
560
+ tag('warning').log(`Skipped test: ${task.scenario}`);
561
+ } else if (task.hasFailed) {
562
+ tag('error').log(`Failed test: ${task.scenario}`);
563
+ } else {
564
+ tag('warning').log(`Test with no result: ${task.scenario}`);
565
+ }
566
+ }
567
+
568
+ getSystemMessage(): string {
569
+ return dedent`
570
+ <role>
571
+ You are a senior test automation engineer with expertise in CodeceptJS and exploratory testing.
572
+ Your task is to execute testing scenario by interacting with web pages using available tools.
573
+ </role>
574
+
575
+ <task>
576
+ You will be provided with scenario goal which should be achieved.
577
+ Expected results will help you to achieve the scenario goal.
578
+ Focus on achieving the main scenario goal
579
+ Check expected results as an optional secondary goal, as they can be wrong or not achievable
580
+ </task>
581
+
582
+ <approach>
583
+ 1. Provide explanation for your next action in your response
584
+ 2. Analyze the current page state and identify elements needed for the scenario
585
+ 3. Plan the sequence of actions required to achieve the scenario goal or expected outcomes
586
+ 4. Execute actions step by step using the available tools
587
+ 5. After each action, check if any expected outcomes have been achieved or failed
588
+ 5.1 If you see page changed interact with that page to achieve a result
589
+ 5.2 Always look for the current URL you are on and use only elements that exist in the current page
590
+ 5.3 If you see the page is irrelevant to current scenario, call reset() tool to return to the initial page
591
+ 6. Some expectations can be wrong so it's ok to skip them and continue testing
592
+ 7. Use finish() ONLY when you have successfully completed the scenario goal and verified it
593
+ 8. ONLY use stop() if the scenario is fundamentally incompatible with the initial page and other pages you visited
594
+ 9. Be methodical and precise in your interactions
595
+ 10. Use record({ notes: ["..."] }) to document your findings, observations, and plans during testing.
596
+ </approach>
597
+
598
+ <rules>
599
+ - Refer to UI Map from <page_ui_map> to understand the page structure and its main elements
600
+ - Use only elements that exist in the provided ARIA tree or HTML, <page_aria> and <page_html>
601
+ - Use click() for buttons, links, and clickable elements ONLY - do NOT include I.fillField() or I.type() commands in click() tool
602
+ - click() commands array is for FALLBACK LOCATORS of the SAME element, NOT for clicking different elements in sequence. If you need to click two different elements, make two separate click() calls.
603
+ - Use form() for text input (I.fillField, I.type), dropdown selection (I.selectOption), file uploads (I.attachFile), and multi-step form interactions
604
+ - Use pressKey() for pressing special keys (Enter, Escape, Tab, Arrow keys) or key combinations with modifiers (Ctrl+A, Shift+Delete, etc.)
605
+ - Use container CSS locators from <page_ui_map> to interact with elements inside sections
606
+ - Systematically use record({ notes: ["..."] }) to write your findings, planned actions, observations, etc.
607
+ - Call record({ notes: ["..."], status: "success" }) when you see success/info message on a page or when expected outcome is achieved
608
+ - Call record({ notes: ["..."], status: "fail" }) when an expected outcome cannot be achieved or has failed or you see error/alert/warning message on a page
609
+ - NEVER call record(status: "success") if your last verify() or see() call FAILED. A failed check means the outcome is NOT confirmed — use record(status: "fail") instead, or retry with a different approach.
610
+ - Use finish() to complete the test, not record(). record() is for intermediate notes.
611
+ - Call finish(verify) when all goals are achieved — provide an assertion to verify
612
+ - ONLY call stop() if the scenario itself is completely irrelevant to this page and no expectations can be achieved
613
+ - Use reset() to navigate back to the initial page if needed. Do not call it if you are already on the initial page
614
+ - Be precise with locators (CSS or XPath)
615
+ - Each click/type call returns the new page state automatically
616
+ - Check for success messages from tool calls to verify if expected outcomes are achieved
617
+ - Check for error messages to understand if there are issues
618
+ - Verify if data was correctly saved and changes are reflected on the page
619
+ - By default, you receive accessibility tree data which shows interactive elements and page structure
620
+ - Understand current context by following <page_html>, <page_aria>, and <page_ui_map>
621
+ - Before submitting form, check all inputs were filled in correctly using see() tool
622
+ - When you interact with form with inputs, ensure that you click corresponding button to save its data
623
+ - Follow <locator_priority> rules when selecting locators for all tools
624
+ - Before retrying your actions check maybe they already achived expected results. Use see() tool for that
625
+ - When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
626
+ - When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
627
+ - For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
628
+ - When click() fails and element is visually present, use visualClick() as fallback
629
+ - If you land on a "Not Found", 404, or error page that is NOT part of the scenario, call reset() immediately to return to the initial page and try again
630
+ - If you see a server error page (500, 503, etc.), record it with record({ notes: ["Server error on /path"], status: "fail" }) and call reset() to continue testing
631
+ </rules>
632
+
633
+ <free_thinking_rule>
634
+ You primary focus to achieve the SCENARIO GOAL
635
+ Expected results were pre-planned and may be wrong or not achievable
636
+ As much as possible use record({ notes: ["..."] }) to document your findings, observations, and plans during testing.
637
+ If you see that scenario goal can be achieved in unexpected way, call record({ notes: ["..."] }) and continue
638
+ You may navigate to different pages to achieve expected results.
639
+ You may interact with different pages to achieve expected results.
640
+ While page is relevant to scenario it is ok to use its elements or try to navigate from it.
641
+ If behavior is unexpected, and irrelevant to scenario, but you assume it is an application bug, call record({ notes: ["explanation"], status: "fail" }).
642
+ If you have succesfully achieved some unexpected outcome, call record({ notes: ["exact outcome text"], status: "success" })
643
+ </free_thinking_rule>
644
+
645
+ ${locatorRule}
646
+
647
+ ${actionRule}
648
+
649
+ ${sectionContextRule}
650
+
651
+ ${this.provider.getSystemPromptForAgent('tester', this.explorer.getStateManager().getCurrentState()?.url) || ''}
652
+ `;
653
+ }
654
+
655
+ private async buildTestPrompt(task: Test, actionResult: ActionResult): Promise<string> {
656
+ const knowledge = this.getKnowledge(actionResult);
657
+ const pageContext = await this.reinjectContextIfNeeded(1, actionResult);
658
+
659
+ return dedent`
660
+ <task>
661
+ SCENARIO GOAL: ${task.scenario}
662
+
663
+ EXPECTED RESULTS:
664
+ Check expected results one by one.
665
+ But some of them can be wrong so it's ok to skip them and continue testing.
666
+
667
+ <expected_results>
668
+ ${task.expected.map((e) => `- ${e}`).join('\n')}
669
+ </expected_results>
670
+
671
+ Your goal is to perform actions on the web page and verify the expected outcomes.
672
+ Try to achieve as many goals as possible.
673
+ If goal is not achievable, log that and skip to next one.
674
+ Do not hallucinate that goal was achieved when it was not.
675
+ When creating or editing items via form() or type() you should include ${task.sessionName} in the value (if it is not restricted by the application logic)
676
+ Initial page URL: ${actionResult.url}
677
+
678
+ ${this.buildDeletionScope(task)}
679
+
680
+ ${this.buildAvailableFiles()}
681
+
682
+ ${knowledge}
683
+
684
+ ${pageContext}
685
+ `;
686
+ }
687
+
688
+ private getDeletableSessionNames(task: Test): string[] {
689
+ if (!task.plan) return [];
690
+ return task.plan
691
+ .listTests()
692
+ .filter((t) => t.isSuccessful && t.sessionName)
693
+ .map((t) => t.sessionName!);
694
+ }
695
+
696
+ private buildAvailableFiles(): string {
697
+ const userFiles = this.explorer.getConfig().files || {};
698
+ const codeceptDir = (global as any).codecept_dir || process.cwd();
699
+ const lines: string[] = [];
700
+
701
+ for (const [description, filename] of Object.entries(SAMPLE_FILES)) {
702
+ lines.push(`- ${description}: ${relative(codeceptDir, join(SAMPLE_FILES_DIR, filename))}`);
703
+ }
704
+ for (const [description, filePath] of Object.entries(userFiles)) {
705
+ lines.push(`- ${description}: ${relative(codeceptDir, resolve(filePath))}`);
706
+ }
707
+
708
+ return dedent`
709
+ <available_files>
710
+ When a test requires file uploading, use I.attachFile() via form() tool with these files:
711
+ ${lines.join('\n')}
712
+ </available_files>
713
+ `;
714
+ }
715
+
716
+ private buildDeletionScope(task: Test): string {
717
+ const deletableItems = this.getDeletableSessionNames(task);
718
+ if (deletableItems.length > 0) {
719
+ return `When deleting items, ONLY delete items whose title contains one of these session names: ${deletableItems.join(', ')}. These were created by previous tests.`;
720
+ }
721
+ const scenarioLower = task.scenario.toLowerCase();
722
+ if (scenarioLower.includes('delete') || scenarioLower.includes('remove')) {
723
+ return 'No items from previous tests are available for deletion. You need to create an item first before deleting it.';
724
+ }
725
+ return '';
726
+ }
727
+
728
+ private createTestFlowTools(task: Test, currentState: ActionResult, conversation: Conversation) {
729
+ const resetUrl = task.startUrl;
730
+ const visitedUrls = task.getVisitedUrls();
731
+ return {
732
+ reset: tool({
733
+ description: dedent`
734
+ Reset the testing flow by navigating back to the original page.
735
+ Use this when navigated too far from the desired state and
736
+ there's no clear path to achieve the expected result. This restarts the
737
+ testing flow from a known good state.
738
+ `,
739
+ inputSchema: z.object({
740
+ reason: z.string().optional().describe('Explanation why you need to navigate'),
741
+ }),
742
+ execute: async ({ reason }) => {
743
+ if (this.getCurrentState().isInsideIframe) {
744
+ await this.explorer.switchToMainFrame();
745
+ }
746
+
747
+ if (this.explorer.getStateManager().getCurrentState()?.url === resetUrl!) {
748
+ return {
749
+ success: false,
750
+ message: 'Reset failed - already on initial page!',
751
+ suggestion: 'Try different approach or use stop() if the scenario is fundamentally incompatible with the page.',
752
+ action: 'reset',
753
+ };
754
+ }
755
+
756
+ const explanation = reason ? `${reason} (RESET)` : 'Resetting to initial page';
757
+ const targetUrl = resetUrl!;
758
+ task.addNote(explanation);
759
+ const resetAction = this.explorer.createAction();
760
+ const success = await resetAction.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, explanation);
761
+
762
+ if (success) {
763
+ return {
764
+ success: true,
765
+ action: 'reset',
766
+ message: `Navigated back to ${targetUrl}`,
767
+ explanation,
768
+ };
769
+ }
770
+
771
+ const result: any = {
772
+ success: false,
773
+ action: 'reset',
774
+ message: `Failed to navigate back to ${targetUrl}`,
775
+ suggestion: 'Try navigating manually or use stop() if the scenario can no longer continue.',
776
+ explanation,
777
+ };
778
+
779
+ if (resetAction.lastError) {
780
+ result.error = resetAction.lastError.toString();
781
+ }
782
+
783
+ return result;
784
+ },
785
+ }),
786
+ stop: tool({
787
+ description: dedent`
788
+ Stop the current test because the scenario is fundamentally incompatible with the page.
789
+ Use this ONLY when the scenario cannot be executed on the current page or application.
790
+ Do NOT use this for failures — use reset() and retry instead.
791
+ `,
792
+ inputSchema: z.object({
793
+ reason: z.string().describe('Explanation why the scenario is incompatible'),
794
+ }),
795
+ execute: async ({ reason }) => {
796
+ task.addNote(`Stop requested: ${reason}`);
797
+
798
+ if (this.pilot) {
799
+ const currentState = this.getCurrentState();
800
+ await this.pilot.reviewStop(task, currentState, conversation);
801
+ if (!task.hasFinished) {
802
+ return {
803
+ success: false,
804
+ action: 'stop',
805
+ message: 'Stop rejected; Continue execution',
806
+ };
807
+ }
808
+ } else {
809
+ task.addNote(reason, TestResult.FAILED);
810
+ task.finish(TestResult.FAILED);
811
+ }
812
+
813
+ return {
814
+ success: true,
815
+ action: 'stop',
816
+ message: reason,
817
+ };
818
+ },
819
+ }),
820
+ finish: tool({
821
+ description: dedent`
822
+ Finish the current test successfully because all goals are achieved and verified.
823
+ ONLY use this when you have successfully completed the scenario goal.
824
+
825
+ Provide a specific assertion to verify the final state.
826
+ The assertion MUST prove that YOUR ACTIONS changed the page state.
827
+ Do NOT verify something that was already true before you started testing.
828
+
829
+ Examples of good assertions:
830
+ - "New user 'john@example.com' is visible in the users list"
831
+ - "Success message 'Item created' is displayed"
832
+
833
+ Pilot will review and decide the final verdict.
834
+ `,
835
+ inputSchema: z.object({
836
+ verify: z.string().describe('Specific assertion to verify on the page before finishing (e.g., "New item appears in the list")'),
837
+ }),
838
+ execute: async ({ verify }) => {
839
+ task.addNote(`Finish requested: ${verify}`);
840
+
841
+ if (this.pilot) {
842
+ const currentState = this.getCurrentState();
843
+ await this.pilot.reviewFinish(task, currentState, conversation);
844
+ if (!task.hasFinished) {
845
+ return {
846
+ success: false,
847
+ action: 'finish',
848
+ message: 'Finishing rejected; Continue execution',
849
+ };
850
+ }
851
+ } else {
852
+ task.addNote('Test finished successfully', TestResult.PASSED);
853
+ task.finish(TestResult.PASSED);
854
+ }
855
+
856
+ return {
857
+ success: true,
858
+ action: 'finish',
859
+ message: verify,
860
+ };
861
+ },
862
+ }),
863
+ record: tool({
864
+ description: dedent`
865
+ Record test results, outcomes, or notes during testing.
866
+
867
+ DO NOT CALL THIS TOOL TWICE IN A ROW.
868
+ Use it only after each action or assertion performed.
869
+
870
+ Notes must be SHORT - no longer than 10 words each.
871
+ Be explicit: which action was done, which element interactied with
872
+
873
+ Recommended format (3 notes):
874
+ - "describe what action performed"
875
+ - "describe what has changed"
876
+ - "what you expect to do next"
877
+
878
+ Use status="success" when:
879
+ - One of the expected results has been successfully achieved
880
+ - You see a success/info message on a page
881
+
882
+ Use status="fail" when:
883
+ - Expected result cannot be achieved or has failed
884
+ - You see an error/alert/warning message on a page
885
+ - You unsuccessfully tried multiple iterations and failed
886
+ - If the expected result was expected to fail, use status="success" instead
887
+
888
+ Example:
889
+ - record({ notes: ["clicked login button", "login form appeared", "fill credentials"], status: "success" })
890
+ `,
891
+ inputSchema: z.object({
892
+ notes: z.array(z.string()).describe('Array of notes to add. Each note must be short (max 15 words). Recommended format: "> ACT: ...", "> ASSERT: ...", "> PLAN: ..."'),
893
+ status: z.enum(['fail', 'success']).optional().describe('Status: "success" for achieved outcomes, "fail" for failed outcomes, null for general notes'),
894
+ }),
895
+ execute: async (input) => {
896
+ let mappedStatus: TestResultType = null;
897
+ if (input.status === 'success') {
898
+ mappedStatus = TestResult.PASSED;
899
+ } else if (input.status === 'fail') {
900
+ mappedStatus = TestResult.FAILED;
901
+ }
902
+
903
+ const screenshotFile = this.explorer.getStateManager().getCurrentState()?.screenshotFile;
904
+
905
+ for (const noteText of input.notes) {
906
+ task.addNote(noteText, mappedStatus, screenshotFile);
907
+
908
+ if (input.status === 'success') {
909
+ tag('success').log(`✔ ${noteText}`);
910
+ } else if (input.status === 'fail') {
911
+ tag('warning').log(`✘ ${noteText}`);
912
+ }
913
+ }
914
+
915
+ if (input.status !== null && task.isComplete()) {
916
+ if (this.pilot) {
917
+ const currentState = this.getCurrentState();
918
+ await this.pilot.reviewCompletion(task, currentState, conversation);
919
+ } else {
920
+ const hasPassed = task.hasAchievedAny();
921
+ task.finish(hasPassed ? TestResult.PASSED : TestResult.FAILED);
922
+ }
923
+ }
924
+
925
+ const remainingExpectations = task.getRemainingExpectations();
926
+ // const suggestion = input.status !== null && remainingExpectations.length > 0 ? `Continue testing to check the remaining expected outcomes: ${remainingExpectations.join(', ')}` : 'Continue with your testing strategy based on these findings.';
927
+
928
+ return {
929
+ success: true,
930
+ action: 'record',
931
+ status: input.status,
932
+ message: `Added ${input.notes.length} note(s)`,
933
+ suggestion: 'Continue testing. Do not call record() tool again until you perform next actions',
934
+ };
935
+ },
936
+ }),
937
+ };
938
+ }
939
+ }