@testdriverai/agent 7.8.0-test.38

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,633 @@
1
+ const { events } = require("../events");
2
+ const crypto = require("crypto");
3
+
4
+ // get the version from package.json
5
+ const { version } = require("../../package.json");
6
+ const axios = require("axios");
7
+
8
+ /**
9
+ * Default retry configuration
10
+ */
11
+ const DEFAULT_RETRY_CONFIG = {
12
+ maxRetries: 10,
13
+ baseDelayMs: 3000,
14
+ maxDelayMs: 30000,
15
+ // Error codes that should trigger a retry
16
+ retryableNetworkCodes: [
17
+ 'ECONNRESET',
18
+ 'ECONNREFUSED',
19
+ 'ETIMEDOUT',
20
+ 'ENOTFOUND',
21
+ 'ENETUNREACH',
22
+ 'ERR_NETWORK',
23
+ 'ECONNABORTED',
24
+ 'EPIPE',
25
+ 'EAI_AGAIN',
26
+ ],
27
+ // HTTP status codes that should trigger a retry
28
+ retryableStatusCodes: [429, 500, 502, 503, 504],
29
+ };
30
+
31
+ /**
32
+ * Determines if an error is retryable
33
+ * @param {Error} error - The axios error
34
+ * @param {Object} config - Retry configuration
35
+ * @returns {boolean} Whether the request should be retried
36
+ */
37
+ function isRetryableError(error, config = DEFAULT_RETRY_CONFIG) {
38
+ // Network-level errors (no response received)
39
+ if (!error.response) {
40
+ return config.retryableNetworkCodes.includes(error.code);
41
+ }
42
+
43
+ // HTTP status code based retries
44
+ const status = error.response?.status;
45
+ return config.retryableStatusCodes.includes(status);
46
+ }
47
+
48
+ /**
49
+ * Calculate delay for next retry using exponential backoff with jitter
50
+ * @param {number} attempt - Current attempt number (0-indexed)
51
+ * @param {Error} error - The error that triggered the retry
52
+ * @param {Object} config - Retry configuration
53
+ * @returns {number} Delay in milliseconds
54
+ */
55
+ function calculateRetryDelay(attempt, error, config = DEFAULT_RETRY_CONFIG) {
56
+ // Respect Retry-After header for rate limiting
57
+ if (error.response?.status === 429) {
58
+ const retryAfter = error.response.headers?.['retry-after'];
59
+ if (retryAfter) {
60
+ const retryAfterMs = parseInt(retryAfter, 10) * 1000;
61
+ if (!isNaN(retryAfterMs) && retryAfterMs > 0) {
62
+ return Math.min(retryAfterMs, config.maxDelayMs);
63
+ }
64
+ }
65
+ }
66
+
67
+ // Exponential backoff: baseDelay * 2^attempt + random jitter
68
+ const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt);
69
+ const jitter = Math.random() * config.baseDelayMs * 0.5;
70
+ return Math.min(exponentialDelay + jitter, config.maxDelayMs);
71
+ }
72
+
73
+ /**
74
+ * Sleep for a specified duration
75
+ * @param {number} ms - Milliseconds to sleep
76
+ * @returns {Promise<void>}
77
+ */
78
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
79
+
80
+ /**
81
+ * Execute an async function with retry logic
82
+ * @param {Function} fn - Async function to execute
83
+ * @param {Object} options - Options
84
+ * @param {Object} options.retryConfig - Retry configuration (uses defaults if not provided)
85
+ * @param {Function} options.onRetry - Callback called before each retry (attempt, error, delayMs)
86
+ * @returns {Promise<*>} Result of the function
87
+ */
88
+ async function withRetry(fn, options = {}) {
89
+ const config = { ...DEFAULT_RETRY_CONFIG, ...options.retryConfig };
90
+ let lastError;
91
+
92
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
93
+ try {
94
+ return await fn();
95
+ } catch (error) {
96
+ lastError = error;
97
+
98
+ // Don't retry if we've exhausted attempts or error isn't retryable
99
+ if (attempt >= config.maxRetries || !isRetryableError(error, config)) {
100
+ throw error;
101
+ }
102
+
103
+ const delayMs = calculateRetryDelay(attempt, error, config);
104
+
105
+ // Call onRetry callback if provided
106
+ if (options.onRetry) {
107
+ options.onRetry(attempt + 1, error, delayMs);
108
+ }
109
+
110
+ await sleep(delayMs);
111
+ }
112
+ }
113
+
114
+ throw lastError;
115
+ }
116
+
117
+ /**
118
+ * Generate Sentry trace headers for distributed tracing
119
+ * Uses the same trace ID derivation as the API (MD5 hash of session ID)
120
+ * @param {string} sessionId - The session ID
121
+ * @returns {Object} Headers object with sentry-trace and baggage
122
+ */
123
+ function getSentryTraceHeaders(sessionId) {
124
+ if (!sessionId) return {};
125
+
126
+ // Same logic as API: derive trace ID from session ID
127
+ const traceId = crypto.createHash('md5').update(sessionId).digest('hex');
128
+ const spanId = crypto.randomBytes(8).toString('hex');
129
+
130
+ return {
131
+ 'sentry-trace': `${traceId}-${spanId}-1`,
132
+ 'baggage': `sentry-trace_id=${traceId},sentry-sample_rate=1.0,sentry-sampled=true`
133
+ };
134
+ }
135
+
136
+ // Factory function that creates SDK with the provided emitter, config, and session
137
+ let token = null;
138
+ const createSDK = (emitter, config, sessionInstance) => {
139
+ // Config is required - no fallback to avoid process.env usage
140
+ if (!config) {
141
+ throw new Error("Config must be provided to createSDK");
142
+ }
143
+
144
+ // Session is required
145
+ if (!sessionInstance) {
146
+ throw new Error("Session instance must be provided to createSDK");
147
+ }
148
+
149
+ const outputError = (error) => {
150
+ emitter.emit(events.error.sdk, {
151
+ message: error.status || error.reason || error.message,
152
+ code: error.response?.data?.raw || error.statusText || error.code,
153
+ fullError: error,
154
+ });
155
+ };
156
+
157
+ const parseBody = async (response, body) => {
158
+ const contentType = response.headers.get("Content-Type")?.toLowerCase();
159
+ try {
160
+ if (body === null || body === undefined) {
161
+ if (!contentType.includes("json") && !contentType.includes("text")) {
162
+ return await response.arrayBuffer();
163
+ }
164
+ body = response.data;
165
+ }
166
+
167
+ if (typeof body === "string") {
168
+ if (contentType.includes("jsonl")) {
169
+ const result = body
170
+ .split("\n")
171
+ .filter((line) => line.trim().length)
172
+ .map((line) => JSON.parse(line))
173
+ .reduce((result, { type, data }) => {
174
+ if (result[type]) {
175
+ if (typeof result[type] === "string") {
176
+ result[type] += data;
177
+ } else {
178
+ result[type].push(data);
179
+ }
180
+ } else {
181
+ result[type] = typeof data === "string" ? data : [data];
182
+ }
183
+ return result;
184
+ }, {});
185
+ for (const key of Object.keys(result)) {
186
+ if (Array.isArray(result[key]) && result[key].length === 1) {
187
+ result[key] = result[key][0];
188
+ }
189
+ }
190
+ return result;
191
+ }
192
+ if (contentType.includes("json")) {
193
+ return JSON.parse(body);
194
+ }
195
+ }
196
+ return body;
197
+ } catch (err) {
198
+ emitter.emit(events.error.sdk, {
199
+ error: err,
200
+ message: "Parsing Error",
201
+ });
202
+ throw err;
203
+ }
204
+ };
205
+
206
+ const auth = async () => {
207
+ if (!config["TD_API_KEY"]) {
208
+ const error = new Error(
209
+ "TD_API_KEY is not configured. Get your API key at https://console.testdriver.ai/team"
210
+ );
211
+ error.code = "MISSING_API_KEY";
212
+ error.isAuthError = true;
213
+ throw error;
214
+ }
215
+
216
+ const url = [config["TD_API_ROOT"], "auth/exchange-api-key"].join("/");
217
+ const c = {
218
+ method: "post",
219
+ headers: {
220
+ "Content-Type": "application/json",
221
+ "User-Agent": `TestDriverSDK/${version} (Node.js ${process.version})`,
222
+ },
223
+ timeout: 15000, // 15 second timeout for auth requests
224
+ data: {
225
+ apiKey: config["TD_API_KEY"],
226
+ version,
227
+ },
228
+ };
229
+
230
+ try {
231
+ let res = await withRetry(
232
+ () => axios(url, c),
233
+ {
234
+ retryConfig: { maxRetries: 2 },
235
+ onRetry: (attempt, error, delayMs) => {
236
+ emitter.emit(events.sdk.retry, {
237
+ path: 'auth/exchange-api-key',
238
+ attempt,
239
+ error: error.message || error.code,
240
+ delayMs,
241
+ });
242
+ },
243
+ }
244
+ );
245
+
246
+ token = res.data.token;
247
+ return token;
248
+ } catch (error) {
249
+ // Classify the error for better user feedback
250
+ const classifiedError = classifyAuthError(error, config["TD_API_ROOT"]);
251
+ outputError(classifiedError);
252
+ throw classifiedError;
253
+ }
254
+ };
255
+
256
+ /**
257
+ * Classify authentication errors into user-friendly categories
258
+ * @param {Error} error - The original axios error
259
+ * @param {string} apiRoot - The API root URL for context
260
+ * @returns {Error} A classified error with code and helpful message
261
+ */
262
+ function classifyAuthError(error, apiRoot) {
263
+ const status = error.response?.status;
264
+ const data = error.response?.data;
265
+
266
+ // Check for network-level errors (no response received)
267
+ if (!error.response) {
268
+ const networkError = new Error(
269
+ `Unable to reach TestDriver API at ${apiRoot}. ` +
270
+ getNetworkErrorHint(error.code)
271
+ );
272
+ networkError.code = "NETWORK_ERROR";
273
+ networkError.isNetworkError = true;
274
+ networkError.originalError = error;
275
+ return networkError;
276
+ }
277
+
278
+ // Invalid API key (401)
279
+ if (status === 401) {
280
+ const authError = new Error(
281
+ data?.message ||
282
+ "Invalid API key. Please check your TD_API_KEY and try again. " +
283
+ "Get your API key at https://console.testdriver.ai/team"
284
+ );
285
+ authError.code = data?.error || "INVALID_API_KEY";
286
+ authError.isAuthError = true;
287
+ authError.originalError = error;
288
+ return authError;
289
+ }
290
+
291
+ // Server errors (5xx) - API is down or having issues
292
+ if (status >= 500) {
293
+ const serverError = new Error(
294
+ data?.message ||
295
+ `TestDriver API is currently unavailable (HTTP ${status}). Please try again later.`
296
+ );
297
+ serverError.code = data?.error || "API_UNAVAILABLE";
298
+ serverError.isServerError = true;
299
+ serverError.originalError = error;
300
+ return serverError;
301
+ }
302
+
303
+ // Rate limiting (429)
304
+ if (status === 429) {
305
+ const rateLimitError = new Error(
306
+ "Too many requests to TestDriver API. Please wait a moment and try again."
307
+ );
308
+ rateLimitError.code = "RATE_LIMITED";
309
+ rateLimitError.isRateLimitError = true;
310
+ rateLimitError.originalError = error;
311
+ return rateLimitError;
312
+ }
313
+
314
+ // Forbidden (403) - likely Cloudflare or WAF blocking the request
315
+ if (status === 403) {
316
+ const forbiddenError = new Error(
317
+ "Request blocked (HTTP 403). This may be caused by a firewall or bot protection. " +
318
+ "If this persists, please contact support."
319
+ );
320
+ forbiddenError.code = "REQUEST_BLOCKED";
321
+ forbiddenError.isForbiddenError = true;
322
+ forbiddenError.originalError = error;
323
+ return forbiddenError;
324
+ }
325
+
326
+ // Other HTTP errors - return with context
327
+ const url = error.config?.url || apiRoot;
328
+ const genericError = new Error(
329
+ `Authentication failed: ${status} ${error.response?.statusText || "Unknown error"} (${url})`
330
+ );
331
+ genericError.code = "AUTH_FAILED";
332
+ genericError.originalError = error;
333
+ return genericError;
334
+ }
335
+
336
+ /**
337
+ * Get a helpful hint based on the network error code
338
+ * @param {string} code - The error code (ECONNREFUSED, ETIMEDOUT, etc.)
339
+ * @returns {string} A helpful message for the user
340
+ */
341
+ function getNetworkErrorHint(code) {
342
+ const hints = {
343
+ ECONNREFUSED: "The server refused the connection. Check if the API is running.",
344
+ ETIMEDOUT: "The connection timed out. Check your internet connection.",
345
+ ENOTFOUND: "Could not resolve the hostname. Check your internet connection or DNS settings.",
346
+ ENETUNREACH: "Network is unreachable. Check your internet connection.",
347
+ ECONNRESET: "Connection was reset. This may be a temporary network issue.",
348
+ ERR_NETWORK: "A network error occurred. Check your internet connection.",
349
+ ECONNABORTED: "The request was aborted due to a timeout.",
350
+ };
351
+ return hints[code] || "Check your internet connection and try again.";
352
+ }
353
+
354
+ const req = async (path, data, onChunk) => {
355
+ // for each value of data, if it is null/undefined remove it
356
+ // Note: use == null to match both null and undefined, but preserve
357
+ // other falsy values like 0, false, and "" which may be intentional
358
+ for (let key in data) {
359
+ if (data[key] == null) {
360
+ delete data[key];
361
+ }
362
+ }
363
+
364
+ // ── S3 upload: replace large inline base64 images with S3 keys ──────
365
+ // If data.image is a large base64 string (>50KB), upload the raw PNG
366
+ // to S3 via a presigned URL and send only the imageKey instead.
367
+ // This reduces JSON body size from ~1.3MB to ~60 bytes.
368
+ const MIN_IMAGE_SIZE = 50_000; // 50KB base64 chars
369
+ if (
370
+ data &&
371
+ typeof data.image === "string" &&
372
+ data.image.length > MIN_IMAGE_SIZE
373
+ ) {
374
+ try {
375
+ const apiRoot = config["TD_API_ROOT"];
376
+ const uploadUrlEndpoint = [apiRoot, "api", version, "testdriver", "upload-url"].join("/");
377
+
378
+ // Step 1: Get presigned upload URL from API
379
+ const uploadRes = await axios(uploadUrlEndpoint, {
380
+ method: "post",
381
+ headers: {
382
+ "Content-Type": "application/json",
383
+ "User-Agent": `TestDriverSDK/${version} (Node.js ${process.version})`,
384
+ ...(token && { Authorization: `Bearer ${token}` }),
385
+ ...getSentryTraceHeaders(sessionInstance.get()),
386
+ },
387
+ timeout: 15000,
388
+ data: {
389
+ session: sessionInstance.get(),
390
+ contentType: "image/png",
391
+ },
392
+ });
393
+
394
+ const { uploadUrl, imageKey } = uploadRes.data;
395
+
396
+ if (uploadUrl && imageKey) {
397
+ // Step 2: Upload raw PNG bytes to S3 via presigned PUT URL
398
+ const base64Data = data.image.replace(/^data:image\/\w+;base64,/, "");
399
+ const pngBuffer = Buffer.from(base64Data, "base64");
400
+
401
+ await axios(uploadUrl, {
402
+ method: "put",
403
+ headers: {
404
+ "Content-Type": "image/png",
405
+ "Content-Length": pngBuffer.length,
406
+ },
407
+ data: pngBuffer,
408
+ timeout: 30000,
409
+ maxBodyLength: Infinity,
410
+ maxContentLength: Infinity,
411
+ });
412
+
413
+ // Step 3: Replace image with imageKey in the request data
414
+ const savedKB = (data.image.length / 1024).toFixed(0);
415
+ delete data.image;
416
+ data.imageKey = imageKey;
417
+ emitter.emit(events.log?.debug || events.sdk.request, {
418
+ path,
419
+ message: `[sdk] uploaded screenshot to S3 (saved ${savedKB}KB inline), imageKey=${imageKey}`,
420
+ });
421
+ }
422
+ } catch (uploadErr) {
423
+ // Non-fatal: fall back to sending base64 inline
424
+ // This ensures old API servers without the upload-url endpoint still work
425
+ emitter.emit(events.log?.debug || events.sdk.request, {
426
+ path,
427
+ message: `[sdk] S3 upload failed, falling back to inline base64: ${uploadErr.message}`,
428
+ });
429
+ }
430
+ }
431
+ // ── End S3 upload ───────────────────────────────────────────────────
432
+
433
+ emitter.emit(events.sdk.request, {
434
+ path,
435
+ });
436
+
437
+ const url = path.startsWith("/api")
438
+ ? [config["TD_API_ROOT"], path].join("")
439
+ : [config["TD_API_ROOT"], "api", version, "testdriver", path].join("/");
440
+
441
+ // Get session ID for Sentry trace headers
442
+ const sessionId = sessionInstance.get();
443
+ const sentryHeaders = getSentryTraceHeaders(sessionId);
444
+
445
+ const c = {
446
+ method: "post",
447
+ headers: {
448
+ "Content-Type": "application/json",
449
+ "User-Agent": `TestDriverSDK/${version} (Node.js ${process.version})`,
450
+ ...(token && { Authorization: `Bearer ${token}` }), // Add the authorization bearer token only if token is set
451
+ ...sentryHeaders, // Add Sentry distributed tracing headers
452
+ },
453
+ responseType: typeof onChunk === "function" ? "stream" : "json",
454
+ timeout: 120000, // 120 second timeout to prevent hanging requests
455
+ data: {
456
+ ...data,
457
+ session: sessionInstance.get(),
458
+ stream: typeof onChunk === "function",
459
+ },
460
+ };
461
+
462
+ try {
463
+ let response;
464
+
465
+ // Use retry logic for non-streaming requests
466
+ // Streaming requests are not retried as they involve ongoing data transfer
467
+ if (typeof onChunk !== "function") {
468
+ response = await withRetry(
469
+ () => axios(url, c),
470
+ {
471
+ onRetry: (attempt, error, delayMs) => {
472
+ emitter.emit(events.sdk.retry, {
473
+ path,
474
+ attempt,
475
+ error: error.message || error.code,
476
+ delayMs,
477
+ });
478
+ },
479
+ }
480
+ );
481
+ } else {
482
+ response = await axios(url, c);
483
+ }
484
+
485
+ emitter.emit(events.sdk.response, {
486
+ path,
487
+ });
488
+
489
+ const contentType = response.headers["content-type"]?.toLowerCase();
490
+ const isJsonl = contentType === "application/jsonl";
491
+ let result;
492
+
493
+ if (onChunk) {
494
+ result = "";
495
+ let lastLineIndex = -1;
496
+
497
+ await new Promise((resolve, reject) => {
498
+ // theres some kind of race condition here that makes things resolve
499
+ // before the stream is done
500
+
501
+ response.data.on("data", (chunk) => {
502
+ result += chunk.toString();
503
+ const lines = result.split("\n");
504
+
505
+ const events = lines
506
+ .slice(lastLineIndex + 1, lines.length - 1)
507
+ .filter((line) => line.length)
508
+ .map((line) => JSON.parse(line));
509
+
510
+ for (const event of events) {
511
+ onChunk(event);
512
+ }
513
+
514
+ lastLineIndex = lines.length - 2;
515
+ });
516
+
517
+ response.data.on("end", () => {
518
+ if (isJsonl) {
519
+ const events = result
520
+ .split("\n")
521
+ .slice(lastLineIndex + 2)
522
+ .filter((line) => line.length)
523
+ .map((line) => JSON.parse(line));
524
+
525
+ for (const event of events) {
526
+ onChunk(event);
527
+ }
528
+ }
529
+
530
+ resolve();
531
+ });
532
+
533
+ response.data.on("error", (error) => {
534
+ reject(error);
535
+ });
536
+ });
537
+ }
538
+
539
+ const value = await parseBody(response, result);
540
+
541
+ return value;
542
+ } catch (error) {
543
+ // Check for network-level errors (no response received)
544
+ if (!error.response) {
545
+ const networkError = new Error(
546
+ `Unable to reach TestDriver API at ${config["TD_API_ROOT"]}. ` +
547
+ getNetworkErrorHint(error.code)
548
+ );
549
+ networkError.code = "NETWORK_ERROR";
550
+ networkError.isNetworkError = true;
551
+ networkError.originalError = error;
552
+ networkError.path = path;
553
+
554
+ emitter.emit(events.error.sdk, {
555
+ message: networkError.message,
556
+ code: networkError.code,
557
+ fullError: error,
558
+ });
559
+
560
+ throw networkError;
561
+ }
562
+
563
+ // Check if this is an API validation error with detailed problems
564
+ if (error.response?.data?.problems) {
565
+ const problems = error.response.data.problems;
566
+ const errorMessage = error.response.data.message || 'API validation error';
567
+ const detailedError = new Error(
568
+ `${errorMessage}\n\nDetails:\n${problems.map(p => ` - ${p}`).join('\n')}`
569
+ );
570
+ detailedError.originalError = error;
571
+ detailedError.problems = problems;
572
+
573
+ // Emit the formatted error
574
+ emitter.emit(events.error.sdk, {
575
+ message: detailedError.message,
576
+ code: error.response?.data?.code || error.code,
577
+ problems: problems,
578
+ fullError: error,
579
+ });
580
+
581
+ throw detailedError;
582
+ }
583
+
584
+ // Server errors (5xx) - API is down or having issues
585
+ const status = error.response?.status;
586
+ if (status >= 500) {
587
+ const serverError = new Error(
588
+ error.response?.data?.message ||
589
+ `TestDriver API is currently unavailable (HTTP ${status}). Please try again later.`
590
+ );
591
+ serverError.code = error.response?.data?.error || "API_UNAVAILABLE";
592
+ serverError.isServerError = true;
593
+ serverError.originalError = error;
594
+ serverError.path = path;
595
+
596
+ emitter.emit(events.error.sdk, {
597
+ message: serverError.message,
598
+ code: serverError.code,
599
+ fullError: error,
600
+ });
601
+
602
+ throw serverError;
603
+ }
604
+
605
+ // Rate limiting (429)
606
+ if (status === 429) {
607
+ const rateLimitError = new Error(
608
+ "Too many requests to TestDriver API. Please wait a moment and try again."
609
+ );
610
+ rateLimitError.code = "RATE_LIMITED";
611
+ rateLimitError.isRateLimitError = true;
612
+ rateLimitError.originalError = error;
613
+ rateLimitError.path = path;
614
+
615
+ emitter.emit(events.error.sdk, {
616
+ message: rateLimitError.message,
617
+ code: rateLimitError.code,
618
+ fullError: error,
619
+ });
620
+
621
+ throw rateLimitError;
622
+ }
623
+
624
+ outputError(error);
625
+ throw error; // Re-throw the error so calling code can handle it properly
626
+ }
627
+ };
628
+
629
+ return { req, auth };
630
+ };
631
+
632
+ // Export the factory function and shared utilities
633
+ module.exports = { createSDK, withRetry, getSentryTraceHeaders, sleep };
@@ -0,0 +1,25 @@
1
+ // Factory function to create session instance
2
+ function createSession() {
3
+ let session = null;
4
+
5
+ return {
6
+ get: () => {
7
+ return session;
8
+ },
9
+ set: (s) => {
10
+ if (s && !session) {
11
+ session = s;
12
+ }
13
+ },
14
+ };
15
+ }
16
+
17
+ // Export both factory function and legacy static instance for backward compatibility
18
+ const staticSession = createSession();
19
+
20
+ module.exports = {
21
+ createSession,
22
+ // Legacy static exports for backward compatibility
23
+ get: staticSession.get,
24
+ set: staticSession.set,
25
+ };