testdriverai 7.8.0-test.4 → 7.8.0-test.40

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 (87) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/agent/index.js +6 -5
  3. package/agent/lib/commands.js +3 -2
  4. package/agent/lib/http.js +144 -0
  5. package/agent/lib/sandbox.js +326 -164
  6. package/agent/lib/sdk.js +4 -2
  7. package/agent/lib/system.js +25 -65
  8. package/ai/skills/testdriver-cache/SKILL.md +221 -0
  9. package/ai/skills/testdriver-errors/SKILL.md +246 -0
  10. package/ai/skills/testdriver-events/SKILL.md +356 -0
  11. package/ai/skills/testdriver-mcp/SKILL.md +7 -0
  12. package/ai/skills/testdriver-provision/SKILL.md +331 -0
  13. package/ai/skills/testdriver-redraw/SKILL.md +214 -0
  14. package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
  15. package/ai/skills/testdriver-screenshots/SKILL.md +184 -0
  16. package/docs/_data/examples-manifest.json +46 -46
  17. package/docs/changelog.mdx +155 -3
  18. package/docs/docs.json +44 -37
  19. package/docs/images/content/vscode/v7-chat.png +0 -0
  20. package/docs/images/content/vscode/v7-choose-agent.png +0 -0
  21. package/docs/images/content/vscode/v7-full.png +0 -0
  22. package/docs/images/content/vscode/v7-onboarding.png +0 -0
  23. package/docs/v7/cache.mdx +223 -0
  24. package/docs/v7/copilot/auto-healing.mdx +265 -0
  25. package/docs/v7/copilot/creating-tests.mdx +156 -0
  26. package/docs/v7/copilot/github.mdx +143 -0
  27. package/docs/v7/copilot/running-tests.mdx +149 -0
  28. package/docs/v7/copilot/setup.mdx +143 -0
  29. package/docs/v7/enterprise.mdx +3 -110
  30. package/docs/v7/errors.mdx +248 -0
  31. package/docs/v7/events.mdx +358 -0
  32. package/docs/v7/examples/ai.mdx +1 -1
  33. package/docs/v7/examples/assert.mdx +1 -1
  34. package/docs/v7/examples/captcha-api.mdx +1 -1
  35. package/docs/v7/examples/chrome-extension.mdx +1 -1
  36. package/docs/v7/examples/drag-and-drop.mdx +1 -1
  37. package/docs/v7/examples/element-not-found.mdx +1 -1
  38. package/docs/v7/examples/exec-output.mdx +85 -0
  39. package/docs/v7/examples/exec-pwsh.mdx +83 -0
  40. package/docs/v7/examples/focus-window.mdx +62 -0
  41. package/docs/v7/examples/hover-image.mdx +1 -1
  42. package/docs/v7/examples/hover-text.mdx +1 -1
  43. package/docs/v7/examples/installer.mdx +1 -1
  44. package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
  45. package/docs/v7/examples/match-image.mdx +1 -1
  46. package/docs/v7/examples/press-keys.mdx +1 -1
  47. package/docs/v7/examples/scroll-keyboard.mdx +1 -1
  48. package/docs/v7/examples/scroll-until-image.mdx +1 -1
  49. package/docs/v7/examples/scroll-until-text.mdx +1 -1
  50. package/docs/v7/examples/scroll.mdx +1 -1
  51. package/docs/v7/examples/type.mdx +1 -1
  52. package/docs/v7/examples/windows-installer.mdx +1 -1
  53. package/docs/v7/{cloud.mdx → hosted.mdx} +43 -5
  54. package/docs/v7/mcp.mdx +9 -0
  55. package/docs/v7/provision.mdx +333 -0
  56. package/docs/v7/quickstart.mdx +30 -2
  57. package/docs/v7/redraw.mdx +216 -0
  58. package/docs/v7/running-tests.mdx +1 -1
  59. package/docs/v7/screenshots.mdx +186 -0
  60. package/docs/v7/self-hosted.mdx +127 -44
  61. package/interfaces/logger.js +0 -12
  62. package/interfaces/vitest-plugin.mjs +53 -43
  63. package/lib/core/Dashcam.js +30 -23
  64. package/lib/environments.json +18 -0
  65. package/lib/github-comment.mjs +58 -40
  66. package/lib/resolve-channel.js +4 -3
  67. package/lib/sentry.js +5 -0
  68. package/{examples → manual}/drag-and-drop.test.mjs +1 -1
  69. package/mcp-server/dist/server.mjs +4 -0
  70. package/mcp-server/src/server.ts +5 -0
  71. package/package.json +3 -3
  72. package/sdk.js +3 -3
  73. package/setup/aws/install-dev-runner.sh +79 -0
  74. package/setup/aws/spawn-runner.sh +134 -0
  75. package/vitest.config.mjs +20 -32
  76. package/vitest.runner.config.mjs +33 -0
  77. /package/{examples → manual}/flake-diffthreshold-001.test.mjs +0 -0
  78. /package/{examples → manual}/flake-diffthreshold-01.test.mjs +0 -0
  79. /package/{examples → manual}/flake-diffthreshold-05.test.mjs +0 -0
  80. /package/{examples → manual}/flake-noredraw-cache.test.mjs +0 -0
  81. /package/{examples → manual}/flake-noredraw-nocache.test.mjs +0 -0
  82. /package/{examples → manual}/flake-redraw-cache.test.mjs +0 -0
  83. /package/{examples → manual}/flake-redraw-nocache.test.mjs +0 -0
  84. /package/{examples → manual}/flake-rocket-match.test.mjs +0 -0
  85. /package/{examples → manual}/flake-shared.mjs +0 -0
  86. /package/{examples → manual}/no-provision.test.mjs +0 -0
  87. /package/{examples → manual}/scroll-until-text.test.mjs +0 -0
@@ -0,0 +1,356 @@
1
+ ---
2
+ name: testdriver:events
3
+ description: Listen to SDK lifecycle events with wildcard support
4
+ ---
5
+ <!-- Generated from events.mdx. DO NOT EDIT. -->
6
+
7
+ ## Overview
8
+
9
+ TestDriver uses [EventEmitter2](https://github.com/EventEmitter2/EventEmitter2) for its event system. Events use a colon-delimited namespace pattern and support wildcard listeners.
10
+
11
+ Access the emitter through `testdriver.emitter`:
12
+
13
+ ```javascript
14
+ testdriver.emitter.on('command:start', (data) => {
15
+ console.log(`Running: ${data.command}`);
16
+ });
17
+ ```
18
+
19
+ ### Configuration
20
+
21
+ The internal emitter is created with:
22
+
23
+ ```javascript
24
+ new EventEmitter2({
25
+ wildcard: true,
26
+ delimiter: ':',
27
+ maxListeners: 20,
28
+ verboseMemoryLeak: false,
29
+ ignoreErrors: false,
30
+ });
31
+ ```
32
+
33
+ ### Wildcard Listeners
34
+
35
+ Use `*` to match a single level or `**` to match multiple levels:
36
+
37
+ ```javascript
38
+ // Match all log events
39
+ testdriver.emitter.on('log:*', (message) => {
40
+ console.log(message);
41
+ });
42
+
43
+ // Match all events in any namespace
44
+ testdriver.emitter.on('**', (...args) => {
45
+ console.log('Event:', this.event, args);
46
+ });
47
+ ```
48
+
49
+ ## Event Reference
50
+
51
+ ### Command Events
52
+
53
+ Emitted during the execution of SDK commands (`click`, `type`, `find`, etc.).
54
+
55
+ | Event | Payload |
56
+ |---|---|
57
+ | `command:start` | `{ command, depth, data, timestamp, sourcePosition }` |
58
+ | `command:success` | `{ command, depth, data, duration, response, timestamp, sourcePosition }` |
59
+ | `command:error` | `{ command, depth, data, error, duration, timestamp, sourcePosition }` |
60
+ | `command:status` | `{ command, status: "executing", data, depth, timestamp }` |
61
+ | `command:progress` | `{ command, status: "completed", timing, data, depth, timestamp }` |
62
+
63
+ ```javascript
64
+ testdriver.emitter.on('command:start', ({ command, data }) => {
65
+ console.log(`Starting ${command}`, data);
66
+ });
67
+
68
+ testdriver.emitter.on('command:error', ({ command, error, duration }) => {
69
+ console.error(`${command} failed after ${duration}ms: ${error}`);
70
+ });
71
+ ```
72
+
73
+ ### Step Events
74
+
75
+ Emitted for each AI reasoning step within a command.
76
+
77
+ | Event | Payload |
78
+ |---|---|
79
+ | `step:start` | `{ stepIndex, prompt, commandCount, timestamp, sourcePosition }` |
80
+ | `step:success` | `{ stepIndex, prompt, commandCount, duration, timestamp, sourcePosition }` |
81
+ | `step:error` | `{ stepIndex, prompt, error, duration?, timestamp, sourcePosition? }` |
82
+
83
+ ```javascript
84
+ testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
85
+ console.log(`Step ${stepIndex}: ${prompt}`);
86
+ });
87
+ ```
88
+
89
+ ### Test Events
90
+
91
+ Emitted when a test file execution starts.
92
+
93
+ | Event | Payload |
94
+ |---|---|
95
+ | `test:start` | `{ filePath, timestamp }` |
96
+ | `test:success` | *Emitted on test completion* |
97
+ | `test:error` | *Emitted on test failure* |
98
+
99
+ ### Log Events
100
+
101
+ Emitted for all log output from the SDK.
102
+
103
+ | Event | Payload |
104
+ |---|---|
105
+ | `log:log` | `(message: string)` — general log message |
106
+ | `log:warn` | `(message: string)` — warning message |
107
+ | `log:debug` | `(message: string)` — debug output (only when `VERBOSE`/`DEBUG`/`TD_DEBUG` env set) |
108
+ | `log:info` | `(message: string)` — informational message |
109
+ | `log:error` | `(message: string)` — error message |
110
+ | `log:narration` | `(message: string, overwrite?: boolean)` — in-place status line |
111
+ | `log:markdown` | `(markdown: string)` — full static markdown content |
112
+ | `log:markdown:start` | `(streamId: string)` — begin streaming markdown |
113
+ | `log:markdown:chunk` | `(streamId: string, chunk: string)` — incremental chunk |
114
+ | `log:markdown:end` | `(streamId: string)` — end streaming markdown |
115
+
116
+ ```javascript
117
+ // Capture all logs
118
+ testdriver.emitter.on('log:*', function (message) {
119
+ console.log(`[${this.event}]`, message);
120
+ });
121
+ ```
122
+
123
+ ### Screen Capture Events
124
+
125
+ Emitted during screenshot capture.
126
+
127
+ | Event | Payload |
128
+ |---|---|
129
+ | `screen-capture:start` | `{ scale, silent, display }` |
130
+ | `screen-capture:end` | `{ scale, silent, display }` |
131
+ | `screen-capture:error` | `{ error, scale, silent, display }` |
132
+
133
+ ### Sandbox Events
134
+
135
+ Emitted for sandbox WebSocket lifecycle and communication.
136
+
137
+ | Event | Payload |
138
+ |---|---|
139
+ | `sandbox:connected` | *No payload* — WebSocket connection established |
140
+ | `sandbox:authenticated` | `{ traceId }` — authentication successful |
141
+ | `sandbox:error` | `(err: Error \| string)` — connection or sandbox error |
142
+ | `sandbox:sent` | `(message: object)` — WebSocket message sent |
143
+ | `sandbox:received` | *No payload* — successful message reply received |
144
+ | `sandbox:progress` | `{ step, message }` — sandbox setup progress |
145
+
146
+ ```javascript
147
+ testdriver.emitter.on('sandbox:connected', () => {
148
+ console.log('Connected to sandbox');
149
+ });
150
+
151
+ testdriver.emitter.on('sandbox:progress', ({ step, message }) => {
152
+ console.log(`Sandbox: [${step}] ${message}`);
153
+ });
154
+ ```
155
+
156
+ ### Redraw Events
157
+
158
+ Emitted during screen stability detection. See [Redraw](/v7/redraw) for more details.
159
+
160
+ | Event | Payload |
161
+ |---|---|
162
+ | `redraw:status` | `{ redraw: { enabled, settled, hasChangedFromInitial, consecutiveFramesStable, diffFromInitial, diffFromLast, text }, network: { enabled, settled, rxBytes, txBytes, text }, timeout: { isTimeout, elapsed, max, text } }` |
163
+ | `redraw:complete` | `{ screenSettled, hasChangedFromInitial, consecutiveFramesStable, networkSettled, isTimeout, timeElapsed }` |
164
+
165
+ ```javascript
166
+ testdriver.emitter.on('redraw:complete', (result) => {
167
+ if (result.isTimeout) {
168
+ console.warn('Redraw timed out after', result.timeElapsed, 'ms');
169
+ }
170
+ });
171
+ ```
172
+
173
+ ### File Events
174
+
175
+ Emitted during file load/save operations in the agent.
176
+
177
+ | Event | Payload |
178
+ |---|---|
179
+ | `file:start` | `{ operation: "load" \| "save" \| "run", filePath, timestamp }` |
180
+ | `file:stop` | `{ operation, filePath, duration, success, sourceMap?, reason?, timestamp }` |
181
+ | `file:load` | `{ filePath, size, timestamp }` |
182
+ | `file:save` | `{ filePath, size, timestamp }` |
183
+ | `file:diff` | `{ filePath, diff: { patches, sourceMaps, summary: { additions, deletions, modifications } }, timestamp }` |
184
+ | `file:error` | `{ operation, filePath, error, duration?, timestamp }` |
185
+
186
+ ### Error Events
187
+
188
+ Emitted for errors at various severity levels.
189
+
190
+ | Event | Payload |
191
+ |---|---|
192
+ | `error:fatal` | `(error: string \| Error)` — terminates the process |
193
+ | `error:general` | `(message: string)` — non-fatal error |
194
+ | `error:sandbox` | `(err: Error \| string)` — sandbox/WebSocket error |
195
+
196
+ ```javascript
197
+ testdriver.emitter.on('error:*', function (err) {
198
+ console.error(`[${this.event}]`, err);
199
+ });
200
+ ```
201
+
202
+ ### SDK Events
203
+
204
+ Emitted for API request lifecycle.
205
+
206
+ | Event | Payload |
207
+ |---|---|
208
+ | `sdk:request` | `{ path }` — outgoing API request |
209
+ | `sdk:response` | `{ path }` — API response received |
210
+ | `sdk:retry` | `{ path, attempt, error, delayMs }` — request retry |
211
+
212
+ ### Other Events
213
+
214
+ | Event | Payload |
215
+ |---|---|
216
+ | `exit` | `(exitCode: number)` — `0` for success, `1` for failure |
217
+ | `status` | `(message: string)` — general status updates |
218
+ | `mouse-click` | `{ x, y, button, click, double }` — mouse click performed |
219
+ | `terminal:stdout` | Terminal stdout output |
220
+ | `terminal:stderr` | Terminal stderr output |
221
+
222
+ ## Practical Examples
223
+
224
+ ### Custom Test Reporter
225
+
226
+ ```javascript
227
+ const results = [];
228
+
229
+ testdriver.emitter.on('command:success', ({ command, duration }) => {
230
+ results.push({ command, duration, status: 'pass' });
231
+ });
232
+
233
+ testdriver.emitter.on('command:error', ({ command, duration, error }) => {
234
+ results.push({ command, duration, status: 'fail', error });
235
+ });
236
+
237
+ // After test completes
238
+ afterAll(() => {
239
+ console.table(results);
240
+ });
241
+ ```
242
+
243
+ ### Progress Monitoring
244
+
245
+ ```javascript
246
+ testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
247
+ process.stdout.write(`\r Step ${stepIndex}: ${prompt}`);
248
+ });
249
+
250
+ testdriver.emitter.on('command:progress', ({ command, timing }) => {
251
+ process.stdout.write(`\r ${command} completed in ${timing}ms`);
252
+ });
253
+ ```
254
+
255
+ ### Debug Logging
256
+
257
+ ```javascript
258
+ // Log every event (verbose)
259
+ testdriver.emitter.on('**', function (...args) {
260
+ console.debug(`[EVENT] ${this.event}`, ...args);
261
+ });
262
+ ```
263
+
264
+ ## Types
265
+
266
+ ```typescript
267
+ interface CommandStartEvent {
268
+ command: string;
269
+ depth: number;
270
+ data: Record<string, any>;
271
+ timestamp: number;
272
+ sourcePosition: SourcePosition;
273
+ }
274
+
275
+ interface CommandSuccessEvent {
276
+ command: string;
277
+ depth: number;
278
+ data: Record<string, any>;
279
+ duration: number;
280
+ response: any;
281
+ timestamp: number;
282
+ sourcePosition: SourcePosition;
283
+ }
284
+
285
+ interface CommandErrorEvent {
286
+ command: string;
287
+ depth: number;
288
+ data: Record<string, any>;
289
+ error: string;
290
+ duration: number;
291
+ timestamp: number;
292
+ sourcePosition: SourcePosition;
293
+ }
294
+
295
+ interface StepStartEvent {
296
+ stepIndex: number;
297
+ prompt: string;
298
+ commandCount: number;
299
+ timestamp: number;
300
+ sourcePosition: SourcePosition;
301
+ }
302
+
303
+ interface StepSuccessEvent {
304
+ stepIndex: number;
305
+ prompt: string;
306
+ commandCount: number;
307
+ duration: number;
308
+ timestamp: number;
309
+ sourcePosition: SourcePosition;
310
+ }
311
+
312
+ interface RedrawStatusEvent {
313
+ redraw: {
314
+ enabled: boolean;
315
+ settled: boolean;
316
+ hasChangedFromInitial: boolean;
317
+ consecutiveFramesStable: number;
318
+ diffFromInitial: number;
319
+ diffFromLast: number;
320
+ text: string;
321
+ };
322
+ network: {
323
+ enabled: boolean;
324
+ settled: boolean;
325
+ rxBytes: number;
326
+ txBytes: number;
327
+ text: string;
328
+ };
329
+ timeout: {
330
+ isTimeout: boolean;
331
+ elapsed: number;
332
+ max: number;
333
+ text: string;
334
+ };
335
+ }
336
+
337
+ interface RedrawCompleteEvent {
338
+ screenSettled: boolean;
339
+ hasChangedFromInitial: boolean;
340
+ consecutiveFramesStable: number;
341
+ networkSettled: boolean;
342
+ isTimeout: boolean;
343
+ timeElapsed: number;
344
+ }
345
+
346
+ interface SandboxProgressEvent {
347
+ step: string;
348
+ message: string;
349
+ }
350
+
351
+ interface SourcePosition {
352
+ file: string;
353
+ line: number;
354
+ column: number;
355
+ }
356
+ ```
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: testdriver:mcp
3
+ description: mcp
4
+ ---
5
+ <!-- Generated from mcp.mdx. DO NOT EDIT. -->
6
+
7
+
@@ -0,0 +1,331 @@
1
+ ---
2
+ name: testdriver:provision
3
+ description: Launch browsers, desktop apps, and extensions in your sandbox
4
+ ---
5
+ <!-- Generated from provision.mdx. DO NOT EDIT. -->
6
+
7
+ ## Overview
8
+
9
+ The Provision API sets up applications in your sandbox before tests run. It handles downloading, installing, and launching browsers, desktop apps, VS Code, Chrome extensions, and more.
10
+
11
+ Access provision methods via `testdriver.provision.*`:
12
+
13
+ ```javascript
14
+ await testdriver.provision.chrome({ url: 'https://example.com' });
15
+ ```
16
+
17
+ <Note>
18
+ When `reconnect: true` is set on the client, **all provision methods are skipped** since the application is assumed to already be running.
19
+ </Note>
20
+
21
+ ## Methods
22
+
23
+ ### chrome()
24
+
25
+ Launch Google Chrome with an optional URL.
26
+
27
+ ```javascript
28
+ await testdriver.provision.chrome(options?)
29
+ ```
30
+
31
+ <ParamField path="options" type="ProvisionChromeOptions">
32
+ <Expandable title="properties">
33
+ <ParamField path="url" type="string" default="http://testdriver-sandbox.vercel.app/">
34
+ URL to navigate to after launch.
35
+ </ParamField>
36
+
37
+ <ParamField path="maximized" type="boolean" default={true}>
38
+ Launch Chrome in maximized window mode.
39
+ </ParamField>
40
+
41
+ <ParamField path="guest" type="boolean" default={false}>
42
+ Launch Chrome in guest profile mode.
43
+ </ParamField>
44
+ </Expandable>
45
+ </ParamField>
46
+
47
+ ```javascript
48
+ // Basic
49
+ await testdriver.provision.chrome({ url: 'https://example.com' });
50
+
51
+ // With guest mode
52
+ await testdriver.provision.chrome({
53
+ url: 'https://example.com',
54
+ guest: true,
55
+ maximized: true,
56
+ });
57
+ ```
58
+
59
+ ### chromeExtension()
60
+
61
+ Install and launch a Chrome extension. You can install from a local unpacked directory or from the Chrome Web Store by extension ID.
62
+
63
+ ```javascript
64
+ await testdriver.provision.chromeExtension(options)
65
+ ```
66
+
67
+ <ParamField path="options" type="ProvisionChromeExtensionOptions" required>
68
+ One of `extensionPath` or `extensionId` is required.
69
+
70
+ <Expandable title="properties">
71
+ <ParamField path="extensionPath" type="string">
72
+ Local path to an unpacked extension directory. The extension files are uploaded to the sandbox.
73
+ </ParamField>
74
+
75
+ <ParamField path="extensionId" type="string">
76
+ Chrome Web Store extension ID to install.
77
+ </ParamField>
78
+
79
+ <ParamField path="maximized" type="boolean" default={true}>
80
+ Launch Chrome in maximized window mode.
81
+ </ParamField>
82
+ </Expandable>
83
+ </ParamField>
84
+
85
+ ```javascript
86
+ // From local directory
87
+ await testdriver.provision.chromeExtension({
88
+ extensionPath: './my-extension',
89
+ });
90
+
91
+ // From Chrome Web Store
92
+ await testdriver.provision.chromeExtension({
93
+ extensionId: 'abcdefghijklmnop',
94
+ });
95
+ ```
96
+
97
+ ### vscode()
98
+
99
+ Launch Visual Studio Code with an optional workspace and extensions.
100
+
101
+ ```javascript
102
+ await testdriver.provision.vscode(options?)
103
+ ```
104
+
105
+ <ParamField path="options" type="ProvisionVSCodeOptions">
106
+ <Expandable title="properties">
107
+ <ParamField path="workspace" type="string">
108
+ Path to a workspace folder or `.code-workspace` file to open.
109
+ </ParamField>
110
+
111
+ <ParamField path="extensions" type="string[]" default={[]}>
112
+ Array of VS Code extension IDs to install before launching.
113
+ </ParamField>
114
+ </Expandable>
115
+ </ParamField>
116
+
117
+ ```javascript
118
+ await testdriver.provision.vscode({
119
+ workspace: '/home/testdriver/project',
120
+ extensions: ['ms-python.python', 'esbenp.prettier-vscode'],
121
+ });
122
+ ```
123
+
124
+ ### installer()
125
+
126
+ Download and run an application installer. Supports `.msi`, `.exe`, `.deb`, `.rpm`, `.appimage`, `.sh`, `.dmg`, and `.pkg` formats.
127
+
128
+ ```javascript
129
+ await testdriver.provision.installer(options)
130
+ ```
131
+
132
+ <ParamField path="options" type="ProvisionInstallerOptions" required>
133
+ <Expandable title="properties">
134
+ <ParamField path="url" type="string" required>
135
+ Download URL for the installer.
136
+ </ParamField>
137
+
138
+ <ParamField path="filename" type="string">
139
+ Override the auto-detected filename from the URL.
140
+ </ParamField>
141
+
142
+ <ParamField path="appName" type="string">
143
+ Application name to focus after installation completes.
144
+ </ParamField>
145
+
146
+ <ParamField path="launch" type="boolean" default={true}>
147
+ Whether to focus/launch the app after installation.
148
+ </ParamField>
149
+ </Expandable>
150
+ </ParamField>
151
+
152
+ **Behavior:**
153
+ - Downloads the installer from the URL
154
+ - Auto-detects the install method based on file extension
155
+ - Runs the appropriate install command (e.g., `msiexec` for `.msi`, `dpkg` for `.deb`)
156
+ - Optionally focuses the installed application
157
+
158
+ ```javascript
159
+ // Windows MSI installer
160
+ await testdriver.provision.installer({
161
+ url: 'https://example.com/app-setup.msi',
162
+ appName: 'MyApp',
163
+ });
164
+
165
+ // Linux DEB package
166
+ await testdriver.provision.installer({
167
+ url: 'https://example.com/app.deb',
168
+ appName: 'MyApp',
169
+ launch: true,
170
+ });
171
+ ```
172
+
173
+ ### electron()
174
+
175
+ Launch an Electron application.
176
+
177
+ ```javascript
178
+ await testdriver.provision.electron(options)
179
+ ```
180
+
181
+ <ParamField path="options" type="ProvisionElectronOptions" required>
182
+ <Expandable title="properties">
183
+ <ParamField path="appPath" type="string" required>
184
+ Path to the Electron application directory or executable.
185
+ </ParamField>
186
+
187
+ <ParamField path="args" type="string[]" default={[]}>
188
+ Additional command-line arguments to pass to the Electron app.
189
+ </ParamField>
190
+ </Expandable>
191
+ </ParamField>
192
+
193
+ ```javascript
194
+ await testdriver.provision.electron({
195
+ appPath: '/home/testdriver/my-electron-app',
196
+ args: ['--no-sandbox'],
197
+ });
198
+ ```
199
+
200
+ ### dashcam()
201
+
202
+ Start Dashcam recording with custom options. Usually called automatically by other provision methods, but can be called directly for custom configurations.
203
+
204
+ ```javascript
205
+ await testdriver.provision.dashcam(options?)
206
+ ```
207
+
208
+ <ParamField path="options" type="ProvisionDashcamOptions">
209
+ <Expandable title="properties">
210
+ <ParamField path="logPath" type="string">
211
+ Path to the TestDriver log file. Defaults to `/tmp/testdriver.log` (Linux) or `C:\Users\testdriver\testdriver.log` (Windows).
212
+ </ParamField>
213
+
214
+ <ParamField path="logName" type="string" default="TestDriver Log">
215
+ Display name for the log file in the Dashcam replay.
216
+ </ParamField>
217
+
218
+ <ParamField path="webLogs" type="boolean" default={true}>
219
+ Enable web traffic log capture.
220
+ </ParamField>
221
+
222
+ <ParamField path="title" type="string">
223
+ Recording title for the Dashcam session.
224
+ </ParamField>
225
+ </Expandable>
226
+ </ParamField>
227
+
228
+ ```javascript
229
+ await testdriver.provision.dashcam({
230
+ title: 'Login Flow Test',
231
+ logPath: '/tmp/my-app.log',
232
+ logName: 'Application Log',
233
+ webLogs: true,
234
+ });
235
+ ```
236
+
237
+ ## Reconnect Behavior
238
+
239
+ When `reconnect: true` is set on the client, all provision methods are wrapped in a Proxy that intercepts calls and skips them silently. This is because when reconnecting to an existing sandbox, the applications are already running.
240
+
241
+ ```javascript
242
+ const testdriver = new TestDriver({
243
+ reconnect: true,
244
+ });
245
+
246
+ await testdriver.ready();
247
+
248
+ // These calls are silently skipped:
249
+ await testdriver.provision.chrome({ url: 'https://example.com' });
250
+ await testdriver.provision.dashcam();
251
+ ```
252
+
253
+ ## Types
254
+
255
+ ```typescript
256
+ interface ProvisionChromeOptions {
257
+ url?: string; // Default: "http://testdriver-sandbox.vercel.app/"
258
+ maximized?: boolean; // Default: true
259
+ guest?: boolean; // Default: false
260
+ }
261
+
262
+ interface ProvisionChromeExtensionOptions {
263
+ extensionPath?: string; // Local unpacked extension path
264
+ extensionId?: string; // Chrome Web Store ID
265
+ maximized?: boolean; // Default: true
266
+ }
267
+
268
+ interface ProvisionVSCodeOptions {
269
+ workspace?: string; // Workspace path
270
+ extensions?: string[]; // Extension IDs to install
271
+ }
272
+
273
+ interface ProvisionInstallerOptions {
274
+ url: string; // Download URL (required)
275
+ filename?: string; // Override filename
276
+ appName?: string; // App name to focus
277
+ launch?: boolean; // Default: true
278
+ }
279
+
280
+ interface ProvisionElectronOptions {
281
+ appPath: string; // Path to Electron app (required)
282
+ args?: string[]; // Additional args
283
+ }
284
+
285
+ interface ProvisionDashcamOptions {
286
+ logPath?: string; // Log file path
287
+ logName?: string; // Default: "TestDriver Log"
288
+ webLogs?: boolean; // Default: true
289
+ title?: string; // Recording title
290
+ }
291
+ ```
292
+
293
+ ## Complete Example
294
+
295
+ ```javascript
296
+ import { describe, it, beforeAll, afterAll } from 'vitest';
297
+ import TestDriver from 'testdriverai';
298
+
299
+ describe('Chrome Extension Test', () => {
300
+ let testdriver;
301
+
302
+ beforeAll(async () => {
303
+ testdriver = new TestDriver({
304
+ os: 'linux',
305
+ resolution: '1920x1080',
306
+ });
307
+
308
+ await testdriver.ready();
309
+
310
+ // Install extension and open Chrome
311
+ await testdriver.provision.chromeExtension({
312
+ extensionPath: './my-extension',
313
+ });
314
+
315
+ // Start Dashcam with custom logs
316
+ await testdriver.provision.dashcam({
317
+ title: 'Extension Test',
318
+ webLogs: true,
319
+ });
320
+ });
321
+
322
+ afterAll(async () => {
323
+ await testdriver.disconnect();
324
+ });
325
+
326
+ it('tests the extension popup', async () => {
327
+ await testdriver.find('extension icon').click();
328
+ await testdriver.find('popup content').click();
329
+ });
330
+ });
331
+ ```