testdriverai 7.1.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 (325) hide show
  1. package/.env.example +2 -0
  2. package/.github/workflows/linux-tests.yml +28 -0
  3. package/agent/index.js +18 -45
  4. package/agent/interface.js +13 -2
  5. package/agent/lib/commands.js +1 -1
  6. package/agent/lib/redraw.js +1 -1
  7. package/agent/lib/sandbox.js +30 -2
  8. package/agent/lib/valid-version.js +2 -2
  9. package/debugger/index.html +1 -1
  10. package/docs/docs.json +86 -125
  11. package/docs/v6/getting-started/self-hosting.mdx +3 -2
  12. package/docs/v7/_drafts/agents.mdx +852 -0
  13. package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
  14. package/docs/v7/{guides → _drafts}/caching-selectors.mdx +125 -17
  15. package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
  16. package/docs/v7/_drafts/error-handling.mdx +501 -0
  17. package/docs/v7/_drafts/implementation-plan.mdx +994 -0
  18. package/docs/v7/_drafts/init-command.mdx +95 -0
  19. package/docs/v7/_drafts/optimal-sdk-design.mdx +1348 -0
  20. package/docs/v7/_drafts/plugin-migration.mdx +222 -0
  21. package/docs/v7/_drafts/prompt-cache.mdx +200 -0
  22. package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
  23. package/docs/v7/_drafts/sdk-logging.mdx +222 -0
  24. package/docs/v7/_drafts/sdk-migration.mdx +474 -0
  25. package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
  26. package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
  27. package/docs/v7/{guides → _drafts}/troubleshooting.mdx +2 -2
  28. package/docs/v7/{guides → _drafts}/vitest-plugin.mdx +4 -4
  29. package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
  30. package/docs/v7/api/client.mdx +1 -1
  31. package/docs/v7/api/dashcam.mdx +2 -2
  32. package/docs/v7/api/elements.mdx +143 -41
  33. package/docs/v7/api/find.mdx +258 -0
  34. package/docs/v7/api/type.mdx +51 -7
  35. package/docs/v7/features/ai-native.mdx +427 -0
  36. package/docs/v7/features/easy-to-write.mdx +351 -0
  37. package/docs/v7/features/enterprise.mdx +540 -0
  38. package/docs/v7/features/fast.mdx +424 -0
  39. package/docs/v7/features/observable.mdx +623 -0
  40. package/docs/v7/features/powerful.mdx +531 -0
  41. package/docs/v7/features/scalable.mdx +417 -0
  42. package/docs/v7/features/stable.mdx +514 -0
  43. package/docs/v7/getting-started/configuration.mdx +1 -1
  44. package/docs/v7/getting-started/generating-tests.mdx +525 -0
  45. package/docs/v7/getting-started/installation.mdx +486 -0
  46. package/docs/v7/getting-started/quickstart.mdx +51 -5
  47. package/docs/v7/getting-started/running-and-debugging.mdx +511 -0
  48. package/docs/v7/getting-started/setting-up-in-ci.mdx +612 -0
  49. package/docs/v7/getting-started/writing-tests.mdx +535 -0
  50. package/docs/v7/overview/what-is-testdriver.mdx +398 -0
  51. package/docs/v7/playwright.mdx +3 -3
  52. package/docs/v7/presets/chrome.mdx +16 -0
  53. package/docs/v7/presets/electron.mdx +18 -0
  54. package/docs/v7/presets/vscode.mdx +19 -0
  55. package/examples/run-tests-with-recording.sh +70 -0
  56. package/examples/screenshot-example.js +63 -0
  57. package/examples/sdk-awesome-logs-demo.js +177 -0
  58. package/examples/sdk-cache-thresholds.js +96 -0
  59. package/examples/sdk-element-properties.js +155 -0
  60. package/examples/sdk-simple-example.js +65 -0
  61. package/examples/test-recording-example.test.js +166 -0
  62. package/interfaces/cli/commands/init.js +358 -0
  63. package/interfaces/vitest-plugin.mjs +214 -10
  64. package/{src → lib}/core/Dashcam.js +41 -4
  65. package/{src → lib}/vitest/hooks.mjs +118 -100
  66. package/lib/vitest/setup.mjs +44 -0
  67. package/package.json +9 -10
  68. package/sdk.d.ts +15 -2
  69. package/sdk.js +70 -18
  70. package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
  71. package/{testdriver/acceptance-sdk → test/manual}/test-console-logs.test.mjs +1 -1
  72. package/test/manual/test-find-api.js +73 -0
  73. package/test/manual/test-init.sh +54 -0
  74. package/test/manual/test-prompt-cache.js +96 -0
  75. package/test/manual/test-provision-auth.mjs +22 -0
  76. package/test/manual/test-sandbox-render.js +28 -0
  77. package/test/manual/test-sdk-methods.js +15 -0
  78. package/test/manual/test-sdk-refactor.js +53 -0
  79. package/test/manual/test-stack-trace.mjs +57 -0
  80. package/test/testdriver/assert.test.mjs +41 -0
  81. package/{testdriver/acceptance-sdk → test/testdriver}/auto-cache-key-demo.test.mjs +1 -1
  82. package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +1 -1
  83. package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +1 -1
  84. package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +1 -1
  85. package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +3 -3
  86. package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +3 -3
  87. package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +1 -1
  88. package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +1 -1
  89. package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +1 -1
  90. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +1 -1
  91. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +1 -1
  92. package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +1 -1
  93. package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +1 -1
  94. package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +2 -2
  95. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +1 -1
  96. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +1 -1
  97. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-text.test.mjs +1 -1
  98. package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +1 -1
  99. package/{src/vitest/lifecycle.mjs → test/testdriver/setup/lifecycleHelpers.mjs} +84 -99
  100. package/test/testdriver/setup/testHelpers.mjs +653 -0
  101. package/{testdriver/acceptance-sdk → test/testdriver}/type.test.mjs +1 -1
  102. package/vitest.config.mjs +11 -57
  103. package/.github/dependabot.yml +0 -11
  104. package/.github/workflows/acceptance-linux.yml +0 -75
  105. package/.github/workflows/acceptance-sdk-tests.yml +0 -133
  106. package/.github/workflows/acceptance-tests.yml +0 -130
  107. package/.github/workflows/lint.yml +0 -27
  108. package/.github/workflows/publish-canary.yml +0 -40
  109. package/.github/workflows/publish-latest.yml +0 -61
  110. package/.github/workflows/test-install.yml +0 -29
  111. package/.vscode/extensions.json +0 -3
  112. package/.vscode/launch.json +0 -22
  113. package/.vscode/settings.json +0 -14
  114. package/AGENTS.md +0 -550
  115. package/CODEOWNERS +0 -2
  116. package/_testdriver/acceptance/assert.yaml +0 -7
  117. package/_testdriver/acceptance/dashcam.yaml +0 -9
  118. package/_testdriver/acceptance/drag-and-drop.yaml +0 -49
  119. package/_testdriver/acceptance/embed.yaml +0 -9
  120. package/_testdriver/acceptance/exec-js.yaml +0 -29
  121. package/_testdriver/acceptance/exec-output.yaml +0 -43
  122. package/_testdriver/acceptance/exec-shell.yaml +0 -40
  123. package/_testdriver/acceptance/focus-window.yaml +0 -16
  124. package/_testdriver/acceptance/hover-image.yaml +0 -18
  125. package/_testdriver/acceptance/hover-text-with-description.yaml +0 -29
  126. package/_testdriver/acceptance/hover-text.yaml +0 -14
  127. package/_testdriver/acceptance/if-else.yaml +0 -31
  128. package/_testdriver/acceptance/match-image.yaml +0 -15
  129. package/_testdriver/acceptance/press-keys.yaml +0 -35
  130. package/_testdriver/acceptance/prompt.yaml +0 -11
  131. package/_testdriver/acceptance/remember.yaml +0 -27
  132. package/_testdriver/acceptance/screenshots/cart.png +0 -0
  133. package/_testdriver/acceptance/scroll-keyboard.yaml +0 -34
  134. package/_testdriver/acceptance/scroll-until-image.yaml +0 -26
  135. package/_testdriver/acceptance/scroll-until-text.yaml +0 -20
  136. package/_testdriver/acceptance/scroll.yaml +0 -33
  137. package/_testdriver/acceptance/snippets/login.yaml +0 -29
  138. package/_testdriver/acceptance/snippets/match-cart.yaml +0 -8
  139. package/_testdriver/acceptance/type.yaml +0 -29
  140. package/_testdriver/behavior/failure.yaml +0 -7
  141. package/_testdriver/behavior/hover-text.yaml +0 -13
  142. package/_testdriver/behavior/lifecycle/postrun.yaml +0 -10
  143. package/_testdriver/behavior/lifecycle/prerun.yaml +0 -8
  144. package/_testdriver/behavior/lifecycle/provision.yaml +0 -8
  145. package/_testdriver/behavior/secrets.yaml +0 -7
  146. package/_testdriver/edge-cases/dashcam-chrome.yaml +0 -8
  147. package/_testdriver/edge-cases/exec-pwsh-multiline.yaml +0 -10
  148. package/_testdriver/edge-cases/js-exception.yaml +0 -8
  149. package/_testdriver/edge-cases/js-promise.yaml +0 -19
  150. package/_testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
  151. package/_testdriver/edge-cases/prompt-in-middle.yaml +0 -23
  152. package/_testdriver/edge-cases/prompt-nested.yaml +0 -7
  153. package/_testdriver/edge-cases/success-test.yaml +0 -9
  154. package/_testdriver/examples/android/example.yaml +0 -12
  155. package/_testdriver/examples/android/lifecycle/postrun.yaml +0 -11
  156. package/_testdriver/examples/android/lifecycle/provision.yaml +0 -47
  157. package/_testdriver/examples/android/readme.md +0 -7
  158. package/_testdriver/examples/chrome-extension/lifecycle/provision.yaml +0 -74
  159. package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
  160. package/_testdriver/examples/desktop/lifecycle/provision.yaml +0 -64
  161. package/_testdriver/examples/vscode-extension/lifecycle/provision.yaml +0 -73
  162. package/_testdriver/examples/web/lifecycle/postrun.yaml +0 -7
  163. package/_testdriver/examples/web/lifecycle/prerun.yaml +0 -22
  164. package/_testdriver/lifecycle/postrun.yaml +0 -8
  165. package/_testdriver/lifecycle/prerun.yaml +0 -15
  166. package/_testdriver/lifecycle/provision.yaml +0 -25
  167. package/docs/v7/guides/ci-cd/azure.mdx +0 -587
  168. package/docs/v7/guides/ci-cd/circleci.mdx +0 -523
  169. package/docs/v7/guides/ci-cd/github-actions.mdx +0 -457
  170. package/docs/v7/guides/ci-cd/gitlab.mdx +0 -498
  171. package/docs/v7/guides/ci-cd/jenkins.mdx +0 -664
  172. package/docs/v7/guides/ci-cd/travis.mdx +0 -438
  173. package/scripts/view-test-results.mjs +0 -96
  174. package/src/vitest/extended.mjs +0 -108
  175. package/src/vitest/index.mjs +0 -64
  176. package/src/vitest/utils.mjs +0 -150
  177. package/styles/.vale-config/2-MDX.ini +0 -5
  178. package/styles/Microsoft/AMPM.yml +0 -9
  179. package/styles/Microsoft/Accessibility.yml +0 -30
  180. package/styles/Microsoft/Acronyms.yml +0 -64
  181. package/styles/Microsoft/Adverbs.yml +0 -272
  182. package/styles/Microsoft/Auto.yml +0 -11
  183. package/styles/Microsoft/Avoid.yml +0 -14
  184. package/styles/Microsoft/Contractions.yml +0 -50
  185. package/styles/Microsoft/Dashes.yml +0 -13
  186. package/styles/Microsoft/DateFormat.yml +0 -8
  187. package/styles/Microsoft/DateNumbers.yml +0 -40
  188. package/styles/Microsoft/DateOrder.yml +0 -8
  189. package/styles/Microsoft/Ellipses.yml +0 -9
  190. package/styles/Microsoft/FirstPerson.yml +0 -16
  191. package/styles/Microsoft/Foreign.yml +0 -13
  192. package/styles/Microsoft/Gender.yml +0 -8
  193. package/styles/Microsoft/GenderBias.yml +0 -42
  194. package/styles/Microsoft/GeneralURL.yml +0 -11
  195. package/styles/Microsoft/HeadingAcronyms.yml +0 -7
  196. package/styles/Microsoft/HeadingColons.yml +0 -8
  197. package/styles/Microsoft/HeadingPunctuation.yml +0 -13
  198. package/styles/Microsoft/Headings.yml +0 -28
  199. package/styles/Microsoft/Hyphens.yml +0 -14
  200. package/styles/Microsoft/Negative.yml +0 -13
  201. package/styles/Microsoft/Ordinal.yml +0 -13
  202. package/styles/Microsoft/OxfordComma.yml +0 -8
  203. package/styles/Microsoft/Passive.yml +0 -183
  204. package/styles/Microsoft/Percentages.yml +0 -7
  205. package/styles/Microsoft/Plurals.yml +0 -7
  206. package/styles/Microsoft/Quotes.yml +0 -7
  207. package/styles/Microsoft/RangeTime.yml +0 -13
  208. package/styles/Microsoft/Semicolon.yml +0 -8
  209. package/styles/Microsoft/SentenceLength.yml +0 -6
  210. package/styles/Microsoft/Spacing.yml +0 -8
  211. package/styles/Microsoft/Suspended.yml +0 -7
  212. package/styles/Microsoft/Terms.yml +0 -42
  213. package/styles/Microsoft/URLFormat.yml +0 -9
  214. package/styles/Microsoft/Units.yml +0 -16
  215. package/styles/Microsoft/Vocab.yml +0 -25
  216. package/styles/Microsoft/We.yml +0 -11
  217. package/styles/Microsoft/Wordiness.yml +0 -127
  218. package/styles/Microsoft/meta.json +0 -4
  219. package/styles/alex/Ablist.yml +0 -274
  220. package/styles/alex/Condescending.yml +0 -16
  221. package/styles/alex/Gendered.yml +0 -110
  222. package/styles/alex/LGBTQ.yml +0 -55
  223. package/styles/alex/OCD.yml +0 -10
  224. package/styles/alex/Press.yml +0 -12
  225. package/styles/alex/ProfanityLikely.yml +0 -1289
  226. package/styles/alex/ProfanityMaybe.yml +0 -282
  227. package/styles/alex/ProfanityUnlikely.yml +0 -251
  228. package/styles/alex/README.md +0 -27
  229. package/styles/alex/Race.yml +0 -85
  230. package/styles/alex/Suicide.yml +0 -26
  231. package/styles/alex/meta.json +0 -4
  232. package/styles/config/vocabularies/Docs/accept.txt +0 -47
  233. package/styles/config/vocabularies/Docs/reject.txt +0 -4
  234. package/styles/proselint/Airlinese.yml +0 -8
  235. package/styles/proselint/AnimalLabels.yml +0 -48
  236. package/styles/proselint/Annotations.yml +0 -9
  237. package/styles/proselint/Apologizing.yml +0 -8
  238. package/styles/proselint/Archaisms.yml +0 -52
  239. package/styles/proselint/But.yml +0 -8
  240. package/styles/proselint/Cliches.yml +0 -782
  241. package/styles/proselint/CorporateSpeak.yml +0 -30
  242. package/styles/proselint/Currency.yml +0 -5
  243. package/styles/proselint/Cursing.yml +0 -15
  244. package/styles/proselint/DateCase.yml +0 -7
  245. package/styles/proselint/DateMidnight.yml +0 -7
  246. package/styles/proselint/DateRedundancy.yml +0 -10
  247. package/styles/proselint/DateSpacing.yml +0 -7
  248. package/styles/proselint/DenizenLabels.yml +0 -52
  249. package/styles/proselint/Diacritical.yml +0 -95
  250. package/styles/proselint/GenderBias.yml +0 -45
  251. package/styles/proselint/GroupTerms.yml +0 -39
  252. package/styles/proselint/Hedging.yml +0 -8
  253. package/styles/proselint/Hyperbole.yml +0 -6
  254. package/styles/proselint/Jargon.yml +0 -11
  255. package/styles/proselint/LGBTOffensive.yml +0 -13
  256. package/styles/proselint/LGBTTerms.yml +0 -15
  257. package/styles/proselint/Malapropisms.yml +0 -8
  258. package/styles/proselint/Needless.yml +0 -358
  259. package/styles/proselint/Nonwords.yml +0 -38
  260. package/styles/proselint/Oxymorons.yml +0 -22
  261. package/styles/proselint/P-Value.yml +0 -6
  262. package/styles/proselint/RASSyndrome.yml +0 -30
  263. package/styles/proselint/README.md +0 -12
  264. package/styles/proselint/Skunked.yml +0 -13
  265. package/styles/proselint/Spelling.yml +0 -17
  266. package/styles/proselint/Typography.yml +0 -11
  267. package/styles/proselint/Uncomparables.yml +0 -50
  268. package/styles/proselint/Very.yml +0 -6
  269. package/styles/proselint/meta.json +0 -15
  270. package/styles/write-good/Cliches.yml +0 -702
  271. package/styles/write-good/E-Prime.yml +0 -32
  272. package/styles/write-good/Illusions.yml +0 -11
  273. package/styles/write-good/Passive.yml +0 -183
  274. package/styles/write-good/README.md +0 -27
  275. package/styles/write-good/So.yml +0 -5
  276. package/styles/write-good/ThereIs.yml +0 -6
  277. package/styles/write-good/TooWordy.yml +0 -221
  278. package/styles/write-good/Weasel.yml +0 -29
  279. package/styles/write-good/meta.json +0 -4
  280. package/test/dashcam.test.js +0 -137
  281. package/test/mcp-example-test.yaml +0 -27
  282. package/test/test_parser.js +0 -47
  283. package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +0 -61
  284. package/testdriver/acceptance-sdk/README.md +0 -128
  285. package/testdriver/acceptance-sdk/TEST_REPORTING.md +0 -245
  286. package/testdriver/acceptance-sdk/assert.test.mjs +0 -26
  287. package/testdriver/acceptance-sdk/hooks-example.test.mjs +0 -38
  288. package/testdriver/acceptance-sdk/presets-example.test.mjs +0 -87
  289. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +0 -420
  290. package/testdriver/acceptance-sdk/sully-ai.test.mjs +0 -234
  291. package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
  292. package/vale.ini +0 -18
  293. package/vitest.config.example.js +0 -19
  294. package/vitest.config.mjs.bak +0 -44
  295. /package/docs/{ARCHITECTURE.md → v7/_drafts/architecture.mdx} +0 -0
  296. /package/docs/{AWESOME_LOGS_QUICK_REF.md → v7/_drafts/awesome-logs-quick-ref.mdx} +0 -0
  297. /package/docs/v7/{guides → _drafts}/best-practices.mdx +0 -0
  298. /package/docs/v7/{guides → _drafts}/caching-ai.mdx +0 -0
  299. /package/docs/v7/{guides → _drafts}/caching.mdx +0 -0
  300. /package/docs/{MIGRATION.md → v7/_drafts/cli-to-sdk-migration.mdx} +0 -0
  301. /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
  302. /package/docs/v7/{progressive-apis/CORE.md → _drafts/core.mdx} +0 -0
  303. /package/docs/v7/{guides → _drafts}/debugging.mdx +0 -0
  304. /package/docs/v7/{guides → _drafts}/faq.mdx +0 -0
  305. /package/docs/v7/{progressive-apis/HOOKS.md → _drafts/hooks.mdx} +0 -0
  306. /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
  307. /package/docs/v7/{guides → _drafts}/performance.mdx +0 -0
  308. /package/docs/{PRESETS.md → v7/_drafts/presets.mdx} +0 -0
  309. /package/docs/v7/{progressive-apis/PROGRESSIVE_DISCLOSURE.md → _drafts/progressive-disclosure.mdx} +0 -0
  310. /package/docs/v7/{progressive-apis/PROVISION.md → _drafts/provision.mdx} +0 -0
  311. /package/docs/{SDK_AWESOME_LOGS.md → v7/_drafts/sdk-awesome-logs.mdx} +0 -0
  312. /package/docs/{sdk-browser-rendering.md → v7/_drafts/sdk-browser-rendering.mdx} +0 -0
  313. /package/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
  314. /package/docs/v7/{guides → _drafts}/vitest.mdx +0 -0
  315. /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
  316. /package/{src → lib}/core/index.d.ts +0 -0
  317. /package/{src → lib}/core/index.js +0 -0
  318. /package/{src → lib}/presets/index.mjs +0 -0
  319. /package/{src → lib}/vitest/hooks.d.ts +0 -0
  320. /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +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}/chrome-extension.test.mjs +0 -0
  324. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
  325. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
@@ -17,6 +17,7 @@ class Dashcam {
17
17
  * @param {string} [options.apiKey] - Dashcam API key
18
18
  * @param {boolean} [options.autoStart=false] - Auto-start recording
19
19
  * @param {Array} [options.logs=[]] - Log configurations to add
20
+ * @param {string} [options.title] - Recording title (defaults to generated title)
20
21
  */
21
22
  constructor(client, options = {}) {
22
23
  if (!client) {
@@ -31,6 +32,30 @@ class Dashcam {
31
32
  this.recording = false;
32
33
  this._authenticated = false;
33
34
  this.startTime = null; // Track when dashcam recording started
35
+ this.title = options.title || this._generateDefaultTitle();
36
+ }
37
+
38
+ /**
39
+ * Generate a default title for the recording
40
+ * Uses test context if available, otherwise falls back to timestamp
41
+ * @private
42
+ */
43
+ _generateDefaultTitle() {
44
+ // Check for Vitest context
45
+ if (this.client.__vitestContext) {
46
+ const task = this.client.__vitestContext;
47
+ const testName = task.name || 'Test';
48
+ const fileName = task.file?.name || task.file?.filepath;
49
+ if (fileName) {
50
+ const baseName = fileName.split('/').pop().replace(/\.(test|spec)\.(js|mjs|ts|tsx)$/, '');
51
+ return `${baseName} - ${testName}`;
52
+ }
53
+ return testName;
54
+ }
55
+
56
+ // Fallback to timestamp
57
+ const now = new Date();
58
+ return `Recording ${now.toISOString().replace(/T/, ' ').replace(/\..+/, '')}`;
34
59
  }
35
60
 
36
61
  /**
@@ -46,7 +71,7 @@ class Dashcam {
46
71
  * @private
47
72
  */
48
73
  _getApiRoot() {
49
- return this.client.config?.TD_API_ROOT || 'http://localhost:1337';
74
+ return this.client.config?.TD_API_ROOT || 'https://testdriver-api.onrender.com';
50
75
  }
51
76
 
52
77
  /**
@@ -291,10 +316,11 @@ class Dashcam {
291
316
 
292
317
  // Start dashcam record and redirect output with TD_API_ROOT
293
318
  const outputFile = 'C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log';
319
+ const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
294
320
  const startScript = `
295
321
  try {
296
322
  $env:TD_API_ROOT="${apiRoot}"
297
- $process = Start-Process "cmd.exe" -ArgumentList "/c", "${dashcamPath} record > ${outputFile} 2>&1" -PassThru
323
+ $process = Start-Process "cmd.exe" -ArgumentList "/c", "${dashcamPath} record${titleArg} > ${outputFile} 2>&1" -PassThru
298
324
  Write-Output "Process started with PID: $($process.Id)"
299
325
  Start-Sleep -Seconds 2
300
326
  if ($process.HasExited) {
@@ -327,7 +353,8 @@ class Dashcam {
327
353
  } else {
328
354
  // Linux/Mac with TD_API_ROOT
329
355
  this._log('info', 'Starting dashcam recording on Linux/Mac...');
330
- await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" dashcam record >/dev/null 2>&1 &`);
356
+ const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
357
+ await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" dashcam record${titleArg} >/dev/null 2>&1 &`);
331
358
  this._log('info', 'Dashcam recording started');
332
359
  }
333
360
 
@@ -337,7 +364,7 @@ class Dashcam {
337
364
  // Update the session with dashcam start time for interaction timestamp synchronization
338
365
  if (this.client && this.client.agent && this.client.agent.session) {
339
366
  try {
340
- const apiRoot = this.apiRoot || process.env.TD_API_ROOT || 'https://app.testdriver.ai';
367
+ const apiRoot = this.apiRoot || process.env.TD_API_ROOT || 'https://console.testdriver.ai';
341
368
  const response = await fetch(`${apiRoot}/api/v7.0.0/testdriver/session/${this.client.agent.session}/update-dashcam-time`, {
342
369
  method: 'POST',
343
370
  headers: {
@@ -358,6 +385,16 @@ class Dashcam {
358
385
  }
359
386
  }
360
387
 
388
+ /**
389
+ * Set the recording title
390
+ * This can be called before start() to customize the title
391
+ * @param {string} title - Custom recording title
392
+ */
393
+ setTitle(title) {
394
+ this.title = title;
395
+ this._log('info', `Set dashcam recording title: ${title}`);
396
+ }
397
+
361
398
  /**
362
399
  * Stop dashcam recording and retrieve replay URL
363
400
  * @returns {Promise<string|null>} Replay URL if available
@@ -18,102 +18,106 @@
18
18
  import fs from 'fs';
19
19
  import os from 'os';
20
20
  import path from 'path';
21
+ import { vi } from 'vitest';
21
22
  import TestDriverSDK from '../../sdk.js';
22
23
 
23
24
  /**
24
- * Intercept console logs and write to a log file on the remote machine
25
- * This allows test logs to appear in Dashcam recordings
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.
26
28
  * @param {TestDriver} client - TestDriver client instance
27
29
  * @param {string} taskId - Unique task identifier for this test
28
30
  */
29
- function setupConsoleInterceptor(client, taskId) {
30
- // Store original console methods
31
- const originalConsole = {
32
- log: console.log,
33
- error: console.error,
34
- warn: console.warn,
35
- info: console.info,
36
- };
31
+ function setupConsoleSpy(client, taskId) {
37
32
 
38
- // Determine log file path based on OS
39
- const logPath = client.os === "windows"
40
- ? "C:\\Users\\testdriver\\Documents\\testdriver.log"
41
- : "/tmp/testdriver.log";
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
+ }
42
41
 
43
- // Store log path on client for later use
44
- client._testLogPath = logPath;
42
+ // Track forwarding stats
43
+ let forwardedCount = 0;
44
+ let skippedCount = 0;
45
45
 
46
- // Track if we're currently writing to avoid infinite loops
47
- let isWriting = false;
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(" ");
48
55
 
49
- // Create wrapper that writes to log file
50
- const createInterceptor = (level, originalMethod) => {
51
- return function (...args) {
52
- // Call original console method first
53
- originalMethod.apply(console, args);
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
+ };
54
79
 
55
- // Skip if already writing to avoid infinite loops
56
- if (isWriting) return;
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
+ });
57
87
 
58
- // Format the log message
59
- const message = args
60
- .map((arg) =>
61
- typeof arg === "object"
62
- ? JSON.stringify(arg, null, 2)
63
- : String(arg),
64
- )
65
- .join(" ");
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
+ });
66
92
 
67
- // Also send to sandbox for immediate visibility
68
- if (client.sandbox && client.sandbox.instanceSocketConnected) {
69
-
70
- client.sandbox.send({
71
- type: "output",
72
- output: Buffer.from(message, "utf8").toString("base64"),
73
- });
74
- }
75
- };
76
- };
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
+ });
77
97
 
78
- // Replace console methods with interceptors
79
- console.log = createInterceptor("log", originalConsole.log);
80
- console.error = createInterceptor("error", originalConsole.error);
81
- console.warn = createInterceptor("warn", originalConsole.warn);
82
- console.info = createInterceptor("info", originalConsole.info);
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
+ });
83
102
 
84
- // Store original methods and taskId on client for cleanup
85
- client._consoleInterceptor = {
86
- taskId,
87
- original: originalConsole,
88
- };
103
+ // Store spies on client for cleanup
104
+ client._consoleSpies = { logSpy, errorSpy, warnSpy, infoSpy };
89
105
 
90
- // Use original console for this message
91
- originalConsole.log(
92
- `[useTestDriver] Console interceptor enabled for task: ${taskId}`,
93
- );
106
+ console.log(`[testdriver] Console spy set up for task: ${taskId}`);
94
107
  }
95
108
 
96
109
  /**
97
- * Remove console interceptor and restore original console methods
110
+ * Clean up console spies and restore original console methods
98
111
  * @param {TestDriver} client - TestDriver client instance
99
112
  */
100
- function removeConsoleInterceptor(client) {
101
- if (client._consoleInterceptor) {
102
- const { original, taskId } = client._consoleInterceptor;
103
-
104
- // Restore original console methods
105
- console.log = original.log;
106
- console.error = original.error;
107
- console.warn = original.warn;
108
- console.info = original.info;
109
-
110
- // Use original console for cleanup message
111
- original.log(
112
- `[useTestDriver] Console interceptor removed for task: ${taskId}`,
113
- );
114
-
115
- // Clean up reference
116
- delete client._consoleInterceptor;
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;
117
121
  }
118
122
  }
119
123
 
@@ -176,16 +180,27 @@ export function TestDriver(context, options = {}) {
176
180
 
177
181
  // Auto-connect if enabled (default: true)
178
182
  const autoConnect = config.autoConnect !== undefined ? config.autoConnect : true;
183
+ const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === 'true';
184
+
179
185
  if (autoConnect) {
180
186
  testdriver.__connectionPromise = (async () => {
181
- try {
182
187
  console.log('[testdriver] Connecting to sandbox...');
188
+ if (debugConsoleSpy) {
189
+ console.log('[DEBUG] Before auth - sandbox.instanceSocketConnected:', testdriver.sandbox?.instanceSocketConnected);
190
+ }
191
+
183
192
  await testdriver.auth();
184
193
  await testdriver.connect();
194
+
185
195
  console.log('[testdriver] ✅ Connected to sandbox');
186
196
 
187
- // Set up console interceptor after connection
188
- setupConsoleInterceptor(testdriver, context.task.id);
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);
189
204
 
190
205
  // Create the log file on the remote machine
191
206
  const shell = testdriver.os === "windows" ? "pwsh" : "sh";
@@ -202,23 +217,9 @@ export function TestDriver(context, options = {}) {
202
217
 
203
218
  // Add automatic log tracking when dashcam starts
204
219
  // Store original start method
205
- const originalDashcamStart = testdriver.dashcam.start.bind(testdriver.dashcam);
206
- testdriver.dashcam.start = async function() {
207
- // Call original start (which handles auth)
208
- await originalDashcamStart();
209
-
210
- // Add log file tracking after dashcam starts
211
- try {
212
- await testdriver.dashcam.addFileLog(logPath, "TestDriver Log");
213
- console.log('[testdriver] ✅ Added log file to dashcam tracking');
214
- } catch (error) {
215
- console.warn('[testdriver] ⚠️ Failed to add log tracking:', error.message);
216
- }
217
- };
218
- } catch (error) {
219
- console.error('[testdriver] Error during setup:', error);
220
- throw error;
221
- }
220
+
221
+ await testdriver.dashcam.addFileLog(logPath, "TestDriver Log");
222
+
222
223
  })();
223
224
  }
224
225
 
@@ -227,10 +228,18 @@ export function TestDriver(context, options = {}) {
227
228
  const cleanup = async () => {
228
229
  console.log('[testdriver] Cleaning up TestDriver client...');
229
230
  try {
230
- // Stop dashcam if it was started
231
+ // Stop dashcam if it was started - with timeout to prevent hanging
231
232
  if (testdriver._dashcam && testdriver._dashcam.recording) {
232
233
  try {
233
- const dashcamUrl = await testdriver.dashcam.stop();
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;
234
243
  console.log('🎥 Dashcam URL:', dashcamUrl);
235
244
 
236
245
  // Write dashcam URL to file for the reporter (cross-process communication)
@@ -272,17 +281,26 @@ export function TestDriver(context, options = {}) {
272
281
  if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
273
282
  console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
274
283
  }
284
+ // Mark as not recording to prevent retries
285
+ if (testdriver._dashcam) {
286
+ testdriver._dashcam.recording = false;
287
+ }
275
288
  }
276
289
  }
277
290
 
278
- // Remove console interceptor before disconnecting
279
- removeConsoleInterceptor(testdriver);
291
+ // Clean up console spies
292
+ cleanupConsoleSpy(testdriver);
280
293
 
281
294
  // Wait for connection to finish if it was initiated
282
295
  if (testdriver.__connectionPromise) {
283
296
  await testdriver.__connectionPromise.catch(() => {}); // Ignore connection errors during cleanup
284
297
  }
285
- await testdriver.disconnect();
298
+
299
+ // Disconnect with timeout
300
+ await Promise.race([
301
+ testdriver.disconnect(),
302
+ new Promise((resolve) => setTimeout(resolve, 5000)) // 5s timeout for disconnect
303
+ ]);
286
304
  console.log('✅ Client disconnected');
287
305
  } catch (error) {
288
306
  console.error('Error disconnecting client:', error);
@@ -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,18 +1,16 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.1.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
6
  "exports": {
7
7
  ".": "./sdk.js",
8
- "./core": "./src/core/index.js",
9
- "./vitest": "./src/vitest/index.mjs",
10
- "./vitest/hooks": "./src/vitest/hooks.mjs",
11
- "./vitest/extended": "./src/vitest/extended.mjs",
12
- "./vitest/lifecycle": "./src/vitest/lifecycle.mjs",
13
- "./vitest/utils": "./src/vitest/utils.mjs",
8
+ "./core": "./lib/core/index.js",
9
+ "./vitest": "./interfaces/vitest-plugin.mjs",
14
10
  "./vitest/plugin": "./interfaces/vitest-plugin.mjs",
15
- "./presets": "./src/presets/index.mjs"
11
+ "./vitest/setup": "./lib/vitest/setup.mjs",
12
+ "./vitest/hooks": "./lib/vitest/hooks.mjs",
13
+ "./presets": "./lib/presets/index.mjs"
16
14
  },
17
15
  "bin": {
18
16
  "testdriverai": "bin/testdriverai.js"
@@ -61,7 +59,7 @@
61
59
  "chalk": "^4.1.2",
62
60
  "cli-progress": "^3.12.0",
63
61
  "diff": "^8.0.2",
64
- "dotenv": "^16.4.5",
62
+ "dotenv": "^16.6.1",
65
63
  "eventemitter2": "^6.4.9",
66
64
  "jimp": "^0.22.12",
67
65
  "js-yaml": "^4.1.0",
@@ -99,7 +97,8 @@
99
97
  "mocha": "^10.8.2",
100
98
  "node-addon-api": "^8.0.0",
101
99
  "prettier": "3.3.3",
102
- "vitest": "^4.0.8"
100
+ "testdriverai": "^6.1.11",
101
+ "vitest": "^4.0.15"
103
102
  },
104
103
  "optionalDependencies": {
105
104
  "@esbuild/linux-x64": "^0.21.5"
package/sdk.d.ts CHANGED
@@ -1014,13 +1014,26 @@ export default class TestDriverSDK {
1014
1014
  *
1015
1015
  * @example
1016
1016
  * // Simple execution
1017
- * await client.ai('Click the submit button');
1017
+ * await client.act('Click the submit button');
1018
1018
  *
1019
1019
  * @example
1020
1020
  * // With validation loop
1021
- * const result = await client.ai('Fill out the contact form', { validateAndLoop: true });
1021
+ * const result = await client.act('Fill out the contact form', { validateAndLoop: true });
1022
1022
  * console.log(result); // AI's final assessment
1023
1023
  */
1024
+ act(
1025
+ task: string,
1026
+ options?: { validateAndLoop?: boolean },
1027
+ ): Promise<string | void>;
1028
+
1029
+ /**
1030
+ * @deprecated Use act() instead
1031
+ * Execute a natural language task using AI
1032
+ *
1033
+ * @param task - Natural language description of what to do
1034
+ * @param options - Execution options
1035
+ * @returns Final AI response if validateAndLoop is true
1036
+ */
1024
1037
  ai(
1025
1038
  task: string,
1026
1039
  options?: { validateAndLoop?: boolean },