testdriverai 7.0.0 → 7.1.1

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 (324) hide show
  1. package/.env.example +2 -0
  2. package/.github/workflows/linux-tests.yml +28 -0
  3. package/README.md +126 -0
  4. package/agent/index.js +7 -9
  5. package/agent/interface.js +13 -2
  6. package/agent/lib/commands.js +795 -136
  7. package/agent/lib/redraw.js +124 -39
  8. package/agent/lib/sandbox.js +40 -3
  9. package/agent/lib/sdk.js +21 -0
  10. package/agent/lib/valid-version.js +2 -2
  11. package/debugger/index.html +1 -1
  12. package/docs/docs.json +86 -71
  13. package/docs/guide/best-practices-polling.mdx +154 -0
  14. package/docs/v6/getting-started/self-hosting.mdx +3 -2
  15. package/docs/v7/_drafts/agents.mdx +852 -0
  16. package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
  17. package/docs/v7/_drafts/best-practices.mdx +486 -0
  18. package/docs/v7/_drafts/caching-ai.mdx +215 -0
  19. package/docs/v7/_drafts/caching-selectors.mdx +400 -0
  20. package/docs/v7/_drafts/caching.mdx +366 -0
  21. package/docs/v7/_drafts/cli-to-sdk-migration.mdx +425 -0
  22. package/docs/v7/_drafts/core.mdx +459 -0
  23. package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
  24. package/docs/v7/_drafts/debugging.mdx +349 -0
  25. package/docs/v7/_drafts/error-handling.mdx +501 -0
  26. package/docs/v7/_drafts/faq.mdx +393 -0
  27. package/docs/v7/_drafts/hooks.mdx +360 -0
  28. package/docs/v7/_drafts/implementation-plan.mdx +994 -0
  29. package/docs/v7/_drafts/init-command.mdx +95 -0
  30. package/docs/v7/_drafts/optimal-sdk-design.mdx +1348 -0
  31. package/docs/v7/_drafts/performance.mdx +517 -0
  32. package/docs/v7/_drafts/presets.mdx +210 -0
  33. package/docs/v7/_drafts/progressive-disclosure.mdx +230 -0
  34. package/docs/v7/_drafts/provision.mdx +266 -0
  35. package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
  36. package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
  37. package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
  38. package/docs/v7/_drafts/troubleshooting.mdx +526 -0
  39. package/docs/v7/_drafts/vitest-plugin.mdx +477 -0
  40. package/docs/v7/_drafts/vitest.mdx +535 -0
  41. package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
  42. package/docs/v7/api/client.mdx +1 -1
  43. package/docs/v7/api/dashcam.mdx +497 -0
  44. package/docs/v7/api/doubleClick.mdx +102 -0
  45. package/docs/v7/api/elements.mdx +143 -41
  46. package/docs/v7/api/find.mdx +258 -0
  47. package/docs/v7/api/mouseDown.mdx +161 -0
  48. package/docs/v7/api/mouseUp.mdx +164 -0
  49. package/docs/v7/api/rightClick.mdx +123 -0
  50. package/docs/v7/api/type.mdx +51 -7
  51. package/docs/v7/features/ai-native.mdx +427 -0
  52. package/docs/v7/features/easy-to-write.mdx +351 -0
  53. package/docs/v7/features/enterprise.mdx +540 -0
  54. package/docs/v7/features/fast.mdx +424 -0
  55. package/docs/v7/features/observable.mdx +623 -0
  56. package/docs/v7/features/powerful.mdx +531 -0
  57. package/docs/v7/features/scalable.mdx +417 -0
  58. package/docs/v7/features/stable.mdx +514 -0
  59. package/docs/v7/getting-started/configuration.mdx +380 -0
  60. package/docs/v7/getting-started/generating-tests.mdx +525 -0
  61. package/docs/v7/getting-started/installation.mdx +486 -0
  62. package/docs/v7/getting-started/quickstart.mdx +320 -141
  63. package/docs/v7/getting-started/running-and-debugging.mdx +511 -0
  64. package/docs/v7/getting-started/setting-up-in-ci.mdx +612 -0
  65. package/docs/v7/getting-started/writing-tests.mdx +535 -0
  66. package/docs/v7/overview/what-is-testdriver.mdx +398 -0
  67. package/docs/v7/platforms/linux.mdx +308 -0
  68. package/docs/v7/platforms/macos.mdx +433 -0
  69. package/docs/v7/platforms/windows.mdx +430 -0
  70. package/docs/v7/playwright.mdx +3 -3
  71. package/docs/v7/presets/chrome-extension.mdx +223 -0
  72. package/docs/v7/presets/chrome.mdx +303 -0
  73. package/docs/v7/presets/electron.mdx +453 -0
  74. package/docs/v7/presets/vscode.mdx +417 -0
  75. package/docs/v7/presets/webapp.mdx +396 -0
  76. package/examples/run-tests-with-recording.sh +2 -2
  77. package/interfaces/cli/commands/init.js +358 -0
  78. package/interfaces/vitest-plugin.mjs +393 -103
  79. package/lib/core/Dashcam.js +506 -0
  80. package/lib/core/index.d.ts +150 -0
  81. package/lib/core/index.js +12 -0
  82. package/lib/presets/index.mjs +331 -0
  83. package/lib/vitest/hooks.d.ts +119 -0
  84. package/lib/vitest/hooks.mjs +316 -0
  85. package/lib/vitest/setup.mjs +44 -0
  86. package/package.json +13 -3
  87. package/sdk.d.ts +350 -44
  88. package/sdk.js +818 -105
  89. package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
  90. package/test/manual/test-console-logs.test.mjs +42 -0
  91. package/test/manual/test-init.sh +54 -0
  92. package/test/manual/test-provision-auth.mjs +22 -0
  93. package/test/testdriver/assert.test.mjs +41 -0
  94. package/test/testdriver/auto-cache-key-demo.test.mjs +56 -0
  95. package/test/testdriver/chrome-extension.test.mjs +89 -0
  96. package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +7 -19
  97. package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +6 -19
  98. package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +6 -18
  99. package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +9 -21
  100. package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +14 -26
  101. package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +8 -20
  102. package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +5 -20
  103. package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +10 -19
  104. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +7 -19
  105. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +5 -19
  106. package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +7 -19
  107. package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +5 -19
  108. package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +7 -19
  109. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +6 -20
  110. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +6 -18
  111. package/test/testdriver/scroll-until-text.test.mjs +28 -0
  112. package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +12 -21
  113. package/test/testdriver/setup/lifecycleHelpers.mjs +262 -0
  114. package/{testdriver/acceptance-sdk → test/testdriver}/setup/testHelpers.mjs +25 -20
  115. package/test/testdriver/type.test.mjs +45 -0
  116. package/vitest.config.mjs +11 -56
  117. package/.github/dependabot.yml +0 -11
  118. package/.github/workflows/acceptance-linux.yml +0 -75
  119. package/.github/workflows/acceptance-sdk-tests.yml +0 -133
  120. package/.github/workflows/acceptance-tests.yml +0 -130
  121. package/.github/workflows/lint.yml +0 -27
  122. package/.github/workflows/publish-canary.yml +0 -40
  123. package/.github/workflows/publish-latest.yml +0 -61
  124. package/.github/workflows/test-install.yml +0 -29
  125. package/.vscode/extensions.json +0 -3
  126. package/.vscode/launch.json +0 -22
  127. package/.vscode/mcp.json +0 -9
  128. package/.vscode/settings.json +0 -14
  129. package/CODEOWNERS +0 -3
  130. package/MIGRATION.md +0 -389
  131. package/SDK_README.md +0 -1122
  132. package/_testdriver/acceptance/assert.yaml +0 -7
  133. package/_testdriver/acceptance/dashcam.yaml +0 -9
  134. package/_testdriver/acceptance/drag-and-drop.yaml +0 -49
  135. package/_testdriver/acceptance/embed.yaml +0 -9
  136. package/_testdriver/acceptance/exec-js.yaml +0 -29
  137. package/_testdriver/acceptance/exec-output.yaml +0 -43
  138. package/_testdriver/acceptance/exec-shell.yaml +0 -40
  139. package/_testdriver/acceptance/focus-window.yaml +0 -16
  140. package/_testdriver/acceptance/hover-image.yaml +0 -18
  141. package/_testdriver/acceptance/hover-text-with-description.yaml +0 -29
  142. package/_testdriver/acceptance/hover-text.yaml +0 -14
  143. package/_testdriver/acceptance/if-else.yaml +0 -31
  144. package/_testdriver/acceptance/match-image.yaml +0 -15
  145. package/_testdriver/acceptance/press-keys.yaml +0 -35
  146. package/_testdriver/acceptance/prompt.yaml +0 -11
  147. package/_testdriver/acceptance/remember.yaml +0 -27
  148. package/_testdriver/acceptance/screenshots/cart.png +0 -0
  149. package/_testdriver/acceptance/scroll-keyboard.yaml +0 -34
  150. package/_testdriver/acceptance/scroll-until-image.yaml +0 -26
  151. package/_testdriver/acceptance/scroll-until-text.yaml +0 -20
  152. package/_testdriver/acceptance/scroll.yaml +0 -33
  153. package/_testdriver/acceptance/snippets/login.yaml +0 -29
  154. package/_testdriver/acceptance/snippets/match-cart.yaml +0 -8
  155. package/_testdriver/acceptance/type.yaml +0 -29
  156. package/_testdriver/behavior/failure.yaml +0 -7
  157. package/_testdriver/behavior/hover-text.yaml +0 -13
  158. package/_testdriver/behavior/lifecycle/postrun.yaml +0 -10
  159. package/_testdriver/behavior/lifecycle/prerun.yaml +0 -8
  160. package/_testdriver/behavior/lifecycle/provision.yaml +0 -8
  161. package/_testdriver/behavior/secrets.yaml +0 -7
  162. package/_testdriver/edge-cases/dashcam-chrome.yaml +0 -8
  163. package/_testdriver/edge-cases/exec-pwsh-multiline.yaml +0 -10
  164. package/_testdriver/edge-cases/js-exception.yaml +0 -8
  165. package/_testdriver/edge-cases/js-promise.yaml +0 -19
  166. package/_testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
  167. package/_testdriver/edge-cases/prompt-in-middle.yaml +0 -23
  168. package/_testdriver/edge-cases/prompt-nested.yaml +0 -7
  169. package/_testdriver/edge-cases/success-test.yaml +0 -9
  170. package/_testdriver/examples/android/example.yaml +0 -12
  171. package/_testdriver/examples/android/lifecycle/postrun.yaml +0 -11
  172. package/_testdriver/examples/android/lifecycle/provision.yaml +0 -47
  173. package/_testdriver/examples/android/readme.md +0 -7
  174. package/_testdriver/examples/chrome-extension/lifecycle/provision.yaml +0 -74
  175. package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
  176. package/_testdriver/examples/desktop/lifecycle/provision.yaml +0 -64
  177. package/_testdriver/examples/vscode-extension/lifecycle/provision.yaml +0 -73
  178. package/_testdriver/examples/web/lifecycle/postrun.yaml +0 -7
  179. package/_testdriver/examples/web/lifecycle/prerun.yaml +0 -22
  180. package/_testdriver/lifecycle/postrun.yaml +0 -8
  181. package/_testdriver/lifecycle/prerun.yaml +0 -15
  182. package/_testdriver/lifecycle/provision.yaml +0 -25
  183. package/debug-screenshot-1763401388589.png +0 -0
  184. package/mcp-server/AI_GUIDELINES.md +0 -57
  185. package/scripts/view-test-results.mjs +0 -96
  186. package/styles/.vale-config/2-MDX.ini +0 -5
  187. package/styles/Microsoft/AMPM.yml +0 -9
  188. package/styles/Microsoft/Accessibility.yml +0 -30
  189. package/styles/Microsoft/Acronyms.yml +0 -64
  190. package/styles/Microsoft/Adverbs.yml +0 -272
  191. package/styles/Microsoft/Auto.yml +0 -11
  192. package/styles/Microsoft/Avoid.yml +0 -14
  193. package/styles/Microsoft/Contractions.yml +0 -50
  194. package/styles/Microsoft/Dashes.yml +0 -13
  195. package/styles/Microsoft/DateFormat.yml +0 -8
  196. package/styles/Microsoft/DateNumbers.yml +0 -40
  197. package/styles/Microsoft/DateOrder.yml +0 -8
  198. package/styles/Microsoft/Ellipses.yml +0 -9
  199. package/styles/Microsoft/FirstPerson.yml +0 -16
  200. package/styles/Microsoft/Foreign.yml +0 -13
  201. package/styles/Microsoft/Gender.yml +0 -8
  202. package/styles/Microsoft/GenderBias.yml +0 -42
  203. package/styles/Microsoft/GeneralURL.yml +0 -11
  204. package/styles/Microsoft/HeadingAcronyms.yml +0 -7
  205. package/styles/Microsoft/HeadingColons.yml +0 -8
  206. package/styles/Microsoft/HeadingPunctuation.yml +0 -13
  207. package/styles/Microsoft/Headings.yml +0 -28
  208. package/styles/Microsoft/Hyphens.yml +0 -14
  209. package/styles/Microsoft/Negative.yml +0 -13
  210. package/styles/Microsoft/Ordinal.yml +0 -13
  211. package/styles/Microsoft/OxfordComma.yml +0 -8
  212. package/styles/Microsoft/Passive.yml +0 -183
  213. package/styles/Microsoft/Percentages.yml +0 -7
  214. package/styles/Microsoft/Plurals.yml +0 -7
  215. package/styles/Microsoft/Quotes.yml +0 -7
  216. package/styles/Microsoft/RangeTime.yml +0 -13
  217. package/styles/Microsoft/Semicolon.yml +0 -8
  218. package/styles/Microsoft/SentenceLength.yml +0 -6
  219. package/styles/Microsoft/Spacing.yml +0 -8
  220. package/styles/Microsoft/Suspended.yml +0 -7
  221. package/styles/Microsoft/Terms.yml +0 -42
  222. package/styles/Microsoft/URLFormat.yml +0 -9
  223. package/styles/Microsoft/Units.yml +0 -16
  224. package/styles/Microsoft/Vocab.yml +0 -25
  225. package/styles/Microsoft/We.yml +0 -11
  226. package/styles/Microsoft/Wordiness.yml +0 -127
  227. package/styles/Microsoft/meta.json +0 -4
  228. package/styles/alex/Ablist.yml +0 -274
  229. package/styles/alex/Condescending.yml +0 -16
  230. package/styles/alex/Gendered.yml +0 -110
  231. package/styles/alex/LGBTQ.yml +0 -55
  232. package/styles/alex/OCD.yml +0 -10
  233. package/styles/alex/Press.yml +0 -12
  234. package/styles/alex/ProfanityLikely.yml +0 -1289
  235. package/styles/alex/ProfanityMaybe.yml +0 -282
  236. package/styles/alex/ProfanityUnlikely.yml +0 -251
  237. package/styles/alex/README.md +0 -27
  238. package/styles/alex/Race.yml +0 -85
  239. package/styles/alex/Suicide.yml +0 -26
  240. package/styles/alex/meta.json +0 -4
  241. package/styles/config/vocabularies/Docs/accept.txt +0 -47
  242. package/styles/config/vocabularies/Docs/reject.txt +0 -4
  243. package/styles/proselint/Airlinese.yml +0 -8
  244. package/styles/proselint/AnimalLabels.yml +0 -48
  245. package/styles/proselint/Annotations.yml +0 -9
  246. package/styles/proselint/Apologizing.yml +0 -8
  247. package/styles/proselint/Archaisms.yml +0 -52
  248. package/styles/proselint/But.yml +0 -8
  249. package/styles/proselint/Cliches.yml +0 -782
  250. package/styles/proselint/CorporateSpeak.yml +0 -30
  251. package/styles/proselint/Currency.yml +0 -5
  252. package/styles/proselint/Cursing.yml +0 -15
  253. package/styles/proselint/DateCase.yml +0 -7
  254. package/styles/proselint/DateMidnight.yml +0 -7
  255. package/styles/proselint/DateRedundancy.yml +0 -10
  256. package/styles/proselint/DateSpacing.yml +0 -7
  257. package/styles/proselint/DenizenLabels.yml +0 -52
  258. package/styles/proselint/Diacritical.yml +0 -95
  259. package/styles/proselint/GenderBias.yml +0 -45
  260. package/styles/proselint/GroupTerms.yml +0 -39
  261. package/styles/proselint/Hedging.yml +0 -8
  262. package/styles/proselint/Hyperbole.yml +0 -6
  263. package/styles/proselint/Jargon.yml +0 -11
  264. package/styles/proselint/LGBTOffensive.yml +0 -13
  265. package/styles/proselint/LGBTTerms.yml +0 -15
  266. package/styles/proselint/Malapropisms.yml +0 -8
  267. package/styles/proselint/Needless.yml +0 -358
  268. package/styles/proselint/Nonwords.yml +0 -38
  269. package/styles/proselint/Oxymorons.yml +0 -22
  270. package/styles/proselint/P-Value.yml +0 -6
  271. package/styles/proselint/RASSyndrome.yml +0 -30
  272. package/styles/proselint/README.md +0 -12
  273. package/styles/proselint/Skunked.yml +0 -13
  274. package/styles/proselint/Spelling.yml +0 -17
  275. package/styles/proselint/Typography.yml +0 -11
  276. package/styles/proselint/Uncomparables.yml +0 -50
  277. package/styles/proselint/Very.yml +0 -6
  278. package/styles/proselint/meta.json +0 -15
  279. package/styles/write-good/Cliches.yml +0 -702
  280. package/styles/write-good/E-Prime.yml +0 -32
  281. package/styles/write-good/Illusions.yml +0 -11
  282. package/styles/write-good/Passive.yml +0 -183
  283. package/styles/write-good/README.md +0 -27
  284. package/styles/write-good/So.yml +0 -5
  285. package/styles/write-good/ThereIs.yml +0 -6
  286. package/styles/write-good/TooWordy.yml +0 -221
  287. package/styles/write-good/Weasel.yml +0 -29
  288. package/styles/write-good/meta.json +0 -4
  289. package/test/mcp-example-test.yaml +0 -27
  290. package/test/test_parser.js +0 -47
  291. package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +0 -61
  292. package/testdriver/acceptance-sdk/README.md +0 -128
  293. package/testdriver/acceptance-sdk/TEST_REPORTING.md +0 -245
  294. package/testdriver/acceptance-sdk/assert.test.mjs +0 -44
  295. package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +0 -42
  296. package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
  297. package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
  298. package/testdriver/acceptance-sdk/type.test.mjs +0 -84
  299. package/vale.ini +0 -18
  300. package/vitest.config.example.js +0 -19
  301. package/vitest.config.mjs.bak +0 -44
  302. /package/docs/{ARCHITECTURE.md → v7/_drafts/architecture.mdx} +0 -0
  303. /package/docs/{AWESOME_LOGS_QUICK_REF.md → v7/_drafts/awesome-logs-quick-ref.mdx} +0 -0
  304. /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
  305. /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
  306. /package/{PLUGIN_MIGRATION.md → docs/v7/_drafts/plugin-migration.mdx} +0 -0
  307. /package/{PROMPT_CACHE.md → docs/v7/_drafts/prompt-cache.mdx} +0 -0
  308. /package/docs/{SDK_AWESOME_LOGS.md → v7/_drafts/sdk-awesome-logs.mdx} +0 -0
  309. /package/docs/{sdk-browser-rendering.md → v7/_drafts/sdk-browser-rendering.mdx} +0 -0
  310. /package/{SDK_LOGGING.md → docs/v7/_drafts/sdk-logging.mdx} +0 -0
  311. /package/{SDK_MIGRATION.md → docs/v7/_drafts/sdk-migration.mdx} +0 -0
  312. /package/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
  313. /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
  314. /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +0 -0
  315. /package/{test-find-api.js → test/manual/test-find-api.js} +0 -0
  316. /package/{test-prompt-cache.js → test/manual/test-prompt-cache.js} +0 -0
  317. /package/{test-sandbox-render.js → test/manual/test-sandbox-render.js} +0 -0
  318. /package/{test-sdk-methods.js → test/manual/test-sdk-methods.js} +0 -0
  319. /package/{test-sdk-refactor.js → test/manual/test-sdk-refactor.js} +0 -0
  320. /package/{test-stack-trace.mjs → test/manual/test-stack-trace.mjs} +0 -0
  321. /package/{verify-element-api.js → test/manual/verify-element-api.js} +0 -0
  322. /package/{verify-types.js → test/manual/verify-types.js} +0 -0
  323. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
  324. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Vitest Hooks for TestDriver
3
+ *
4
+ * Provides lifecycle management for TestDriver in Vitest tests.
5
+ *
6
+ * @example
7
+ * import { TestDriver } from 'testdriverai/vitest/hooks';
8
+ *
9
+ * test('my test', async (context) => {
10
+ * const testdriver = TestDriver(context, { headless: true });
11
+ *
12
+ * await testdriver.ready();
13
+ * await testdriver.provision.chrome({ url: 'https://example.com' });
14
+ * await testdriver.find('button').click();
15
+ * });
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import os from 'os';
20
+ import path from 'path';
21
+ import { vi } from 'vitest';
22
+ import TestDriverSDK from '../../sdk.js';
23
+
24
+ /**
25
+ * Set up console spies using Vitest's vi.spyOn to intercept console logs
26
+ * and forward them to the sandbox for Dashcam visibility.
27
+ * This is test-isolated and doesn't cause conflicts with concurrent tests.
28
+ * @param {TestDriver} client - TestDriver client instance
29
+ * @param {string} taskId - Unique task identifier for this test
30
+ */
31
+ function setupConsoleSpy(client, taskId) {
32
+
33
+ // Debug logging for console spy setup
34
+ const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === 'true';
35
+ if (debugConsoleSpy) {
36
+ process.stdout.write(`[DEBUG setupConsoleSpy] taskId: ${taskId}\n`);
37
+ process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox exists: ${!!client.sandbox}\n`);
38
+ process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox?.instanceSocketConnected: ${client.sandbox?.instanceSocketConnected}\n`);
39
+ process.stdout.write(`[DEBUG setupConsoleSpy] client.sandbox?.send: ${typeof client.sandbox?.send}\n`);
40
+ }
41
+
42
+ // Track forwarding stats
43
+ let forwardedCount = 0;
44
+ let skippedCount = 0;
45
+
46
+ // Helper to forward logs to sandbox
47
+ const forwardToSandbox = (args) => {
48
+ const message = args
49
+ .map((arg) =>
50
+ typeof arg === "object"
51
+ ? JSON.stringify(arg, null, 2)
52
+ : String(arg),
53
+ )
54
+ .join(" ");
55
+
56
+ // Send to sandbox for immediate visibility in dashcam
57
+ if (client.sandbox && client.sandbox.instanceSocketConnected) {
58
+ try {
59
+ client.sandbox.send({
60
+ type: "output",
61
+ output: Buffer.from(message, "utf8").toString("base64"),
62
+ });
63
+ forwardedCount++;
64
+ if (debugConsoleSpy && forwardedCount <= 3) {
65
+ process.stdout.write(`[DEBUG forwardToSandbox] Forwarded message #${forwardedCount}: "${message.substring(0, 50)}..."\n`);
66
+ }
67
+ } catch (err) {
68
+ if (debugConsoleSpy) {
69
+ process.stdout.write(`[DEBUG forwardToSandbox] Error sending: ${err.message}\n`);
70
+ }
71
+ }
72
+ } else {
73
+ skippedCount++;
74
+ if (debugConsoleSpy && skippedCount <= 3) {
75
+ process.stdout.write(`[DEBUG forwardToSandbox] SKIPPED (sandbox not connected): "${message.substring(0, 50)}..."\n`);
76
+ }
77
+ }
78
+ };
79
+
80
+ // Create spies for each console method
81
+ const logSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
82
+ // Call through to original
83
+ logSpy.mock.calls; // Track calls
84
+ process.stdout.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
85
+ forwardToSandbox(args);
86
+ });
87
+
88
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
89
+ process.stderr.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
90
+ forwardToSandbox(args);
91
+ });
92
+
93
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation((...args) => {
94
+ process.stderr.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
95
+ forwardToSandbox(args);
96
+ });
97
+
98
+ const infoSpy = vi.spyOn(console, 'info').mockImplementation((...args) => {
99
+ process.stdout.write(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ') + '\n');
100
+ forwardToSandbox(args);
101
+ });
102
+
103
+ // Store spies on client for cleanup
104
+ client._consoleSpies = { logSpy, errorSpy, warnSpy, infoSpy };
105
+
106
+ console.log(`[testdriver] Console spy set up for task: ${taskId}`);
107
+ }
108
+
109
+ /**
110
+ * Clean up console spies and restore original console methods
111
+ * @param {TestDriver} client - TestDriver client instance
112
+ */
113
+ function cleanupConsoleSpy(client) {
114
+ if (client._consoleSpies) {
115
+ const { logSpy, errorSpy, warnSpy, infoSpy } = client._consoleSpies;
116
+ logSpy.mockRestore();
117
+ errorSpy.mockRestore();
118
+ warnSpy.mockRestore();
119
+ infoSpy.mockRestore();
120
+ delete client._consoleSpies;
121
+ }
122
+ }
123
+
124
+ // Weak maps to store instances per test context
125
+ const testDriverInstances = new WeakMap();
126
+ const lifecycleHandlers = new WeakMap();
127
+
128
+ /**
129
+ * Create a TestDriver client in a Vitest test with automatic lifecycle management
130
+ *
131
+ * @param {object} context - Vitest test context (from async (context) => {})
132
+ * @param {object} options - TestDriver options (passed directly to TestDriver constructor)
133
+ * @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
134
+ * @param {boolean} [options.headless] - Run sandbox in headless mode
135
+ * @param {boolean} [options.newSandbox] - Create new sandbox
136
+ * @param {boolean} [options.autoConnect=true] - Automatically connect to sandbox
137
+ * @returns {TestDriver} TestDriver client instance
138
+ *
139
+ * @example
140
+ * test('my test', async (context) => {
141
+ * const testdriver = TestDriver(context, { headless: true });
142
+ *
143
+ * // provision.chrome() automatically calls ready() and starts dashcam
144
+ * await testdriver.provision.chrome({ url: 'https://example.com' });
145
+ *
146
+ * await testdriver.find('Login button').click();
147
+ * });
148
+ */
149
+ export function TestDriver(context, options = {}) {
150
+ if (!context || !context.task) {
151
+ throw new Error('TestDriver() requires Vitest context. Pass the context parameter from your test function: test("name", async (context) => { ... })');
152
+ }
153
+
154
+ // Return existing instance if already created for this test
155
+ if (testDriverInstances.has(context.task)) {
156
+ return testDriverInstances.get(context.task);
157
+ }
158
+
159
+ // Get global plugin options if available
160
+ const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
161
+
162
+ // Merge options: plugin global options < test-specific options
163
+ const mergedOptions = { ...pluginOptions, ...options };
164
+
165
+ // Extract TestDriver-specific options
166
+ const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
167
+
168
+ // Build config for TestDriverSDK constructor
169
+ const config = { ...mergedOptions };
170
+ delete config.apiKey;
171
+
172
+ // Use TD_API_ROOT from environment if not provided in config
173
+ if (!config.apiRoot && process.env.TD_API_ROOT) {
174
+ config.apiRoot = process.env.TD_API_ROOT;
175
+ }
176
+
177
+ const testdriver = new TestDriverSDK(apiKey, config);
178
+ testdriver.__vitestContext = context.task;
179
+ testDriverInstances.set(context.task, testdriver);
180
+
181
+ // Auto-connect if enabled (default: true)
182
+ const autoConnect = config.autoConnect !== undefined ? config.autoConnect : true;
183
+ const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === 'true';
184
+
185
+ if (autoConnect) {
186
+ testdriver.__connectionPromise = (async () => {
187
+ console.log('[testdriver] Connecting to sandbox...');
188
+ if (debugConsoleSpy) {
189
+ console.log('[DEBUG] Before auth - sandbox.instanceSocketConnected:', testdriver.sandbox?.instanceSocketConnected);
190
+ }
191
+
192
+ await testdriver.auth();
193
+ await testdriver.connect();
194
+
195
+ console.log('[testdriver] ✅ Connected to sandbox');
196
+
197
+ if (debugConsoleSpy) {
198
+ console.log('[DEBUG] After connect - sandbox.instanceSocketConnected:', testdriver.sandbox?.instanceSocketConnected);
199
+ console.log('[DEBUG] After connect - sandbox.send:', typeof testdriver.sandbox?.send);
200
+ }
201
+
202
+ // Set up console spy using vi.spyOn (test-isolated)
203
+ setupConsoleSpy(testdriver, context.task.id);
204
+
205
+ // Create the log file on the remote machine
206
+ const shell = testdriver.os === "windows" ? "pwsh" : "sh";
207
+ const logPath = testdriver.os === "windows"
208
+ ? "C:\\Users\\testdriver\\Documents\\testdriver.log"
209
+ : "/tmp/testdriver.log";
210
+
211
+ const createLogCmd = testdriver.os === "windows"
212
+ ? `New-Item -ItemType File -Path "${logPath}" -Force | Out-Null`
213
+ : `touch ${logPath}`;
214
+
215
+ await testdriver.exec(shell, createLogCmd, 10000, true);
216
+ console.log('[testdriver] ✅ Created log file:', logPath);
217
+
218
+ // Add automatic log tracking when dashcam starts
219
+ // Store original start method
220
+
221
+ await testdriver.dashcam.addFileLog(logPath, "TestDriver Log");
222
+
223
+ })();
224
+ }
225
+
226
+ // Register cleanup handler with dashcam.stop()
227
+ if (!lifecycleHandlers.has(context.task)) {
228
+ const cleanup = async () => {
229
+ console.log('[testdriver] Cleaning up TestDriver client...');
230
+ try {
231
+ // Stop dashcam if it was started - with timeout to prevent hanging
232
+ if (testdriver._dashcam && testdriver._dashcam.recording) {
233
+ try {
234
+ // Add a timeout wrapper to prevent dashcam.stop from hanging indefinitely
235
+ const stopWithTimeout = Promise.race([
236
+ testdriver.dashcam.stop(),
237
+ new Promise((_, reject) =>
238
+ setTimeout(() => reject(new Error('Dashcam stop timed out after 30s')), 30000)
239
+ )
240
+ ]);
241
+
242
+ const dashcamUrl = await stopWithTimeout;
243
+ console.log('🎥 Dashcam URL:', dashcamUrl);
244
+
245
+ // Write dashcam URL to file for the reporter (cross-process communication)
246
+ if (dashcamUrl) {
247
+ const testId = context.task.id;
248
+ const platform = testdriver.os || 'linux';
249
+ const testFile = context.task.file?.filepath || context.task.file?.name || 'unknown';
250
+
251
+ // Create results directory if it doesn't exist
252
+ const resultsDir = path.join(os.tmpdir(), 'testdriver-results');
253
+ if (!fs.existsSync(resultsDir)) {
254
+ fs.mkdirSync(resultsDir, { recursive: true });
255
+ }
256
+
257
+ // Write test result file
258
+ const testResultFile = path.join(resultsDir, `${testId}.json`);
259
+ const testResult = {
260
+ dashcamUrl,
261
+ platform,
262
+ testFile,
263
+ testOrder: 0,
264
+ sessionId: testdriver.getSessionId(),
265
+ };
266
+
267
+ fs.writeFileSync(testResultFile, JSON.stringify(testResult, null, 2));
268
+ console.log(`[testdriver] ✅ Wrote dashcam URL to ${testResultFile}`);
269
+
270
+ // Also register in memory if plugin is available
271
+ if (globalThis.__testdriverPlugin?.registerDashcamUrl) {
272
+ globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
273
+ console.log(`[testdriver] ✅ Registered dashcam URL in memory for test ${testId}`);
274
+ }
275
+ }
276
+ } catch (error) {
277
+ // Log more detailed error information for debugging
278
+ console.error('❌ Failed to stop dashcam:', error.name || error.constructor?.name || 'Error');
279
+ if (error.message) console.error(' Message:', error.message);
280
+ // NotFoundError during cleanup is expected if sandbox already terminated
281
+ if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
282
+ console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
283
+ }
284
+ // Mark as not recording to prevent retries
285
+ if (testdriver._dashcam) {
286
+ testdriver._dashcam.recording = false;
287
+ }
288
+ }
289
+ }
290
+
291
+ // Clean up console spies
292
+ cleanupConsoleSpy(testdriver);
293
+
294
+ // Wait for connection to finish if it was initiated
295
+ if (testdriver.__connectionPromise) {
296
+ await testdriver.__connectionPromise.catch(() => {}); // Ignore connection errors during cleanup
297
+ }
298
+
299
+ // Disconnect with timeout
300
+ await Promise.race([
301
+ testdriver.disconnect(),
302
+ new Promise((resolve) => setTimeout(resolve, 5000)) // 5s timeout for disconnect
303
+ ]);
304
+ console.log('✅ Client disconnected');
305
+ } catch (error) {
306
+ console.error('Error disconnecting client:', error);
307
+ }
308
+ };
309
+ lifecycleHandlers.set(context.task, cleanup);
310
+
311
+ // Vitest will call this automatically after the test
312
+ context.onTestFinished?.(cleanup);
313
+ }
314
+
315
+ return testdriver;
316
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Vitest Setup File for TestDriver
3
+ *
4
+ * This file is run by Vitest before each test file to set up
5
+ * the TestDriver plugin state and global helpers.
6
+ *
7
+ * Usage in vitest.config.mjs:
8
+ * ```js
9
+ * export default defineConfig({
10
+ * test: {
11
+ * setupFiles: ['testdriverai/vitest/setup'],
12
+ * },
13
+ * });
14
+ * ```
15
+ */
16
+
17
+ import {
18
+ clearDashcamUrls,
19
+ clearSuiteTestRun,
20
+ getDashcamUrl,
21
+ getPluginState,
22
+ getSuiteTestRun,
23
+ pluginState,
24
+ registerDashcamUrl,
25
+ setSuiteTestRun,
26
+ } from '../../interfaces/vitest-plugin.mjs';
27
+
28
+ // Set up global TestDriver plugin interface
29
+ // This allows tests and the SDK to communicate with the reporter
30
+ globalThis.__testdriverPlugin = {
31
+ state: pluginState,
32
+ registerDashcamUrl,
33
+ getDashcamUrl,
34
+ clearDashcamUrls,
35
+ getPluginState,
36
+ getSuiteTestRun,
37
+ setSuiteTestRun,
38
+ clearSuiteTestRun,
39
+ };
40
+
41
+ // Log that setup is complete (only in debug mode)
42
+ if (process.env.TD_LOG_LEVEL?.toLowerCase() === 'debug') {
43
+ console.log('[TestDriver] Setup file initialized, global plugin interface available');
44
+ }
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.0.0",
3
+ "version": "7.1.1",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
+ "exports": {
7
+ ".": "./sdk.js",
8
+ "./core": "./lib/core/index.js",
9
+ "./vitest": "./interfaces/vitest-plugin.mjs",
10
+ "./vitest/plugin": "./interfaces/vitest-plugin.mjs",
11
+ "./vitest/setup": "./lib/vitest/setup.mjs",
12
+ "./vitest/hooks": "./lib/vitest/hooks.mjs",
13
+ "./presets": "./lib/presets/index.mjs"
14
+ },
6
15
  "bin": {
7
16
  "testdriverai": "bin/testdriverai.js"
8
17
  },
@@ -50,7 +59,7 @@
50
59
  "chalk": "^4.1.2",
51
60
  "cli-progress": "^3.12.0",
52
61
  "diff": "^8.0.2",
53
- "dotenv": "^16.4.5",
62
+ "dotenv": "^16.6.1",
54
63
  "eventemitter2": "^6.4.9",
55
64
  "jimp": "^0.22.12",
56
65
  "js-yaml": "^4.1.0",
@@ -88,7 +97,8 @@
88
97
  "mocha": "^10.8.2",
89
98
  "node-addon-api": "^8.0.0",
90
99
  "prettier": "3.3.3",
91
- "vitest": "^4.0.8"
100
+ "testdriverai": "^6.1.11",
101
+ "vitest": "^4.0.15"
92
102
  },
93
103
  "optionalDependencies": {
94
104
  "@esbuild/linux-x64": "^0.21.5"