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
@@ -1,9 +1,46 @@
1
+ import { execSync } from "child_process";
1
2
  import crypto from "crypto";
2
3
  import fs from "fs";
4
+ import { createRequire } from "module";
3
5
  import os from "os";
4
6
  import path from "path";
5
7
  import { setTestRunInfo } from "./shared-test-state.mjs";
6
8
 
9
+ // Use createRequire to import CommonJS modules without esbuild processing
10
+ const require = createRequire(import.meta.url);
11
+
12
+ /**
13
+ * Simple logger for the vitest plugin
14
+ * Supports log levels: debug, info, warn, error
15
+ * Control via TD_LOG_LEVEL environment variable (default: "info")
16
+ * Set TD_LOG_LEVEL=debug for verbose output
17
+ */
18
+ const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
19
+ const currentLogLevel = LOG_LEVELS[process.env.TD_LOG_LEVEL?.toLowerCase()] ?? LOG_LEVELS.info;
20
+
21
+ const logger = {
22
+ debug: (...args) => {
23
+ if (currentLogLevel <= LOG_LEVELS.debug) {
24
+ console.log("[TestDriver]", ...args);
25
+ }
26
+ },
27
+ info: (...args) => {
28
+ if (currentLogLevel <= LOG_LEVELS.info) {
29
+ console.log("[TestDriver]", ...args);
30
+ }
31
+ },
32
+ warn: (...args) => {
33
+ if (currentLogLevel <= LOG_LEVELS.warn) {
34
+ console.warn("[TestDriver]", ...args);
35
+ }
36
+ },
37
+ error: (...args) => {
38
+ if (currentLogLevel <= LOG_LEVELS.error) {
39
+ console.error("[TestDriver]", ...args);
40
+ }
41
+ },
42
+ };
43
+
7
44
  /**
8
45
  * Timeout wrapper for promises
9
46
  * @param {Promise} promise - Promise to wrap
@@ -58,6 +95,7 @@ function withTimeout(promise, timeoutMs, operationName) {
58
95
  export const pluginState = {
59
96
  testRun: null,
60
97
  testRunId: null,
98
+ testRunCompleted: false,
61
99
  client: null,
62
100
  startTime: null,
63
101
  testCases: new Map(),
@@ -68,6 +106,8 @@ export const pluginState = {
68
106
  gitInfo: {},
69
107
  apiKey: null,
70
108
  apiRoot: null,
109
+ // TestDriver options to pass to all instances
110
+ testDriverOptions: {},
71
111
  // Dashcam URL tracking (in-memory, no files needed!)
72
112
  dashcamUrls: new Map(), // testId -> dashcamUrl
73
113
  lastDashcamUrl: null, // Fallback for when test ID isn't available
@@ -77,7 +117,7 @@ export const pluginState = {
77
117
 
78
118
  // Export functions that can be used by the reporter or tests
79
119
  export function registerDashcamUrl(testId, url, platform) {
80
- console.log(`[Plugin] Registering dashcam URL for test ${testId}:`, url);
120
+ logger.debug(`Registering dashcam URL for test ${testId}:`, url);
81
121
  pluginState.dashcamUrls.set(testId, { url, platform });
82
122
  pluginState.lastDashcamUrl = url;
83
123
  }
@@ -96,7 +136,7 @@ export function getSuiteTestRun(suiteId) {
96
136
  }
97
137
 
98
138
  export function setSuiteTestRun(suiteId, runData) {
99
- console.log(`[Plugin] Setting test run for suite ${suiteId}:`, runData);
139
+ logger.debug(`Setting test run for suite ${suiteId}:`, runData);
100
140
  pluginState.suiteTestRuns.set(suiteId, runData);
101
141
  }
102
142
 
@@ -183,23 +223,223 @@ export async function recordTestCaseDirect(token, apiRoot, testCaseData) {
183
223
  return await response.json();
184
224
  }
185
225
 
226
+ // Import TestDriverSDK using require to avoid esbuild transformation issues
227
+ const TestDriverSDK = require('../sdk.js');
228
+
229
+ /**
230
+ * Create a TestDriver client for use in beforeAll/beforeEach hooks
231
+ * This is for the shared instance pattern where one driver is used across multiple tests
232
+ *
233
+ * @param {object} options - TestDriver options
234
+ * @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
235
+ * @param {boolean} [options.headless] - Run sandbox in headless mode
236
+ * @returns {Promise<TestDriver>} Connected TestDriver client instance
237
+ *
238
+ * @example
239
+ * let testdriver;
240
+ * beforeAll(async () => {
241
+ * testdriver = await createTestDriver({ headless: true });
242
+ * await testdriver.provision.chrome({ url: 'https://example.com' });
243
+ * });
244
+ */
245
+ export async function createTestDriver(options = {}) {
246
+ // Get global plugin options if available
247
+ const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
248
+
249
+ // Merge options: plugin global options < test-specific options
250
+ const mergedOptions = { ...pluginOptions, ...options };
251
+
252
+ // Extract TestDriver-specific options
253
+ const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
254
+
255
+ // Build config for TestDriverSDK constructor
256
+ const config = { ...mergedOptions };
257
+ delete config.apiKey;
258
+
259
+ // Use TD_API_ROOT from environment if not provided in config
260
+ if (!config.apiRoot && process.env.TD_API_ROOT) {
261
+ config.apiRoot = process.env.TD_API_ROOT;
262
+ }
263
+
264
+ const testdriver = new TestDriverSDK(apiKey, config);
265
+
266
+ // Connect to sandbox
267
+ console.log('[testdriver] Connecting to sandbox...');
268
+ await testdriver.auth();
269
+ await testdriver.connect();
270
+ console.log('[testdriver] ✅ Connected to sandbox');
271
+
272
+ return testdriver;
273
+ }
274
+
275
+ /**
276
+ * Register a test with a shared TestDriver instance
277
+ * Call this at the start of each test to associate the test context with the driver
278
+ *
279
+ * @param {TestDriver} testdriver - TestDriver client instance from createTestDriver
280
+ * @param {object} context - Vitest test context (from async (context) => {})
281
+ *
282
+ * @example
283
+ * it("step01: verify login", async (context) => {
284
+ * registerTest(testdriver, context);
285
+ * const result = await testdriver.assert("login form visible");
286
+ * });
287
+ */
288
+ export function registerTest(testdriver, context) {
289
+ if (!testdriver) {
290
+ throw new Error('registerTest() requires a TestDriver instance');
291
+ }
292
+ if (!context || !context.task) {
293
+ throw new Error('registerTest() requires Vitest context. Pass the context parameter from your test function.');
294
+ }
295
+
296
+ testdriver.__vitestContext = context.task;
297
+ logger.debug(`Registered test: ${context.task.name}`);
298
+ }
299
+
300
+ /**
301
+ * Clean up a TestDriver client created with createTestDriver
302
+ * Call this in afterAll to properly disconnect and stop recordings
303
+ *
304
+ * @param {TestDriver} testdriver - TestDriver client instance
305
+ *
306
+ * @example
307
+ * afterAll(async () => {
308
+ * await cleanupTestDriver(testdriver);
309
+ * });
310
+ */
311
+ export async function cleanupTestDriver(testdriver) {
312
+ if (!testdriver) {
313
+ return;
314
+ }
315
+
316
+ console.log('[testdriver] Cleaning up TestDriver client...');
317
+
318
+ try {
319
+ // Stop dashcam if it was started
320
+ if (testdriver._dashcam && testdriver._dashcam.recording) {
321
+ try {
322
+ const dashcamUrl = await testdriver.dashcam.stop();
323
+ console.log('🎥 Dashcam URL:', dashcamUrl);
324
+
325
+ // Register dashcam URL in memory for the reporter
326
+ if (dashcamUrl && globalThis.__testdriverPlugin?.registerDashcamUrl) {
327
+ const testId = testdriver.__vitestContext?.id || 'unknown';
328
+ const platform = testdriver.os || 'linux';
329
+ globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
330
+ }
331
+ } catch (error) {
332
+ console.error('❌ Failed to stop dashcam:', error.message);
333
+ if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
334
+ console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
335
+ }
336
+ }
337
+ }
338
+
339
+ await testdriver.disconnect();
340
+ console.log('✅ Client disconnected');
341
+ } catch (error) {
342
+ console.error('Error disconnecting client:', error);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Handle process termination and mark test run as cancelled
348
+ */
349
+ async function handleProcessExit() {
350
+ if (!pluginState.testRun || !pluginState.testRunId) {
351
+ return;
352
+ }
353
+
354
+ logger.info("Process interrupted, marking test run as cancelled...");
355
+
356
+ try {
357
+ const stats = {
358
+ totalTests: pluginState.testCases.size,
359
+ passedTests: 0,
360
+ failedTests: 0,
361
+ skippedTests: 0,
362
+ };
363
+
364
+ const completeData = {
365
+ runId: pluginState.testRunId,
366
+ status: "cancelled",
367
+ totalTests: stats.totalTests,
368
+ passedTests: stats.passedTests,
369
+ failedTests: stats.failedTests,
370
+ skippedTests: stats.skippedTests,
371
+ duration: Date.now() - pluginState.startTime,
372
+ };
373
+
374
+ // Update platform if detected
375
+ const platform = getPlatform();
376
+ if (platform) {
377
+ completeData.platform = platform;
378
+ }
379
+
380
+ await completeTestRun(completeData);
381
+ logger.info("✅ Test run marked as cancelled");
382
+ } catch (error) {
383
+ logger.error("Failed to mark test run as cancelled:", error.message);
384
+ }
385
+ }
386
+
387
+ // Set up process exit handlers
388
+ let exitHandlersRegistered = false;
389
+
390
+ function registerExitHandlers() {
391
+ if (exitHandlersRegistered) return;
392
+ exitHandlersRegistered = true;
393
+
394
+ // Handle Ctrl+C
395
+ process.on("SIGINT", async () => {
396
+ await handleProcessExit();
397
+ process.exit(130); // Standard exit code for SIGINT
398
+ });
399
+
400
+ // Handle kill command
401
+ process.on("SIGTERM", async () => {
402
+ await handleProcessExit();
403
+ process.exit(143); // Standard exit code for SIGTERM
404
+ });
405
+
406
+ // Handle unexpected exits
407
+ process.on("beforeExit", async () => {
408
+ // Only handle if test run is still running (hasn't been completed normally)
409
+ if (pluginState.testRun && !pluginState.testRunCompleted) {
410
+ await handleProcessExit();
411
+ }
412
+ });
413
+ }
414
+
186
415
  /**
187
416
  * Create the TestDriver Vitest plugin
188
417
  * This sets up global state and provides the registration API
189
418
  */
190
419
  export default function testDriverPlugin(options = {}) {
191
- // Initialize plugin state with options
192
- pluginState.apiKey = options.apiKey;
420
+ // Store options but don't read env vars yet - they may not be loaded
421
+ // Environment variables will be read in onInit after setupFiles run
193
422
  pluginState.apiRoot =
194
- options.apiRoot || process.env.TD_API_ROOT || "http://localhost:1337";
423
+ options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
195
424
  pluginState.ciProvider = detectCI();
196
425
  pluginState.gitInfo = getGitInfo();
426
+
427
+ // Store TestDriver-specific options (excluding plugin-specific ones)
428
+ const { apiKey, apiRoot, ...testDriverOptions } = options;
429
+ pluginState.testDriverOptions = testDriverOptions;
430
+
431
+ // Register process exit handlers to handle cancellation
432
+ registerExitHandlers();
197
433
 
198
434
  // Note: globalThis setup happens in vitestSetup.mjs for worker processes
199
- console.log(
200
- "[TestDriver Plugin] Initialized with API root:",
201
- pluginState.apiRoot,
202
- );
435
+ logger.debug("TestDriver plugin initializing...");
436
+ logger.debug("API root:", pluginState.apiRoot);
437
+ logger.debug("API key from options:", !!options.apiKey);
438
+ logger.debug("API key from env (at config time):", !!process.env.TD_API_KEY);
439
+ logger.debug("CI Provider:", pluginState.ciProvider || "none");
440
+ if (Object.keys(testDriverOptions).length > 0) {
441
+ logger.debug("Global TestDriver options:", testDriverOptions);
442
+ }
203
443
 
204
444
  return new TestDriverReporter(options);
205
445
  }
@@ -211,35 +451,51 @@ export default function testDriverPlugin(options = {}) {
211
451
  class TestDriverReporter {
212
452
  constructor(options = {}) {
213
453
  this.options = options;
214
- console.log("[TestDriver Reporter] Created");
454
+ logger.debug("Reporter created with options:", { hasApiKey: !!options.apiKey, hasApiRoot: !!options.apiRoot });
215
455
  }
216
456
 
217
457
  async onInit(ctx) {
218
458
  this.ctx = ctx;
219
- console.log("[TestDriver Reporter] onInit called");
459
+ logger.debug("onInit called - UPDATED VERSION");
460
+
461
+ // NOW read the API key and API root (after setupFiles have run, including dotenv/config)
462
+ pluginState.apiKey = this.options.apiKey || process.env.TD_API_KEY;
463
+ pluginState.apiRoot = this.options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
464
+ logger.debug("API key from options:", !!this.options.apiKey);
465
+ logger.debug("API key from env (at onInit):", !!process.env.TD_API_KEY);
466
+ logger.debug("API root from options:", this.options.apiRoot);
467
+ logger.debug("API root from env (at onInit):", process.env.TD_API_ROOT);
468
+ logger.debug("Final API key set:", !!pluginState.apiKey);
469
+ logger.debug("Final API root set:", pluginState.apiRoot);
220
470
 
221
471
  // Initialize test run
222
472
  await this.initializeTestRun();
223
473
  }
224
474
 
225
475
  async initializeTestRun() {
226
- console.log("[TestDriver Reporter] Initializing test run...");
476
+ logger.debug("Initializing test run...");
477
+ logger.debug("Current API key in pluginState:", !!pluginState.apiKey);
478
+ logger.debug("Current API root in pluginState:", pluginState.apiRoot);
227
479
 
228
480
  // Check if we should enable the reporter
229
481
  if (!pluginState.apiKey) {
230
- console.log(
231
- "[TestDriver Reporter] No API key provided, skipping test recording",
232
- );
482
+ logger.warn("No API key provided, skipping test recording");
483
+ logger.debug("API key sources - options:", !!this.options.apiKey, "env:", !!process.env.TD_API_KEY);
233
484
  return;
234
485
  }
235
486
 
487
+ logger.info("Starting test run initialization with API key...");
488
+
236
489
  try {
237
490
  // Exchange API key for JWT token
491
+ logger.debug("Authenticating with API...");
238
492
  await authenticate();
493
+ logger.debug("Authentication successful, token received");
239
494
 
240
495
  // Generate unique run ID
241
496
  pluginState.testRunId = generateRunId();
242
497
  pluginState.startTime = Date.now();
498
+ pluginState.testRunCompleted = false; // Reset completion flag
243
499
 
244
500
  // Create test run via direct API call
245
501
  const testRunData = {
@@ -248,6 +504,8 @@ class TestDriverReporter {
248
504
  ...pluginState.gitInfo,
249
505
  };
250
506
 
507
+ // Session ID will be added from the first test result file that includes it
508
+
251
509
  // Only add ciProvider if it's not null
252
510
  if (pluginState.ciProvider) {
253
511
  testRunData.ciProvider = pluginState.ciProvider;
@@ -257,7 +515,9 @@ class TestDriverReporter {
257
515
  // Default to linux if no tests write platform info
258
516
  testRunData.platform = "linux";
259
517
 
518
+ logger.debug("Creating test run with data:", testRunData);
260
519
  pluginState.testRun = await createTestRun(testRunData);
520
+ logger.debug("Test run created successfully:", pluginState.testRun);
261
521
 
262
522
  // Store in environment variables for worker processes to access
263
523
  process.env.TD_TEST_RUN_ID = pluginState.testRunId;
@@ -274,39 +534,35 @@ class TestDriverReporter {
274
534
  startTime: pluginState.startTime,
275
535
  });
276
536
 
277
- console.log(
278
- `[TestDriver Reporter] Test run created: ${pluginState.testRunId}`,
279
- );
537
+ logger.info(`Test run created: ${pluginState.testRunId}`);
280
538
  } catch (error) {
281
- console.error(
282
- "[TestDriver Reporter] Failed to initialize:",
283
- error.message,
284
- );
539
+ logger.error("Failed to initialize:", error.message);
285
540
  pluginState.apiKey = null;
286
541
  pluginState.token = null;
287
542
  }
288
543
  }
289
544
 
290
545
  async onTestRunEnd(testModules, unhandledErrors, reason) {
291
- console.log("[TestDriver Reporter] Test run ending with reason:", reason);
546
+ logger.debug("Test run ending with reason:", reason);
547
+ logger.debug("Plugin state - API key present:", !!pluginState.apiKey, "Test run present:", !!pluginState.testRun);
292
548
 
293
549
  if (!pluginState.apiKey) {
294
- console.log("[TestDriver Reporter] Skipping completion - no API key");
550
+ logger.warn("Skipping completion - no API key (was it cleared after init failure?)");
295
551
  return;
296
552
  }
297
553
 
298
554
  if (!pluginState.testRun) {
299
- console.log(
300
- "[TestDriver Reporter] Skipping completion - no test run created",
301
- );
555
+ logger.warn("Skipping completion - no test run created (check initialization logs)");
302
556
  return;
303
557
  }
304
558
 
559
+ logger.info("Completing test run...");
560
+
305
561
  try {
306
562
  // Calculate statistics from testModules
307
563
  const stats = calculateStatsFromModules(testModules);
308
564
 
309
- console.log(`[TestDriver Reporter] Stats:`, stats);
565
+ logger.debug("Stats:", stats);
310
566
 
311
567
  // Determine overall status based on reason and stats
312
568
  let status = "passed";
@@ -319,9 +575,7 @@ class TestDriverReporter {
319
575
  }
320
576
 
321
577
  // Complete test run via API
322
- console.log(
323
- `[TestDriver Reporter] Completing test run ${pluginState.testRunId} with status: ${status}`,
324
- );
578
+ logger.debug(`Completing test run ${pluginState.testRunId} with status: ${status}`);
325
579
 
326
580
  const completeData = {
327
581
  runId: pluginState.testRunId,
@@ -337,39 +591,28 @@ class TestDriverReporter {
337
591
  const platform = getPlatform();
338
592
  if (platform) {
339
593
  completeData.platform = platform;
340
- console.log(
341
- `[TestDriver Reporter] Updating test run with platform: ${platform}`,
342
- );
594
+ logger.debug(`Updating test run with platform: ${platform}`);
343
595
  }
344
596
 
345
597
  // Wait for any pending operations (shouldn't be any, but just in case)
346
598
  if (pluginState.pendingTestCaseRecords.size > 0) {
347
- console.log(
348
- `[TestDriver Reporter] Waiting for ${pluginState.pendingTestCaseRecords.size} pending operations...`,
349
- );
599
+ logger.debug(`Waiting for ${pluginState.pendingTestCaseRecords.size} pending operations...`);
350
600
  await Promise.all(Array.from(pluginState.pendingTestCaseRecords));
351
601
  }
352
602
 
353
603
  // Test cases are reported directly from teardownTest
354
- console.log(
355
- `[TestDriver Reporter] All test cases reported from teardown`,
356
- );
604
+ logger.debug("All test cases reported from teardown");
357
605
 
358
606
  const completeResponse = await completeTestRun(completeData);
359
- console.log(
360
- `[TestDriver Reporter] ✅ Test run completion API response:`,
361
- completeResponse,
362
- );
607
+ logger.debug("Test run completion API response:", completeResponse);
363
608
 
364
- console.log(
365
- `[TestDriver Reporter] Test run completed: ${stats.passedTests}/${stats.totalTests} passed`,
366
- );
609
+ // Mark test run as completed to prevent duplicate completion
610
+ pluginState.testRunCompleted = true;
611
+
612
+ logger.info(`✅ Test run completed: ${stats.passedTests}/${stats.totalTests} passed`);
367
613
  } catch (error) {
368
- console.error(
369
- "[TestDriver Reporter] Failed to complete test run:",
370
- error.message,
371
- );
372
- console.error("[TestDriver Reporter] Error stack:", error.stack);
614
+ logger.error("Failed to complete test run:", error.message);
615
+ logger.debug("Error stack:", error.stack);
373
616
  }
374
617
  }
375
618
 
@@ -396,15 +639,19 @@ class TestDriverReporter {
396
639
  ? "skipped"
397
640
  : "failed";
398
641
 
399
- console.log(
400
- `[TestDriver Reporter] Test case completed: ${test.name} (${status})`,
401
- );
642
+ logger.info(`Test case completed: ${test.name} (${status})`);
643
+
644
+ // Calculate duration from tracked start time
645
+ const testCase = pluginState.testCases.get(test.id);
646
+ const duration = testCase ? Date.now() - testCase.startTime : 0;
647
+
648
+ logger.debug(`Calculated duration: ${duration}ms (startTime: ${testCase?.startTime}, now: ${Date.now()})`);
402
649
 
403
650
  // Read test metadata from file (cross-process communication)
404
651
  let dashcamUrl = null;
652
+ let sessionId = null;
405
653
  let testFile = "unknown";
406
654
  let testOrder = 0;
407
- let duration = result.duration || 0;
408
655
 
409
656
  const testResultFile = path.join(
410
657
  os.tmpdir(),
@@ -412,11 +659,15 @@ class TestDriverReporter {
412
659
  `${test.id}.json`,
413
660
  );
414
661
 
662
+ logger.debug(`Looking for test result file with test.id: ${test.id}`);
663
+ logger.debug(`Test result file path: ${testResultFile}`);
664
+
415
665
  try {
416
666
  if (fs.existsSync(testResultFile)) {
417
667
  const testResult = JSON.parse(fs.readFileSync(testResultFile, "utf-8"));
418
668
  dashcamUrl = testResult.dashcamUrl || null;
419
669
  const platform = testResult.platform || null;
670
+ sessionId = testResult.sessionId || null;
420
671
  testFile =
421
672
  testResult.testFile ||
422
673
  test.file?.filepath ||
@@ -424,18 +675,15 @@ class TestDriverReporter {
424
675
  "unknown";
425
676
  testOrder =
426
677
  testResult.testOrder !== undefined ? testResult.testOrder : 0;
427
- duration = testResult.duration || result.duration || 0;
678
+ // Don't override duration from file - use Vitest's result.duration
679
+ // duration is already set above from result.duration
428
680
 
429
- console.log(
430
- `[TestDriver Reporter] ✅ Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`,
431
- );
681
+ logger.debug(`Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, sessionId: ${sessionId}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`);
432
682
 
433
683
  // Update test run platform from first test that reports it
434
684
  if (platform && !pluginState.detectedPlatform) {
435
685
  pluginState.detectedPlatform = platform;
436
- console.log(
437
- `[TestDriver Reporter] 🖥️ Detected platform from test: ${platform}`,
438
- );
686
+ logger.debug(`Detected platform from test: ${platform}`);
439
687
  }
440
688
 
441
689
  // Clean up the file after reading
@@ -445,37 +693,36 @@ class TestDriverReporter {
445
693
  // Ignore cleanup errors
446
694
  }
447
695
  } else {
448
- console.log(
449
- `[TestDriver Reporter] ⚠️ No result file found for test: ${test.id}`,
450
- );
696
+ logger.debug(`No result file found for test: ${test.id}`);
451
697
  // Fallback to test object properties - try multiple sources
452
- // In Vitest, the file path is typically on the module, not the test itself
453
- const module = test.module || test.suite;
698
+ // In Vitest, the file path is on test.module.task.filepath
454
699
  testFile =
700
+ test.module?.task?.filepath ||
701
+ test.module?.file?.filepath ||
702
+ test.module?.file?.name ||
455
703
  test.file?.filepath ||
456
704
  test.file?.name ||
457
- module?.file?.filepath ||
458
- module?.file?.name ||
705
+ test.suite?.file?.filepath ||
706
+ test.suite?.file?.name ||
459
707
  test.location?.file ||
460
708
  "unknown";
461
- console.log(
462
- `[TestDriver Reporter] 📂 Resolved testFile for skipped test: ${testFile}`,
463
- );
709
+ logger.debug(`Resolved testFile: ${testFile}`);
464
710
  }
465
711
  } catch (error) {
466
- console.error(
467
- `[TestDriver Reporter] ❌ Failed to read test result file:`,
468
- error.message,
469
- );
712
+ logger.error("Failed to read test result file:", error.message);
470
713
  // Fallback to test object properties - try multiple sources
471
- const module = test.module || test.suite;
714
+ // In Vitest, the file path is on test.module.task.filepath
472
715
  testFile =
716
+ test.module?.task?.filepath ||
717
+ test.module?.file?.filepath ||
718
+ test.module?.file?.name ||
473
719
  test.file?.filepath ||
474
720
  test.file?.name ||
475
- module?.file?.filepath ||
476
- module?.file?.name ||
721
+ test.suite?.file?.filepath ||
722
+ test.suite?.file?.name ||
477
723
  test.location?.file ||
478
724
  "unknown";
725
+ logger.debug(`Resolved testFile from fallback: ${testFile}`);
479
726
  }
480
727
 
481
728
  // Get test run info from environment variables
@@ -483,9 +730,7 @@ class TestDriverReporter {
483
730
  const token = process.env.TD_TEST_RUN_TOKEN;
484
731
 
485
732
  if (!testRunId || !token) {
486
- console.warn(
487
- `[TestDriver Reporter] ⚠️ Test run not initialized, skipping test case recording for: ${test.name}`,
488
- );
733
+ logger.warn(`Test run not initialized, skipping test case recording for: ${test.name}`);
489
734
  return;
490
735
  }
491
736
 
@@ -519,6 +764,11 @@ class TestDriverReporter {
519
764
  retries: result.retryCount || 0,
520
765
  };
521
766
 
767
+ // Add sessionId if available
768
+ if (sessionId) {
769
+ testCaseData.sessionId = sessionId;
770
+ }
771
+
522
772
  // Only include replayUrl if we have a valid dashcam URL
523
773
  if (dashcamUrl) {
524
774
  testCaseData.replayUrl = dashcamUrl;
@@ -528,9 +778,7 @@ class TestDriverReporter {
528
778
  if (errorMessage) testCaseData.errorMessage = errorMessage;
529
779
  if (errorStack) testCaseData.errorStack = errorStack;
530
780
 
531
- console.log(
532
- `[TestDriver Reporter] Recording test case: ${test.name} (${status}) with testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms, replay: ${dashcamUrl ? "yes" : "no"}`,
533
- );
781
+ logger.debug(`Recording test case: ${test.name} (${status}) with testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms, replay: ${dashcamUrl ? "yes" : "no"}`);
534
782
 
535
783
  const testCaseResponse = await recordTestCaseDirect(
536
784
  token,
@@ -541,17 +789,10 @@ class TestDriverReporter {
541
789
  const testCaseDbId = testCaseResponse.data?.id;
542
790
  const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
543
791
 
544
- console.log(
545
- `[TestDriver Reporter] ✅ Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`,
546
- );
547
- console.log(
548
- `[TestDriver Reporter] 🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "app.testdriver.ai")}/test-runs/${testRunDbId}/${testCaseDbId}`,
549
- );
792
+ logger.debug(`Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`);
793
+ logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "console.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
550
794
  } catch (error) {
551
- console.error(
552
- `[TestDriver Reporter] ❌ Failed to report test case:`,
553
- error.message,
554
- );
795
+ logger.error("Failed to report test case:", error.message);
555
796
  }
556
797
  }
557
798
  }
@@ -571,13 +812,11 @@ function getSuiteName() {
571
812
  function getPlatform() {
572
813
  // First try to get platform from SDK client detected during test execution
573
814
  if (pluginState.detectedPlatform) {
574
- console.log(
575
- `[TestDriver Plugin] Using platform from SDK client: ${pluginState.detectedPlatform}`,
576
- );
815
+ logger.debug(`Using platform from SDK client: ${pluginState.detectedPlatform}`);
577
816
  return pluginState.detectedPlatform;
578
817
  }
579
818
 
580
- console.log(`[TestDriver Plugin] Platform not yet detected from client`);
819
+ logger.debug("Platform not yet detected from client");
581
820
  return null;
582
821
  }
583
822
 
@@ -594,9 +833,7 @@ function detectPlatformFromTest(test) {
594
833
  else if (platform === "linux") platform = "linux";
595
834
 
596
835
  pluginState.detectedPlatform = platform;
597
- console.log(
598
- `[TestDriver Plugin] Detected platform from test context: ${platform}`,
599
- );
836
+ logger.debug(`Detected platform from test context: ${platform}`);
600
837
  }
601
838
  }
602
839
 
@@ -657,6 +894,59 @@ function getGitInfo() {
657
894
  if (process.env.CIRCLE_USERNAME) info.author = process.env.CIRCLE_USERNAME;
658
895
  }
659
896
 
897
+ // If not in CI or if commit info is missing, try to get it from local git
898
+ if (!info.commit) {
899
+ try {
900
+ info.commit = execSync("git rev-parse HEAD", {
901
+ encoding: "utf8",
902
+ stdio: ["pipe", "pipe", "ignore"]
903
+ }).trim();
904
+ } catch (e) {
905
+ // Git command failed, ignore
906
+ }
907
+ }
908
+
909
+ if (!info.branch) {
910
+ try {
911
+ info.branch = execSync("git rev-parse --abbrev-ref HEAD", {
912
+ encoding: "utf8",
913
+ stdio: ["pipe", "pipe", "ignore"]
914
+ }).trim();
915
+ } catch (e) {
916
+ // Git command failed, ignore
917
+ }
918
+ }
919
+
920
+ if (!info.author) {
921
+ try {
922
+ info.author = execSync("git config user.name", {
923
+ encoding: "utf8",
924
+ stdio: ["pipe", "pipe", "ignore"]
925
+ }).trim();
926
+ } catch (e) {
927
+ // Git command failed, ignore
928
+ }
929
+ }
930
+
931
+ if (!info.repo) {
932
+ try {
933
+ const remoteUrl = execSync("git config --get remote.origin.url", {
934
+ encoding: "utf8",
935
+ stdio: ["pipe", "pipe", "ignore"]
936
+ }).trim();
937
+
938
+ // Extract repo from git URL (supports both SSH and HTTPS)
939
+ // SSH: git@github.com:user/repo.git
940
+ // HTTPS: https://github.com/user/repo.git
941
+ const match = remoteUrl.match(/[:/]([^/:]+\/[^/:]+?)(\.git)?$/);
942
+ if (match) {
943
+ info.repo = match[1];
944
+ }
945
+ } catch (e) {
946
+ // Git command failed, ignore
947
+ }
948
+ }
949
+
660
950
  return info;
661
951
  }
662
952