@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,547 @@
1
+ /**
2
+ * TestDriver MCP App - displays screenshots with overlays for action results
3
+ */
4
+ import {
5
+ App,
6
+ applyDocumentTheme,
7
+ applyHostFonts,
8
+ applyHostStyleVariables,
9
+ type McpUiHostContext,
10
+ } from "@modelcontextprotocol/ext-apps";
11
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
12
+ import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
13
+ import "./mcp-app.css";
14
+
15
+ // DOM elements
16
+ const mainEl = document.querySelector(".main") as HTMLElement;
17
+ const containerEl = document.getElementById("screenshot-container") as HTMLDivElement;
18
+ const screenshotEl = document.getElementById("screenshot") as HTMLImageElement;
19
+ const overlaysEl = document.getElementById("overlays") as HTMLDivElement;
20
+ const actionStatusEl = document.getElementById("action-status") as HTMLSpanElement;
21
+ const sessionInfoEl = document.getElementById("session-info") as HTMLSpanElement;
22
+ const loadingOverlayEl = document.getElementById("loading-overlay") as HTMLDivElement;
23
+ const loadingTextEl = loadingOverlayEl.querySelector(".loading-text") as HTMLSpanElement;
24
+
25
+ // Create target info element dynamically
26
+ const targetInfoEl = document.createElement("div");
27
+ targetInfoEl.id = "target-info";
28
+ targetInfoEl.className = "target-info hidden";
29
+
30
+ // Track screenshot natural dimensions for coordinate scaling
31
+ let screenshotNaturalWidth = 0;
32
+ let screenshotNaturalHeight = 0;
33
+
34
+ // Zoom state - disabled, always show full view
35
+ let isZoomed = false;
36
+ const ZOOM_LEVEL = 2.0; // 2x zoom (not used when zoom disabled)
37
+
38
+ // Types for tool result data
39
+ interface ToolResultData {
40
+ action?: string;
41
+ success?: boolean;
42
+ imageUrl?: string; // Data URL with cropped image from find() response
43
+ screenshotResourceUri?: string; // Resource URI to fetch screenshot blob
44
+ croppedImageResourceUri?: string; // Resource URI to fetch cropped image from find operations
45
+ element?: {
46
+ description?: string;
47
+ x?: number;
48
+ y?: number;
49
+ centerX?: number;
50
+ centerY?: number;
51
+ width?: number;
52
+ height?: number;
53
+ confidence?: number;
54
+ ref?: string;
55
+ };
56
+ clickPosition?: { x: number; y: number; centerX?: number; centerY?: number };
57
+ scrollDirection?: string;
58
+ assertion?: string;
59
+ text?: string;
60
+ execResult?: string;
61
+ error?: string;
62
+ session?: {
63
+ id?: string;
64
+ expiresIn?: number;
65
+ };
66
+ debuggerUrl?: string;
67
+ sessionId?: string;
68
+ duration?: number;
69
+ }
70
+
71
+ // Store session info globally for display
72
+ let currentDebuggerUrl: string | null = null;
73
+
74
+ /**
75
+ * Extract structured data from tool result
76
+ */
77
+ function extractData(result: CallToolResult): ToolResultData {
78
+ return (result.structuredContent as ToolResultData) ?? {};
79
+ }
80
+
81
+ /**
82
+ * Show loading state with optional custom message
83
+ */
84
+ function showLoading(message = "Waiting for screenshot...") {
85
+ loadingTextEl.textContent = message;
86
+ loadingOverlayEl.classList.remove("hidden");
87
+ }
88
+
89
+ /**
90
+ * Hide loading state
91
+ */
92
+ function hideLoading() {
93
+ loadingOverlayEl.classList.add("hidden");
94
+ }
95
+
96
+ /**
97
+ * Apply host context (theme, styles, safe areas, container dimensions)
98
+ */
99
+ function handleHostContextChanged(ctx: McpUiHostContext) {
100
+ if (ctx.theme) {
101
+ applyDocumentTheme(ctx.theme);
102
+ }
103
+ if (ctx.styles?.variables) {
104
+ applyHostStyleVariables(ctx.styles.variables);
105
+ }
106
+ if (ctx.styles?.css?.fonts) {
107
+ applyHostFonts(ctx.styles.css.fonts);
108
+ }
109
+ if (ctx.safeAreaInsets) {
110
+ mainEl.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
111
+ mainEl.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
112
+ mainEl.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
113
+ mainEl.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
114
+ }
115
+
116
+ // Handle container dimensions to fit the entire image in the frame
117
+ const containerDimensions = (ctx as any).containerDimensions;
118
+ if (containerDimensions) {
119
+ // Handle height
120
+ if ("height" in containerDimensions) {
121
+ // Fixed height: fill the container
122
+ document.documentElement.style.height = "100vh";
123
+ mainEl.style.height = "100%";
124
+ } else if ("maxHeight" in containerDimensions && containerDimensions.maxHeight) {
125
+ // Flexible with max: let content determine size, up to max
126
+ document.documentElement.style.maxHeight = `${containerDimensions.maxHeight}px`;
127
+ mainEl.style.maxHeight = "100%";
128
+ }
129
+
130
+ // Handle width
131
+ if ("width" in containerDimensions) {
132
+ // Fixed width: fill the container
133
+ document.documentElement.style.width = "100vw";
134
+ mainEl.style.width = "100%";
135
+ } else if ("maxWidth" in containerDimensions && containerDimensions.maxWidth) {
136
+ // Flexible with max: let content determine size, up to max
137
+ document.documentElement.style.maxWidth = `${containerDimensions.maxWidth}px`;
138
+ mainEl.style.maxWidth = "100%";
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Scale coordinates from screenshot natural size to displayed size
145
+ */
146
+ function scaleCoord(value: number, naturalSize: number, displayedSize: number): number {
147
+ if (naturalSize === 0) return value;
148
+ return (value / naturalSize) * displayedSize;
149
+ }
150
+
151
+ /**
152
+ * Apply zoom transform to center on a point
153
+ */
154
+ function applyZoom(centerX: number, centerY: number, zoom: boolean) {
155
+ if (!zoom) {
156
+ containerEl.style.transform = "none";
157
+ containerEl.classList.remove("zoomed");
158
+ return;
159
+ }
160
+
161
+ const displayedWidth = screenshotEl.clientWidth;
162
+ const displayedHeight = screenshotEl.clientHeight;
163
+
164
+ // Scale the target coordinates to displayed size
165
+ const scaledX = scaleCoord(centerX, screenshotNaturalWidth, displayedWidth);
166
+ const scaledY = scaleCoord(centerY, screenshotNaturalHeight, displayedHeight);
167
+
168
+ // Calculate translation to center the point
169
+ // After zoom, we need to translate so the point ends up in the center of the container
170
+ const containerWidth = containerEl.parentElement?.clientWidth || displayedWidth;
171
+ const containerHeight = containerEl.parentElement?.clientHeight || displayedHeight;
172
+
173
+ // The point's position after scaling
174
+ const scaledPointX = scaledX * ZOOM_LEVEL;
175
+ const scaledPointY = scaledY * ZOOM_LEVEL;
176
+
177
+ // Translation needed to center the point
178
+ const translateX = (containerWidth / 2) - scaledPointX;
179
+ const translateY = (containerHeight / 2) - scaledPointY;
180
+
181
+ containerEl.style.transform = `scale(${ZOOM_LEVEL}) translate(${translateX / ZOOM_LEVEL}px, ${translateY / ZOOM_LEVEL}px)`;
182
+ containerEl.classList.add("zoomed");
183
+ }
184
+
185
+ /**
186
+ * Reset zoom to show full screenshot
187
+ */
188
+ function resetZoom() {
189
+ containerEl.style.transform = "none";
190
+ containerEl.classList.remove("zoomed");
191
+ }
192
+
193
+ /**
194
+ * Add overlays after screenshot loads (so we know dimensions)
195
+ * Uses requestAnimationFrame to ensure layout is computed
196
+ */
197
+ function addOverlays(data: ToolResultData) {
198
+ // Use requestAnimationFrame to ensure the image has been laid out
199
+ requestAnimationFrame(() => {
200
+ overlaysEl.innerHTML = "";
201
+
202
+ const displayedWidth = screenshotEl.clientWidth;
203
+ const displayedHeight = screenshotEl.clientHeight;
204
+
205
+ console.info("addOverlays:", {
206
+ action: data.action,
207
+ hasElement: !!data.element,
208
+ hasClickPosition: !!data.clickPosition,
209
+ displayedWidth,
210
+ displayedHeight,
211
+ naturalWidth: screenshotNaturalWidth,
212
+ naturalHeight: screenshotNaturalHeight
213
+ });
214
+
215
+ // Skip if dimensions aren't ready yet
216
+ if (displayedWidth === 0 || displayedHeight === 0) {
217
+ console.warn("addOverlays: Dimensions not ready, retrying...");
218
+ // Retry after a short delay
219
+ setTimeout(() => addOverlays(data), 50);
220
+ return;
221
+ }
222
+
223
+ // Track the focal point for zoom
224
+ let focalX: number | undefined;
225
+ let focalY: number | undefined;
226
+
227
+ // Add element target overlay for 'find' and 'find_and_click' actions
228
+ // The cropped image is always centered on the found element, so position target at image center
229
+ const showElementTarget = (data.action === "find" || data.action === "find_and_click" || data.action === "findall") && data.element;
230
+ if (showElementTarget) {
231
+ const target = document.createElement("div");
232
+ target.className = "element-target";
233
+
234
+ // Position at center of displayed image (cropped image is already centered on element)
235
+ target.style.left = `${displayedWidth / 2}px`;
236
+ target.style.top = `${displayedHeight / 2}px`;
237
+
238
+ // Add crosshair lines
239
+ const crosshairH = document.createElement("div");
240
+ crosshairH.className = "crosshair-h";
241
+ target.appendChild(crosshairH);
242
+
243
+ const crosshairV = document.createElement("div");
244
+ crosshairV.className = "crosshair-v";
245
+ target.appendChild(crosshairV);
246
+
247
+ // Add label
248
+ const label = document.createElement("div");
249
+ label.className = "element-label";
250
+ label.textContent = data.element?.description || "Element";
251
+ if (data.element?.confidence) {
252
+ label.textContent += ` (${Math.round(data.element.confidence * 100)}%)`;
253
+ }
254
+ target.appendChild(label);
255
+
256
+ overlaysEl.appendChild(target);
257
+
258
+ // Set focal point for zoom at image center
259
+ focalX = screenshotNaturalWidth / 2;
260
+ focalY = screenshotNaturalHeight / 2;
261
+
262
+ console.info("addOverlays: Added element target at center");
263
+ }
264
+
265
+ // Add click marker overlay for click actions (uses full screenshot, not cropped)
266
+ // Use centerX/centerY if available (where the click actually happens), fallback to x/y
267
+ if (data.clickPosition) {
268
+ const clickX = data.clickPosition.centerX ?? data.clickPosition.x;
269
+ const clickY = data.clickPosition.centerY ?? data.clickPosition.y;
270
+
271
+ if (clickX !== undefined && clickY !== undefined && screenshotNaturalWidth > 0) {
272
+ const marker = document.createElement("div");
273
+ marker.className = "click-marker";
274
+
275
+ const scaledX = scaleCoord(clickX, screenshotNaturalWidth, displayedWidth);
276
+ const scaledY = scaleCoord(clickY, screenshotNaturalHeight, displayedHeight);
277
+
278
+ marker.style.left = `${scaledX}px`;
279
+ marker.style.top = `${scaledY}px`;
280
+
281
+ // Add ripple effect
282
+ const ripple = document.createElement("div");
283
+ ripple.className = "click-ripple";
284
+ marker.appendChild(ripple);
285
+
286
+ overlaysEl.appendChild(marker);
287
+
288
+ console.info("addOverlays: Added click marker at", { clickX, clickY, scaledX, scaledY });
289
+
290
+ // Set focal point for zoom (click position takes priority if no element)
291
+ if (focalX === undefined) {
292
+ focalX = clickX;
293
+ focalY = clickY;
294
+ }
295
+ }
296
+ }
297
+
298
+ // Add scroll indicator (doesn't need scaling - centered)
299
+ if (data.scrollDirection) {
300
+ const arrow = document.createElement("div");
301
+ arrow.className = `scroll-indicator scroll-${data.scrollDirection}`;
302
+ arrow.textContent = data.scrollDirection === "up" ? "↑" :
303
+ data.scrollDirection === "down" ? "↓" :
304
+ data.scrollDirection === "left" ? "←" : "→";
305
+ overlaysEl.appendChild(arrow);
306
+ }
307
+
308
+ // Always show full view (zoom disabled)
309
+ resetZoom();
310
+ delete containerEl.dataset.focalX;
311
+ delete containerEl.dataset.focalY;
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Render screenshot and overlays
317
+ * Fetches screenshot via HTTP from localhost server (enabled by CSP connectDomains)
318
+ * This keeps base64 data out of AI context - only a small URL is passed
319
+ */
320
+ function renderResult(data: ToolResultData) {
321
+ // Clear previous overlays
322
+ overlaysEl.innerHTML = "";
323
+
324
+ // Update action status immediately
325
+ const actionName = data.action || "unknown";
326
+ const statusIcon = data.success ? "✓" : "✗";
327
+ const statusClass = data.success ? "success" : "error";
328
+ let statusText = `${statusIcon} ${actionName}`;
329
+
330
+ if (data.duration) {
331
+ statusText += ` (${data.duration}ms)`;
332
+ }
333
+ if (data.assertion) {
334
+ statusText += `: "${data.assertion}"`;
335
+ }
336
+ if (data.text && data.action === "type") {
337
+ statusText += `: "${data.text}"`;
338
+ }
339
+ if (data.error) {
340
+ statusText += ` - ${data.error}`;
341
+ }
342
+
343
+ actionStatusEl.textContent = statusText;
344
+ actionStatusEl.className = statusClass;
345
+
346
+ // Store debugger URL from session_start
347
+ if (data.debuggerUrl) {
348
+ currentDebuggerUrl = data.debuggerUrl;
349
+ }
350
+
351
+ // Update session info with debugger link
352
+ if (data.session) {
353
+ const expiresIn = data.session.expiresIn ? Math.round(data.session.expiresIn / 1000) : 0;
354
+ sessionInfoEl.innerHTML = "";
355
+
356
+ if (currentDebuggerUrl) {
357
+ const link = document.createElement("a");
358
+ link.href = currentDebuggerUrl;
359
+ link.target = "_blank";
360
+ link.rel = "noopener noreferrer";
361
+ link.textContent = `${expiresIn}s remaining`;
362
+ link.className = "debugger-link";
363
+ link.title = `Open debugger: ${currentDebuggerUrl}`;
364
+ sessionInfoEl.appendChild(link);
365
+ } else {
366
+ sessionInfoEl.textContent = `${expiresIn}s remaining`;
367
+ }
368
+ sessionInfoEl.className = expiresIn < 30 ? "warning" : "";
369
+ } else if (currentDebuggerUrl) {
370
+ // No session data but we have a debugger URL - show it
371
+ sessionInfoEl.innerHTML = "";
372
+ const link = document.createElement("a");
373
+ link.href = currentDebuggerUrl;
374
+ link.target = "_blank";
375
+ link.rel = "noopener noreferrer";
376
+ link.textContent = "Open Debugger";
377
+ link.className = "debugger-link";
378
+ link.title = currentDebuggerUrl;
379
+ sessionInfoEl.appendChild(link);
380
+ } else if (data.action === "session_start") {
381
+ sessionInfoEl.textContent = "Session started";
382
+ }
383
+
384
+ // Update target info for find/find_and_click actions
385
+ if (data.element && (data.action === "find" || data.action === "find_and_click")) {
386
+ const el = data.element;
387
+ let targetHtml = `<strong>Target:</strong> "${el.description || "Element"}"`;
388
+ if (el.centerX !== undefined && el.centerY !== undefined) {
389
+ targetHtml += ` <span class="target-coords">(${Math.round(el.centerX)}, ${Math.round(el.centerY)})</span>`;
390
+ }
391
+ if (el.confidence !== undefined) {
392
+ const confidencePercent = Math.round(el.confidence * 100);
393
+ targetHtml += ` <span class="target-confidence ${confidencePercent >= 70 ? 'high' : confidencePercent >= 40 ? 'medium' : 'low'}">${confidencePercent}%</span>`;
394
+ }
395
+ targetInfoEl.innerHTML = targetHtml;
396
+ targetInfoEl.classList.remove("hidden");
397
+ } else {
398
+ targetInfoEl.classList.add("hidden");
399
+ }
400
+
401
+ // Load cropped image from find() response (data URL)
402
+ if (data.imageUrl) {
403
+ showLoading("Loading image...");
404
+ screenshotEl.onerror = () => {
405
+ console.error("Image failed to load");
406
+ screenshotEl.alt = "Image failed to load";
407
+ containerEl.style.display = "none";
408
+ hideLoading();
409
+ };
410
+ screenshotEl.onload = () => {
411
+ console.info("Image loaded:", screenshotEl.naturalWidth, "x", screenshotEl.naturalHeight);
412
+ // Store natural dimensions
413
+ screenshotNaturalWidth = screenshotEl.naturalWidth;
414
+ screenshotNaturalHeight = screenshotEl.naturalHeight;
415
+ // Show the container and add overlays now that we know dimensions
416
+ containerEl.style.display = "block";
417
+ addOverlays(data);
418
+ // Hide loading state
419
+ hideLoading();
420
+ };
421
+ screenshotEl.src = data.imageUrl;
422
+ screenshotEl.style.display = "block";
423
+ } else {
424
+ // No image available - just show status without visual
425
+ screenshotEl.style.display = "none";
426
+ containerEl.style.display = "none";
427
+ hideLoading();
428
+ }
429
+ }
430
+
431
+ // 1. Create app instance
432
+ const app = new App({ name: "TestDriver Screenshot", version: "1.0.0" });
433
+
434
+ /**
435
+ * Fetch screenshot blob from resource URI and convert to data URL
436
+ */
437
+ async function fetchScreenshotFromResource(resourceUri: string): Promise<string | null> {
438
+ try {
439
+ console.info("Fetching screenshot from resource:", resourceUri);
440
+ const result = await app.request(
441
+ { method: "resources/read", params: { uri: resourceUri } },
442
+ ReadResourceResultSchema,
443
+ );
444
+
445
+ const content = result.contents[0];
446
+ if (!content || !("blob" in content)) {
447
+ console.error("Resource did not contain blob data");
448
+ return null;
449
+ }
450
+
451
+ // Convert base64 blob to data URL
452
+ const dataUrl = `data:${content.mimeType || "image/png"};base64,${content.blob}`;
453
+ console.info("Screenshot fetched successfully, blob length:", content.blob.length);
454
+ return dataUrl;
455
+ } catch (error) {
456
+ console.error("Failed to fetch screenshot resource:", error);
457
+ return null;
458
+ }
459
+ }
460
+
461
+ // 2. Register handlers BEFORE connecting
462
+ app.onteardown = async () => {
463
+ console.info("TestDriver app being torn down");
464
+ return {};
465
+ };
466
+
467
+ app.ontoolinput = (params) => {
468
+ console.info("Received tool input:", params);
469
+
470
+ const toolArgs = params.arguments;
471
+
472
+ // Build a readable summary from the arguments
473
+ const summaryParts: string[] = [];
474
+ if (toolArgs) {
475
+ // Show key params based on what's present
476
+ if (toolArgs.description) summaryParts.push(`"${toolArgs.description}"`);
477
+ if (toolArgs.text) summaryParts.push(`"${toolArgs.text}"`);
478
+ if (toolArgs.url) summaryParts.push(`${toolArgs.url}`);
479
+ if (toolArgs.direction) summaryParts.push(`${toolArgs.direction}`);
480
+ if (toolArgs.assertion) summaryParts.push(`"${toolArgs.assertion}"`);
481
+ if (toolArgs.task) summaryParts.push(`"${toolArgs.task}"`);
482
+ if (toolArgs.keys) summaryParts.push(`[${(toolArgs.keys as string[]).join("+")}]`);
483
+ if (toolArgs.type) summaryParts.push(`${toolArgs.type}`);
484
+ }
485
+
486
+ const actionSummary = summaryParts.length > 0 ? summaryParts.join(" ") : "action";
487
+
488
+ // Show loading state and hide screenshot (to avoid broken image during load)
489
+ actionStatusEl.textContent = `Running ${actionSummary}...`;
490
+ actionStatusEl.className = "loading";
491
+ containerEl.style.display = "none";
492
+ showLoading(`Running ${actionSummary}...`);
493
+ };
494
+
495
+ app.ontoolresult = async (result) => {
496
+ console.info("Received tool result:", result);
497
+ console.info("structuredContent:", result.structuredContent);
498
+ const data = extractData(result);
499
+ console.info("Extracted data keys:", Object.keys(data));
500
+ console.info("Has imageUrl:", !!data.imageUrl);
501
+ console.info("Has screenshotResourceUri:", !!data.screenshotResourceUri);
502
+ console.info("Has croppedImageResourceUri:", !!data.croppedImageResourceUri);
503
+
504
+ // If a screenshot or cropped image resource URI is provided, fetch the image from it
505
+ const resourceUri = data.screenshotResourceUri || data.croppedImageResourceUri;
506
+ if (resourceUri && !data.imageUrl) {
507
+ showLoading("Fetching image...");
508
+ const imageUrl = await fetchScreenshotFromResource(resourceUri);
509
+ if (imageUrl) {
510
+ data.imageUrl = imageUrl;
511
+ }
512
+ }
513
+
514
+ renderResult(data);
515
+ };
516
+
517
+ app.ontoolcancelled = (params) => {
518
+ console.info("Tool cancelled:", params.reason);
519
+ actionStatusEl.textContent = `Cancelled: ${params.reason}`;
520
+ actionStatusEl.className = "error";
521
+ hideLoading();
522
+ };
523
+
524
+ app.onerror = (error) => {
525
+ console.error("App error:", error);
526
+ actionStatusEl.textContent = `Error: ${error}`;
527
+ actionStatusEl.className = "error";
528
+ hideLoading();
529
+ };
530
+
531
+ app.onhostcontextchanged = handleHostContextChanged;
532
+
533
+ // 3. Connect to host
534
+ app.connect().then(() => {
535
+ const ctx = app.getHostContext();
536
+ if (ctx) {
537
+ handleHostContextChanged(ctx);
538
+ }
539
+ });
540
+
541
+ // Insert target info element after screenshot wrapper
542
+ const screenshotWrapper = document.querySelector(".screenshot-wrapper");
543
+ if (screenshotWrapper && screenshotWrapper.parentNode) {
544
+ screenshotWrapper.parentNode.insertBefore(targetInfoEl, screenshotWrapper.nextSibling);
545
+ }
546
+
547
+ // Zoom toggle disabled - always show full view