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
@@ -0,0 +1,653 @@
1
+ /**
2
+ * Test Helpers and Utilities
3
+ * Shared functions for SDK tests
4
+ */
5
+
6
+ import crypto from "crypto";
7
+ import { config } from "dotenv";
8
+ import fs from "fs";
9
+ import os from "os";
10
+ import path, { dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import TestDriver from "../../../sdk.js";
13
+ import {
14
+ addDashcamLog,
15
+ authDashcam,
16
+ launchChrome,
17
+ runPostrun,
18
+ runPrerun,
19
+ startDashcam,
20
+ stopDashcam,
21
+ waitForPage,
22
+ } from "./lifecycleHelpers.mjs";
23
+
24
+ // Re-export lifecycle helpers for backward compatibility
25
+ export {
26
+ addDashcamLog,
27
+ authDashcam,
28
+ launchChrome,
29
+ runPostrun,
30
+ runPrerun,
31
+ startDashcam,
32
+ stopDashcam,
33
+ waitForPage
34
+ };
35
+
36
+ // Get the directory of the current module
37
+ const __filename = fileURLToPath(import.meta.url);
38
+ const __dirname = dirname(__filename);
39
+
40
+ // Load environment variables from .env file in the project root
41
+ // Go up 3 levels from setup/ to reach the project root
42
+ const envPath = path.resolve(__dirname, "../../../.env");
43
+ config({ path: envPath });
44
+
45
+ // Log loaded env vars for debugging
46
+ console.log("🔧 Environment variables loaded from:", envPath);
47
+ console.log(" TD_API_KEY:", process.env.TD_API_KEY ? "✓ Set" : "✗ Not set");
48
+ console.log(" TD_API_ROOT:", process.env.TD_API_ROOT || "Not set");
49
+ console.log(
50
+ " TD_OS:",
51
+ process.env.TD_OS || "Not set (will default to linux)",
52
+ );
53
+
54
+ // Global test results storage
55
+ const testResults = {
56
+ tests: [],
57
+ startTime: Date.now(),
58
+ };
59
+
60
+ /**
61
+ * Store test result with dashcam URL
62
+ * @param {string} testName - Name of the test
63
+ * @param {string} testFile - Test file path
64
+ * @param {string|null} dashcamUrl - Dashcam URL if available
65
+ * @param {Object} sessionInfo - Session information
66
+ */
67
+ export function storeTestResult(
68
+ testName,
69
+ testFile,
70
+ dashcamUrl,
71
+ sessionInfo = {},
72
+ ) {
73
+ console.log(`📝 Storing test result: ${testName}`);
74
+ console.log(` Dashcam URL: ${dashcamUrl || "none"}`);
75
+
76
+ // Extract replay object ID from dashcam URL
77
+ let replayObjectId = null;
78
+ if (dashcamUrl) {
79
+ const replayIdMatch = dashcamUrl.match(/\/replay\/([^?]+)/);
80
+ replayObjectId = replayIdMatch ? replayIdMatch[1] : null;
81
+ if (replayObjectId) {
82
+ console.log(` Replay Object ID: ${replayObjectId}`);
83
+ }
84
+ }
85
+
86
+ testResults.tests.push({
87
+ name: testName,
88
+ file: testFile,
89
+ dashcamUrl,
90
+ replayObjectId,
91
+ sessionId: sessionInfo.sessionId,
92
+ timestamp: new Date().toISOString(),
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Get all test results
98
+ * @returns {Object} All collected test results
99
+ */
100
+ export function getTestResults() {
101
+ return {
102
+ ...testResults,
103
+ endTime: Date.now(),
104
+ duration: Date.now() - testResults.startTime,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Save test results to a JSON file
110
+ * @param {string} outputPath - Path to save the results
111
+ */
112
+ export function saveTestResults(outputPath = "test-results/sdk-summary.json") {
113
+ const results = getTestResults();
114
+ const dir = path.dirname(outputPath);
115
+
116
+ // Create directory if it doesn't exist
117
+ if (!fs.existsSync(dir)) {
118
+ fs.mkdirSync(dir, { recursive: true });
119
+ }
120
+
121
+ fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
122
+ console.log(`\n📊 Test results saved to: ${outputPath}`);
123
+
124
+ // Also print dashcam URLs to console
125
+ console.log("\n🎥 Dashcam URLs:");
126
+ results.tests.forEach((test) => {
127
+ if (test.dashcamUrl) {
128
+ console.log(` ${test.name}: ${test.dashcamUrl}`);
129
+ }
130
+ });
131
+
132
+ return results;
133
+ }
134
+
135
+ /**
136
+ * Intercept console logs and forward to TestDriver sandbox
137
+ * @param {TestDriver} client - TestDriver client instance
138
+ * @param {string} taskId - Unique task identifier for this test
139
+ */
140
+ function setupConsoleInterceptor(client, taskId) {
141
+ // Store original console methods
142
+ const originalConsole = {
143
+ log: console.log,
144
+ error: console.error,
145
+ warn: console.warn,
146
+ info: console.info,
147
+ };
148
+
149
+ // Create wrapper that forwards to sandbox
150
+ const createInterceptor = (level, originalMethod) => {
151
+ return function (...args) {
152
+ // Call original console method first
153
+ originalMethod.apply(console, args);
154
+
155
+ // Forward to sandbox if connected
156
+ if (client.sandbox && client.sandbox.instanceSocketConnected) {
157
+ try {
158
+ // Format the log message
159
+ const message = args
160
+ .map((arg) =>
161
+ typeof arg === "object"
162
+ ? JSON.stringify(arg, null, 2)
163
+ : String(arg),
164
+ )
165
+ .join(" ");
166
+
167
+ // Preserve ANSI color codes and emojis for rich sandbox output
168
+ // (don't add level prefix - sdk-log-formatter handles styling)
169
+ const logOutput = message;
170
+
171
+ client.sandbox.send({
172
+ type: "output",
173
+ output: Buffer.from(logOutput, "utf8").toString("base64"),
174
+ });
175
+ } catch (error) {
176
+ // Silently fail to avoid breaking the test
177
+ // Use original console to avoid infinite loop
178
+ originalConsole.error(
179
+ `[TestHelpers] Failed to forward log to sandbox:`,
180
+ error.message,
181
+ );
182
+ }
183
+ }
184
+ };
185
+ };
186
+
187
+ // Replace console methods with interceptors
188
+ console.log = createInterceptor("log", originalConsole.log);
189
+ console.error = createInterceptor("error", originalConsole.error);
190
+ console.warn = createInterceptor("warn", originalConsole.warn);
191
+ console.info = createInterceptor("info", originalConsole.info);
192
+
193
+ // Store original methods and taskId on client for cleanup
194
+ client._consoleInterceptor = {
195
+ taskId,
196
+ original: originalConsole,
197
+ };
198
+
199
+ // Use original console for this message
200
+ originalConsole.log(
201
+ `[TestHelpers] Console interceptor enabled for task: ${taskId}`,
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Remove console interceptor and restore original console methods
207
+ * @param {TestDriver} client - TestDriver client instance
208
+ */
209
+ function removeConsoleInterceptor(client) {
210
+ if (client._consoleInterceptor) {
211
+ const { original, taskId } = client._consoleInterceptor;
212
+
213
+ // Restore original console methods
214
+ console.log = original.log;
215
+ console.error = original.error;
216
+ console.warn = original.warn;
217
+ console.info = original.info;
218
+
219
+ // Use original console for cleanup message
220
+ original.log(
221
+ `[TestHelpers] Console interceptor removed for task: ${taskId}`,
222
+ );
223
+
224
+ // Clean up reference
225
+ delete client._consoleInterceptor;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Create a configured TestDriver client
231
+ * @param {Object} options - Additional options
232
+ * @param {Object} options.task - Vitest task context (from beforeEach/it context)
233
+ * @returns {TestDriver} Configured client
234
+ */
235
+ export function createTestClient(options = {}) {
236
+ // Check if API key is set
237
+ if (!process.env.TD_API_KEY) {
238
+ console.error("\n❌ Error: TD_API_KEY is not set!");
239
+ console.error("Please set it in one of the following ways:");
240
+ console.error(
241
+ " 1. Create a .env file in the project root with: TD_API_KEY=your_key",
242
+ );
243
+ console.error(
244
+ " 2. Pass it as an environment variable: TD_API_KEY=your_key npm run test:sdk",
245
+ );
246
+ console.error(" 3. Export it in your shell: export TD_API_KEY=your_key\n");
247
+ throw new Error("TD_API_KEY environment variable is required");
248
+ }
249
+
250
+ // Determine OS from TEST_PLATFORM or TD_OS
251
+ const os = process.env.TEST_PLATFORM || "linux";
252
+
253
+ // Extract task context if provided - we use taskId but remove task from clientOptions
254
+ let taskId = options.task?.id || options.task?.name || null;
255
+
256
+ // Remove task from options before passing to TestDriver (eslint wants us to use 'task')
257
+ // eslint-disable-next-line no-unused-vars
258
+ const { task, ...clientOptions } = options;
259
+
260
+ const client = new TestDriver(process.env.TD_API_KEY, {
261
+ resolution: "1366x768",
262
+ analytics: true,
263
+ os: os, // Use OS from environment variable (windows or linux)
264
+ apiKey: process.env.TD_API_KEY,
265
+ apiRoot: process.env.TD_API_ROOT || "https://testdriver-api.onrender.com",
266
+ // headless: false,
267
+ newSandbox: true,
268
+ // ip: '18.217.194.23'
269
+ // ...clientOptions,
270
+ // cache: false,
271
+ });
272
+
273
+ console.log(
274
+ "🔧 createTestClient: SDK created, cacheThresholds =",
275
+ client.cacheThresholds,
276
+ );
277
+
278
+ console.log(`[TestHelpers] Client OS configured as: ${client.os}`);
279
+
280
+ // Set Vitest task ID if available (for log filtering in parallel tests)
281
+ if (taskId) {
282
+ console.log(`[TestHelpers] Storing task ID on client: ${taskId}`);
283
+ // Store task ID directly on client for later use in teardown
284
+ client.vitestTaskId = taskId;
285
+ } else {
286
+ console.log(`[TestHelpers] No task ID available`);
287
+ }
288
+
289
+ // Enable detailed event logging if requested
290
+ if (process.env.DEBUG_EVENTS === "true") {
291
+ setupEventLogging(client);
292
+ }
293
+
294
+ return client;
295
+ }
296
+
297
+ /**
298
+ * Set up detailed event logging for debugging
299
+ * @param {TestDriver} client - TestDriver client
300
+ */
301
+ export function setupEventLogging(client) {
302
+ const emitter = client.getEmitter();
303
+
304
+ // Log all events
305
+ emitter.on("**", function (data) {
306
+ const event = this.event;
307
+ if (event.startsWith("log:debug")) return; // Skip debug logs
308
+ console.log(`[EVENT] ${event}`, data || "");
309
+ });
310
+
311
+ // Log command lifecycle
312
+ emitter.on("command:start", (data) => {
313
+ console.log("🚀 Command started:", data);
314
+ });
315
+
316
+ emitter.on("command:success", (data) => {
317
+ console.log("✅ Command succeeded:", data);
318
+ });
319
+
320
+ emitter.on("command:error", (data) => {
321
+ console.error("❌ Command error:", data);
322
+ });
323
+
324
+ // Log sandbox events
325
+ emitter.on("sandbox:connected", () => {
326
+ console.log("🔌 Sandbox connected");
327
+ });
328
+
329
+ emitter.on("sandbox:authenticated", () => {
330
+ console.log("🔐 Sandbox authenticated");
331
+ });
332
+
333
+ emitter.on("sandbox:error", (error) => {
334
+ console.error("⚠️ Sandbox error:", error);
335
+ });
336
+
337
+ // Log SDK API calls
338
+ emitter.on("sdk:request", (data) => {
339
+ console.log("📤 SDK Request:", data);
340
+ });
341
+
342
+ emitter.on("sdk:response", (data) => {
343
+ console.log("📥 SDK Response:", data);
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Setup function to run before each test
349
+ * Authenticates and connects to sandbox
350
+ * @param {TestDriver} client - TestDriver client
351
+ * @param {Object} options - Connection options
352
+ * @returns {Promise<Object>} Sandbox instance
353
+ */
354
+ export async function setupTest(client, options = {}) {
355
+ await client.auth();
356
+ const instance = await client.connect({
357
+ ...options,
358
+ });
359
+
360
+ // Set up console interceptor after connection (needs sandbox to be connected)
361
+ if (client.vitestTaskId) {
362
+ setupConsoleInterceptor(client, client.vitestTaskId);
363
+ }
364
+
365
+ // Run prerun lifecycle if enabled
366
+ if (options.prerun !== false) {
367
+ await runPrerun(client);
368
+ }
369
+
370
+ return instance;
371
+ }
372
+
373
+ /**
374
+ * Initialize a test run for the entire suite
375
+ * Should be called once in beforeEach
376
+ * @param {Object} suiteTask - Vitest suite task context
377
+ * @returns {Promise<Object>} Test run info { runId, testRunDbId, token }
378
+ */
379
+ export async function initializeSuiteTestRun(suiteTask) {
380
+ const apiKey = process.env.TD_API_KEY;
381
+ const apiRoot =
382
+ process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
383
+
384
+ if (!apiKey || !globalThis.__testdriverPlugin) {
385
+ console.log(
386
+ `[TestHelpers] Skipping suite test run initialization - no API key or plugin`,
387
+ );
388
+ return null;
389
+ }
390
+
391
+ // Check if test run already exists for this suite
392
+ const existingRun = globalThis.__testdriverPlugin.getSuiteTestRun(
393
+ suiteTask.id,
394
+ );
395
+ if (existingRun) {
396
+ console.log(
397
+ `[TestHelpers] Test run already exists for suite: ${existingRun.runId}`,
398
+ );
399
+ return existingRun;
400
+ }
401
+
402
+ try {
403
+ console.log(
404
+ `[TestHelpers] Initializing test run for suite: ${suiteTask.name}`,
405
+ );
406
+
407
+ // Authenticate
408
+ const token = await globalThis.__testdriverPlugin.authenticateWithApiKey(
409
+ apiKey,
410
+ apiRoot,
411
+ );
412
+ console.log(`[TestHelpers] ✅ Authenticated for suite`);
413
+
414
+ // Create test run for the suite
415
+ const runId = `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
416
+ const testFile = suiteTask.file?.name || "unknown";
417
+ const testRunData = {
418
+ runId,
419
+ suiteName: suiteTask.name || testFile,
420
+ };
421
+
422
+ const testRunResponse =
423
+ await globalThis.__testdriverPlugin.createTestRunDirect(
424
+ token,
425
+ apiRoot,
426
+ testRunData,
427
+ );
428
+ const testRunDbId = testRunResponse.data?.id;
429
+
430
+ const runInfo = { runId, testRunDbId, token };
431
+
432
+ // Store in plugin state
433
+ globalThis.__testdriverPlugin.setSuiteTestRun(suiteTask.id, runInfo);
434
+
435
+ // Set environment variables for the reporter to use
436
+ process.env.TD_TEST_RUN_ID = runId;
437
+ process.env.TD_TEST_RUN_DB_ID = testRunDbId;
438
+ process.env.TD_TEST_RUN_TOKEN = token;
439
+
440
+ console.log(
441
+ `[TestHelpers] ✅ Created test run for suite: ${runId} (DB ID: ${testRunDbId})`,
442
+ );
443
+
444
+ return runInfo;
445
+ } catch (error) {
446
+ console.error(
447
+ `[TestHelpers] ❌ Failed to initialize suite test run:`,
448
+ error.message,
449
+ );
450
+ return null;
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Teardown function to run after each test
456
+ * @param {TestDriver} client - TestDriver client
457
+ * @param {Object} options - Teardown options
458
+ * @param {Object} options.task - Vitest task context (optional, for storing in task.meta)
459
+ * @param {string} options.dashcamUrl - Dashcam URL if already retrieved
460
+ * @param {boolean} options.postrun - Whether to run postrun lifecycle (default: true)
461
+ * @param {boolean} options.disconnect - Whether to disconnect client (default: true)
462
+ * @returns {Promise<Object>} Session info including dashcam URL
463
+ */
464
+ export async function teardownTest(client, options = {}) {
465
+ let dashcamUrl = options.dashcamUrl || null;
466
+
467
+ console.log("🧹 Running teardown...");
468
+
469
+ try {
470
+ // Run postrun lifecycle if enabled and dashcamUrl not already provided
471
+ if (options.postrun !== false && !dashcamUrl) {
472
+ dashcamUrl = await runPostrun(client);
473
+
474
+ // Store dashcamUrl in client for reporter access
475
+ if (dashcamUrl) {
476
+ // Extract replay object ID from URL
477
+ // URL format: https://app.testdriver.ai/replay/{replayObjectId}?share={shareToken}
478
+ const replayIdMatch = dashcamUrl.match(/\/replay\/([^?]+)/);
479
+ const replayObjectId = replayIdMatch ? replayIdMatch[1] : null;
480
+
481
+ console.log(`🎥 Dashcam URL: ${dashcamUrl}`);
482
+ if (replayObjectId) {
483
+ console.log(`📝 Replay Object ID: ${replayObjectId}`);
484
+ }
485
+
486
+ // Store dashcam URL in task meta
487
+ if (options.task) {
488
+ options.task.meta.testdriverDashcamUrl = dashcamUrl;
489
+ options.task.meta.testdriverReplayObjectId = replayObjectId;
490
+ console.log(
491
+ `[TestHelpers] ✅ Stored dashcam URL in task.meta for test: ${options.task.name}`,
492
+ );
493
+ }
494
+ }
495
+ } else {
496
+ console.log("⏭️ Postrun skipped (disabled in options)");
497
+ }
498
+
499
+ // Write test result to a file for the reporter to pick up (cross-process communication)
500
+ if (options.task) {
501
+ const testResultFile = path.join(
502
+ os.tmpdir(),
503
+ "testdriver-results",
504
+ `${options.task.id}.json`,
505
+ );
506
+
507
+ try {
508
+ // Ensure directory exists
509
+ const dir = path.dirname(testResultFile);
510
+ if (!fs.existsSync(dir)) {
511
+ fs.mkdirSync(dir, { recursive: true });
512
+ }
513
+
514
+ // Get test file path
515
+ const testFile =
516
+ options.task.file?.filepath || options.task.file?.name || "unknown";
517
+
518
+ // Calculate test order (index within parent suite)
519
+ let testOrder = 0;
520
+ if (options.task.suite && options.task.suite.tasks) {
521
+ testOrder = options.task.suite.tasks.indexOf(options.task);
522
+ }
523
+
524
+ // Note: Duration is calculated by Vitest and passed via result.duration
525
+ // We include it in the test result file so the reporter can use it
526
+
527
+ // Get duration from Vitest result
528
+ const result = options.task.result?.();
529
+ const duration = result?.duration || 0;
530
+
531
+ // Write test result with dashcam URL, platform, and metadata
532
+ const testResult = {
533
+ testId: options.task.id,
534
+ testName: options.task.name,
535
+ testFile: testFile,
536
+ testOrder: testOrder,
537
+ dashcamUrl: dashcamUrl,
538
+ replayObjectId: dashcamUrl
539
+ ? dashcamUrl.match(/\/replay\/([^?]+)/)?.[1]
540
+ : null,
541
+ platform: client.os, // Include platform from SDK client (source of truth)
542
+ timestamp: Date.now(),
543
+ duration: duration, // Include duration from Vitest
544
+ };
545
+
546
+ fs.writeFileSync(testResultFile, JSON.stringify(testResult, null, 2));
547
+ console.log(
548
+ `[TestHelpers] ✅ Wrote test result to file: ${testResultFile} (testFile: ${testFile}, testOrder: ${testOrder})`,
549
+ );
550
+ } catch (error) {
551
+ console.error(
552
+ `[TestHelpers] ❌ Failed to write test result file:`,
553
+ error.message,
554
+ );
555
+ }
556
+ }
557
+ } catch (error) {
558
+ console.error("❌ Error in postrun:", error);
559
+ console.error("❌ Error stack:", error.stack);
560
+ } finally {
561
+ // Remove console interceptor before disconnecting
562
+ removeConsoleInterceptor(client);
563
+
564
+ // Only disconnect if not explicitly disabled
565
+ if (options.disconnect !== false) {
566
+ console.log("🔌 Disconnecting client...");
567
+ try {
568
+ await client.disconnect();
569
+ console.log("✅ Client disconnected");
570
+ } catch (disconnectError) {
571
+ console.error("❌ Error disconnecting:", disconnectError.message);
572
+ // Don't throw - we're already in cleanup
573
+ }
574
+ } else {
575
+ console.log("⏭️ Disconnect skipped (disabled in options)");
576
+ }
577
+ }
578
+
579
+ // Extract replay object ID from dashcam URL
580
+ let replayObjectId = null;
581
+ if (dashcamUrl) {
582
+ const replayIdMatch = dashcamUrl.match(/\/replay\/([^?]+)/);
583
+ replayObjectId = replayIdMatch ? replayIdMatch[1] : null;
584
+ }
585
+
586
+ const sessionInfo = {
587
+ sessionId: client.getSessionId(),
588
+ dashcamUrl: dashcamUrl,
589
+ replayObjectId: replayObjectId,
590
+ instance: client.getInstance(),
591
+ };
592
+
593
+ console.log("📊 Session info:", JSON.stringify(sessionInfo, null, 2));
594
+
595
+ return sessionInfo;
596
+ }
597
+
598
+ /**
599
+ * Perform login flow (reusable snippet)
600
+ * @param {TestDriver} client - TestDriver client
601
+ * @param {string} username - Username (default: 'standard_user')
602
+ * @param {string} password - Password (default: retrieved from screen)
603
+ */
604
+ export async function performLogin(
605
+ client,
606
+ username = "standard_user",
607
+ password = null,
608
+ ) {
609
+ await client.focusApplication("Google Chrome");
610
+
611
+ // Get password from screen if not provided
612
+ if (!password) {
613
+ password = await client.remember("the password");
614
+ }
615
+
616
+ const usernameField = await client.find(
617
+ "Username, label above the username input field on the login form",
618
+ );
619
+ await usernameField.click();
620
+ await client.type(username);
621
+
622
+ // Enter password (marked as secret so it's not logged or stored)
623
+ await client.pressKeys(["tab"]);
624
+ await client.type(password, { secret: true });
625
+
626
+ // Submit form
627
+ await client.pressKeys(["tab"]);
628
+ await client.pressKeys(["enter"]);
629
+ }
630
+
631
+ /**
632
+ * Wait with retry logic
633
+ * @param {Function} fn - Async function to retry
634
+ * @param {number} retries - Number of retries (default: 3)
635
+ * @param {number} delay - Delay between retries in ms (default: 1000)
636
+ * @returns {Promise} Result of successful execution
637
+ */
638
+ export async function retryAsync(fn, retries = 3, delay = 1000) {
639
+ let lastError;
640
+
641
+ for (let i = 0; i < retries; i++) {
642
+ try {
643
+ return await fn();
644
+ } catch (error) {
645
+ lastError = error;
646
+ if (i < retries - 1) {
647
+ await new Promise((resolve) => setTimeout(resolve, delay));
648
+ }
649
+ }
650
+ }
651
+
652
+ throw lastError;
653
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { TestDriver } from "../../src/vitest/hooks.mjs";
2
+ import { TestDriver } from "../../lib/vitest/hooks.mjs";
3
3
 
4
4
  describe("Type Test", () => {
5
5
  it("should enter standard_user in username field", async (context) => {