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,506 @@
1
+ /**
2
+ * Dashcam Class
3
+ * Manages Dashcam CLI recording lifecycle
4
+ *
5
+ * Provides a clean interface for:
6
+ * - Authentication
7
+ * - Log tracking
8
+ * - Starting/stopping recordings
9
+ * - Retrieving replay URLs
10
+ */
11
+
12
+ class Dashcam {
13
+ /**
14
+ * Create a Dashcam instance
15
+ * @param {Object} client - TestDriver client instance
16
+ * @param {Object} options - Configuration options
17
+ * @param {string} [options.apiKey] - Dashcam API key
18
+ * @param {boolean} [options.autoStart=false] - Auto-start recording
19
+ * @param {Array} [options.logs=[]] - Log configurations to add
20
+ * @param {string} [options.title] - Recording title (defaults to generated title)
21
+ */
22
+ constructor(client, options = {}) {
23
+ if (!client) {
24
+ throw new Error('Dashcam requires a TestDriver client instance');
25
+ }
26
+
27
+ this.client = client;
28
+ // Use provided apiKey, or client's apiKey, or fallback to a default
29
+ this.apiKey = options.apiKey || client.apiKey || client.config?.TD_API_KEY || '4e93d8bf-3886-4d26-a144-116c4063522d';
30
+ this.autoStart = options.autoStart ?? false;
31
+ this.logs = options.logs || [];
32
+ this.recording = false;
33
+ this._authenticated = false;
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(/\..+/, '')}`;
59
+ }
60
+
61
+ /**
62
+ * Get shell type based on client OS
63
+ * @private
64
+ */
65
+ _getShell() {
66
+ return this.client.os === 'windows' ? 'pwsh' : 'sh';
67
+ }
68
+
69
+ /**
70
+ * Get TD_API_ROOT from client config
71
+ * @private
72
+ */
73
+ _getApiRoot() {
74
+ return this.client.config?.TD_API_ROOT || 'https://testdriver-api.onrender.com';
75
+ }
76
+
77
+ /**
78
+ * Get dashcam executable path
79
+ * @private
80
+ */
81
+ async _getDashcamPath() {
82
+ const shell = this._getShell();
83
+ const npmPrefix = await this.client.exec(shell, 'npm prefix -g', 40000, true);
84
+
85
+ if (this.client.os === 'windows') {
86
+ return npmPrefix.trim() + '\\dashcam.cmd';
87
+ }
88
+ return npmPrefix.trim() + '/bin/dashcam';
89
+ }
90
+
91
+ /**
92
+ * Authenticate dashcam with API key
93
+ * @param {string} [apiKey] - Override API key
94
+ * @returns {Promise<void>}
95
+ */
96
+ async auth(apiKey) {
97
+ const key = apiKey || this.apiKey;
98
+ const shell = this._getShell();
99
+ const apiRoot = this._getApiRoot();
100
+
101
+ if (this.client.os === 'windows') {
102
+ // Debug session info
103
+ const debug = await this.client.exec(shell, 'query session', 40000, true);
104
+ this._log('debug', 'Debug version output:', debug);
105
+
106
+ // Uninstall and clear cache for fresh install
107
+ await this.client.exec(shell, 'npm uninstall dashcam -g', 40000, true);
108
+ await this.client.exec(shell, 'npm cache clean --force', 40000, true);
109
+
110
+ // Install dashcam with TD_API_ROOT environment variable
111
+ const installOutput = await this.client.exec(
112
+ shell,
113
+ `$env:TD_API_ROOT="${apiRoot}"; npm install dashcam@beta -g`,
114
+ 120000,
115
+ true
116
+ );
117
+ this._log('debug', 'Install dashcam output:', installOutput);
118
+
119
+ // Verify version
120
+ const latestVersion = await this.client.exec(
121
+ shell,
122
+ 'npm view dashcam@beta version',
123
+ 40000,
124
+ true
125
+ );
126
+ this._log('debug', 'Latest beta version available:', latestVersion);
127
+
128
+ const dashcamPath = await this._getDashcamPath();
129
+ this._log('debug', 'Dashcam executable path:', dashcamPath);
130
+
131
+ const installedVersion = await this.client.exec(
132
+ shell,
133
+ 'npm ls dashcam -g',
134
+ 40000,
135
+ true
136
+ );
137
+ this._log('debug', 'Installed dashcam version:', installedVersion);
138
+
139
+ // Test version command
140
+ const versionTest = await this.client.exec(
141
+ shell,
142
+ `& "${dashcamPath}" version`,
143
+ 40000,
144
+ true
145
+ );
146
+ this._log('debug', 'Dashcam version test:', versionTest);
147
+
148
+ // Verify installation
149
+ if (!installedVersion) {
150
+ this._log('error', 'Dashcam version command returned null/empty');
151
+ this._log('debug', 'Install output was:', installOutput);
152
+ } else if (!installedVersion.includes('1.3.')) {
153
+ this._log('warn', 'Dashcam version may be outdated. Expected 1.3.x, got:', installedVersion);
154
+ } else {
155
+ this._log('debug', 'Dashcam version verified:', installedVersion);
156
+ }
157
+
158
+ // Authenticate with TD_API_ROOT
159
+ const authOutput = await this.client.exec(
160
+ shell,
161
+ `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" auth ${key}`,
162
+ 120000,
163
+ true
164
+ );
165
+ this._log('debug', 'Auth output:', authOutput);
166
+ } else {
167
+ // Linux/Mac authentication with TD_API_ROOT
168
+ const authOutput = await this.client.exec(
169
+ shell,
170
+ `TD_API_ROOT="${apiRoot}" dashcam auth ${key}`,
171
+ 120000,
172
+ true
173
+ );
174
+ this._log('debug', 'Auth output:', authOutput);
175
+ }
176
+
177
+ this._authenticated = true;
178
+ }
179
+
180
+ /**
181
+ * Add file log tracking
182
+ * @param {string} path - Path to log file
183
+ * @param {string} name - Display name
184
+ * @returns {Promise<void>}
185
+ */
186
+ async addFileLog(path, name) {
187
+ const shell = this._getShell();
188
+ const apiRoot = this._getApiRoot();
189
+
190
+ if (this.client.os === 'windows') {
191
+ // Create log file if it doesn't exist
192
+ const createFileOutput = await this.client.exec(
193
+ shell,
194
+ `New-Item -ItemType File -Path "${path}" -Force`,
195
+ 10000,
196
+ true
197
+ );
198
+ this._log('debug', 'Create log file output:', createFileOutput);
199
+
200
+ const dashcamPath = await this._getDashcamPath();
201
+ const addLogOutput = await this.client.exec(
202
+ shell,
203
+ `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=file --file="${path}" --name="${name}"`,
204
+ 120000,
205
+ true
206
+ );
207
+ this._log('debug', 'Add log tracking output:', addLogOutput);
208
+ } else {
209
+ // Create log file
210
+ await this.client.exec(shell, `touch ${path}`, 10000, true);
211
+
212
+ // Add log tracking with TD_API_ROOT
213
+ const addLogOutput = await this.client.exec(
214
+ shell,
215
+ `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=file --file="${path}" --name="${name}"`,
216
+ 10000,
217
+ true
218
+ );
219
+ this._log('debug', 'Add log tracking output:', addLogOutput);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Add application log tracking
225
+ * @param {string} application - Application name
226
+ * @param {string} name - Display name
227
+ * @returns {Promise<void>}
228
+ */
229
+ async addApplicationLog(application, name) {
230
+ const shell = this._getShell();
231
+ const dashcamPath = await this._getDashcamPath();
232
+ const apiRoot = this._getApiRoot();
233
+
234
+ if (this.client.os === 'windows') {
235
+ const addLogOutput = await this.client.exec(
236
+ shell,
237
+ `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=application --application="${application}" --name="${name}"`,
238
+ 120000,
239
+ true
240
+ );
241
+ this._log('debug', 'Add application log tracking output:', addLogOutput);
242
+ } else {
243
+ const addLogOutput = await this.client.exec(
244
+ shell,
245
+ `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=application --application="${application}" --name="${name}"`,
246
+ 10000,
247
+ true
248
+ );
249
+ this._log('debug', 'Add application log tracking output:', addLogOutput);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Add web log tracking
255
+ * @param {string} pattern - URL pattern to match (e.g., "*example.com*")
256
+ * @param {string} name - Display name
257
+ * @returns {Promise<void>}
258
+ */
259
+ async addWebLog(pattern, name) {
260
+ const shell = this._getShell();
261
+ const dashcamPath = await this._getDashcamPath();
262
+ const apiRoot = this._getApiRoot();
263
+
264
+ if (this.client.os === 'windows') {
265
+ const addLogOutput = await this.client.exec(
266
+ shell,
267
+ `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=web --pattern="${pattern}" --name="${name}"`,
268
+ 120000,
269
+ true
270
+ );
271
+ this._log('debug', 'Add web log tracking output:', addLogOutput);
272
+ } else {
273
+ const addLogOutput = await this.client.exec(
274
+ shell,
275
+ `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=web --pattern="${pattern}" --name="${name}"`,
276
+ 10000,
277
+ true
278
+ );
279
+ this._log('debug', 'Add web log tracking output:', addLogOutput);
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Start dashcam recording
285
+ * @returns {Promise<void>}
286
+ */
287
+ async start() {
288
+ if (this.recording) {
289
+ this._log('warn', 'Dashcam already recording');
290
+ return;
291
+ }
292
+
293
+ // Auto-authenticate if not already done
294
+ if (!this._authenticated) {
295
+ this._log('info', 'Auto-authenticating dashcam...');
296
+ await this.auth();
297
+ }
298
+
299
+ const shell = this._getShell();
300
+ const apiRoot = this._getApiRoot();
301
+
302
+ if (this.client.os === 'windows') {
303
+ this._log('info', 'Starting dashcam recording on Windows...');
304
+
305
+ const dashcamPath = await this._getDashcamPath();
306
+ this._log('debug', 'Dashcam path:', dashcamPath);
307
+
308
+ // Verify dashcam exists
309
+ const dashcamExists = await this.client.exec(
310
+ shell,
311
+ `Test-Path "${dashcamPath}"`,
312
+ 10000,
313
+ true
314
+ );
315
+ this._log('debug', 'Dashcam.cmd exists:', dashcamExists);
316
+
317
+ // Start dashcam record and redirect output with TD_API_ROOT
318
+ const outputFile = 'C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log';
319
+ const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
320
+ const startScript = `
321
+ try {
322
+ $env:TD_API_ROOT="${apiRoot}"
323
+ $process = Start-Process "cmd.exe" -ArgumentList "/c", "${dashcamPath} record${titleArg} > ${outputFile} 2>&1" -PassThru
324
+ Write-Output "Process started with PID: $($process.Id)"
325
+ Start-Sleep -Seconds 2
326
+ if ($process.HasExited) {
327
+ Write-Output "Process has already exited with code: $($process.ExitCode)"
328
+ } else {
329
+ Write-Output "Process is still running"
330
+ }
331
+ } catch {
332
+ Write-Output "ERROR: $_"
333
+ }
334
+ `;
335
+
336
+ const startOutput = await this.client.exec(shell, startScript, 10000, true);
337
+ this._log('debug', 'Start-Process output:', startOutput);
338
+
339
+ // Wait and check output
340
+ await new Promise(resolve => setTimeout(resolve, 2000));
341
+ const dashcamOutput = await this.client.exec(
342
+ shell,
343
+ `Get-Content "${outputFile}" -ErrorAction SilentlyContinue`,
344
+ 10000,
345
+ true
346
+ );
347
+ this._log('debug', 'Dashcam record output:', dashcamOutput);
348
+
349
+ // Give process time to initialize
350
+ await new Promise(resolve => setTimeout(resolve, 5000));
351
+
352
+ this._log('info', 'Dashcam recording started');
353
+ } else {
354
+ // Linux/Mac with TD_API_ROOT
355
+ this._log('info', 'Starting dashcam recording on Linux/Mac...');
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 &`);
358
+ this._log('info', 'Dashcam recording started');
359
+ }
360
+
361
+ this.recording = true;
362
+ this.startTime = Date.now(); // Record the timestamp when dashcam started
363
+
364
+ // Update the session with dashcam start time for interaction timestamp synchronization
365
+ if (this.client && this.client.agent && this.client.agent.session) {
366
+ try {
367
+ const apiRoot = this.apiRoot || process.env.TD_API_ROOT || 'https://console.testdriver.ai';
368
+ const response = await fetch(`${apiRoot}/api/v7.0.0/testdriver/session/${this.client.agent.session}/update-dashcam-time`, {
369
+ method: 'POST',
370
+ headers: {
371
+ 'Content-Type': 'application/json',
372
+ 'Authorization': `Bearer ${this.apiKey}`
373
+ },
374
+ body: JSON.stringify({ dashcamStartTime: this.startTime })
375
+ });
376
+
377
+ if (response.ok) {
378
+ this._log('info', `Updated session ${this.client.agent.session} with dashcam start time: ${this.startTime}`);
379
+ } else {
380
+ this._log('warn', 'Failed to update session with dashcam start time:', response.statusText);
381
+ }
382
+ } catch (err) {
383
+ this._log('warn', 'Error updating session with dashcam start time:', err.message);
384
+ }
385
+ }
386
+ }
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
+
398
+ /**
399
+ * Stop dashcam recording and retrieve replay URL
400
+ * @returns {Promise<string|null>} Replay URL if available
401
+ */
402
+ async stop() {
403
+ if (!this.recording) {
404
+ // Internal log only - don't spam user console
405
+ this._log('warn', 'Dashcam not recording');
406
+ return null;
407
+ }
408
+
409
+ this._log('info', 'Stopping dashcam and retrieving URL...');
410
+ const shell = this._getShell();
411
+ const apiRoot = this._getApiRoot();
412
+ let output;
413
+
414
+ if (this.client.os === 'windows') {
415
+ this._log('info', 'Stopping dashcam process on Windows...');
416
+
417
+ const dashcamPath = await this._getDashcamPath();
418
+
419
+ // Stop and get output with TD_API_ROOT
420
+ output = await this.client.exec(shell, `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`, 120000);
421
+ this._log('debug', 'Dashcam stop command output:', output);
422
+ } else {
423
+ // Linux/Mac with TD_API_ROOT
424
+ const dashcamPath = await this._getDashcamPath();
425
+ output = await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`, 60000, false);
426
+ this._log('debug', 'Dashcam command output:', output);
427
+ }
428
+
429
+ this.recording = false;
430
+
431
+ // Extract URL from output
432
+ if (output) {
433
+ // Look for replay URL with optional query parameters (most specific)
434
+ // Matches: http://localhost:3001/replay/abc123?share=xyz or https://app.dashcam.io/replay/abc123
435
+ const replayUrlMatch = output.match(/https?:\/\/[^\s"',}]+\/replay\/[^\s"',}]+/);
436
+ if (replayUrlMatch) {
437
+ let url = replayUrlMatch[0];
438
+ // Remove trailing punctuation but keep query params
439
+ url = url.replace(/[.,;:!\)\]]+$/, '').trim();
440
+ this._log('info', 'Found dashcam URL:', url);
441
+ return url;
442
+ }
443
+
444
+ // Fallback: any dashcam.io or testdriver.ai URL
445
+ const dashcamUrlMatch = output.match(/https?:\/\/(?:app\.)?(?:dashcam\.io|testdriver\.ai)[^\s"',}]+/);
446
+ if (dashcamUrlMatch) {
447
+ let url = dashcamUrlMatch[0];
448
+ url = url.replace(/[.,;:!\?\)\]]+$/, '').trim();
449
+ this._log('info', 'Found dashcam URL:', url);
450
+ return url;
451
+ }
452
+
453
+ this._log('warn', 'No replay URL found in dashcam output');
454
+ } else {
455
+ this._log('warn', 'Dashcam command returned no output');
456
+ }
457
+
458
+ return null;
459
+ }
460
+
461
+ /**
462
+ * Internal logging - writes to testdriver log file but not user console
463
+ * @private
464
+ */
465
+ _log(level, ...args) {
466
+ const message = args.map(arg =>
467
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
468
+ ).join(' ');
469
+
470
+ const timestamp = new Date().toISOString();
471
+ const logLine = `[${timestamp}] [DASHCAM:${level.toUpperCase()}] ${message}`;
472
+
473
+ // Send to sandbox log file via output command (same as console interceptor)
474
+ if (this.client?.sandbox?.instanceSocketConnected) {
475
+ try {
476
+ this.client.sandbox.send({
477
+ type: "output",
478
+ output: Buffer.from(logLine, "utf8").toString("base64"),
479
+ });
480
+ } catch {
481
+ // Silently fail
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Check if currently recording
488
+ * @returns {Promise<boolean>}
489
+ */
490
+ async isRecording() {
491
+ return this.recording;
492
+ }
493
+
494
+ /**
495
+ * Get milliseconds elapsed since dashcam started recording
496
+ * @returns {number|null} Milliseconds since start, or null if not recording
497
+ */
498
+ getElapsedTime() {
499
+ if (!this.recording || !this.startTime) {
500
+ return null;
501
+ }
502
+ return Date.now() - this.startTime;
503
+ }
504
+ }
505
+
506
+ module.exports = Dashcam;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * TypeScript definitions for TestDriver Core Module
3
+ * @module testdriverai/core
4
+ */
5
+
6
+ export class Dashcam {
7
+ /**
8
+ * Create a new Dashcam instance
9
+ * @param client - TestDriver client instance
10
+ * @param options - Dashcam options
11
+ */
12
+ constructor(client: any, options?: DashcamOptions);
13
+
14
+ /**
15
+ * Authenticate with Dashcam CLI
16
+ * @param apiKey - Dashcam API key (optional, uses DASHCAM_API_KEY env var if not provided)
17
+ * @returns Promise that resolves when authenticated
18
+ */
19
+ auth(apiKey?: string): Promise<void>;
20
+
21
+ /**
22
+ * Add a log entry to Dashcam
23
+ * @param config - Log configuration
24
+ */
25
+ addLog(config: LogConfig): Promise<void>;
26
+
27
+ /**
28
+ * Add a file log to Dashcam
29
+ * @param path - Path to file to log
30
+ * @param name - Name/description for the log entry
31
+ */
32
+ addFileLog(path: string, name: string): Promise<void>;
33
+
34
+ /**
35
+ * Add an application log to Dashcam
36
+ * @param application - Application name to track
37
+ * @param name - Name/description for the log entry
38
+ */
39
+ addApplicationLog(application: string, name: string): Promise<void>;
40
+
41
+ /**
42
+ * Start recording
43
+ * @returns Promise that resolves when recording starts
44
+ */
45
+ start(): Promise<void>;
46
+
47
+ /**
48
+ * Stop recording and get replay URL
49
+ * @returns Promise that resolves to the replay URL (or null if not recording)
50
+ */
51
+ stop(): Promise<string | null>;
52
+
53
+ /**
54
+ * Check if currently recording
55
+ * @returns true if recording, false otherwise
56
+ */
57
+ isRecording(): boolean;
58
+ }
59
+
60
+ export interface DashcamOptions {
61
+ /**
62
+ * Dashcam API key (defaults to DASHCAM_API_KEY env var)
63
+ */
64
+ apiKey?: string;
65
+ }
66
+
67
+ export interface LogConfig {
68
+ /**
69
+ * Type of log entry
70
+ */
71
+ type: 'file' | 'application';
72
+
73
+ /**
74
+ * Path to file (for file logs)
75
+ */
76
+ path?: string;
77
+
78
+ /**
79
+ * Application name (for application logs)
80
+ */
81
+ application?: string;
82
+
83
+ /**
84
+ * Name/description for the log entry
85
+ */
86
+ name: string;
87
+ }
88
+
89
+ /**
90
+ * TestDriver SDK class
91
+ * Re-exported from main module for convenience
92
+ */
93
+ export class TestDriver {
94
+ constructor(apiKey: string, options?: TestDriverOptions);
95
+
96
+ auth(): Promise<void>;
97
+ connect(options?: ConnectOptions): Promise<any>;
98
+ disconnect(): Promise<void>;
99
+
100
+ find(query: string): Promise<any>;
101
+ findAll(query: string): Promise<any[]>;
102
+ click(target: string): Promise<void>;
103
+ type(target: string, text: string): Promise<void>;
104
+ exec(shell: string, command: string, timeout?: number, ignoreError?: boolean): Promise<string>;
105
+ focusApplication(appName: string): Promise<void>;
106
+
107
+ // Add other TestDriver methods as needed
108
+ }
109
+
110
+ export interface TestDriverOptions {
111
+ /**
112
+ * API endpoint URL
113
+ */
114
+ apiRoot?: string;
115
+
116
+ /**
117
+ * Target OS: 'linux', 'mac', or 'windows'
118
+ */
119
+ os?: 'linux' | 'mac' | 'windows';
120
+
121
+ /**
122
+ * Create new sandbox
123
+ */
124
+ newSandbox?: boolean;
125
+
126
+ /**
127
+ * Screen resolution
128
+ */
129
+ resolution?: string;
130
+
131
+ /**
132
+ * Enable analytics
133
+ */
134
+ analytics?: boolean;
135
+
136
+ /**
137
+ * Cache thresholds for find operations
138
+ */
139
+ cacheThresholds?: {
140
+ find?: number;
141
+ findAll?: number;
142
+ };
143
+ }
144
+
145
+ export interface ConnectOptions {
146
+ /**
147
+ * Create new sandbox instance
148
+ */
149
+ new?: boolean;
150
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Core SDK Exports
3
+ * Main exports for the TestDriver SDK
4
+ */
5
+
6
+ const TestDriver = require('../../sdk.js');
7
+ const Dashcam = require('./Dashcam.js');
8
+
9
+ module.exports = {
10
+ TestDriver,
11
+ Dashcam,
12
+ };