@testdriverai/mcp 7.10.0-canary.0 → 7.10.1-test

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 (196) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/agent/index.js +116 -71
  3. package/agent/interface.js +13 -1
  4. package/agent/lib/commands.js +35 -15
  5. package/agent/lib/provision-commands.js +92 -11
  6. package/agent/lib/sandbox.js +263 -30
  7. package/agent/lib/sdk.js +16 -12
  8. package/agent/lib/system.js +107 -31
  9. package/ai/.claude-plugin/plugin.json +89 -4
  10. package/ai/agents/testdriver.md +4 -8
  11. package/ai/skills/testdriver-assert/SKILL.md +0 -1
  12. package/ai/skills/testdriver-aws-setup/SKILL.md +1 -1
  13. package/ai/skills/testdriver-caching/SKILL.md +13 -6
  14. package/ai/skills/testdriver-ci-cd/SKILL.md +57 -14
  15. package/ai/skills/testdriver-client/SKILL.md +10 -6
  16. package/ai/skills/testdriver-customizing-devices/SKILL.md +7 -5
  17. package/ai/skills/testdriver-dashcam/SKILL.md +36 -3
  18. package/ai/skills/testdriver-debugging-with-screenshots/SKILL.md +28 -4
  19. package/ai/skills/testdriver-device-config/SKILL.md +5 -5
  20. package/ai/skills/testdriver-find/SKILL.md +50 -11
  21. package/ai/skills/testdriver-generating-tests/SKILL.md +163 -8
  22. package/ai/skills/testdriver-hosted/SKILL.md +1 -1
  23. package/ai/skills/testdriver-machine-setup/SKILL.md +331 -0
  24. package/ai/skills/testdriver-making-assertions/SKILL.md +78 -2
  25. package/ai/skills/testdriver-mcp-workflow/SKILL.md +2 -2
  26. package/ai/skills/testdriver-parse/SKILL.md +1 -1
  27. package/ai/skills/testdriver-performing-actions/SKILL.md +97 -3
  28. package/ai/skills/testdriver-provision/SKILL.md +0 -20
  29. package/ai/skills/testdriver-quickstart/SKILL.md +299 -21
  30. package/ai/skills/testdriver-reusable-code/SKILL.md +3 -3
  31. package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
  32. package/ai/skills/testdriver-screenshot/SKILL.md +5 -5
  33. package/ai/skills/testdriver-scroll/SKILL.md +30 -160
  34. package/ai/skills/testdriver-test-writer/SKILL.md +3 -5
  35. package/ai/skills/testdriver-testdriver/SKILL.md +3 -7
  36. package/ai/skills/testdriver-wait/SKILL.md +1 -1
  37. package/{docs/v7/ai.mdx → ai/skills/testdriver:ai/SKILL.md} +3 -4
  38. package/ai/skills/testdriver:assert/SKILL.md +315 -0
  39. package/ai/skills/testdriver:aws-setup/SKILL.md +448 -0
  40. package/ai/skills/testdriver:cache/SKILL.md +221 -0
  41. package/ai/skills/testdriver:caching/SKILL.md +124 -0
  42. package/ai/skills/testdriver:captcha/SKILL.md +158 -0
  43. package/ai/skills/testdriver:ci-cd/SKILL.md +659 -0
  44. package/ai/skills/testdriver:click/SKILL.md +286 -0
  45. package/ai/skills/testdriver:client/SKILL.md +477 -0
  46. package/ai/skills/testdriver:customizing-devices/SKILL.md +319 -0
  47. package/ai/skills/testdriver:dashcam/SKILL.md +451 -0
  48. package/ai/skills/testdriver:debugging-with-screenshots/SKILL.md +415 -0
  49. package/{docs/v7/device-config.mdx → ai/skills/testdriver:device-config/SKILL.md} +8 -8
  50. package/ai/skills/testdriver:double-click/SKILL.md +102 -0
  51. package/ai/skills/testdriver:elements/SKILL.md +605 -0
  52. package/ai/skills/testdriver:enterprise/SKILL.md +7 -0
  53. package/ai/skills/testdriver:errors/SKILL.md +246 -0
  54. package/ai/skills/testdriver:events/SKILL.md +356 -0
  55. package/ai/skills/testdriver:exec/SKILL.md +317 -0
  56. package/ai/skills/testdriver:find/SKILL.md +860 -0
  57. package/ai/skills/testdriver:focus-application/SKILL.md +293 -0
  58. package/ai/skills/testdriver:generating-tests/SKILL.md +25 -0
  59. package/ai/skills/testdriver:hosted/SKILL.md +156 -0
  60. package/ai/skills/testdriver:hover/SKILL.md +278 -0
  61. package/{docs/v7/locating-elements.mdx → ai/skills/testdriver:locating-elements/SKILL.md} +3 -3
  62. package/ai/skills/testdriver:making-assertions/SKILL.md +32 -0
  63. package/ai/skills/testdriver:mcp/SKILL.md +7 -0
  64. package/ai/skills/testdriver:mouse-down/SKILL.md +161 -0
  65. package/ai/skills/testdriver:mouse-up/SKILL.md +164 -0
  66. package/ai/skills/testdriver:parse/SKILL.md +236 -0
  67. package/ai/skills/testdriver:performing-actions/SKILL.md +53 -0
  68. package/ai/skills/testdriver:press-keys/SKILL.md +348 -0
  69. package/ai/skills/testdriver:provision/SKILL.md +331 -0
  70. package/ai/skills/testdriver:quickstart/SKILL.md +172 -0
  71. package/ai/skills/testdriver:redraw/SKILL.md +214 -0
  72. package/ai/skills/testdriver:reusable-code/SKILL.md +249 -0
  73. package/ai/skills/testdriver:right-click/SKILL.md +123 -0
  74. package/{docs/v7/running-tests.mdx → ai/skills/testdriver:running-tests/SKILL.md} +4 -4
  75. package/ai/skills/testdriver:screenshot/SKILL.md +248 -0
  76. package/ai/skills/testdriver:screenshots/SKILL.md +184 -0
  77. package/ai/skills/testdriver:scroll/SKILL.md +205 -0
  78. package/ai/skills/testdriver:secrets/SKILL.md +115 -0
  79. package/ai/skills/testdriver:self-hosted/SKILL.md +147 -0
  80. package/ai/skills/testdriver:test-results-json/SKILL.md +257 -0
  81. package/ai/skills/testdriver:testdriver/SKILL.md +624 -0
  82. package/ai/skills/testdriver:type/SKILL.md +357 -0
  83. package/ai/skills/testdriver:variables/SKILL.md +111 -0
  84. package/ai/skills/testdriver:wait/SKILL.md +50 -0
  85. package/{docs/v7/waiting-for-elements.mdx → ai/skills/testdriver:waiting-for-elements/SKILL.md} +3 -3
  86. package/ai/skills/testdriver:what-is-testdriver/SKILL.md +54 -0
  87. package/debugger/index.html +12 -0
  88. package/docs/_data/examples-manifest.json +54 -46
  89. package/docs/_data/examples-manifest.schema.json +12 -2
  90. package/docs/_scripts/extract-example-urls.js +21 -3
  91. package/docs/_scripts/generate-examples.js +64 -16
  92. package/docs/changelog.mdx +29 -2
  93. package/docs/claude-mcp-plugin.mdx +65 -18
  94. package/docs/docs.json +16 -33
  95. package/docs/snippets/tests/scroll-yaml.mdx +2 -2
  96. package/docs/v6/commands/scroll-until-image.mdx +1 -1
  97. package/docs/v6/commands/scroll-until-text.mdx +2 -2
  98. package/docs/v6/commands/scroll.mdx +2 -2
  99. package/docs/v7/_drafts/agents.mdx +0 -4
  100. package/docs/v7/_drafts/best-practices.mdx +0 -12
  101. package/docs/v7/_drafts/commands/scroll-until-image.mdx +1 -1
  102. package/docs/v7/_drafts/commands/scroll-until-text.mdx +2 -2
  103. package/docs/v7/_drafts/commands/scroll.mdx +1 -1
  104. package/docs/v7/_drafts/configuration.mdx +0 -12
  105. package/docs/v7/_drafts/faq.mdx +2 -10
  106. package/docs/v7/_drafts/readme.mdx +0 -1
  107. package/docs/v7/_drafts/troubleshooting.mdx +1 -4
  108. package/docs/v7/assert.mdx +0 -1
  109. package/docs/v7/aws-setup.mdx +1 -1
  110. package/docs/v7/caching.mdx +14 -10
  111. package/docs/v7/ci-cd.mdx +98 -14
  112. package/docs/v7/client.mdx +10 -6
  113. package/docs/v7/copilot/auto-healing.mdx +167 -18
  114. package/docs/v7/copilot/running-tests.mdx +915 -54
  115. package/docs/v7/customizing-devices.mdx +7 -5
  116. package/docs/v7/dashcam.mdx +36 -3
  117. package/docs/v7/debugging-with-screenshots.mdx +31 -7
  118. package/docs/v7/examples/ai.mdx +13 -8
  119. package/docs/v7/examples/assert.mdx +1 -1
  120. package/docs/v7/examples/chrome-extension.mdx +30 -13
  121. package/docs/v7/examples/element-not-found.mdx +1 -1
  122. package/docs/v7/examples/findall-coffee-icons.mdx +3 -2
  123. package/docs/v7/examples/formatted-logging.mdx +74 -0
  124. package/docs/v7/examples/hover-image.mdx +1 -1
  125. package/docs/v7/examples/hover-text-with-description.mdx +2 -2
  126. package/docs/v7/examples/hover-text.mdx +1 -1
  127. package/docs/v7/examples/installer.mdx +1 -1
  128. package/docs/v7/examples/launch-vscode-linux.mdx +27 -27
  129. package/docs/v7/examples/match-image.mdx +1 -1
  130. package/docs/v7/examples/parse.mdx +3 -2
  131. package/docs/v7/examples/press-keys.mdx +2 -2
  132. package/docs/v7/examples/scroll-keyboard.mdx +2 -2
  133. package/docs/v7/examples/scroll-until-text.mdx +1 -1
  134. package/docs/v7/examples/scroll.mdx +1 -1
  135. package/docs/v7/examples/type.mdx +1 -1
  136. package/docs/v7/find.mdx +50 -11
  137. package/docs/v7/generating-tests.mdx +166 -10
  138. package/docs/v7/hosted.mdx +1 -1
  139. package/docs/v7/making-assertions.mdx +81 -4
  140. package/docs/v7/parse.mdx +1 -1
  141. package/docs/v7/performing-actions.mdx +100 -5
  142. package/docs/v7/provision.mdx +0 -20
  143. package/docs/v7/quickstart.mdx +299 -21
  144. package/docs/v7/reusable-code.mdx +3 -3
  145. package/docs/v7/screenshot.mdx +5 -5
  146. package/docs/v7/scroll.mdx +30 -160
  147. package/docs/v7/wait.mdx +1 -1
  148. package/examples/chrome-extension.test.mjs +29 -12
  149. package/examples/config.mjs +1 -1
  150. package/examples/launch-vscode-linux.test.mjs +25 -25
  151. package/interfaces/cli/commands/init.js +84 -2
  152. package/interfaces/vitest-plugin.mjs +51 -61
  153. package/lib/core/Dashcam.js +43 -2
  154. package/lib/core/index.d.ts +13 -1
  155. package/lib/github-comment.mjs +11 -5
  156. package/lib/init-project.js +98 -44
  157. package/lib/install-clients.js +470 -0
  158. package/lib/provision.js +749 -0
  159. package/lib/resolve-channel.js +2 -2
  160. package/lib/sentry.js +15 -2
  161. package/lib/vitest/hooks.mjs +44 -34
  162. package/mcp-server/README.md +32 -0
  163. package/mcp-server/dist/core/actions.d.ts +176 -0
  164. package/mcp-server/dist/core/actions.js +753 -0
  165. package/mcp-server/dist/env-utils.d.ts +45 -0
  166. package/mcp-server/dist/env-utils.js +63 -0
  167. package/mcp-server/dist/provision-types.d.ts +6 -2
  168. package/mcp-server/dist/provision-types.js +3 -1
  169. package/mcp-server/dist/server.mjs +449 -751
  170. package/mcp-server/package-lock.json +384 -1
  171. package/mcp-server/package.json +5 -2
  172. package/mcp-server/src/core/actions.ts +912 -0
  173. package/mcp-server/src/env-utils.test.ts +82 -0
  174. package/mcp-server/src/env-utils.ts +77 -0
  175. package/mcp-server/src/provision-types.ts +4 -1
  176. package/mcp-server/src/server.ts +533 -954
  177. package/mcp-server/tsconfig.json +1 -1
  178. package/mcp-server/vitest.config.ts +7 -0
  179. package/package.json +15 -3
  180. package/sdk.d.ts +11 -9
  181. package/sdk.js +116 -746
  182. package/setup/aws/spawn-runner.sh +24 -3
  183. package/docs/v7/_drafts/caching-ai.mdx +0 -215
  184. package/docs/v7/_drafts/caching.mdx +0 -366
  185. package/docs/v7/_drafts/prompt-cache.mdx +0 -200
  186. package/docs/v7/copilot/creating-tests.mdx +0 -156
  187. package/docs/v7/copilot/github.mdx +0 -143
  188. package/docs/v7/copilot/setup.mdx +0 -143
  189. package/docs/v7/examples/exec-output.mdx +0 -84
  190. package/docs/v7/examples/exec-pwsh.mdx +0 -82
  191. package/docs/v7/examples/focus-window.mdx +0 -61
  192. package/docs/v7/examples/scroll-until-image.mdx +0 -82
  193. package/docs/v7/examples/windows-installer.mdx +0 -95
  194. package/examples/formatted-logging.test.mjs +0 -27
  195. package/examples/match-image.test.mjs +0 -55
  196. package/manual/exec-stream-logs.test.mjs +0 -25
@@ -27,21 +27,21 @@ Scroll the page or active element in any direction using mouse wheel or keyboard
27
27
  ## Syntax
28
28
 
29
29
  ```javascript
30
- await testdriver.scroll(direction, amount, method)
30
+ await testdriver.scroll(direction, options)
31
31
  ```
32
32
 
33
33
  ## Parameters
34
34
 
35
35
  <ParamField path="direction" type="string" default="down">
36
- Direction to scroll: `'up'`, `'down'`, `'left'`, `'right'`
36
+ Direction to scroll: `'up'`, `'down'`
37
37
  </ParamField>
38
38
 
39
- <ParamField path="amount" type="number" default="300">
40
- Amount to scroll in pixels
41
- </ParamField>
42
-
43
- <ParamField path="method" type="string" default="mouse">
44
- Scroll method: `'mouse'` or `'keyboard'`
39
+ <ParamField path="options" type="object">
40
+ <Expandable title="properties">
41
+ <ParamField path="amount" type="number" default="300">
42
+ Amount to scroll in pixels
43
+ </ParamField>
44
+ </Expandable>
45
45
  </ParamField>
46
46
 
47
47
  ## Returns
@@ -56,95 +56,34 @@ await testdriver.scroll(direction, amount, method)
56
56
  // Scroll down (default)
57
57
  await testdriver.scroll();
58
58
 
59
- // Scroll down 500 pixels
60
- await testdriver.scroll('down', 500);
59
+ // Scroll down 5 clicks
60
+ await testdriver.scroll('down', { amount: 5 });
61
61
 
62
62
  // Scroll up
63
63
  await testdriver.scroll('up');
64
64
 
65
- // Scroll up 200 pixels
66
- await testdriver.scroll('up', 200);
65
+ // Scroll up 2 clicks
66
+ await testdriver.scroll('up', { amount: 2 });
67
67
  ```
68
68
 
69
69
  ### Horizontal Scrolling
70
70
 
71
71
  ```javascript
72
72
  // Scroll right
73
- await testdriver.scroll('right', 300);
73
+ await testdriver.scroll('right', { amount: 3 });
74
74
 
75
75
  // Scroll left
76
- await testdriver.scroll('left', 300);
76
+ await testdriver.scroll('left', { amount: 3 });
77
77
  ```
78
78
 
79
79
  ### Scroll Methods
80
80
 
81
81
  ```javascript
82
- // Mouse wheel scroll (smooth, pixel-precise)
83
- await testdriver.scroll('down', 300, 'mouse');
84
-
85
- // Keyboard scroll (uses Page Down/Up, more compatible)
86
- await testdriver.scroll('down', 300, 'keyboard');
87
- ```
88
-
89
- ## Scroll Until Found
90
-
91
- ### scrollUntilText()
92
-
93
- Scroll until specific text appears on screen.
94
-
95
- ```javascript
96
- await testdriver.scrollUntilText(text, direction, maxDistance, textMatchMethod, method, invert)
97
- ```
98
-
99
- **Parameters:**
100
- - `text` (string) - Text to find
101
- - `direction` (string) - Scroll direction (default: `'down'`)
102
- - `maxDistance` (number) - Max pixels to scroll (default: 10000)
103
- - `textMatchMethod` (string) - `'turbo'` or `'ai'` (default: `'turbo'`)
104
- - `method` (string) - `'keyboard'` or `'mouse'` (default: `'keyboard'`)
105
- - `invert` (boolean) - Scroll until text disappears (default: false)
106
-
107
- **Examples:**
108
- ```javascript
109
- // Scroll down until "Contact Us" appears
110
- await testdriver.scrollUntilText('Contact Us');
111
-
112
- // Scroll up to find text
113
- await testdriver.scrollUntilText('Header', 'up');
114
-
115
- // Scroll until text disappears
116
- await testdriver.scrollUntilText('Loading...', 'down', 5000, 'turbo', 'keyboard', true);
82
+ // Mouse wheel scroll (default)
83
+ await testdriver.scroll('down', { amount: 3 });
117
84
 
118
- // Use AI matching for fuzzy text
119
- await testdriver.scrollUntilText('footer content', 'down', 10000, 'ai');
120
- ```
121
-
122
- ### scrollUntilImage()
123
-
124
- Scroll until a visual element appears.
125
-
126
- ```javascript
127
- await testdriver.scrollUntilImage(description, direction, maxDistance, method, path, invert)
128
- ```
129
-
130
- **Parameters:**
131
- - `description` (string) - Description of the image/element
132
- - `direction` (string) - Scroll direction (default: `'down'`)
133
- - `maxDistance` (number) - Max pixels to scroll (default: 10000)
134
- - `method` (string) - `'keyboard'` or `'mouse'` (default: `'keyboard'`)
135
- - `path` (string | null) - Path to image template (optional)
136
- - `invert` (boolean) - Scroll until image disappears (default: false)
137
-
138
- **Examples:**
139
- ```javascript
140
- // Scroll until visual element appears
141
- await testdriver.scrollUntilImage('red subscribe button');
142
-
143
- // Scroll using image template
144
- await testdriver.scrollUntilImage('', 'down', 10000, 'keyboard', './footer-logo.png');
145
-
146
- // Scroll until image disappears
147
- await testdriver.scrollUntilImage('loading spinner', 'down', 5000, 'keyboard', null, true);
85
+ // For keyboard-based scrolling, use pressKeys instead
86
+ await testdriver.pressKeys(['pagedown']);
148
87
  ```
149
88
 
150
89
  ## Best Practices
@@ -163,7 +102,7 @@ await testdriver.scrollUntilImage('loading spinner', 'down', 5000, 'keyboard', n
163
102
  // await testdriver.find('page background').click();
164
103
 
165
104
  // Now scroll will work properly
166
- await testdriver.scroll('down', 300);
105
+ await testdriver.scroll('down');
167
106
 
168
107
  // If scroll still doesn't work, use Page Down directly
169
108
  // await testdriver.pressKeys(['pagedown']);
@@ -171,84 +110,31 @@ await testdriver.scrollUntilImage('loading spinner', 'down', 5000, 'keyboard', n
171
110
  </Check>
172
111
 
173
112
  <Check>
174
- **Choose the right scroll method**
175
-
176
- ```javascript
177
- // For web pages, mouse scroll is usually smoother
178
- await testdriver.scroll('down', 300, 'mouse');
179
-
180
- // For desktop apps or when mouse doesn't work
181
- await testdriver.scroll('down', 300, 'keyboard');
182
- ```
183
- </Check>
184
-
185
- <Check>
186
- **Use scrollUntil for dynamic content**
113
+ **Control scroll distance with the options object**
187
114
 
188
115
  ```javascript
189
- // Instead of guessing scroll amount
190
- await testdriver.scrollUntilText('Load More button');
116
+ // For web pages, mouse scroll works well
117
+ await testdriver.scroll('down', { amount: 3 });
191
118
 
192
- const loadMoreBtn = await testdriver.find('Load More button');
193
- await loadMoreBtn.click();
194
- ```
195
- </Check>
196
-
197
- <Check>
198
- **Set reasonable max distance**
199
-
200
- ```javascript
201
- // Avoid infinite scrolling
202
- await testdriver.scrollUntilText('Footer', 'down', 5000); // Max 5000px
119
+ // For desktop apps or when mouse doesn't work, use keyboard
120
+ await testdriver.pressKeys(['pagedown']);
203
121
  ```
204
122
  </Check>
205
123
 
206
124
  <Warning>
207
125
  **Keyboard scroll uses Page Down/Up**
208
126
 
209
- Keyboard scrolling typically moves by one "page" at a time, which may be more than the specified pixel amount. It's more compatible but less precise than mouse scrolling.
127
+ Keyboard scrolling typically moves by one "page" at a time, which may be more than the specified click amount. It's more compatible but less precise than mouse scrolling.
210
128
  </Warning>
211
129
 
212
130
  ## Use Cases
213
131
 
214
132
  <AccordionGroup>
215
- <Accordion title="Navigate to Footer">
216
- ```javascript
217
- // Scroll to bottom of page
218
- await testdriver.scrollUntilText('Contact Us');
219
-
220
- const contactLink = await testdriver.find('Contact Us link');
221
- await contactLink.click();
222
- ```
223
- </Accordion>
224
-
225
- <Accordion title="Load More Results">
226
- ```javascript
227
- // Scroll to load more button
228
- await testdriver.scrollUntilText('Load More');
229
-
230
- const loadBtn = await testdriver.find('Load More button');
231
- await loadBtn.click();
232
-
233
- await new Promise(r => setTimeout(r, 2000));
234
- ```
235
- </Accordion>
236
-
237
- <Accordion title="Find Element in Long List">
238
- ```javascript
239
- // Scroll through list to find item
240
- await testdriver.scrollUntilText('Product #42');
241
-
242
- const product = await testdriver.find('Product #42');
243
- await product.click();
244
- ```
245
- </Accordion>
246
-
247
133
  <Accordion title="Infinite Scroll">
248
134
  ```javascript
249
135
  // Scroll multiple times for infinite scroll
250
136
  for (let i = 0; i < 5; i++) {
251
- await testdriver.scroll('down', 500);
137
+ await testdriver.scroll('down', { amount: 5 });
252
138
  await new Promise(r => setTimeout(r, 1000)); // Wait for load
253
139
  }
254
140
  ```
@@ -257,7 +143,7 @@ await testdriver.scrollUntilImage('loading spinner', 'down', 5000, 'keyboard', n
257
143
  <Accordion title="Horizontal Gallery">
258
144
  ```javascript
259
145
  // Navigate horizontal carousel
260
- await testdriver.scroll('right', 300);
146
+ await testdriver.scroll('right', { amount: 3 });
261
147
  await new Promise(r => setTimeout(r, 500));
262
148
 
263
149
  const nextImage = await testdriver.find('next image in carousel');
@@ -288,8 +174,8 @@ describe('Scrolling', () => {
288
174
  it('should scroll to find elements', async () => {
289
175
  await testdriver.focusApplication('Google Chrome');
290
176
 
291
- // Scroll to footer
292
- await testdriver.scrollUntilText('Contact Information');
177
+ // Scroll down the page
178
+ await testdriver.scroll('down', { amount: 5 });
293
179
 
294
180
  // Click footer link
295
181
  const privacyLink = await testdriver.find('Privacy Policy link');
@@ -303,29 +189,13 @@ describe('Scrolling', () => {
303
189
 
304
190
  // Scroll multiple times to load content
305
191
  for (let i = 0; i < 3; i++) {
306
- await testdriver.scroll('down', 500);
192
+ await testdriver.scroll('down', { amount: 5 });
307
193
  await new Promise(r => setTimeout(r, 1500)); // Wait for load
308
194
  }
309
195
 
310
196
  // Verify content loaded
311
197
  await testdriver.assert('more than 10 items are visible');
312
198
  });
313
-
314
- it('should scroll until loading completes', async () => {
315
- // Scroll until loading spinner disappears
316
- await testdriver.scrollUntilImage(
317
- 'loading spinner',
318
- 'down',
319
- 5000,
320
- 'keyboard',
321
- null,
322
- true // invert - wait for it to disappear
323
- );
324
-
325
- // Now interact with loaded content
326
- const firstResult = await testdriver.find('first search result');
327
- await firstResult.click();
328
- });
329
199
  });
330
200
  ```
331
201
 
package/docs/v7/wait.mdx CHANGED
@@ -49,4 +49,4 @@ await testdriver.wait();
49
49
  const element = await testdriver.find('success message');
50
50
  ```
51
51
  - Avoid excessively long timeouts to keep tests efficient.
52
- - Use sparingly — TestDriver's [redraw detection](/v7/waiting-for-elements) automatically waits for screen and network stability after each action.
52
+ - Use sparingly — TestDriver's [redraw detection](/v7/performing-actions#waiting-for-dynamic-content) automatically waits for screen and network stability after each action.
@@ -52,18 +52,35 @@ describe("Chrome Extension Test", () => {
52
52
  // The hello-world extension adds a puzzle piece icon to the toolbar
53
53
  // When clicked, it shows a popup with "Hello Extensions"
54
54
 
55
- // Click on the extensions button (puzzle piece icon) in Chrome toolbar
56
- const extensionsButton = await testdriver.find("The extensions button in the Chrome toolbar", {zoom: true});
57
- await extensionsButton.click();
58
-
59
- // Look for the hello world extension in the extensions menu
60
- const helloExtension = await testdriver.find("Hello Extensions extension in the extensions dropdown");
61
- await helloExtension.click();
62
-
63
- await testdriver.wait(2000); // wait for the popup to open
64
-
65
- // Verify the extension popup shows "Hello Extensions" text
66
- const popupResult = await testdriver.assert("a popup shows with the text 'Hello Extensions'");
55
+ // Retry opening the extension popup and verifying it up to 3 times
56
+ let popupResult;
57
+ let lastError;
58
+ for (let attempt = 1; attempt <= 3; attempt++) {
59
+ try {
60
+ // Click on the extensions button (puzzle piece icon) in Chrome toolbar
61
+ const extensionsButton = await testdriver.find("The extensions button in the Chrome toolbar", {zoom: true, verify: true, timeout: 10000});
62
+ await extensionsButton.click();
63
+
64
+ // Look for the hello world extension in the extensions menu
65
+ const helloExtension = await testdriver.find("Hello Extensions extension in the extensions dropdown", {zoom: true, verify: true, timeout: 10000});
66
+ await helloExtension.click();
67
+
68
+ await testdriver.wait(2000); // wait for the popup to open
69
+
70
+ // Verify the extension popup shows "Hello Extensions" text
71
+ popupResult = await testdriver.assert("a popup shows with the text 'Hello Extensions'");
72
+ if (popupResult) break;
73
+ } catch (err) {
74
+ lastError = err;
75
+ console.log(`Attempt ${attempt} failed:`, err.message);
76
+ }
77
+ if (attempt < 3) {
78
+ // Dismiss any open popup/menu before retrying
79
+ await testdriver.pressKeys(["escape"]);
80
+ await testdriver.wait(1000);
81
+ }
82
+ }
83
+ if (!popupResult && lastError) throw lastError;
67
84
  expect(popupResult).toBeTruthy();
68
85
  });
69
86
 
@@ -1,5 +1,5 @@
1
1
  export const getDefaults = (context) => ({
2
2
  ip: context.ip || process.env.TD_IP,
3
- redraw: { enabled: false },
4
3
  preview: 'web',
4
+ cache: 'false'
5
5
  });
@@ -22,34 +22,34 @@ describe("Launch VS Code on Linux", () => {
22
22
  },
23
23
  );
24
24
 
25
- it.skipIf(!isLinux)(
26
- "should install and use a VS Code extension",
27
- async (context) => {
28
- const testdriver = TestDriver(context, { ...getDefaults(context) });
25
+ // it.skipIf(!isLinux)(
26
+ // "should install and use a VS Code extension",
27
+ // async (context) => {
28
+ // const testdriver = TestDriver(context, { ...getDefaults(context) });
29
29
 
30
- // Launch VS Code with the Prettier extension installed
31
- await testdriver.provision.vscode({
32
- extensions: ["esbenp.prettier-vscode"],
33
- });
30
+ // // Launch VS Code with the Prettier extension installed
31
+ // await testdriver.provision.vscode({
32
+ // extensions: ["esbenp.prettier-vscode"],
33
+ // });
34
34
 
35
- const vsCodeWindow = await testdriver.find(
36
- "Visual Studio Code window",
37
- { timeout: 60000 }
38
- );
35
+ // const vsCodeWindow = await testdriver.find(
36
+ // "Visual Studio Code window",
37
+ // { timeout: 60000 }
38
+ // );
39
39
 
40
- expect(vsCodeWindow.found()).toBeTruthy();
40
+ // expect(vsCodeWindow.found()).toBeTruthy();
41
41
 
42
- // Open the extensions panel to verify Prettier is installed
43
- await testdriver.pressKeys(["ctrl", "shift", "x"]);
42
+ // // Open the extensions panel to verify Prettier is installed
43
+ // await testdriver.pressKeys(["ctrl", "shift", "x"]);
44
44
 
45
- // Wait for extensions panel to open
46
- await new Promise((resolve) => setTimeout(resolve, 2000));
47
-
48
- // Assert that Prettier extension is visible in the installed extensions
49
- const prettierVisible = await testdriver.assert(
50
- "Prettier extension is visible in the extensions panel or sidebar",
51
- );
52
- expect(prettierVisible).toBeTruthy();
53
- },
54
- );
45
+ // // Wait for extensions panel to open
46
+ // await new Promise((resolve) => setTimeout(resolve, 2000));
47
+
48
+ // // Assert that Prettier extension is visible in the installed extensions
49
+ // const prettierVisible = await testdriver.assert(
50
+ // "Prettier extension is visible in the extensions panel or sidebar",
51
+ // );
52
+ // expect(prettierVisible).toBeTruthy();
53
+ // },
54
+ // );
55
55
  });
@@ -1,6 +1,7 @@
1
1
  const BaseCommand = require("../lib/base.js");
2
2
  const { createCommandDefinitions } = require("../../../agent/interface.js");
3
3
  const { initProject } = require("../../../lib/init-project.js");
4
+ const { CLIENTS, detectClients } = require("../../../lib/install-clients.js");
4
5
  const fs = require("fs");
5
6
  const path = require("path");
6
7
  const chalk = require("chalk");
@@ -22,13 +23,24 @@ const POLL_TIMEOUT = 900000; // 15 minutes
22
23
  */
23
24
  class InitCommand extends BaseCommand {
24
25
  async run() {
25
- await this.parse(InitCommand);
26
+ const { flags } = await this.parse(InitCommand);
26
27
 
27
28
  console.log(chalk.cyan("\n🚀 Initializing TestDriver project...\n"));
28
29
 
29
30
  // Prompt for API key first
30
31
  const apiKey = await this.promptForApiKey();
31
32
 
33
+ // Determine which AI clients to wire up: from --client flag, or interactively.
34
+ let clients;
35
+ if (flags.client) {
36
+ clients = flags.client
37
+ .split(",")
38
+ .map((c) => c.trim())
39
+ .filter(Boolean);
40
+ } else {
41
+ clients = await this.promptForClients();
42
+ }
43
+
32
44
  // Helper to print progress messages with appropriate colors
33
45
  const printProgress = (msg) => {
34
46
  if (msg.startsWith("✓")) {
@@ -47,6 +59,10 @@ class InitCommand extends BaseCommand {
47
59
  targetDir: process.cwd(),
48
60
  apiKey: apiKey,
49
61
  skipInstall: false,
62
+ // --no-sample-test sets flags["sample-test"] to false
63
+ skipSampleTest: flags["sample-test"] === false,
64
+ clients: clients && clients.length ? clients : undefined,
65
+ channel: channelConfig.active,
50
66
  onProgress: printProgress,
51
67
  });
52
68
 
@@ -159,6 +175,72 @@ class InitCommand extends BaseCommand {
159
175
  }
160
176
  }
161
177
 
178
+ /**
179
+ * Prompt the user to select which AI clients to install TestDriver into.
180
+ * Returns an array of client ids (possibly empty).
181
+ * @returns {Promise<string[]>}
182
+ */
183
+ async promptForClients() {
184
+ const ids = Object.keys(CLIENTS);
185
+ const detected = new Set(detectClients(process.cwd()));
186
+
187
+ // Non-interactive stdin (CI, piped input, no TTY): don't prompt — fall back
188
+ // to detected clients so init can still scaffold all files unattended.
189
+ if (!process.stdin.isTTY) {
190
+ return [...detected];
191
+ }
192
+
193
+ console.log(chalk.cyan("\n Which AI clients should TestDriver be installed into?\n"));
194
+ ids.forEach((id, i) => {
195
+ const c = CLIENTS[id];
196
+ const tag = detected.has(id) ? chalk.green(" (detected)") : "";
197
+ const web = c.type === "web" ? chalk.gray(" — web, manual steps") : "";
198
+ console.log(` ${chalk.cyan(i + 1)}. ${c.label}${tag}${web}`);
199
+ });
200
+ console.log(` ${chalk.cyan("a")}. All of the above`);
201
+ console.log(
202
+ chalk.gray(
203
+ "\n Enter numbers separated by commas (e.g. 1,3), 'a' for all, or press Enter to skip.\n",
204
+ ),
205
+ );
206
+
207
+ const answer = await new Promise((resolve) => {
208
+ const rl = readline.createInterface({
209
+ input: process.stdin,
210
+ output: process.stdout,
211
+ });
212
+ // Default to detected clients if any, else skip.
213
+ const defaultHint = detected.size
214
+ ? ` [${[...detected].map((d) => ids.indexOf(d) + 1).join(",")}]`
215
+ : "";
216
+ let answered = false;
217
+ rl.question(` Select clients${defaultHint}: `, (a) => {
218
+ answered = true;
219
+ rl.close();
220
+ resolve(a.trim());
221
+ });
222
+ // If stdin closes (EOF) before a line is entered, the question callback
223
+ // never fires — resolve empty so init doesn't hang.
224
+ rl.on("close", () => {
225
+ if (!answered) resolve("");
226
+ });
227
+ });
228
+
229
+ if (!answer) {
230
+ // Enter pressed: use detected clients if any, otherwise none.
231
+ return [...detected];
232
+ }
233
+ if (answer.toLowerCase() === "a" || answer.toLowerCase() === "all") {
234
+ return ["all"];
235
+ }
236
+ const selected = answer
237
+ .split(",")
238
+ .map((s) => parseInt(s.trim(), 10))
239
+ .filter((n) => Number.isInteger(n) && n >= 1 && n <= ids.length)
240
+ .map((n) => ids[n - 1]);
241
+ return selected;
242
+ }
243
+
162
244
  /**
163
245
  * Browser-based login flow using device code
164
246
  * @returns {Promise<string>} The API key
@@ -411,7 +493,7 @@ class InitCommand extends BaseCommand {
411
493
  console.log(" 2. Use AI agents to write tests:");
412
494
  console.log(chalk.gray(" Open VSCode/Cursor and use @testdriver agent\n"));
413
495
  console.log(" 3. MCP server configured:");
414
- console.log(chalk.gray(" TestDriver tools available via MCP in .vscode/mcp.json\n"));
496
+ console.log(chalk.gray(" TestDriver tools are now available in your selected AI client(s)\n"));
415
497
  console.log(
416
498
  " 4. For CI/CD, add TD_API_KEY to your GitHub repository secrets",
417
499
  );