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
@@ -3,13 +3,28 @@ const fs = require("fs");
3
3
  const { events } = require("../events");
4
4
  const theme = require("./theme");
5
5
 
6
+ // Default redraw options
7
+ const DEFAULT_REDRAW_OPTIONS = {
8
+ enabled: true, // Master switch to enable/disable redraw detection
9
+ screenRedraw: true, // Enable screen redraw detection
10
+ networkMonitor: true, // Enable network activity monitoring
11
+ diffThreshold: 0.01, // Percentage threshold for screen diff (0.01 = 0.01%)
12
+ };
13
+
6
14
  // Factory function that creates redraw functionality with the provided system instance
7
15
  const createRedraw = (
8
16
  emitter,
9
17
  system,
10
18
  sandbox,
11
- redrawThresholdPercent = 0.1,
19
+ defaultOptions = {},
12
20
  ) => {
21
+ // Merge default options with provided defaults
22
+ const baseOptions = { ...DEFAULT_REDRAW_OPTIONS, ...defaultOptions };
23
+ // Support legacy redrawThresholdPercent number argument
24
+ if (typeof defaultOptions === 'number') {
25
+ baseOptions.diffThreshold = defaultOptions;
26
+ }
27
+
13
28
  const networkUpdateInterval = 15000;
14
29
 
15
30
  let lastTxBytes = null;
@@ -116,16 +131,13 @@ const createRedraw = (
116
131
  { threshold: 0.1 },
117
132
  );
118
133
 
119
- if (differentPixels === 0) {
120
- return false;
121
- } else {
122
- // Calculate percentage difference based on pixel differences
123
- const diffPercentage = (differentPixels / totalPixels) * 100;
124
- return diffPercentage.toFixed(1);
125
- }
134
+ // Calculate percentage difference based on pixel differences
135
+ // Always return a number (0 if no difference)
136
+ const diffPercentage = (differentPixels / totalPixels) * 100;
137
+ return parseFloat(diffPercentage.toFixed(2));
126
138
  } catch (error) {
127
139
  console.error("Error comparing images:", error);
128
- return false;
140
+ return 0; // Return 0 on error instead of false
129
141
  }
130
142
  }
131
143
 
@@ -146,46 +158,104 @@ const createRedraw = (
146
158
  }
147
159
  }
148
160
 
149
- async function start() {
161
+ // Current options for the active redraw cycle
162
+ let currentOptions = { ...baseOptions };
163
+
164
+ async function start(options = {}) {
165
+ // Merge base options with per-call options
166
+ currentOptions = { ...baseOptions, ...options };
167
+
168
+ console.log('[redraw] start() called with options:', JSON.stringify(currentOptions));
169
+
170
+ // If redraw is completely disabled, return early
171
+ if (!currentOptions.enabled) {
172
+ console.log('[redraw] start() - redraw disabled, returning null');
173
+ return null;
174
+ }
175
+
176
+ // If both screenRedraw and networkMonitor are disabled, disable redraw
177
+ if (!currentOptions.screenRedraw && !currentOptions.networkMonitor) {
178
+ currentOptions.enabled = false;
179
+ console.log('[redraw] start() - both screenRedraw and networkMonitor disabled, returning null');
180
+ return null;
181
+ }
182
+
150
183
  resetState();
151
- startNetworkMonitoring();
152
- startImage = await system.captureScreenPNG(0.25, true);
184
+
185
+ // Only start network monitoring if enabled
186
+ if (currentOptions.networkMonitor) {
187
+ startNetworkMonitoring();
188
+ }
189
+
190
+ // Only capture start image if screen redraw is enabled
191
+ if (currentOptions.screenRedraw) {
192
+ startImage = await system.captureScreenPNG(0.25, true);
193
+ console.log('[redraw] start() - captured startImage:', startImage);
194
+ }
195
+
153
196
  return startImage;
154
197
  }
155
198
 
156
- async function checkCondition(resolve, startTime, timeoutMs) {
157
- let nowImage = await system.captureScreenPNG(0.25, true);
199
+ async function checkCondition(resolve, startTime, timeoutMs, options) {
200
+ const { enabled, screenRedraw, networkMonitor, diffThreshold } = options;
201
+
202
+ // If redraw is disabled, resolve immediately
203
+ if (!enabled) {
204
+ resolve("true");
205
+ return;
206
+ }
207
+
208
+ let nowImage = screenRedraw ? await system.captureScreenPNG(0.25, true) : null;
158
209
  let timeElapsed = Date.now() - startTime;
159
210
  let diffPercent = 0;
160
211
  let isTimeout = timeElapsed > timeoutMs;
161
212
 
162
- if (!screenHasRedrawn) {
213
+ // Check screen redraw if enabled and we have a start image to compare against
214
+ if (screenRedraw && !screenHasRedrawn && startImage && nowImage) {
215
+ console.log('[redraw] checkCondition() - comparing images:', { startImage, nowImage });
163
216
  diffPercent = await imageDiffPercent(startImage, nowImage);
164
- screenHasRedrawn = diffPercent > redrawThresholdPercent;
217
+ console.log('[redraw] checkCondition() - diffPercent:', diffPercent, 'threshold:', diffThreshold);
218
+ screenHasRedrawn = diffPercent > diffThreshold;
219
+ console.log('[redraw] checkCondition() - screenHasRedrawn:', screenHasRedrawn);
220
+ } else if (screenRedraw && !startImage) {
221
+ // If no start image was captured, capture one now and wait for next check
222
+ console.log('[redraw] checkCondition() - no startImage, capturing now');
223
+ startImage = await system.captureScreenPNG(0.25, true);
165
224
  }
166
-
167
- // // log redraw as output
168
- let redrawText = screenHasRedrawn
169
- ? theme.green(`y`)
170
- : theme.dim(`${diffPercent}/${redrawThresholdPercent}%`);
171
- let networkText = networkSettled
172
- ? theme.green(`y`)
173
- : theme.dim(
174
- `${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`,
175
- );
225
+
226
+ // If screen redraw is disabled, consider it as "redrawn"
227
+ const effectiveScreenRedrawn = screenRedraw ? screenHasRedrawn : true;
228
+ // If network monitor is disabled, consider it as "settled"
229
+ const effectiveNetworkSettled = networkMonitor ? networkSettled : true;
230
+
231
+ // Log redraw status
232
+ let redrawText = !screenRedraw
233
+ ? theme.dim(`disabled`)
234
+ : effectiveScreenRedrawn
235
+ ? theme.green(`y`)
236
+ : theme.dim(`${diffPercent}/${diffThreshold}%`);
237
+ let networkText = !networkMonitor
238
+ ? theme.dim(`disabled`)
239
+ : effectiveNetworkSettled
240
+ ? theme.green(`y`)
241
+ : theme.dim(
242
+ `${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`,
243
+ );
176
244
  let timeoutText = isTimeout
177
245
  ? theme.green(`y`)
178
246
  : theme.dim(`${Math.floor(timeElapsed / 1000)}/${timeoutMs / 1000}s`);
179
247
 
180
248
  emitter.emit(events.redraw.status, {
181
249
  redraw: {
182
- hasRedrawn: screenHasRedrawn,
250
+ enabled: screenRedraw,
251
+ hasRedrawn: effectiveScreenRedrawn,
183
252
  diffPercent,
184
- threshold: redrawThresholdPercent,
253
+ threshold: diffThreshold,
185
254
  text: redrawText,
186
255
  },
187
256
  network: {
188
- settled: networkSettled,
257
+ enabled: networkMonitor,
258
+ settled: effectiveNetworkSettled,
189
259
  rxBytes: diffRxBytes,
190
260
  txBytes: diffTxBytes,
191
261
  text: networkText,
@@ -198,27 +268,42 @@ const createRedraw = (
198
268
  },
199
269
  });
200
270
 
201
- if ((screenHasRedrawn && networkSettled) || isTimeout) {
271
+ if ((effectiveScreenRedrawn && effectiveNetworkSettled) || isTimeout) {
202
272
  emitter.emit(events.redraw.complete, {
203
- screenHasRedrawn,
204
- networkSettled,
273
+ screenHasRedrawn: effectiveScreenRedrawn,
274
+ networkSettled: effectiveNetworkSettled,
205
275
  isTimeout,
206
276
  timeElapsed,
207
277
  });
208
278
  resolve("true");
209
279
  } else {
210
280
  setTimeout(() => {
211
- checkCondition(resolve, startTime, timeoutMs);
281
+ checkCondition(resolve, startTime, timeoutMs, options);
212
282
  }, 500);
213
283
  }
214
284
  }
215
285
 
216
- function wait(timeoutMs) {
286
+ function wait(timeoutMs, options = {}) {
287
+ // Merge current options with any per-call overrides
288
+ const waitOptions = { ...currentOptions, ...options };
289
+
290
+ // If redraw is disabled, resolve immediately
291
+ if (!waitOptions.enabled) {
292
+ return Promise.resolve("true");
293
+ }
294
+
295
+ // If both are disabled, resolve immediately
296
+ if (!waitOptions.screenRedraw && !waitOptions.networkMonitor) {
297
+ return Promise.resolve("true");
298
+ }
299
+
217
300
  return new Promise((resolve) => {
218
301
  const startTime = Date.now();
219
- // Start network monitoring if not already started
220
- startNetworkMonitoring();
221
- checkCondition(resolve, startTime, timeoutMs);
302
+ // Start network monitoring if not already started and enabled
303
+ if (waitOptions.networkMonitor) {
304
+ startNetworkMonitoring();
305
+ }
306
+ checkCondition(resolve, startTime, timeoutMs, waitOptions);
222
307
  });
223
308
  }
224
309
 
@@ -226,7 +311,7 @@ const createRedraw = (
226
311
  stopNetworkMonitoring(networkInterval);
227
312
  }
228
313
 
229
- return { start, wait, cleanup };
314
+ return { start, wait, cleanup, DEFAULT_OPTIONS: DEFAULT_REDRAW_OPTIONS };
230
315
  };
231
316
 
232
- module.exports = { createRedraw };
317
+ module.exports = { createRedraw, DEFAULT_REDRAW_OPTIONS };
@@ -2,7 +2,7 @@ const WebSocket = require("ws");
2
2
  const marky = require("marky");
3
3
  const { events } = require("../events");
4
4
 
5
- const createSandbox = (emitter, analytics) => {
5
+ const createSandbox = (emitter, analytics, sessionInstance) => {
6
6
  class Sandbox {
7
7
  constructor() {
8
8
  this.socket = null;
@@ -15,6 +15,7 @@ const createSandbox = (emitter, analytics) => {
15
15
  this.messageId = 0;
16
16
  this.uniqueId = Math.random().toString(36).substring(7);
17
17
  this.os = null; // Store OS value to send with every message
18
+ this.sessionInstance = sessionInstance; // Store session instance to include in messages
18
19
  }
19
20
 
20
21
  send(message) {
@@ -35,6 +36,14 @@ const createSandbox = (emitter, analytics) => {
35
36
  message.os = this.os;
36
37
  }
37
38
 
39
+ // Add session to every message if available (for interaction tracking)
40
+ if (this.sessionInstance && !message.session) {
41
+ const sessionId = this.sessionInstance.get();
42
+ if (sessionId) {
43
+ message.session = sessionId;
44
+ }
45
+ }
46
+
38
47
  // Start timing for this message
39
48
  const timingKey = `sandbox-${message.type}`;
40
49
  marky.mark(timingKey);
@@ -114,8 +123,8 @@ const createSandbox = (emitter, analytics) => {
114
123
  this.socket.on("open", async () => {
115
124
  this.apiSocketConnected = true;
116
125
 
117
- setInterval(() => {
118
- if (this.socket.readyState === WebSocket.OPEN) {
126
+ this.heartbeat = setInterval(() => {
127
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
119
128
  this.socket.ping();
120
129
  }
121
130
  }, 5000);
@@ -166,6 +175,34 @@ const createSandbox = (emitter, analytics) => {
166
175
  });
167
176
  });
168
177
  }
178
+
179
+ /**
180
+ * Close the WebSocket connection and clean up resources
181
+ */
182
+ close() {
183
+ if (this.heartbeat) {
184
+ clearInterval(this.heartbeat);
185
+ this.heartbeat = null;
186
+ }
187
+
188
+ if (this.socket) {
189
+ try {
190
+ this.socket.close();
191
+ } catch (err) {
192
+ // Ignore close errors
193
+ }
194
+ this.socket = null;
195
+ }
196
+
197
+ this.apiSocketConnected = false;
198
+ this.instanceSocketConnected = false;
199
+ this.authenticated = false;
200
+ this.instance = null;
201
+
202
+ // Silently clear pending promises without rejecting
203
+ // (rejecting causes unhandled promise rejections during cleanup)
204
+ this.ps = {};
205
+ }
169
206
  }
170
207
 
171
208
  return new Sandbox();
package/agent/lib/sdk.js CHANGED
@@ -194,6 +194,27 @@ const createSDK = (emitter, config, sessionInstance) => {
194
194
 
195
195
  return value;
196
196
  } catch (error) {
197
+ // Check if this is an API validation error with detailed problems
198
+ if (error.response?.data?.problems) {
199
+ const problems = error.response.data.problems;
200
+ const errorMessage = error.response.data.message || 'API validation error';
201
+ const detailedError = new Error(
202
+ `${errorMessage}\n\nDetails:\n${problems.map(p => ` - ${p}`).join('\n')}`
203
+ );
204
+ detailedError.originalError = error;
205
+ detailedError.problems = problems;
206
+
207
+ // Emit the formatted error
208
+ emitter.emit(events.error.sdk, {
209
+ message: detailedError.message,
210
+ code: error.response?.data?.code || error.code,
211
+ problems: problems,
212
+ fullError: error,
213
+ });
214
+
215
+ throw detailedError;
216
+ }
217
+
197
218
  outputError(error);
198
219
  throw error; // Re-throw the error so calling code can handle it properly
199
220
  }
@@ -1,9 +1,9 @@
1
1
  const semver = require("semver");
2
- const package = require("../../package.json");
2
+ const packageJson = require("../../package.json");
3
3
 
4
4
  // Function to check if the new version's minor version is >= current version's minor version
5
5
  module.exports = (inputVersion) => {
6
- const currentParsed = semver.parse(package.version);
6
+ const currentParsed = semver.parse(packageJson.version);
7
7
  const inputParsed = semver.parse(inputVersion.replace("v", ""));
8
8
 
9
9
  if (!currentParsed || !inputParsed) {
@@ -8,7 +8,7 @@
8
8
  rel="stylesheet"
9
9
  />
10
10
  <link rel="icon" type="image/png" href="icon.png" />
11
- <title>TestDriver</title>
11
+ <title>TestDriver - Debugger</title>
12
12
  <style>
13
13
  body {
14
14
  background-color: #111;
package/docs/docs.json CHANGED
@@ -17,67 +17,7 @@
17
17
  "tab": "Computer-Use SDK",
18
18
  "versions": [
19
19
  {
20
- "version": "7.0.0 (SDK)",
21
- "groups": [
22
- {
23
- "group": "Getting Started",
24
- "pages": [
25
- "/v7/getting-started/quickstart"
26
- ]
27
- },
28
- {
29
- "group": "API Reference",
30
- "pages": [
31
- {
32
- "group": "Setup",
33
- "icon": "gear",
34
- "pages": [
35
- "/v7/api/client",
36
- "/v7/api/sandbox"
37
- ]
38
- },
39
- {
40
- "group": "Actions",
41
- "icon": "wand-magic-sparkles",
42
- "pages": [
43
- "/v7/api/ai"
44
- ]
45
- },
46
- {
47
- "group": "Methods",
48
- "icon": "code",
49
- "pages": [
50
- "/v7/api/find",
51
- "/v7/api/click",
52
- "/v7/api/type",
53
- "/v7/api/pressKeys",
54
- "/v7/api/scroll",
55
- "/v7/api/hover",
56
- "/v7/api/exec",
57
- "/v7/api/focusApplication",
58
- "/v7/api/assert"
59
- ]
60
- },
61
- {
62
- "group": "Reference",
63
- "icon": "book",
64
- "pages": [
65
- "/v7/api/elements",
66
- "/v7/api/assertions"
67
- ]
68
- }
69
- ]
70
- },
71
- {
72
- "group": "Guides",
73
- "pages": [
74
- "/v7/guides/migration"
75
- ]
76
- }
77
- ]
78
- },
79
- {
80
- "version": "6.X.X (YAML)",
20
+ "version": "v6 (YAML)",
81
21
  "groups": [
82
22
  {
83
23
  "group": "Overview",
@@ -249,18 +189,93 @@
249
189
  ]
250
190
  }
251
191
  ]
192
+ },
193
+ {
194
+ "version": "v7 (JS Beta)",
195
+ "groups": [
196
+ {
197
+ "group": "Overview",
198
+ "icon": "circle-info",
199
+ "pages": [
200
+ "/v7/overview/what-is-testdriver",
201
+ {
202
+ "group": "Features",
203
+ "icon": "star",
204
+ "pages": [
205
+ "/v7/features/easy-to-write",
206
+ "/v7/features/fast",
207
+ "/v7/features/stable",
208
+ "/v7/features/scalable",
209
+ "/v7/features/ai-native",
210
+ "/v7/features/powerful",
211
+ "/v7/features/observable",
212
+ "/v7/features/enterprise"
213
+ ]
214
+ }
215
+ ]
216
+ },
217
+ {
218
+ "group": "Getting Started",
219
+ "icon": "rocket",
220
+ "pages": [
221
+ "/v7/getting-started/installation",
222
+ "/v7/getting-started/writing-tests",
223
+ "/v7/getting-started/generating-tests",
224
+ "/v7/getting-started/running-and-debugging",
225
+ "/v7/getting-started/setting-up-in-ci"
226
+ ]
227
+ },
228
+ {
229
+ "group": "Examples",
230
+ "icon": "code",
231
+ "pages": [
232
+ "/v7/presets/chrome",
233
+ "/v7/presets/chrome-extension",
234
+ "/v7/presets/vscode",
235
+ "/v7/presets/electron",
236
+ "/v7/presets/webapp"
237
+ ]
238
+ },
239
+ {
240
+ "group": "API Reference",
241
+ "icon": "book",
242
+ "pages": [
243
+ "/v7/api/client",
244
+ "/v7/api/sandbox",
245
+ {
246
+ "group": "Interactions",
247
+ "icon": "bolt",
248
+ "pages": [
249
+ "/v7/api/find",
250
+ "/v7/api/elements",
251
+ "/v7/api/click",
252
+ "/v7/api/doubleClick",
253
+ "/v7/api/rightClick",
254
+ "/v7/api/hover",
255
+ "/v7/api/mouseDown",
256
+ "/v7/api/mouseUp",
257
+ "/v7/api/type",
258
+ "/v7/api/pressKeys",
259
+ "/v7/api/scroll",
260
+ "/v7/api/focusApplication"
261
+ ]
262
+ },
263
+ {
264
+ "group": "AI & Assertions",
265
+ "icon": "wand-magic-sparkles",
266
+ "pages": [
267
+ "/v7/api/act",
268
+ "/v7/api/assert",
269
+ "/v7/api/assertions"
270
+ ]
271
+ },
272
+ "/v7/api/exec",
273
+ "/v7/api/dashcam"
274
+ ]
275
+ }
276
+ ]
252
277
  }
253
278
  ]
254
- },
255
- {
256
- "tab": "Playwright SDK",
257
- "pages": ["v7/playwright"]
258
- },
259
- {
260
- "tab": "Playwright Studio"
261
- },
262
- {
263
- "tab": "Dashcam CLI"
264
279
  }
265
280
  ]
266
281
  },
@@ -0,0 +1,154 @@
1
+ # Best Practices: Element Polling
2
+
3
+ **⚠️ CRITICAL: Never use `wait()` for waiting for elements to appear.**
4
+
5
+ ## Why Avoid `wait()`?
6
+
7
+ Arbitrary waits with `wait()` have several problems:
8
+
9
+ 1. **Brittle**: Fixed timeouts may be too short (causing flaky tests) or too long (wasting time)
10
+ 2. **Slow**: You always wait the full duration, even if the element appears sooner
11
+ 3. **Unreliable**: Network conditions, system load, and other factors affect timing
12
+ 4. **Hard to debug**: When tests fail, you don't know if it was a timing issue or actual failure
13
+
14
+ ## The Right Way: Element Polling with `find()`
15
+
16
+ TestDriver's `find()` method is designed for element detection. Use it in a polling loop to wait for elements:
17
+
18
+ ### Basic Polling Pattern
19
+
20
+ ```javascript
21
+ // ❌ WRONG: Using wait()
22
+ await testdriver.wait(2000);
23
+ const button = await testdriver.find("Submit button");
24
+
25
+ // ✅ CORRECT: Polling with find()
26
+ let button;
27
+ for (let i = 0; i < 10; i++) {
28
+ try {
29
+ button = await testdriver.find("Submit button");
30
+ if (button.found()) break;
31
+ } catch (e) {
32
+ if (i === 9) throw e; // Re-throw on last attempt
33
+ }
34
+ await new Promise(resolve => setTimeout(resolve, 1000));
35
+ }
36
+ ```
37
+
38
+ ### Helper Function for Polling
39
+
40
+ Create a reusable helper function:
41
+
42
+ ```javascript
43
+ async function waitForElement(testdriver, description, maxAttempts = 10, delayMs = 1000) {
44
+ for (let i = 0; i < maxAttempts; i++) {
45
+ try {
46
+ const element = await testdriver.find(description);
47
+ if (element.found()) {
48
+ return element;
49
+ }
50
+ } catch (e) {
51
+ if (i === maxAttempts - 1) throw e;
52
+ }
53
+ await new Promise(resolve => setTimeout(resolve, delayMs));
54
+ }
55
+ throw new Error(`Element not found after ${maxAttempts} attempts: ${description}`);
56
+ }
57
+
58
+ // Usage
59
+ const emailField = await waitForElement(testdriver, "Email input field");
60
+ await emailField.click();
61
+ ```
62
+
63
+ ## When to Use Polling
64
+
65
+ Use element polling in these scenarios:
66
+
67
+ - **After navigation**: Waiting for a new page to load
68
+ - **After user action**: Waiting for UI updates (form submission, modal opening, etc.)
69
+ - **Dynamic content**: Waiting for AJAX-loaded elements
70
+ - **State transitions**: Waiting for loading spinners to disappear or success messages to appear
71
+
72
+ ## Example: Complete Login Flow
73
+
74
+ ```javascript
75
+ import { chrome } from "testdriverai/presets";
76
+
77
+ // Helper function
78
+ async function waitForElement(testdriver, description, maxAttempts = 10, delayMs = 1000) {
79
+ for (let i = 0; i < maxAttempts; i++) {
80
+ try {
81
+ const element = await testdriver.find(description);
82
+ if (element.found()) return element;
83
+ } catch (e) {
84
+ if (i === maxAttempts - 1) throw e;
85
+ }
86
+ await new Promise(resolve => setTimeout(resolve, delayMs));
87
+ }
88
+ throw new Error(`Element not found after ${maxAttempts} attempts: ${description}`);
89
+ }
90
+
91
+ it("should log in successfully", async (context) => {
92
+ const { testdriver } = await chrome(context, {
93
+ url: 'https://example.com/login',
94
+ });
95
+
96
+ // Wait for login page to load
97
+ const emailField = await waitForElement(testdriver, "Email input field");
98
+ await emailField.click();
99
+ await testdriver.type("user@example.com");
100
+
101
+ const passwordField = await testdriver.find("Password input field");
102
+ await passwordField.click();
103
+ await testdriver.type("password123");
104
+
105
+ const loginButton = await testdriver.find("Login button");
106
+ await loginButton.click();
107
+
108
+ // Wait for dashboard to load after login
109
+ await waitForElement(testdriver, "Dashboard welcome message");
110
+
111
+ // Verify login successful
112
+ const isLoggedIn = await testdriver.assert("user is logged in to dashboard");
113
+ expect(isLoggedIn).toBeTruthy();
114
+ });
115
+ ```
116
+
117
+ ## Advanced: Conditional Polling
118
+
119
+ For elements that may or may not appear (like dialogs or notifications):
120
+
121
+ ```javascript
122
+ // Try to find and dismiss optional dialog
123
+ try {
124
+ const dialog = await waitForElement(testdriver, "Cookie consent dialog", 3, 500);
125
+ const acceptButton = await testdriver.find("Accept button");
126
+ await acceptButton.click();
127
+ console.log("Dismissed cookie dialog");
128
+ } catch {
129
+ console.log("No cookie dialog found, continuing...");
130
+ }
131
+ ```
132
+
133
+ ## Configuration
134
+
135
+ Adjust polling parameters based on your needs:
136
+
137
+ ```javascript
138
+ // Quick polling for fast UI updates (check every 300ms, up to 3 seconds)
139
+ await waitForElement(testdriver, "Success message", 10, 300);
140
+
141
+ // Patient polling for slow operations (check every 2s, up to 20 seconds)
142
+ await waitForElement(testdriver, "Processing complete indicator", 10, 2000);
143
+ ```
144
+
145
+ ## Summary
146
+
147
+ | Pattern | Use Case |
148
+ |---------|----------|
149
+ | **Polling with `find()`** | ✅ Waiting for UI elements to appear or disappear |
150
+ | **`wait()`** | ❌ NEVER use for element waiting |
151
+ | **Helper function** | ✅ Recommended for cleaner, reusable code |
152
+ | **Conditional polling** | ✅ For optional elements (dialogs, notifications) |
153
+
154
+ Remember: **If you're waiting for something to appear on screen, use `find()` in a polling loop, not `wait()`.**