@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,452 @@
1
+ /**
2
+ * GitHub Comment Generator for TestDriver Test Results
3
+ *
4
+ * Creates beautifully formatted GitHub comments with:
5
+ * - Test results summary
6
+ * - Dashcam GIF replays
7
+ * - Exception details
8
+ * - Links to test runs
9
+ */
10
+
11
+ import { Octokit } from '@octokit/rest';
12
+
13
+ /**
14
+ * Format test duration in human-readable format
15
+ * @param {number} ms - Duration in milliseconds
16
+ * @returns {string} Formatted duration
17
+ */
18
+ function formatDuration(ms) {
19
+ if (ms < 1000) return `${ms}ms`;
20
+ if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
21
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
22
+ }
23
+
24
+ /**
25
+ * Get status emoji for test result
26
+ * @param {string} status - Test status (passed, failed, skipped)
27
+ * @returns {string} Emoji
28
+ */
29
+ function getStatusEmoji(status) {
30
+ switch (status) {
31
+ case 'passed': return '✅';
32
+ case 'failed': return '❌';
33
+ case 'skipped': return '⏭️';
34
+ case 'cancelled': return '🚫';
35
+ default: return '❓';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Generate markdown for test results table
41
+ * @param {Array} testCases - Array of test case objects
42
+ * @param {string} testRunUrl - Base URL for test run
43
+ * @returns {string} Markdown table
44
+ */
45
+ function generateTestResultsTable(testCases, testRunUrl) {
46
+ if (!testCases || testCases.length === 0) {
47
+ return '_No test cases recorded_';
48
+ }
49
+
50
+ // Filter out skipped tests
51
+ const nonSkippedTests = testCases.filter(test => test.status !== 'skipped');
52
+
53
+ if (nonSkippedTests.length === 0) {
54
+ return '_No test cases to display (all tests were skipped)_';
55
+ }
56
+
57
+ let table = '| Status | Test | File | Duration | Replay |\n';
58
+ table += '|--------|------|------|----------|--------|\n';
59
+
60
+ for (const test of nonSkippedTests) {
61
+ const status = getStatusEmoji(test.status);
62
+ const name = test.testName || 'Unknown';
63
+ const file = test.testFile || 'unknown';
64
+ const duration = formatDuration(test.duration || 0);
65
+
66
+ // Use test run context URL instead of direct replay URL
67
+ let replay = '-';
68
+ if (test.replayUrl) {
69
+ const linkUrl = (test.id && testRunUrl) ? `${testRunUrl}/${test.id}` : test.replayUrl;
70
+
71
+ // Extract replay ID and generate GIF URL
72
+ const replayId = extractReplayId(test.replayUrl);
73
+ if (replayId) {
74
+ const gifUrl = getReplayGifUrl(test.replayUrl, replayId);
75
+ // Embed GIF with link using HTML for height control
76
+ replay = `<a href="${linkUrl}"><img src="${gifUrl}" height="100" alt="Test replay" /></a>`;
77
+ } else {
78
+ // Fallback to text link if no GIF available
79
+ replay = `[🎥 View](${linkUrl})`;
80
+ }
81
+ } else if (test.id && testRunUrl) {
82
+ // Link without replay URL
83
+ replay = `[🎥 View](${testRunUrl}/${test.id})`;
84
+ }
85
+
86
+ table += `| ${status} | ${name} | \`${file}\` | ${duration} | ${replay} |\n`;
87
+ }
88
+
89
+ return table;
90
+ }
91
+
92
+ /**
93
+ * Generate markdown for exceptions/errors
94
+ * @param {Array} testCases - Array of test case objects with errors
95
+ * @param {string} testRunUrl - Base URL for test run
96
+ * @returns {string} Markdown with error details
97
+ */
98
+ function generateExceptionsSection(testCases, testRunUrl) {
99
+ const failedTests = testCases.filter(t => t.status === 'failed' && t.errorMessage);
100
+
101
+ if (failedTests.length === 0) {
102
+ return '';
103
+ }
104
+
105
+ let section = '\n## 🔴 Failures\n\n';
106
+
107
+ for (const test of failedTests) {
108
+ section += `### ${test.testName}\n\n`;
109
+ section += `**File:** \`${test.testFile}\`\n\n`;
110
+
111
+ // Use test run context URL instead of direct replay URL
112
+ if (test.id && testRunUrl) {
113
+ section += `**📹 [Watch Replay](${testRunUrl}/${test.id})**\n\n`;
114
+ } else if (test.replayUrl) {
115
+ section += `**📹 [Watch Replay](${test.replayUrl})**\n\n`;
116
+ }
117
+
118
+ section += '```\n';
119
+ section += test.errorMessage || 'Unknown error';
120
+ section += '\n```\n\n';
121
+
122
+ if (test.errorStack) {
123
+ section += '<details>\n';
124
+ section += '<summary>Stack Trace</summary>\n\n';
125
+ section += '```\n';
126
+ section += test.errorStack;
127
+ section += '\n```\n';
128
+ section += '</details>\n\n';
129
+ }
130
+ }
131
+
132
+ return section;
133
+ }
134
+
135
+ /**
136
+ * Generate markdown for dashcam replays section
137
+ * @param {Array} testCases - Array of test case objects
138
+ * @param {string} testRunUrl - Base URL for test run
139
+ * @returns {string} Markdown with replay embeds
140
+ */
141
+ function generateReplaySection(testCases, testRunUrl) {
142
+ const testsWithReplays = testCases.filter(t => t.replayUrl);
143
+
144
+ if (testsWithReplays.length === 0) {
145
+ return '';
146
+ }
147
+
148
+ let section = '\n## 🎥 Dashcam Replays\n\n';
149
+
150
+ for (const test of testsWithReplays) {
151
+ section += `### ${test.testName}\n\n`;
152
+
153
+ // Determine the link URL - prefer test run context
154
+ let linkUrl = test.replayUrl;
155
+ if (test.id && testRunUrl) {
156
+ linkUrl = `${testRunUrl}/${test.id}`;
157
+ }
158
+
159
+ // Extract replay ID from URL for GIF embed
160
+ const replayId = extractReplayId(test.replayUrl);
161
+ if (replayId) {
162
+ const gifUrl = getReplayGifUrl(test.replayUrl, replayId);
163
+ section += `[![${test.testName}](${gifUrl})](${linkUrl})\n\n`;
164
+ section += `[🎬 View Full Replay](${linkUrl})\n\n`;
165
+ } else {
166
+ section += `[🎬 View Replay](${linkUrl})\n\n`;
167
+ }
168
+ }
169
+
170
+ return section;
171
+ }
172
+
173
+ /**
174
+ * Extract replay ID from dashcam URL
175
+ * @param {string} url - Dashcam replay URL
176
+ * @returns {string|null} Replay ID or null
177
+ */
178
+ function extractReplayId(url) {
179
+ if (!url) return null;
180
+
181
+ // Match pattern: /replay/{id} or /replay/{id}?params
182
+ const match = url.match(/\/replay\/([^?/#]+)/);
183
+ return match ? match[1] : null;
184
+ }
185
+
186
+ /**
187
+ * Extract share key from dashcam URL
188
+ * @param {string} url - Dashcam replay URL
189
+ * @returns {string|null} Share key or null
190
+ */
191
+ function extractShareKey(url) {
192
+ if (!url) return null;
193
+
194
+ // Match pattern: ?share=KEY or &share=KEY
195
+ const match = url.match(/[?&]share=([^&#]+)/);
196
+ return match ? match[1] : null;
197
+ }
198
+
199
+ /**
200
+ * Get GIF URL for replay
201
+ * @param {string} replayUrl - Full replay URL
202
+ * @param {string} replayId - Replay ID
203
+ * @returns {string} GIF URL
204
+ */
205
+ function getReplayGifUrl(replayUrl, replayId) {
206
+ // Determine the API base URL based on the replay URL
207
+ // Replay URLs use console domains; GIF endpoints live on the corresponding API domain
208
+ let apiBaseUrl;
209
+
210
+ if (replayUrl.includes('app.dashcam.io')) {
211
+ // Production dashcam uses Heroku API
212
+ apiBaseUrl = 'https://testdriverai-v6-c96fc597be11.herokuapp.com';
213
+ } else if (replayUrl.includes('localhost')) {
214
+ // Local development
215
+ apiBaseUrl = 'http://localhost:1337';
216
+ } else {
217
+ // Map console URLs → API URLs for all environments
218
+ // console-test.testdriver.ai → api-test.testdriver.ai
219
+ // console-canary.testdriver.ai → api-canary.testdriver.ai
220
+ // console.testdriver.ai → api.testdriver.ai
221
+ const consoleEnvMatch = replayUrl.match(/https:\/\/console-(test|canary)\.testdriver\.ai/);
222
+ if (consoleEnvMatch) {
223
+ apiBaseUrl = `https://api-${consoleEnvMatch[1]}.testdriver.ai`;
224
+ } else if (replayUrl.includes('console.testdriver.ai')) {
225
+ apiBaseUrl = 'https://api.testdriver.ai';
226
+ }
227
+ // Fly.io: map web app → API app
228
+ // pr-123-web.fly.dev → pr-123-api.fly.dev
229
+ // td-test-web.fly.dev → td-test-api.fly.dev
230
+ else {
231
+ const flyWebMatch = replayUrl.match(/https:\/\/([\w-]+)-web\.fly\.dev/);
232
+ if (flyWebMatch) {
233
+ apiBaseUrl = `https://${flyWebMatch[1]}-api.fly.dev`;
234
+ } else {
235
+ // Fallback: extract base URL from replay URL as-is
236
+ const urlObj = new URL(replayUrl);
237
+ apiBaseUrl = `${urlObj.protocol}//${urlObj.host}`;
238
+ }
239
+ }
240
+ }
241
+
242
+ // Extract share key if present
243
+ const shareKey = extractShareKey(replayUrl);
244
+
245
+ // Build GIF URL with shareKey parameter
246
+ let gifUrl = `${apiBaseUrl}/replay/${replayId}/gif`;
247
+ if (shareKey) {
248
+ gifUrl += `?shareKey=${shareKey}`;
249
+ }
250
+
251
+ return gifUrl;
252
+ }
253
+
254
+ /**
255
+ * Generate complete GitHub comment markdown
256
+ * @param {Object} testRunData - Test run data
257
+ * @param {Array} testCases - Array of test case objects
258
+ * @returns {string} Complete markdown comment
259
+ */
260
+ export function generateGitHubComment(testRunData, testCases = []) {
261
+ const {
262
+ runId,
263
+ status,
264
+ totalTests = 0,
265
+ passedTests = 0,
266
+ failedTests = 0,
267
+ skippedTests = 0,
268
+ duration = 0,
269
+ testRunUrl,
270
+ platform = 'unknown',
271
+ branch = 'unknown',
272
+ commit = 'unknown',
273
+ } = testRunData;
274
+
275
+ // Header with overall status
276
+ const statusEmoji = getStatusEmoji(status);
277
+ const statusColor = status === 'passed' ? '🟢' : status === 'failed' ? '🔴' : '🟡';
278
+
279
+ let comment = `# ${statusColor} TestDriver Test Results\n\n`;
280
+
281
+ // Compact summary line
282
+ comment += `**Status:** ${statusEmoji} ${status.toUpperCase()}`;
283
+ comment += ` • **Duration:** ${formatDuration(duration)}`;
284
+ comment += ` • ${passedTests} passed`;
285
+ if (failedTests > 0) comment += `, ${failedTests} failed`;
286
+ // Only show skipped count if there are no passed or failed tests
287
+ if (skippedTests > 0 && passedTests === 0 && failedTests === 0) {
288
+ comment += `, ${skippedTests} skipped`;
289
+ }
290
+ comment += `\n\n`;
291
+
292
+ // Exceptions section (only if there are failures) - show first
293
+ comment += generateExceptionsSection(testCases, testRunUrl);
294
+
295
+ // Test results table (now includes embedded GIFs)
296
+ comment += '## 📝 Test Results\n\n';
297
+ comment += generateTestResultsTable(testCases, testRunUrl);
298
+
299
+ // Link to full test run (below table)
300
+ if (testRunUrl) {
301
+ comment += `\n[📋 View Full Test Run](${testRunUrl})\n`;
302
+ }
303
+
304
+ // Footer
305
+ comment += '\n---\n';
306
+ comment += `<sub>Generated by [TestDriver](https://testdriver.ai) • Run ID: \`${runId}\`</sub>\n`;
307
+
308
+ return comment;
309
+ }
310
+
311
+ /**
312
+ * Post comment to GitHub PR or commit
313
+ * @param {Object} options - Options
314
+ * @param {string} options.token - GitHub token
315
+ * @param {string} options.owner - Repository owner
316
+ * @param {string} options.repo - Repository name
317
+ * @param {number} [options.prNumber] - Pull request number (if commenting on PR)
318
+ * @param {string} [options.commitSha] - Commit SHA (if commenting on commit)
319
+ * @param {string} options.body - Comment body (markdown)
320
+ * @returns {Promise<Object>} GitHub API response
321
+ */
322
+ export async function postGitHubComment(options) {
323
+ const { token, owner, repo, prNumber, commitSha, body } = options;
324
+
325
+ if (!token) {
326
+ throw new Error('GitHub token is required');
327
+ }
328
+
329
+ if (!owner || !repo) {
330
+ throw new Error('Repository owner and name are required');
331
+ }
332
+
333
+ if (!prNumber && !commitSha) {
334
+ throw new Error('Either prNumber or commitSha must be provided');
335
+ }
336
+
337
+ const octokit = new Octokit({ auth: token });
338
+
339
+ if (prNumber) {
340
+ // Comment on PR
341
+ const response = await octokit.rest.issues.createComment({
342
+ owner,
343
+ repo,
344
+ issue_number: prNumber,
345
+ body,
346
+ });
347
+ return response.data;
348
+ } else if (commitSha) {
349
+ // Comment on commit
350
+ const response = await octokit.rest.repos.createCommitComment({
351
+ owner,
352
+ repo,
353
+ commit_sha: commitSha,
354
+ body,
355
+ });
356
+ return response.data;
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Update existing GitHub comment
362
+ * @param {Object} options - Options
363
+ * @param {string} options.token - GitHub token
364
+ * @param {string} options.owner - Repository owner
365
+ * @param {string} options.repo - Repository name
366
+ * @param {number} options.commentId - Comment ID to update
367
+ * @param {string} options.body - Updated comment body (markdown)
368
+ * @returns {Promise<Object>} GitHub API response
369
+ */
370
+ export async function updateGitHubComment(options) {
371
+ const { token, owner, repo, commentId, body } = options;
372
+
373
+ if (!token || !owner || !repo || !commentId) {
374
+ throw new Error('Token, owner, repo, and commentId are required');
375
+ }
376
+
377
+ const octokit = new Octokit({ auth: token });
378
+
379
+ const response = await octokit.rest.issues.updateComment({
380
+ owner,
381
+ repo,
382
+ comment_id: commentId,
383
+ body,
384
+ });
385
+
386
+ return response.data;
387
+ }
388
+
389
+ /**
390
+ * Find existing TestDriver comment on PR
391
+ * @param {Object} options - Options
392
+ * @param {string} options.token - GitHub token
393
+ * @param {string} options.owner - Repository owner
394
+ * @param {string} options.repo - Repository name
395
+ * @param {number} options.prNumber - Pull request number
396
+ * @returns {Promise<Object|null>} Existing comment or null
397
+ */
398
+ export async function findExistingComment(options) {
399
+ const { token, owner, repo, prNumber } = options;
400
+
401
+ if (!token || !owner || !repo || !prNumber) {
402
+ return null;
403
+ }
404
+
405
+ const octokit = new Octokit({ auth: token });
406
+
407
+ const comments = await octokit.rest.issues.listComments({
408
+ owner,
409
+ repo,
410
+ issue_number: prNumber,
411
+ });
412
+
413
+ // Find comment with TestDriver signature
414
+ const existingComment = comments.data.find(comment =>
415
+ comment.body && comment.body.includes('Generated by [TestDriver]')
416
+ );
417
+
418
+ return existingComment || null;
419
+ }
420
+
421
+ /**
422
+ * Post or update GitHub comment with test results
423
+ * Always keeps the comment at the end of the thread by deleting and recreating
424
+ * @param {Object} testRunData - Test run data
425
+ * @param {Array} testCases - Array of test case objects
426
+ * @param {Object} githubOptions - GitHub API options
427
+ * @returns {Promise<Object>} GitHub API response
428
+ */
429
+ export async function postOrUpdateTestResults(testRunData, testCases, githubOptions) {
430
+ const commentBody = generateGitHubComment(testRunData, testCases);
431
+
432
+ // Try to find and delete existing comment to keep it at the end
433
+ if (githubOptions.prNumber) {
434
+ const existingComment = await findExistingComment(githubOptions);
435
+
436
+ if (existingComment) {
437
+ // Delete the old comment
438
+ const octokit = new Octokit({ auth: githubOptions.token });
439
+ await octokit.rest.issues.deleteComment({
440
+ owner: githubOptions.owner,
441
+ repo: githubOptions.repo,
442
+ comment_id: existingComment.id,
443
+ });
444
+ }
445
+ }
446
+
447
+ // Always create a new comment (will be at the end of the thread)
448
+ return await postGitHubComment({
449
+ ...githubOptions,
450
+ body: commentBody,
451
+ });
452
+ }