@testdriverai/agent 7.8.0-canary.10

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 (528) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.env.example +4 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc +1 -0
  5. package/CHANGELOG.md +953 -0
  6. package/README.md +81 -0
  7. package/agent/events.js +135 -0
  8. package/agent/index.js +2450 -0
  9. package/agent/interface.js +35 -0
  10. package/agent/lib/analytics.js +22 -0
  11. package/agent/lib/censorship.js +75 -0
  12. package/agent/lib/commander.js +246 -0
  13. package/agent/lib/commands.js +1684 -0
  14. package/agent/lib/config.js +60 -0
  15. package/agent/lib/generator.js +91 -0
  16. package/agent/lib/http.js +144 -0
  17. package/agent/lib/logger.js +56 -0
  18. package/agent/lib/outputs.js +29 -0
  19. package/agent/lib/parser.js +209 -0
  20. package/agent/lib/redraw.js +386 -0
  21. package/agent/lib/resources/cursor-2.png +0 -0
  22. package/agent/lib/sandbox.js +1104 -0
  23. package/agent/lib/sdk.js +633 -0
  24. package/agent/lib/session.js +25 -0
  25. package/agent/lib/source-mapper.js +342 -0
  26. package/agent/lib/subimage/index.js +77 -0
  27. package/agent/lib/subimage/opencv.js +69 -0
  28. package/agent/lib/system.js +204 -0
  29. package/agent/lib/theme.js +14 -0
  30. package/agent/lib/valid-version.js +21 -0
  31. package/agent/lib/validation.js +169 -0
  32. package/ai/.claude-plugin/plugin.json +9 -0
  33. package/ai/agents/testdriver.md +638 -0
  34. package/ai/skills/testdriver-ai/SKILL.md +204 -0
  35. package/ai/skills/testdriver-assert/SKILL.md +315 -0
  36. package/ai/skills/testdriver-aws-setup/SKILL.md +448 -0
  37. package/ai/skills/testdriver-cache/SKILL.md +221 -0
  38. package/ai/skills/testdriver-caching/SKILL.md +124 -0
  39. package/ai/skills/testdriver-captcha/SKILL.md +158 -0
  40. package/ai/skills/testdriver-ci-cd/SKILL.md +602 -0
  41. package/ai/skills/testdriver-click/SKILL.md +286 -0
  42. package/ai/skills/testdriver-client/SKILL.md +477 -0
  43. package/ai/skills/testdriver-cloud/SKILL.md +119 -0
  44. package/ai/skills/testdriver-customizing-devices/SKILL.md +319 -0
  45. package/ai/skills/testdriver-dashcam/SKILL.md +418 -0
  46. package/ai/skills/testdriver-debugging-with-screenshots/SKILL.md +401 -0
  47. package/ai/skills/testdriver-device-config/SKILL.md +317 -0
  48. package/ai/skills/testdriver-double-click/SKILL.md +102 -0
  49. package/ai/skills/testdriver-elements/SKILL.md +605 -0
  50. package/ai/skills/testdriver-enterprise/SKILL.md +114 -0
  51. package/ai/skills/testdriver-errors/SKILL.md +246 -0
  52. package/ai/skills/testdriver-events/SKILL.md +356 -0
  53. package/ai/skills/testdriver-examples/SKILL.md +7 -0
  54. package/ai/skills/testdriver-exec/SKILL.md +317 -0
  55. package/ai/skills/testdriver-find/SKILL.md +829 -0
  56. package/ai/skills/testdriver-focus-application/SKILL.md +293 -0
  57. package/ai/skills/testdriver-generating-tests/SKILL.md +36 -0
  58. package/ai/skills/testdriver-hover/SKILL.md +278 -0
  59. package/ai/skills/testdriver-locating-elements/SKILL.md +71 -0
  60. package/ai/skills/testdriver-making-assertions/SKILL.md +32 -0
  61. package/ai/skills/testdriver-mcp/SKILL.md +7 -0
  62. package/ai/skills/testdriver-mcp-workflow/SKILL.md +410 -0
  63. package/ai/skills/testdriver-mouse-down/SKILL.md +161 -0
  64. package/ai/skills/testdriver-mouse-up/SKILL.md +164 -0
  65. package/ai/skills/testdriver-parse/SKILL.md +236 -0
  66. package/ai/skills/testdriver-performing-actions/SKILL.md +54 -0
  67. package/ai/skills/testdriver-press-keys/SKILL.md +348 -0
  68. package/ai/skills/testdriver-provision/SKILL.md +331 -0
  69. package/ai/skills/testdriver-quickstart/SKILL.md +144 -0
  70. package/ai/skills/testdriver-redraw/SKILL.md +214 -0
  71. package/ai/skills/testdriver-reusable-code/SKILL.md +249 -0
  72. package/ai/skills/testdriver-right-click/SKILL.md +123 -0
  73. package/ai/skills/testdriver-running-tests/SKILL.md +185 -0
  74. package/ai/skills/testdriver-screenshot/SKILL.md +248 -0
  75. package/ai/skills/testdriver-screenshots/SKILL.md +184 -0
  76. package/ai/skills/testdriver-scroll/SKILL.md +335 -0
  77. package/ai/skills/testdriver-secrets/SKILL.md +115 -0
  78. package/ai/skills/testdriver-self-hosted/SKILL.md +65 -0
  79. package/ai/skills/testdriver-test-writer/SKILL.md +448 -0
  80. package/ai/skills/testdriver-testdriver/SKILL.md +628 -0
  81. package/ai/skills/testdriver-testdriver-mechanic/SKILL.md +165 -0
  82. package/ai/skills/testdriver-type/SKILL.md +357 -0
  83. package/ai/skills/testdriver-variables/SKILL.md +111 -0
  84. package/ai/skills/testdriver-wait/SKILL.md +50 -0
  85. package/ai/skills/testdriver-waiting-for-elements/SKILL.md +90 -0
  86. package/ai/skills/testdriver-what-is-testdriver/SKILL.md +54 -0
  87. package/bin/testdriverai.js +22 -0
  88. package/debugger/bg.png +0 -0
  89. package/debugger/icon.png +0 -0
  90. package/debugger/index.html +469 -0
  91. package/debugger/td.png +0 -0
  92. package/debugger/tray-buffered.png +0 -0
  93. package/debugger/tray.png +0 -0
  94. package/docs/GITHUB_COMMENTS.md +330 -0
  95. package/docs/GITHUB_COMMENTS_ANNOUNCEMENT.md +167 -0
  96. package/docs/QUICK-START-GITHUB-COMMENTS.md +84 -0
  97. package/docs/TEST-GITHUB-COMMENTS.md +129 -0
  98. package/docs/_data/examples-manifest.json +177 -0
  99. package/docs/_data/examples-manifest.schema.json +41 -0
  100. package/docs/_scripts/extract-example-urls.js +165 -0
  101. package/docs/_scripts/generate-examples.js +560 -0
  102. package/docs/_scripts/generate-skills.js +154 -0
  103. package/docs/_scripts/link-replacer.js +164 -0
  104. package/docs/_scripts/upload-docs-to-openai.js +284 -0
  105. package/docs/changelog.mdx +161 -0
  106. package/docs/claude-mcp-plugin.mdx +160 -0
  107. package/docs/docs.json +442 -0
  108. package/docs/github-integration-setup.md +266 -0
  109. package/docs/guide/best-practices-polling.mdx +174 -0
  110. package/docs/images/content/account/newprojectsettings.png +0 -0
  111. package/docs/images/content/account/projectpage.png +0 -0
  112. package/docs/images/content/account/projectreplays.png +0 -0
  113. package/docs/images/content/account/team-manage.png +0 -0
  114. package/docs/images/content/account/teampage.png +0 -0
  115. package/docs/images/content/extension/cursor.svg +1 -0
  116. package/docs/images/content/extension/vscode.svg +57 -0
  117. package/docs/images/content/extension/windsurf.svg +3 -0
  118. package/docs/images/content/parse/output.png +0 -0
  119. package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
  120. package/docs/images/content/side-by-side.png +0 -0
  121. package/docs/images/content/vscode/ide-full.png +0 -0
  122. package/docs/images/content/vscode/running.png +0 -0
  123. package/docs/images/content/vscode/v7-chat.png +0 -0
  124. package/docs/images/content/vscode/v7-choose-agent.png +0 -0
  125. package/docs/images/content/vscode/v7-full.png +0 -0
  126. package/docs/images/content/vscode/v7-onboarding.png +0 -0
  127. package/docs/images/content/vscode/vscode-2-assert.png +0 -0
  128. package/docs/images/content/vscode/vscode-agent-preview.png +0 -0
  129. package/docs/images/content/vscode/vscode-copilot-ask.png +0 -0
  130. package/docs/images/content/vscode/vscode-file-creation.png +0 -0
  131. package/docs/images/content/vscode/vscode-install.png +0 -0
  132. package/docs/images/content/vscode/vscode-overview.png +0 -0
  133. package/docs/images/content/vscode/vscode-setup-walkthrough.png +0 -0
  134. package/docs/images/content/vscode/vscode-stopchat.png +0 -0
  135. package/docs/images/content/vscode/vscode-stoptest.png +0 -0
  136. package/docs/images/content/vscode/vscode-tdservice.png +0 -0
  137. package/docs/images/content/vscode/vscode-test-output.png +0 -0
  138. package/docs/images/content/vscode/vscode-testhistory.png +0 -0
  139. package/docs/images/content/vscode/vscode-testpane-runtests.png +0 -0
  140. package/docs/images/content/vscode/vscode-testpane.png +0 -0
  141. package/docs/images/template/dark.png +0 -0
  142. package/docs/images/template/icon.png +0 -0
  143. package/docs/images/template/light.png +0 -0
  144. package/docs/snippets/calendar-link.mdx +4 -0
  145. package/docs/snippets/gitignore-warning.mdx +7 -0
  146. package/docs/snippets/lifecycle-warning.mdx +6 -0
  147. package/docs/snippets/test-prereqs.mdx +12 -0
  148. package/docs/snippets/tests/assert-replay.mdx +7 -0
  149. package/docs/snippets/tests/assert-yaml.mdx +8 -0
  150. package/docs/snippets/tests/exec-js-replay.mdx +7 -0
  151. package/docs/snippets/tests/exec-js-yaml.mdx +32 -0
  152. package/docs/snippets/tests/exec-shell-replay.mdx +7 -0
  153. package/docs/snippets/tests/exec-shell-yaml.mdx +15 -0
  154. package/docs/snippets/tests/hover-image-replay.mdx +7 -0
  155. package/docs/snippets/tests/hover-image-yaml.mdx +17 -0
  156. package/docs/snippets/tests/hover-text-replay.mdx +7 -0
  157. package/docs/snippets/tests/hover-text-with-description-replay.mdx +7 -0
  158. package/docs/snippets/tests/hover-text-with-description-yaml.mdx +24 -0
  159. package/docs/snippets/tests/hover-text-yaml.mdx +14 -0
  160. package/docs/snippets/tests/match-image-replay.mdx +7 -0
  161. package/docs/snippets/tests/match-image-yaml.mdx +17 -0
  162. package/docs/snippets/tests/press-keys-replay.mdx +7 -0
  163. package/docs/snippets/tests/press-keys-yaml.mdx +36 -0
  164. package/docs/snippets/tests/remember-replay.mdx +7 -0
  165. package/docs/snippets/tests/remember-yaml.mdx +28 -0
  166. package/docs/snippets/tests/scroll-replay.mdx +7 -0
  167. package/docs/snippets/tests/scroll-until-image-replay.mdx +7 -0
  168. package/docs/snippets/tests/scroll-until-image-yaml.mdx +14 -0
  169. package/docs/snippets/tests/scroll-until-text-replay.mdx +7 -0
  170. package/docs/snippets/tests/scroll-until-text-yaml.mdx +17 -0
  171. package/docs/snippets/tests/scroll-yaml.mdx +30 -0
  172. package/docs/snippets/tests/type-repeated-replay.mdx +7 -0
  173. package/docs/snippets/tests/type-repeated-yaml.mdx +22 -0
  174. package/docs/snippets/tests/type-replay.mdx +7 -0
  175. package/docs/snippets/tests/type-yaml.mdx +28 -0
  176. package/docs/snippets/tests/wait-for-image-replay.mdx +7 -0
  177. package/docs/snippets/tests/wait-for-image-yaml.mdx +18 -0
  178. package/docs/snippets/tests/wait-for-text-replay.mdx +7 -0
  179. package/docs/snippets/tests/wait-for-text-yaml.mdx +18 -0
  180. package/docs/snippets/tests/wait-replay.mdx +7 -0
  181. package/docs/snippets/tests/wait-yaml.mdx +13 -0
  182. package/docs/styles.css +65 -0
  183. package/docs/v6/account/dashboard.mdx +16 -0
  184. package/docs/v6/account/enterprise.mdx +110 -0
  185. package/docs/v6/account/pricing.mdx +33 -0
  186. package/docs/v6/account/projects.mdx +33 -0
  187. package/docs/v6/account/team.mdx +35 -0
  188. package/docs/v6/action/ami.mdx +109 -0
  189. package/docs/v6/action/performance.mdx +105 -0
  190. package/docs/v6/action/secrets.mdx +93 -0
  191. package/docs/v6/apps/chrome-extensions.mdx +48 -0
  192. package/docs/v6/apps/desktop-apps.mdx +93 -0
  193. package/docs/v6/apps/mobile-apps.mdx +26 -0
  194. package/docs/v6/apps/static-websites.mdx +54 -0
  195. package/docs/v6/apps/tauri-apps.mdx +361 -0
  196. package/docs/v6/bugs/jira.mdx +232 -0
  197. package/docs/v6/cli/overview.mdx +66 -0
  198. package/docs/v6/commands/assert.mdx +45 -0
  199. package/docs/v6/commands/exec.mdx +276 -0
  200. package/docs/v6/commands/focus-application.mdx +44 -0
  201. package/docs/v6/commands/hover-image.mdx +69 -0
  202. package/docs/v6/commands/hover-text.mdx +47 -0
  203. package/docs/v6/commands/if.mdx +53 -0
  204. package/docs/v6/commands/match-image.mdx +67 -0
  205. package/docs/v6/commands/press-keys.mdx +87 -0
  206. package/docs/v6/commands/remember.mdx +49 -0
  207. package/docs/v6/commands/run.mdx +44 -0
  208. package/docs/v6/commands/scroll-until-image.mdx +66 -0
  209. package/docs/v6/commands/scroll-until-text.mdx +60 -0
  210. package/docs/v6/commands/scroll.mdx +69 -0
  211. package/docs/v6/commands/type.mdx +45 -0
  212. package/docs/v6/commands/wait-for-image.mdx +54 -0
  213. package/docs/v6/commands/wait-for-text.mdx +48 -0
  214. package/docs/v6/commands/wait.mdx +45 -0
  215. package/docs/v6/exporting/junit.mdx +218 -0
  216. package/docs/v6/exporting/playwright.mdx +197 -0
  217. package/docs/v6/features/auto-healing.mdx +144 -0
  218. package/docs/v6/features/generation.mdx +116 -0
  219. package/docs/v6/features/parallel-testing.mdx +151 -0
  220. package/docs/v6/features/reusable-snippets.mdx +131 -0
  221. package/docs/v6/features/selectorless.mdx +80 -0
  222. package/docs/v6/features/visual-assertions.mdx +139 -0
  223. package/docs/v6/getting-started/ci.mdx +146 -0
  224. package/docs/v6/getting-started/cli.mdx +91 -0
  225. package/docs/v6/getting-started/editing.mdx +100 -0
  226. package/docs/v6/getting-started/playwright.mdx +342 -0
  227. package/docs/v6/getting-started/running.mdx +48 -0
  228. package/docs/v6/getting-started/self-hosting.mdx +408 -0
  229. package/docs/v6/getting-started/vscode.mdx +88 -0
  230. package/docs/v6/guide/assertions.mdx +189 -0
  231. package/docs/v6/guide/authentication.mdx +136 -0
  232. package/docs/v6/guide/code.mdx +65 -0
  233. package/docs/v6/guide/dashcam.mdx +118 -0
  234. package/docs/v6/guide/environment-variables.mdx +26 -0
  235. package/docs/v6/guide/lifecycle.mdx +242 -0
  236. package/docs/v6/guide/locating.mdx +141 -0
  237. package/docs/v6/guide/protips.mdx +43 -0
  238. package/docs/v6/guide/variables.mdx +143 -0
  239. package/docs/v6/guide/waiting.mdx +130 -0
  240. package/docs/v6/importing/csv.mdx +196 -0
  241. package/docs/v6/importing/gherkin.mdx +143 -0
  242. package/docs/v6/importing/jira.mdx +164 -0
  243. package/docs/v6/importing/testrail.mdx +162 -0
  244. package/docs/v6/integrations/electron.mdx +146 -0
  245. package/docs/v6/integrations/netlify.mdx +100 -0
  246. package/docs/v6/integrations/vercel.mdx +125 -0
  247. package/docs/v6/interactive/explore.mdx +99 -0
  248. package/docs/v6/interactive/run.mdx +52 -0
  249. package/docs/v6/interactive/save.mdx +63 -0
  250. package/docs/v6/overview/comparison.mdx +101 -0
  251. package/docs/v6/overview/faq.mdx +162 -0
  252. package/docs/v6/overview/performance.mdx +52 -0
  253. package/docs/v6/overview/quickstart.mdx +137 -0
  254. package/docs/v6/overview/what-is-testdriver.mdx +85 -0
  255. package/docs/v6/scenarios/ai-chatbot.mdx +28 -0
  256. package/docs/v6/scenarios/cookie-banner.mdx +32 -0
  257. package/docs/v6/scenarios/file-upload.mdx +33 -0
  258. package/docs/v6/scenarios/form-filling.mdx +32 -0
  259. package/docs/v6/scenarios/log-in.mdx +75 -0
  260. package/docs/v6/scenarios/pdf-generation.mdx +25 -0
  261. package/docs/v6/scenarios/spell-check.mdx +22 -0
  262. package/docs/v6/security/action.mdx +84 -0
  263. package/docs/v6/security/agent.mdx +73 -0
  264. package/docs/v6/security/platform.mdx +77 -0
  265. package/docs/v6/tutorials/advanced-test.mdx +81 -0
  266. package/docs/v6/tutorials/basic-test.mdx +45 -0
  267. package/docs/v7/_drafts/agents.mdx +843 -0
  268. package/docs/v7/_drafts/architecture.mdx +399 -0
  269. package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
  270. package/docs/v7/_drafts/awesome-logs-quick-ref.mdx +100 -0
  271. package/docs/v7/_drafts/best-practices.mdx +486 -0
  272. package/docs/v7/_drafts/caching-ai.mdx +215 -0
  273. package/docs/v7/_drafts/caching-selectors.mdx +424 -0
  274. package/docs/v7/_drafts/caching.mdx +366 -0
  275. package/docs/v7/_drafts/cli-to-sdk-migration.mdx +425 -0
  276. package/docs/v7/_drafts/commands/assert.mdx +45 -0
  277. package/docs/v7/_drafts/commands/exec.mdx +276 -0
  278. package/docs/v7/_drafts/commands/focus-application.mdx +44 -0
  279. package/docs/v7/_drafts/commands/hover-image.mdx +69 -0
  280. package/docs/v7/_drafts/commands/hover-text.mdx +47 -0
  281. package/docs/v7/_drafts/commands/if.mdx +53 -0
  282. package/docs/v7/_drafts/commands/match-image.mdx +67 -0
  283. package/docs/v7/_drafts/commands/press-keys.mdx +87 -0
  284. package/docs/v7/_drafts/commands/remember.mdx +49 -0
  285. package/docs/v7/_drafts/commands/run.mdx +44 -0
  286. package/docs/v7/_drafts/commands/scroll-until-image.mdx +66 -0
  287. package/docs/v7/_drafts/commands/scroll-until-text.mdx +60 -0
  288. package/docs/v7/_drafts/commands/scroll.mdx +69 -0
  289. package/docs/v7/_drafts/commands/type.mdx +45 -0
  290. package/docs/v7/_drafts/commands/wait-for-image.mdx +54 -0
  291. package/docs/v7/_drafts/commands/wait-for-text.mdx +48 -0
  292. package/docs/v7/_drafts/commands/wait.mdx +45 -0
  293. package/docs/v7/_drafts/configuration.mdx +378 -0
  294. package/docs/v7/_drafts/contributing.mdx +174 -0
  295. package/docs/v7/_drafts/core.mdx +458 -0
  296. package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
  297. package/docs/v7/_drafts/debugging.mdx +349 -0
  298. package/docs/v7/_drafts/error-handling.mdx +501 -0
  299. package/docs/v7/_drafts/faq.mdx +393 -0
  300. package/docs/v7/_drafts/hooks.mdx +360 -0
  301. package/docs/v7/_drafts/init-command.mdx +95 -0
  302. package/docs/v7/_drafts/installation.mdx +420 -0
  303. package/docs/v7/_drafts/migration.mdx +562 -0
  304. package/docs/v7/_drafts/observable.mdx +604 -0
  305. package/docs/v7/_drafts/playwright.mdx +342 -0
  306. package/docs/v7/_drafts/plugin-migration.mdx +220 -0
  307. package/docs/v7/_drafts/powerful.mdx +419 -0
  308. package/docs/v7/_drafts/presets.mdx +210 -0
  309. package/docs/v7/_drafts/progressive-disclosure.mdx +230 -0
  310. package/docs/v7/_drafts/prompt-cache.mdx +200 -0
  311. package/docs/v7/_drafts/provision.mdx +390 -0
  312. package/docs/v7/_drafts/quick-start-test-recording.mdx +214 -0
  313. package/docs/v7/_drafts/readme.mdx +135 -0
  314. package/docs/v7/_drafts/reports.mdx +414 -0
  315. package/docs/v7/_drafts/scalable.mdx +763 -0
  316. package/docs/v7/_drafts/screenshot.mdx +155 -0
  317. package/docs/v7/_drafts/sdk-awesome-logs.mdx +468 -0
  318. package/docs/v7/_drafts/sdk-browser-rendering.mdx +167 -0
  319. package/docs/v7/_drafts/sdk-migration.mdx +474 -0
  320. package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
  321. package/docs/v7/_drafts/self-hosting.mdx +369 -0
  322. package/docs/v7/_drafts/test-recording.mdx +382 -0
  323. package/docs/v7/_drafts/troubleshooting.mdx +526 -0
  324. package/docs/v7/_drafts/vitest-plugin.mdx +477 -0
  325. package/docs/v7/_drafts/vitest.mdx +535 -0
  326. package/docs/v7/_drafts/writing-tests.mdx +25 -0
  327. package/docs/v7/ai.mdx +205 -0
  328. package/docs/v7/assert.mdx +316 -0
  329. package/docs/v7/aws-setup.mdx +449 -0
  330. package/docs/v7/cache.mdx +223 -0
  331. package/docs/v7/caching.mdx +128 -0
  332. package/docs/v7/captcha.mdx +159 -0
  333. package/docs/v7/ci-cd.mdx +603 -0
  334. package/docs/v7/click.mdx +287 -0
  335. package/docs/v7/client.mdx +478 -0
  336. package/docs/v7/copilot/auto-healing.mdx +265 -0
  337. package/docs/v7/copilot/creating-tests.mdx +156 -0
  338. package/docs/v7/copilot/github.mdx +143 -0
  339. package/docs/v7/copilot/running-tests.mdx +149 -0
  340. package/docs/v7/copilot/setup.mdx +143 -0
  341. package/docs/v7/customizing-devices.mdx +319 -0
  342. package/docs/v7/dashcam.mdx +419 -0
  343. package/docs/v7/debugging-with-screenshots.mdx +402 -0
  344. package/docs/v7/device-config.mdx +317 -0
  345. package/docs/v7/double-click.mdx +102 -0
  346. package/docs/v7/elements.mdx +606 -0
  347. package/docs/v7/enterprise.mdx +9 -0
  348. package/docs/v7/errors.mdx +248 -0
  349. package/docs/v7/events.mdx +358 -0
  350. package/docs/v7/examples/ai.mdx +72 -0
  351. package/docs/v7/examples/assert.mdx +72 -0
  352. package/docs/v7/examples/captcha-api.mdx +92 -0
  353. package/docs/v7/examples/chrome-extension.mdx +132 -0
  354. package/docs/v7/examples/drag-and-drop.mdx +100 -0
  355. package/docs/v7/examples/element-not-found.mdx +67 -0
  356. package/docs/v7/examples/exec-output.mdx +85 -0
  357. package/docs/v7/examples/exec-pwsh.mdx +83 -0
  358. package/docs/v7/examples/focus-window.mdx +62 -0
  359. package/docs/v7/examples/hover-image.mdx +94 -0
  360. package/docs/v7/examples/hover-text.mdx +69 -0
  361. package/docs/v7/examples/installer.mdx +91 -0
  362. package/docs/v7/examples/launch-vscode-linux.mdx +101 -0
  363. package/docs/v7/examples/match-image.mdx +96 -0
  364. package/docs/v7/examples/press-keys.mdx +92 -0
  365. package/docs/v7/examples/scroll-keyboard.mdx +79 -0
  366. package/docs/v7/examples/scroll-until-image.mdx +81 -0
  367. package/docs/v7/examples/scroll-until-text.mdx +109 -0
  368. package/docs/v7/examples/scroll.mdx +81 -0
  369. package/docs/v7/examples/type.mdx +92 -0
  370. package/docs/v7/examples/windows-installer.mdx +89 -0
  371. package/docs/v7/exec.mdx +318 -0
  372. package/docs/v7/find.mdx +830 -0
  373. package/docs/v7/focus-application.mdx +294 -0
  374. package/docs/v7/generating-tests.mdx +36 -0
  375. package/docs/v7/hosted.mdx +158 -0
  376. package/docs/v7/hover.mdx +279 -0
  377. package/docs/v7/locating-elements.mdx +71 -0
  378. package/docs/v7/making-assertions.mdx +32 -0
  379. package/docs/v7/mcp.mdx +9 -0
  380. package/docs/v7/mouse-down.mdx +161 -0
  381. package/docs/v7/mouse-up.mdx +164 -0
  382. package/docs/v7/parse.mdx +237 -0
  383. package/docs/v7/performing-actions.mdx +54 -0
  384. package/docs/v7/press-keys.mdx +349 -0
  385. package/docs/v7/provision.mdx +333 -0
  386. package/docs/v7/quickstart.mdx +173 -0
  387. package/docs/v7/redraw.mdx +216 -0
  388. package/docs/v7/reusable-code.mdx +249 -0
  389. package/docs/v7/right-click.mdx +123 -0
  390. package/docs/v7/running-tests.mdx +185 -0
  391. package/docs/v7/screenshot.mdx +249 -0
  392. package/docs/v7/screenshots.mdx +186 -0
  393. package/docs/v7/scroll.mdx +336 -0
  394. package/docs/v7/secrets.mdx +115 -0
  395. package/docs/v7/self-hosted.mdx +149 -0
  396. package/docs/v7/type.mdx +358 -0
  397. package/docs/v7/variables.mdx +111 -0
  398. package/docs/v7/wait.mdx +52 -0
  399. package/docs/v7/waiting-for-elements.mdx +90 -0
  400. package/docs/v7/what-is-testdriver.mdx +54 -0
  401. package/eslint.config.js +67 -0
  402. package/examples/ai.test.mjs +31 -0
  403. package/examples/assert.test.mjs +47 -0
  404. package/examples/chrome-extension.test.mjs +97 -0
  405. package/examples/config.mjs +5 -0
  406. package/examples/element-not-found.test.mjs +27 -0
  407. package/examples/exec-output.test.mjs +60 -0
  408. package/examples/exec-pwsh.test.mjs +58 -0
  409. package/examples/findall-coffee-icons.test.mjs +42 -0
  410. package/examples/focus-window.test.mjs +37 -0
  411. package/examples/formatted-logging.test.mjs +27 -0
  412. package/examples/hover-image.test.mjs +53 -0
  413. package/examples/hover-text-with-description.test.mjs +57 -0
  414. package/examples/hover-text.test.mjs +28 -0
  415. package/examples/installer.test.mjs +50 -0
  416. package/examples/launch-vscode-linux.test.mjs +55 -0
  417. package/examples/match-image.test.mjs +55 -0
  418. package/examples/parse.test.mjs +19 -0
  419. package/examples/press-keys.test.mjs +44 -0
  420. package/examples/prompt.test.mjs +34 -0
  421. package/examples/scroll-keyboard.test.mjs +38 -0
  422. package/examples/scroll-until-image.test.mjs +40 -0
  423. package/examples/scroll.test.mjs +42 -0
  424. package/examples/type.test.mjs +46 -0
  425. package/examples/windows-installer.test.mjs +54 -0
  426. package/index.js +2 -0
  427. package/interfaces/cli/commands/init.js +438 -0
  428. package/interfaces/cli/commands/setup.js +382 -0
  429. package/interfaces/cli/lib/base.js +285 -0
  430. package/interfaces/cli.js +20 -0
  431. package/interfaces/junit-reporter.js +290 -0
  432. package/interfaces/logger.js +388 -0
  433. package/interfaces/readline.js +234 -0
  434. package/interfaces/shared-test-state.mjs +64 -0
  435. package/interfaces/vitest-plugin.d.ts +115 -0
  436. package/interfaces/vitest-plugin.mjs +1698 -0
  437. package/lib/captcha/solver.js +358 -0
  438. package/lib/core/Dashcam.js +533 -0
  439. package/lib/core/index.d.ts +172 -0
  440. package/lib/core/index.js +12 -0
  441. package/lib/environments.json +18 -0
  442. package/lib/github-comment-formatter.js +263 -0
  443. package/lib/github-comment.mjs +452 -0
  444. package/lib/init-project.js +575 -0
  445. package/lib/presets/index.mjs +331 -0
  446. package/lib/resolve-channel.js +46 -0
  447. package/lib/sentry.js +417 -0
  448. package/lib/vitest/hooks.d.ts +57 -0
  449. package/lib/vitest/hooks.mjs +674 -0
  450. package/lib/vitest/setup-aws.mjs +247 -0
  451. package/lib/vitest/setup-self-hosted.mjs +151 -0
  452. package/lib/vitest/setup.mjs +46 -0
  453. package/manual/captcha-api.test.mjs +51 -0
  454. package/manual/drag-and-drop.test.mjs +59 -0
  455. package/manual/flake-diffthreshold-001.test.mjs +9 -0
  456. package/manual/flake-diffthreshold-01.test.mjs +9 -0
  457. package/manual/flake-diffthreshold-05.test.mjs +9 -0
  458. package/manual/flake-noredraw-cache.test.mjs +9 -0
  459. package/manual/flake-noredraw-nocache.test.mjs +9 -0
  460. package/manual/flake-redraw-cache.test.mjs +9 -0
  461. package/manual/flake-redraw-nocache.test.mjs +9 -0
  462. package/manual/flake-rocket-match.test.mjs +30 -0
  463. package/manual/flake-shared.mjs +51 -0
  464. package/manual/no-provision.test.mjs +31 -0
  465. package/manual/packer-hover-image.test.mjs +176 -0
  466. package/manual/scroll-until-text.test.mjs +68 -0
  467. package/manual/test-init-command.js +223 -0
  468. package/mcp-server/README.md +322 -0
  469. package/mcp-server/dist/codegen.d.ts +9 -0
  470. package/mcp-server/dist/codegen.js +165 -0
  471. package/mcp-server/dist/mcp-app.html +114 -0
  472. package/mcp-server/dist/package.json +1 -0
  473. package/mcp-server/dist/provision-types.d.ts +290 -0
  474. package/mcp-server/dist/provision-types.js +174 -0
  475. package/mcp-server/dist/server.d.ts +6 -0
  476. package/mcp-server/dist/server.mjs +1925 -0
  477. package/mcp-server/dist/session.d.ts +85 -0
  478. package/mcp-server/dist/session.js +152 -0
  479. package/mcp-server/mcp-app.html +28 -0
  480. package/mcp-server/mcp-config.example.json +19 -0
  481. package/mcp-server/package-lock.json +4027 -0
  482. package/mcp-server/package.json +31 -0
  483. package/mcp-server/src/codegen.ts +189 -0
  484. package/mcp-server/src/mcp-app.css +360 -0
  485. package/mcp-server/src/mcp-app.ts +547 -0
  486. package/mcp-server/src/provision-types.ts +209 -0
  487. package/mcp-server/src/server.ts +2391 -0
  488. package/mcp-server/src/session.ts +194 -0
  489. package/mcp-server/tsconfig.json +16 -0
  490. package/mcp-server/vite.config.ts +23 -0
  491. package/package.json +158 -0
  492. package/schema.json +1046 -0
  493. package/scripts/generate-skills.js +94 -0
  494. package/sdk-log-formatter.js +1157 -0
  495. package/sdk.d.ts +1486 -0
  496. package/sdk.js +4336 -0
  497. package/setup/aws/cloudformation.yaml +463 -0
  498. package/setup/aws/disable-defender.sh +42 -0
  499. package/setup/aws/install-dev-runner.sh +79 -0
  500. package/setup/aws/spawn-runner.sh +289 -0
  501. package/test/captcha-solver.test.mjs +152 -0
  502. package/test/chrome-remote-debugging.test.mjs +66 -0
  503. package/test/duckduckgo/experiment.test.mjs +28 -0
  504. package/test/duckduckgo/setup.test.mjs +29 -0
  505. package/test/manual/debug-locate-response.js +82 -0
  506. package/test/manual/reconnect-provision.test.mjs +49 -0
  507. package/test/manual/test-console-logs.test.mjs +42 -0
  508. package/test/manual/test-find-api.js +73 -0
  509. package/test/manual/test-init.sh +54 -0
  510. package/test/manual/test-prompt-cache.js +97 -0
  511. package/test/manual/test-provision-auth.mjs +22 -0
  512. package/test/manual/test-sandbox-render.js +29 -0
  513. package/test/manual/test-sdk-methods.js +15 -0
  514. package/test/manual/test-sdk-refactor.js +53 -0
  515. package/test/manual/test-stack-trace.mjs +57 -0
  516. package/test/manual/verify-element-api.js +89 -0
  517. package/test/manual/verify-types.js +0 -0
  518. package/test/manual-unawaited-promise.test.mjs +31 -0
  519. package/vitest.config.mjs +58 -0
  520. package/vitest.runner.config.mjs +33 -0
  521. package/vscode-extension/.vscodeignore +12 -0
  522. package/vscode-extension/README.md +94 -0
  523. package/vscode-extension/media/icon.png +0 -0
  524. package/vscode-extension/package-lock.json +4126 -0
  525. package/vscode-extension/package.json +86 -0
  526. package/vscode-extension/src/extension.ts +829 -0
  527. package/vscode-extension/testdriverai-0.1.0.vsix +0 -0
  528. package/vscode-extension/tsconfig.json +16 -0
@@ -0,0 +1,382 @@
1
+ const { Command } = require("@oclif/core");
2
+ const { createCommandDefinitions } = require("../../../agent/interface.js");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const chalk = require("chalk");
7
+ const { execSync } = require("child_process");
8
+ const readline = require("readline");
9
+
10
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
11
+ const CLAUDE_HOME = path.join(os.homedir(), ".claude");
12
+ const CLAUDE_MCP_FILE = path.join(os.homedir(), ".claude.json");
13
+ const CURSOR_MCP_FILE = path.join(os.homedir(), ".cursor", "mcp.json");
14
+
15
+ const MCP_SERVER_CONFIG = {
16
+ "testdriver-cloud": {
17
+ type: "sse",
18
+ url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
19
+ headers: {
20
+ "x-api-key": "${TD_API_KEY}",
21
+ },
22
+ description:
23
+ "Query TestDriver test runs, test cases, and filters for your team using an API key.",
24
+ },
25
+ };
26
+
27
+ const CURSOR_MCP_SERVER_CONFIG = {
28
+ "testdriver-cloud": {
29
+ type: "sse",
30
+ url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
31
+ headers: {
32
+ "x-api-key": "${TD_API_KEY}",
33
+ },
34
+ description:
35
+ "Query TestDriver test runs, test cases, and filters for your team using an API key.",
36
+ },
37
+ };
38
+
39
+ class SetupCommand extends Command {
40
+ async run() {
41
+ await this.parse(SetupCommand);
42
+
43
+ console.log(chalk.cyan("\nSetting up TestDriver for Claude Code...\n"));
44
+
45
+ const sourceSkills = path.join(PACKAGE_ROOT, "ai", "skills");
46
+ const sourceAgents = path.join(PACKAGE_ROOT, "ai", "agents");
47
+
48
+ this.installSkills(sourceSkills, path.join(CLAUDE_HOME, "skills"));
49
+ this.installAgents(sourceAgents, path.join(CLAUDE_HOME, "agents"));
50
+ this.installClaudeMcp();
51
+ this.installCursorMcp();
52
+ await this.promptForApiKey();
53
+
54
+ console.log(chalk.green("\nSetup complete!\n"));
55
+ this.printNextSteps();
56
+ process.exit(0);
57
+ }
58
+
59
+ /**
60
+ * Recursively copy a directory's contents
61
+ */
62
+ copyDirSync(src, dest) {
63
+ fs.mkdirSync(dest, { recursive: true });
64
+
65
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
66
+ const srcPath = path.join(src, entry.name);
67
+ const destPath = path.join(dest, entry.name);
68
+
69
+ if (entry.isDirectory()) {
70
+ this.copyDirSync(srcPath, destPath);
71
+ } else {
72
+ fs.copyFileSync(srcPath, destPath);
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Install skills to ~/.claude/skills
79
+ */
80
+ installSkills(source, dest) {
81
+ if (!fs.existsSync(source)) {
82
+ console.log(
83
+ chalk.yellow(" Skills source not found, skipping: " + source),
84
+ );
85
+ return;
86
+ }
87
+
88
+ const skills = fs
89
+ .readdirSync(source, { withFileTypes: true })
90
+ .filter((d) => d.isDirectory());
91
+
92
+ for (const skill of skills) {
93
+ const srcDir = path.join(source, skill.name);
94
+ const destDir = path.join(dest, skill.name);
95
+ this.copyDirSync(srcDir, destDir);
96
+ }
97
+
98
+ console.log(chalk.green(` Installed ${skills.length} skills to ${dest}`));
99
+ }
100
+
101
+ /**
102
+ * Install agents to ~/.claude/agents
103
+ */
104
+ installAgents(source, dest) {
105
+ if (!fs.existsSync(source)) {
106
+ console.log(
107
+ chalk.yellow(" Agents source not found, skipping: " + source),
108
+ );
109
+ return;
110
+ }
111
+
112
+ fs.mkdirSync(dest, { recursive: true });
113
+
114
+ const agents = fs.readdirSync(source).filter((f) => f.endsWith(".md"));
115
+
116
+ for (const agent of agents) {
117
+ fs.copyFileSync(path.join(source, agent), path.join(dest, agent));
118
+ }
119
+
120
+ console.log(
121
+ chalk.green(` Installed ${agents.length} agent(s) to ${dest}`),
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Add testdriver MCP server to ~/.claude.json
127
+ */
128
+ installClaudeMcp() {
129
+ let config = {};
130
+
131
+ if (fs.existsSync(CLAUDE_MCP_FILE)) {
132
+ try {
133
+ config = JSON.parse(fs.readFileSync(CLAUDE_MCP_FILE, "utf8"));
134
+ } catch {
135
+ // If the file is malformed, start fresh but warn
136
+ console.log(
137
+ chalk.yellow(
138
+ " Warning: existing ~/.claude.json was not valid JSON, overwriting",
139
+ ),
140
+ );
141
+ }
142
+ }
143
+
144
+ if (!config.mcpServers) {
145
+ config.mcpServers = {};
146
+ }
147
+
148
+ const alreadyConfigured = config.mcpServers["testdriver-cloud"];
149
+
150
+ Object.assign(config.mcpServers, MCP_SERVER_CONFIG);
151
+ fs.writeFileSync(CLAUDE_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
152
+
153
+ if (alreadyConfigured) {
154
+ console.log(
155
+ chalk.green(` Updated testdriver MCP server in ${CLAUDE_MCP_FILE}`),
156
+ );
157
+ } else {
158
+ console.log(
159
+ chalk.green(` Added testdriver MCP server to ${CLAUDE_MCP_FILE}`),
160
+ );
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Add testdriver MCP server to ~/.cursor/mcp.json
166
+ */
167
+ installCursorMcp() {
168
+ const cursorDir = path.dirname(CURSOR_MCP_FILE);
169
+ fs.mkdirSync(cursorDir, { recursive: true });
170
+
171
+ let config = {};
172
+
173
+ if (fs.existsSync(CURSOR_MCP_FILE)) {
174
+ try {
175
+ config = JSON.parse(fs.readFileSync(CURSOR_MCP_FILE, "utf8"));
176
+ } catch {
177
+ console.log(
178
+ chalk.yellow(
179
+ " Warning: existing ~/.cursor/mcp.json was not valid JSON, overwriting",
180
+ ),
181
+ );
182
+ }
183
+ }
184
+
185
+ if (!config.servers) {
186
+ config.servers = {};
187
+ }
188
+
189
+ const alreadyConfigured = config.servers["testdriver-cloud"];
190
+
191
+ Object.assign(config.servers, CURSOR_MCP_SERVER_CONFIG);
192
+ fs.writeFileSync(CURSOR_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
193
+
194
+ if (alreadyConfigured) {
195
+ console.log(
196
+ chalk.green(` Updated testdriver MCP server in ${CURSOR_MCP_FILE}`),
197
+ );
198
+ } else {
199
+ console.log(
200
+ chalk.green(` Added testdriver MCP server to ${CURSOR_MCP_FILE}`),
201
+ );
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Prompt user for API key and save globally to shell profile
207
+ */
208
+ async promptForApiKey() {
209
+ // Check if TD_API_KEY is already set in the environment
210
+ if (process.env.TD_API_KEY) {
211
+ console.log(
212
+ chalk.gray("\n API key already set in environment, skipping...\n"),
213
+ );
214
+ return;
215
+ }
216
+
217
+ console.log(chalk.cyan("\n Setting up your TestDriver API key...\n"));
218
+ console.log(
219
+ chalk.gray(" Get your API key from: https://console.testdriver.ai/team"),
220
+ );
221
+
222
+ const shouldOpen = await this.askYesNo(
223
+ " Open API keys page in browser? (Y/n): ",
224
+ );
225
+ if (shouldOpen) {
226
+ try {
227
+ const open = (await import("open")).default;
228
+ await open("https://console.testdriver.ai/team");
229
+ console.log(chalk.gray(" Opening browser...\n"));
230
+ } catch (error) {
231
+ console.log(
232
+ chalk.yellow(" Could not open browser automatically\n"),
233
+ );
234
+ }
235
+ }
236
+
237
+ const apiKey = await this.promptHidden(
238
+ " Enter your API key (input will be hidden): ",
239
+ );
240
+
241
+ if (apiKey && apiKey.trim()) {
242
+ this.addToShellProfile("TD_API_KEY", apiKey.trim());
243
+ process.env.TD_API_KEY = apiKey.trim();
244
+ } else {
245
+ console.log(
246
+ chalk.yellow(
247
+ "\n No API key entered. You can set it later:\n",
248
+ ),
249
+ );
250
+ console.log(chalk.gray(' export TD_API_KEY="your_api_key"\n'));
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Prompt for hidden input (like password)
256
+ */
257
+ async promptHidden(question) {
258
+ return new Promise((resolve) => {
259
+ process.stdout.write(question);
260
+
261
+ const stdin = process.stdin;
262
+ const wasRaw = stdin.isRaw;
263
+ stdin.setRawMode(true);
264
+ stdin.resume();
265
+ stdin.setEncoding("utf8");
266
+
267
+ let input = "";
268
+
269
+ const onData = (char) => {
270
+ if (char === "\u0003") {
271
+ stdin.setRawMode(wasRaw);
272
+ process.exit();
273
+ }
274
+ if (char === "\r" || char === "\n") {
275
+ stdin.setRawMode(wasRaw);
276
+ stdin.removeListener("data", onData);
277
+ stdin.pause();
278
+ console.log("");
279
+ resolve(input);
280
+ return;
281
+ }
282
+ if (char === "\u007F" || char === "\b") {
283
+ input = input.slice(0, -1);
284
+ return;
285
+ }
286
+ input += char;
287
+ };
288
+
289
+ stdin.on("data", onData);
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Ask a yes/no question
295
+ */
296
+ async askYesNo(question) {
297
+ return new Promise((resolve) => {
298
+ const rl = readline.createInterface({
299
+ input: process.stdin,
300
+ output: process.stdout,
301
+ });
302
+
303
+ rl.question(question, (answer) => {
304
+ rl.close();
305
+ const normalized = answer.toLowerCase().trim();
306
+ resolve(
307
+ normalized === "" || normalized === "y" || normalized === "yes",
308
+ );
309
+ });
310
+ });
311
+ }
312
+
313
+ /**
314
+ * Add an environment variable export to the user's shell profile
315
+ */
316
+ addToShellProfile(key, value) {
317
+ if (process.platform === "win32") {
318
+ try {
319
+ execSync(`setx ${key} "${value}"`, { stdio: "ignore" });
320
+ console.log(
321
+ chalk.green(`\n Set ${key} as user environment variable\n`),
322
+ );
323
+ } catch (error) {
324
+ console.log(
325
+ chalk.yellow(`\n Could not set ${key} via setx. You can set it manually:\n`),
326
+ );
327
+ console.log(chalk.gray(` setx ${key} "your_api_key"\n`));
328
+ }
329
+ return;
330
+ }
331
+
332
+ const shell = process.env.SHELL || "/bin/bash";
333
+ const home = os.homedir();
334
+ let profilePath;
335
+
336
+ if (shell.includes("zsh")) {
337
+ profilePath = path.join(home, ".zshrc");
338
+ } else {
339
+ profilePath = path.join(home, ".bashrc");
340
+ }
341
+
342
+ const exportLine = `export ${key}="${value}"`;
343
+
344
+ if (fs.existsSync(profilePath)) {
345
+ const content = fs.readFileSync(profilePath, "utf8");
346
+ if (content.includes(`export ${key}=`)) {
347
+ const updated = content.replace(
348
+ new RegExp(`^export ${key}=.*$`, "m"),
349
+ exportLine,
350
+ );
351
+ fs.writeFileSync(profilePath, updated);
352
+ console.log(
353
+ chalk.green(`\n Updated ${key} in ${profilePath}\n`),
354
+ );
355
+ return;
356
+ }
357
+ }
358
+
359
+ fs.appendFileSync(profilePath, `\n${exportLine}\n`);
360
+ console.log(
361
+ chalk.green(`\n Added ${key} to ${profilePath}\n`),
362
+ );
363
+ }
364
+
365
+ printNextSteps() {
366
+ console.log(chalk.cyan("Next steps:\n"));
367
+ console.log(" 1. Restart Claude Code to pick up the new MCP server\n");
368
+ }
369
+ }
370
+
371
+ // Get command definition from interface.js
372
+ const tempAgent = { workingDir: process.cwd() };
373
+ const definitions = createCommandDefinitions(tempAgent);
374
+ const commandDef = definitions["setup"];
375
+
376
+ SetupCommand.description =
377
+ commandDef?.description ||
378
+ "Set up TestDriver skills, agents, and MCP for Claude Code";
379
+ SetupCommand.args = commandDef?.args || {};
380
+ SetupCommand.flags = commandDef?.flags || {};
381
+
382
+ module.exports = SetupCommand;
@@ -0,0 +1,285 @@
1
+ /*
2
+ This is an implementation of the TestDriver library. This file should not:
3
+ - modify the agent's state
4
+ - emit events back to the agent
5
+ - etc
6
+ */
7
+
8
+ const { Command } = require("@oclif/core");
9
+ const { events } = require("../../../agent/events.js");
10
+ const { createCommandDefinitions } = require("../../../agent/interface.js");
11
+ const { createJUnitReporter } = require("../../junit-reporter.js");
12
+ const path = require("path");
13
+ const fs = require("fs");
14
+ const os = require("os");
15
+ const logger = require("../../logger.js");
16
+
17
+ async function openBrowser(url) {
18
+ try {
19
+ // Use dynamic import for the 'open' package (ES module)
20
+ const { default: open } = await import("open");
21
+
22
+ // Open the browser
23
+ await open(url, {
24
+ // Wait for the app to open
25
+ wait: false,
26
+ background: true
27
+ });
28
+ } catch (error) {
29
+ console.error("Failed to open browser automatically:", error);
30
+ console.log(`Please manually open: ${url}`);
31
+ }
32
+ }
33
+
34
+ class BaseCommand extends Command {
35
+ constructor(argv, config) {
36
+ super(argv, config);
37
+ this.agent = null; // Initialize as null, create only when needed
38
+ }
39
+
40
+ sendToSandbox(message) {
41
+ if (!message) return;
42
+
43
+ // ensure message is a string
44
+ if (typeof message !== "string") {
45
+ message = JSON.stringify(message);
46
+ }
47
+
48
+ this.agent.sandbox.send({
49
+ type: "output",
50
+ output: Buffer.from(message).toString("base64"),
51
+ }).catch(() => {
52
+ // Silently ignore output send failures to prevent infinite loops
53
+ });
54
+ }
55
+
56
+ setupEventListeners() {
57
+ if (!this.logFilePath) {
58
+ // Create a temp log file for this session
59
+ this.logFilePath = path.join(
60
+ os.tmpdir(),
61
+ `testdriverai-cli-${process.pid}.log`,
62
+ );
63
+
64
+ console.log(`Log file: ${this.logFilePath}`);
65
+ console.log("");
66
+ fs.writeFileSync(this.logFilePath, ""); // Initialize the log file
67
+ }
68
+
69
+ // Helper to append log messages to the temp file
70
+ const appendLog = (level, message) => {
71
+ const timestamp = new Date().toISOString();
72
+ fs.appendFileSync(
73
+ this.logFilePath,
74
+ `[${timestamp}] [${level}] ${message}\n`,
75
+ );
76
+ };
77
+
78
+ let isConnected = false;
79
+ const debugMode = process.env.VERBOSE || process.env.TD_DEBUG;
80
+
81
+ // Use pattern matching for log events, but skip log:Debug unless debug mode is enabled
82
+ this.agent.emitter.on("log:*", (message) => {
83
+ const event = this.agent.emitter.event;
84
+
85
+ if (event === events.log.debug && !debugMode) return;
86
+
87
+ if (event === events.log.narration && isConnected) return;
88
+ console.log(message);
89
+ });
90
+
91
+ // Use pattern matching for error events
92
+ this.agent.emitter.on("error:*", (data) => {
93
+ const event = this.agent.emitter.event;
94
+ console.error(event, ":", data);
95
+ });
96
+
97
+ // Handle status events
98
+ this.agent.emitter.on("status", (message) => {
99
+ console.log(`- ${message}`);
100
+ this.sendToSandbox(`- ${message}`);
101
+ });
102
+
103
+ // Handle sandbox connection with pattern matching for subsequent events
104
+ this.agent.emitter.once("sandbox:connected", () => {
105
+ isConnected = true;
106
+ // Once sandbox is connected, send all log and error events to sandbox
107
+ this.agent.emitter.on("log:*", (message) => {
108
+ this.sendToSandbox(message);
109
+ });
110
+
111
+ this.agent.emitter.on("error:*", (message) => {
112
+ // Don't forward sandbox errors back to sandbox - this creates an infinite loop
113
+ // (sandbox error → error:* event → sendToSandbox → output message → sandbox error → ...)
114
+ const event = this.agent.emitter.event;
115
+ if (event === "error:sandbox") return;
116
+ this.sendToSandbox(message);
117
+ });
118
+ });
119
+
120
+ // Handle all other events with wildcard pattern
121
+ this.agent.emitter.on("**", (data) => {
122
+ const event = this.agent.emitter.event;
123
+ appendLog(event, JSON.stringify(data));
124
+ });
125
+
126
+ logger.createMarkdownLogger(this.agent.emitter);
127
+
128
+ // Initialize JUnit reporter if junit flag is provided
129
+ if (this.agent.cliArgs?.options?.junit) {
130
+ const junitOutputPath = this.agent.cliArgs.options.junit;
131
+ const mainTestFile = this.agent.thisFile; // Get the main test file from the agent
132
+ this.junitReporter = createJUnitReporter(
133
+ this.agent.emitter,
134
+ junitOutputPath,
135
+ mainTestFile,
136
+ );
137
+ console.log(`JUnit reporting enabled: ${junitOutputPath}`);
138
+ }
139
+
140
+ this.agent.emitter.on("exit", (exitCode) => {
141
+ // Ensure sandbox is closed before exiting
142
+ if (this.agent?.sandbox) {
143
+ try {
144
+ this.agent.sandbox.close();
145
+ } catch (err) {
146
+ // Ignore close errors
147
+ }
148
+ }
149
+ process.exit(exitCode);
150
+ });
151
+
152
+ // Handle process signals to ensure clean disconnection
153
+ let isExiting = false;
154
+ const cleanupAndExit = async (signal) => {
155
+ if (isExiting) return;
156
+ isExiting = true;
157
+
158
+ console.log(`\nReceived ${signal}, cleaning up...`);
159
+
160
+ // Use the agent's exit method for proper cleanup
161
+ if (this.agent) {
162
+ try {
163
+ await this.agent.exit(true, false, false);
164
+ } catch (err) {
165
+ console.error("Error during cleanup:", err.message);
166
+ }
167
+ } else {
168
+ // Fallback if no agent
169
+ if (this.agent?.sandbox) {
170
+ try {
171
+ this.agent.sandbox.close();
172
+ } catch (err) {
173
+ // Ignore close errors
174
+ }
175
+ }
176
+ process.exit(1);
177
+ }
178
+ };
179
+
180
+ process.on('SIGINT', () => cleanupAndExit('SIGINT'));
181
+ process.on('SIGTERM', () => cleanupAndExit('SIGTERM'));
182
+
183
+ // Handle unhandled promise rejections to prevent them from interfering with the exit flow
184
+ // This is particularly important when JavaScript execution in VM contexts leaves dangling promises
185
+ process.on("unhandledRejection", (reason) => {
186
+ // Log the rejection but don't let it crash the process
187
+ console.error("Unhandled Promise Rejection:", reason);
188
+ // The exit flow should continue normally
189
+ });
190
+
191
+ // Handle show window events
192
+ this.agent.emitter.on("show-window", async (url) => {
193
+ console.log("");
194
+ console.log(`Live test execution: `);
195
+ console.log(url);
196
+ if (!this.agent.config.CI) {
197
+ await openBrowser(url);
198
+ }
199
+ });
200
+ }
201
+
202
+ async init() {
203
+ // Only start debugger for commands that actually need it
204
+ // Help commands and other static commands don't need the debugger
205
+ }
206
+
207
+ // Extract file path from args or use default
208
+ normalizeFilePath(file) {
209
+ const path = require("path");
210
+ if (!file) {
211
+ // Use config default if agent is available, otherwise fall back to hardcoded default
212
+ file = "testdriver/testdriver.yaml";
213
+ }
214
+ file = path.join(this.agent.workingDir, file);
215
+ if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
216
+ file += ".yaml";
217
+ }
218
+ return file;
219
+ }
220
+
221
+ async setupAgent(firstArg, flags) {
222
+ // Load .env file into process.env for CLI usage
223
+ require("dotenv").config();
224
+
225
+ // Create the agent only when actually needed
226
+ const TestDriverAgent = require("../../../agent/index.js");
227
+
228
+ let args;
229
+ if (this.id === "generate") {
230
+ // For generate command, the first parameter is a prompt, not a file
231
+ args = firstArg ? [firstArg] : [];
232
+ } else {
233
+ // For run and other commands, handle file path
234
+ const filePath = this.id === "run" && flags.path ? flags.path : firstArg;
235
+ args = filePath ? [filePath] : [];
236
+ }
237
+
238
+ // Prepare CLI args for the agent with all derived options
239
+ const cliArgs = {
240
+ command: this.id,
241
+ args,
242
+ options: {
243
+ ...flags,
244
+ resultFile:
245
+ flags.summary && typeof flags.summary === "string"
246
+ ? path.resolve(flags.summary)
247
+ : null,
248
+ },
249
+ };
250
+
251
+ // Create agent with explicit process.env and consolidated CLI args
252
+ this.agent = new TestDriverAgent(process.env, cliArgs);
253
+ this.setupEventListeners();
254
+
255
+ try {
256
+ // Start the agent's initialization
257
+ await this.agent.start();
258
+ } catch (e) {
259
+ console.error("Failed to start agent:", e);
260
+ this.agent.emitter.emit(
261
+ events.error.fatal,
262
+ "Failed to start agent: " + JSON.stringify(e),
263
+ );
264
+ if (this.agent) {
265
+ await this.agent.exit(true);
266
+ } else {
267
+ process.exit(1);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Get unified command definition for this command
273
+ getUnifiedDefinition() {
274
+ const commandName = this.id;
275
+ if (!this.agent) {
276
+ // Create a temporary agent for definition purposes with empty environment
277
+ const tempAgent = { workingDir: process.cwd() };
278
+ return createCommandDefinitions(tempAgent)[commandName];
279
+ }
280
+ const definitions = createCommandDefinitions(this.agent);
281
+ return definitions[commandName];
282
+ }
283
+ }
284
+
285
+ module.exports = BaseCommand;
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { run } = require("@oclif/core");
4
+ const sentry = require("../lib/sentry");
5
+
6
+ // Run oclif (with default command handling built-in)
7
+ run()
8
+ .then(() => {
9
+ // Success
10
+ })
11
+ .catch(async (error) => {
12
+ // Capture error in Sentry
13
+ sentry.captureException(error, {
14
+ tags: { component: "cli-init" },
15
+ });
16
+ await sentry.flush();
17
+
18
+ console.error("Failed to start TestDriver.ai agent:", error);
19
+ process.exit(1);
20
+ });