testdriverai 7.8.0-test.8 → 7.8.0
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.
- package/agent/index.js +6 -5
- package/agent/lib/commands.js +3 -2
- package/agent/lib/http.js +144 -0
- package/agent/lib/sandbox.js +117 -102
- package/agent/lib/sdk.js +4 -2
- package/agent/lib/system.js +25 -65
- package/ai/skills/testdriver-mcp/SKILL.md +7 -0
- package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
- package/docs/changelog.mdx +148 -8
- package/docs/docs.json +44 -37
- package/docs/images/content/vscode/v7-chat.png +0 -0
- package/docs/images/content/vscode/v7-choose-agent.png +0 -0
- package/docs/images/content/vscode/v7-full.png +0 -0
- package/docs/images/content/vscode/v7-onboarding.png +0 -0
- package/docs/v7/cache.mdx +223 -0
- package/docs/v7/copilot/auto-healing.mdx +265 -0
- package/docs/v7/copilot/creating-tests.mdx +156 -0
- package/docs/v7/copilot/github.mdx +143 -0
- package/docs/v7/copilot/running-tests.mdx +149 -0
- package/docs/v7/copilot/setup.mdx +143 -0
- package/docs/v7/enterprise.mdx +3 -110
- package/docs/v7/errors.mdx +248 -0
- package/docs/v7/events.mdx +358 -0
- package/docs/v7/examples/exec-output.mdx +85 -0
- package/docs/v7/examples/exec-pwsh.mdx +83 -0
- package/docs/v7/examples/focus-window.mdx +62 -0
- package/docs/v7/{cloud.mdx → hosted.mdx} +43 -5
- package/docs/v7/mcp.mdx +9 -0
- package/docs/v7/provision.mdx +333 -0
- package/docs/v7/quickstart.mdx +30 -2
- package/docs/v7/redraw.mdx +216 -0
- package/docs/v7/running-tests.mdx +1 -1
- package/docs/v7/screenshots.mdx +186 -0
- package/docs/v7/self-hosted.mdx +127 -44
- package/interfaces/logger.js +0 -12
- package/interfaces/vitest-plugin.mjs +3 -0
- package/lib/core/Dashcam.js +13 -16
- package/lib/environments.json +18 -0
- package/lib/resolve-channel.js +4 -3
- package/{examples → manual}/drag-and-drop.test.mjs +1 -1
- package/package.json +3 -3
- package/sdk.js +3 -3
- package/vitest.config.mjs +20 -32
- /package/{examples → manual}/flake-diffthreshold-001.test.mjs +0 -0
- /package/{examples → manual}/flake-diffthreshold-01.test.mjs +0 -0
- /package/{examples → manual}/flake-diffthreshold-05.test.mjs +0 -0
- /package/{examples → manual}/flake-noredraw-cache.test.mjs +0 -0
- /package/{examples → manual}/flake-noredraw-nocache.test.mjs +0 -0
- /package/{examples → manual}/flake-redraw-cache.test.mjs +0 -0
- /package/{examples → manual}/flake-redraw-nocache.test.mjs +0 -0
- /package/{examples → manual}/flake-rocket-match.test.mjs +0 -0
- /package/{examples → manual}/flake-shared.mjs +0 -0
- /package/{examples → manual}/no-provision.test.mjs +0 -0
- /package/{examples → manual}/scroll-until-text.test.mjs +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Events"
|
|
3
|
+
sidebarTitle: "Events"
|
|
4
|
+
description: "Listen to SDK lifecycle events with wildcard support"
|
|
5
|
+
icon: "bolt"
|
|
6
|
+
mode: "wide"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
TestDriver uses [EventEmitter2](https://github.com/EventEmitter2/EventEmitter2) for its event system. Events use a colon-delimited namespace pattern and support wildcard listeners.
|
|
12
|
+
|
|
13
|
+
Access the emitter through `testdriver.emitter`:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
testdriver.emitter.on('command:start', (data) => {
|
|
17
|
+
console.log(`Running: ${data.command}`);
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Configuration
|
|
22
|
+
|
|
23
|
+
The internal emitter is created with:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
new EventEmitter2({
|
|
27
|
+
wildcard: true,
|
|
28
|
+
delimiter: ':',
|
|
29
|
+
maxListeners: 20,
|
|
30
|
+
verboseMemoryLeak: false,
|
|
31
|
+
ignoreErrors: false,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Wildcard Listeners
|
|
36
|
+
|
|
37
|
+
Use `*` to match a single level or `**` to match multiple levels:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// Match all log events
|
|
41
|
+
testdriver.emitter.on('log:*', (message) => {
|
|
42
|
+
console.log(message);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Match all events in any namespace
|
|
46
|
+
testdriver.emitter.on('**', (...args) => {
|
|
47
|
+
console.log('Event:', this.event, args);
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Event Reference
|
|
52
|
+
|
|
53
|
+
### Command Events
|
|
54
|
+
|
|
55
|
+
Emitted during the execution of SDK commands (`click`, `type`, `find`, etc.).
|
|
56
|
+
|
|
57
|
+
| Event | Payload |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `command:start` | `{ command, depth, data, timestamp, sourcePosition }` |
|
|
60
|
+
| `command:success` | `{ command, depth, data, duration, response, timestamp, sourcePosition }` |
|
|
61
|
+
| `command:error` | `{ command, depth, data, error, duration, timestamp, sourcePosition }` |
|
|
62
|
+
| `command:status` | `{ command, status: "executing", data, depth, timestamp }` |
|
|
63
|
+
| `command:progress` | `{ command, status: "completed", timing, data, depth, timestamp }` |
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
testdriver.emitter.on('command:start', ({ command, data }) => {
|
|
67
|
+
console.log(`Starting ${command}`, data);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
testdriver.emitter.on('command:error', ({ command, error, duration }) => {
|
|
71
|
+
console.error(`${command} failed after ${duration}ms: ${error}`);
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Step Events
|
|
76
|
+
|
|
77
|
+
Emitted for each AI reasoning step within a command.
|
|
78
|
+
|
|
79
|
+
| Event | Payload |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `step:start` | `{ stepIndex, prompt, commandCount, timestamp, sourcePosition }` |
|
|
82
|
+
| `step:success` | `{ stepIndex, prompt, commandCount, duration, timestamp, sourcePosition }` |
|
|
83
|
+
| `step:error` | `{ stepIndex, prompt, error, duration?, timestamp, sourcePosition? }` |
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
|
|
87
|
+
console.log(`Step ${stepIndex}: ${prompt}`);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Test Events
|
|
92
|
+
|
|
93
|
+
Emitted when a test file execution starts.
|
|
94
|
+
|
|
95
|
+
| Event | Payload |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `test:start` | `{ filePath, timestamp }` |
|
|
98
|
+
| `test:success` | *Emitted on test completion* |
|
|
99
|
+
| `test:error` | *Emitted on test failure* |
|
|
100
|
+
|
|
101
|
+
### Log Events
|
|
102
|
+
|
|
103
|
+
Emitted for all log output from the SDK.
|
|
104
|
+
|
|
105
|
+
| Event | Payload |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `log:log` | `(message: string)` — general log message |
|
|
108
|
+
| `log:warn` | `(message: string)` — warning message |
|
|
109
|
+
| `log:debug` | `(message: string)` — debug output (only when `VERBOSE`/`DEBUG`/`TD_DEBUG` env set) |
|
|
110
|
+
| `log:info` | `(message: string)` — informational message |
|
|
111
|
+
| `log:error` | `(message: string)` — error message |
|
|
112
|
+
| `log:narration` | `(message: string, overwrite?: boolean)` — in-place status line |
|
|
113
|
+
| `log:markdown` | `(markdown: string)` — full static markdown content |
|
|
114
|
+
| `log:markdown:start` | `(streamId: string)` — begin streaming markdown |
|
|
115
|
+
| `log:markdown:chunk` | `(streamId: string, chunk: string)` — incremental chunk |
|
|
116
|
+
| `log:markdown:end` | `(streamId: string)` — end streaming markdown |
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// Capture all logs
|
|
120
|
+
testdriver.emitter.on('log:*', function (message) {
|
|
121
|
+
console.log(`[${this.event}]`, message);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Screen Capture Events
|
|
126
|
+
|
|
127
|
+
Emitted during screenshot capture.
|
|
128
|
+
|
|
129
|
+
| Event | Payload |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `screen-capture:start` | `{ scale, silent, display }` |
|
|
132
|
+
| `screen-capture:end` | `{ scale, silent, display }` |
|
|
133
|
+
| `screen-capture:error` | `{ error, scale, silent, display }` |
|
|
134
|
+
|
|
135
|
+
### Sandbox Events
|
|
136
|
+
|
|
137
|
+
Emitted for sandbox WebSocket lifecycle and communication.
|
|
138
|
+
|
|
139
|
+
| Event | Payload |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `sandbox:connected` | *No payload* — WebSocket connection established |
|
|
142
|
+
| `sandbox:authenticated` | `{ traceId }` — authentication successful |
|
|
143
|
+
| `sandbox:error` | `(err: Error \| string)` — connection or sandbox error |
|
|
144
|
+
| `sandbox:sent` | `(message: object)` — WebSocket message sent |
|
|
145
|
+
| `sandbox:received` | *No payload* — successful message reply received |
|
|
146
|
+
| `sandbox:progress` | `{ step, message }` — sandbox setup progress |
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
testdriver.emitter.on('sandbox:connected', () => {
|
|
150
|
+
console.log('Connected to sandbox');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
testdriver.emitter.on('sandbox:progress', ({ step, message }) => {
|
|
154
|
+
console.log(`Sandbox: [${step}] ${message}`);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Redraw Events
|
|
159
|
+
|
|
160
|
+
Emitted during screen stability detection. See [Redraw](/v7/redraw) for more details.
|
|
161
|
+
|
|
162
|
+
| Event | Payload |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `redraw:status` | `{ redraw: { enabled, settled, hasChangedFromInitial, consecutiveFramesStable, diffFromInitial, diffFromLast, text }, network: { enabled, settled, rxBytes, txBytes, text }, timeout: { isTimeout, elapsed, max, text } }` |
|
|
165
|
+
| `redraw:complete` | `{ screenSettled, hasChangedFromInitial, consecutiveFramesStable, networkSettled, isTimeout, timeElapsed }` |
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
testdriver.emitter.on('redraw:complete', (result) => {
|
|
169
|
+
if (result.isTimeout) {
|
|
170
|
+
console.warn('Redraw timed out after', result.timeElapsed, 'ms');
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### File Events
|
|
176
|
+
|
|
177
|
+
Emitted during file load/save operations in the agent.
|
|
178
|
+
|
|
179
|
+
| Event | Payload |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `file:start` | `{ operation: "load" \| "save" \| "run", filePath, timestamp }` |
|
|
182
|
+
| `file:stop` | `{ operation, filePath, duration, success, sourceMap?, reason?, timestamp }` |
|
|
183
|
+
| `file:load` | `{ filePath, size, timestamp }` |
|
|
184
|
+
| `file:save` | `{ filePath, size, timestamp }` |
|
|
185
|
+
| `file:diff` | `{ filePath, diff: { patches, sourceMaps, summary: { additions, deletions, modifications } }, timestamp }` |
|
|
186
|
+
| `file:error` | `{ operation, filePath, error, duration?, timestamp }` |
|
|
187
|
+
|
|
188
|
+
### Error Events
|
|
189
|
+
|
|
190
|
+
Emitted for errors at various severity levels.
|
|
191
|
+
|
|
192
|
+
| Event | Payload |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `error:fatal` | `(error: string \| Error)` — terminates the process |
|
|
195
|
+
| `error:general` | `(message: string)` — non-fatal error |
|
|
196
|
+
| `error:sandbox` | `(err: Error \| string)` — sandbox/WebSocket error |
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
testdriver.emitter.on('error:*', function (err) {
|
|
200
|
+
console.error(`[${this.event}]`, err);
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### SDK Events
|
|
205
|
+
|
|
206
|
+
Emitted for API request lifecycle.
|
|
207
|
+
|
|
208
|
+
| Event | Payload |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `sdk:request` | `{ path }` — outgoing API request |
|
|
211
|
+
| `sdk:response` | `{ path }` — API response received |
|
|
212
|
+
| `sdk:retry` | `{ path, attempt, error, delayMs }` — request retry |
|
|
213
|
+
|
|
214
|
+
### Other Events
|
|
215
|
+
|
|
216
|
+
| Event | Payload |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `exit` | `(exitCode: number)` — `0` for success, `1` for failure |
|
|
219
|
+
| `status` | `(message: string)` — general status updates |
|
|
220
|
+
| `mouse-click` | `{ x, y, button, click, double }` — mouse click performed |
|
|
221
|
+
| `terminal:stdout` | Terminal stdout output |
|
|
222
|
+
| `terminal:stderr` | Terminal stderr output |
|
|
223
|
+
|
|
224
|
+
## Practical Examples
|
|
225
|
+
|
|
226
|
+
### Custom Test Reporter
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
const results = [];
|
|
230
|
+
|
|
231
|
+
testdriver.emitter.on('command:success', ({ command, duration }) => {
|
|
232
|
+
results.push({ command, duration, status: 'pass' });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
testdriver.emitter.on('command:error', ({ command, duration, error }) => {
|
|
236
|
+
results.push({ command, duration, status: 'fail', error });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// After test completes
|
|
240
|
+
afterAll(() => {
|
|
241
|
+
console.table(results);
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Progress Monitoring
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
testdriver.emitter.on('step:start', ({ stepIndex, prompt }) => {
|
|
249
|
+
process.stdout.write(`\r Step ${stepIndex}: ${prompt}`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
testdriver.emitter.on('command:progress', ({ command, timing }) => {
|
|
253
|
+
process.stdout.write(`\r ${command} completed in ${timing}ms`);
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Debug Logging
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
// Log every event (verbose)
|
|
261
|
+
testdriver.emitter.on('**', function (...args) {
|
|
262
|
+
console.debug(`[EVENT] ${this.event}`, ...args);
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Types
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
interface CommandStartEvent {
|
|
270
|
+
command: string;
|
|
271
|
+
depth: number;
|
|
272
|
+
data: Record<string, any>;
|
|
273
|
+
timestamp: number;
|
|
274
|
+
sourcePosition: SourcePosition;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface CommandSuccessEvent {
|
|
278
|
+
command: string;
|
|
279
|
+
depth: number;
|
|
280
|
+
data: Record<string, any>;
|
|
281
|
+
duration: number;
|
|
282
|
+
response: any;
|
|
283
|
+
timestamp: number;
|
|
284
|
+
sourcePosition: SourcePosition;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
interface CommandErrorEvent {
|
|
288
|
+
command: string;
|
|
289
|
+
depth: number;
|
|
290
|
+
data: Record<string, any>;
|
|
291
|
+
error: string;
|
|
292
|
+
duration: number;
|
|
293
|
+
timestamp: number;
|
|
294
|
+
sourcePosition: SourcePosition;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
interface StepStartEvent {
|
|
298
|
+
stepIndex: number;
|
|
299
|
+
prompt: string;
|
|
300
|
+
commandCount: number;
|
|
301
|
+
timestamp: number;
|
|
302
|
+
sourcePosition: SourcePosition;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
interface StepSuccessEvent {
|
|
306
|
+
stepIndex: number;
|
|
307
|
+
prompt: string;
|
|
308
|
+
commandCount: number;
|
|
309
|
+
duration: number;
|
|
310
|
+
timestamp: number;
|
|
311
|
+
sourcePosition: SourcePosition;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
interface RedrawStatusEvent {
|
|
315
|
+
redraw: {
|
|
316
|
+
enabled: boolean;
|
|
317
|
+
settled: boolean;
|
|
318
|
+
hasChangedFromInitial: boolean;
|
|
319
|
+
consecutiveFramesStable: number;
|
|
320
|
+
diffFromInitial: number;
|
|
321
|
+
diffFromLast: number;
|
|
322
|
+
text: string;
|
|
323
|
+
};
|
|
324
|
+
network: {
|
|
325
|
+
enabled: boolean;
|
|
326
|
+
settled: boolean;
|
|
327
|
+
rxBytes: number;
|
|
328
|
+
txBytes: number;
|
|
329
|
+
text: string;
|
|
330
|
+
};
|
|
331
|
+
timeout: {
|
|
332
|
+
isTimeout: boolean;
|
|
333
|
+
elapsed: number;
|
|
334
|
+
max: number;
|
|
335
|
+
text: string;
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface RedrawCompleteEvent {
|
|
340
|
+
screenSettled: boolean;
|
|
341
|
+
hasChangedFromInitial: boolean;
|
|
342
|
+
consecutiveFramesStable: number;
|
|
343
|
+
networkSettled: boolean;
|
|
344
|
+
isTimeout: boolean;
|
|
345
|
+
timeElapsed: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
interface SandboxProgressEvent {
|
|
349
|
+
step: string;
|
|
350
|
+
message: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
interface SourcePosition {
|
|
354
|
+
file: string;
|
|
355
|
+
line: number;
|
|
356
|
+
column: number;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Exec Output Test Example"
|
|
3
|
+
sidebarTitle: "Exec Output"
|
|
4
|
+
description: "Example test demonstrating how to capture and use output from PowerShell exec commands."
|
|
5
|
+
icon: "terminal"
|
|
6
|
+
mode: "wide"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Demo Test Run
|
|
10
|
+
|
|
11
|
+
Watch this test execute in a real sandbox environment:
|
|
12
|
+
|
|
13
|
+
{/* exec-output.test.mjs output */}
|
|
14
|
+
<iframe
|
|
15
|
+
src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b42fc0ac3cc632a918c/replay"
|
|
16
|
+
width="100%"
|
|
17
|
+
height="390"
|
|
18
|
+
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
19
|
+
allow="fullscreen"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Source Code
|
|
23
|
+
|
|
24
|
+
```javascript title="exec-output.test.mjs" {22-27}
|
|
25
|
+
/**
|
|
26
|
+
* TestDriver SDK - Exec Output Test (Vitest)
|
|
27
|
+
* Converted from: testdriver/acceptance/exec-output.yaml
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, expect, it } from "vitest";
|
|
31
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
32
|
+
import { getDefaults } from "./config.mjs";
|
|
33
|
+
|
|
34
|
+
describe.skip("Exec Output Test", () => {
|
|
35
|
+
it(
|
|
36
|
+
"should set date using PowerShell and navigate to calendar",
|
|
37
|
+
async (context) => {
|
|
38
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
39
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Generate date in query string format
|
|
43
|
+
const queryString = await testdriver.exec(
|
|
44
|
+
"pwsh",
|
|
45
|
+
`
|
|
46
|
+
$date = (Get-Date).AddMonths(1)
|
|
47
|
+
Write-Output $date.ToString("yyyy-MM-dd")
|
|
48
|
+
`,
|
|
49
|
+
10000,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Assert that the date is valid
|
|
53
|
+
const dateValidResult = await testdriver.assert(
|
|
54
|
+
`${queryString} is a valid date`,
|
|
55
|
+
);
|
|
56
|
+
expect(dateValidResult).toBeTruthy();
|
|
57
|
+
|
|
58
|
+
// Generate date in display format
|
|
59
|
+
const expectedDate = await testdriver.exec(
|
|
60
|
+
"pwsh",
|
|
61
|
+
`
|
|
62
|
+
$date = (Get-Date).AddMonths(1)
|
|
63
|
+
Write-Output $date.ToString("ddd MMM d yyyy")
|
|
64
|
+
`,
|
|
65
|
+
10000,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Navigate to calendar with date parameter
|
|
69
|
+
await testdriver.focusApplication("Google Chrome");
|
|
70
|
+
await testdriver.pressKeys(["ctrl", "l"]);
|
|
71
|
+
await testdriver.type(
|
|
72
|
+
`https://teamup.com/ks48cf2135e7e080bc?view=d&date=${queryString}`,
|
|
73
|
+
);
|
|
74
|
+
await testdriver.pressKeys(["enter"]);
|
|
75
|
+
|
|
76
|
+
// Assert that the expected date shows
|
|
77
|
+
await testdriver.focusApplication("Google Chrome");
|
|
78
|
+
const result = await testdriver.assert(
|
|
79
|
+
`the text ${expectedDate} is visible on screen`,
|
|
80
|
+
);
|
|
81
|
+
expect(result).toBeTruthy();
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Exec PowerShell Test Example"
|
|
3
|
+
sidebarTitle: "Exec Pwsh"
|
|
4
|
+
description: "Example test demonstrating how to generate dynamic data using PowerShell and use it in test interactions."
|
|
5
|
+
icon: "terminal"
|
|
6
|
+
mode: "wide"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Demo Test Run
|
|
10
|
+
|
|
11
|
+
Watch this test execute in a real sandbox environment:
|
|
12
|
+
|
|
13
|
+
{/* exec-pwsh.test.mjs output */}
|
|
14
|
+
<iframe
|
|
15
|
+
src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b42565d339e8065f180/replay"
|
|
16
|
+
width="100%"
|
|
17
|
+
height="390"
|
|
18
|
+
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
19
|
+
allow="fullscreen"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Source Code
|
|
23
|
+
|
|
24
|
+
```javascript title="exec-pwsh.test.mjs" {20-38}
|
|
25
|
+
/**
|
|
26
|
+
* TestDriver SDK - Exec Shell Test (Vitest)
|
|
27
|
+
* Converted from: testdriver/acceptance/exec-shell.yaml
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, expect, it } from "vitest";
|
|
31
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
32
|
+
import { getDefaults } from "./config.mjs";
|
|
33
|
+
|
|
34
|
+
describe.skip("Exec PowerShell Test", () => {
|
|
35
|
+
it(
|
|
36
|
+
"should generate random email using PowerShell and enter it",
|
|
37
|
+
async (context) => {
|
|
38
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
39
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Generate random email using PowerShell
|
|
43
|
+
const randomEmail = await testdriver.exec({
|
|
44
|
+
language: "pwsh",
|
|
45
|
+
code: `
|
|
46
|
+
# Random email generator in PowerShell
|
|
47
|
+
|
|
48
|
+
# Arrays of possible names and domains
|
|
49
|
+
$firstNames = @("john", "jane", "alex", "chris", "sara", "mike", "lisa", "david", "emma", "ryan")
|
|
50
|
+
$lastNames = @("smith", "johnson", "williams", "brown", "jones", "garcia", "miller", "davis", "martin", "lee")
|
|
51
|
+
$domains = @("example.com", "testmail.com", "mailinator.com", "demo.org", "company.net")
|
|
52
|
+
|
|
53
|
+
# Random selection
|
|
54
|
+
$first = Get-Random -InputObject $firstNames
|
|
55
|
+
$last = Get-Random -InputObject $lastNames
|
|
56
|
+
$domain = Get-Random -InputObject $domains
|
|
57
|
+
$number = Get-Random -Minimum 1 -Maximum 1000
|
|
58
|
+
|
|
59
|
+
# Generate the email
|
|
60
|
+
$email = "$first.$last$number@$domain".ToLower()
|
|
61
|
+
|
|
62
|
+
# Output
|
|
63
|
+
Write-Output "$email"
|
|
64
|
+
`,
|
|
65
|
+
timeout: 10000,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Enter the email in username field
|
|
69
|
+
const usernameField = await testdriver.find(
|
|
70
|
+
"Username, input field for username",
|
|
71
|
+
);
|
|
72
|
+
await usernameField.click();
|
|
73
|
+
await testdriver.type(randomEmail);
|
|
74
|
+
|
|
75
|
+
// Assert that the username field shows a valid email address
|
|
76
|
+
const result = await testdriver.assert(
|
|
77
|
+
`the username field contains ${randomEmail} which is a valid email address`,
|
|
78
|
+
);
|
|
79
|
+
expect(result).toBeTruthy();
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Focus Window Test Example"
|
|
3
|
+
sidebarTitle: "Focus Window"
|
|
4
|
+
description: "Example test demonstrating how to switch focus between application windows."
|
|
5
|
+
icon: "window-maximize"
|
|
6
|
+
mode: "wide"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Demo Test Run
|
|
10
|
+
|
|
11
|
+
Watch this test execute in a real sandbox environment:
|
|
12
|
+
|
|
13
|
+
{/* focus-window.test.mjs output */}
|
|
14
|
+
<iframe
|
|
15
|
+
src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b4549845ced0b71e2b1/replay"
|
|
16
|
+
width="100%"
|
|
17
|
+
height="390"
|
|
18
|
+
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
19
|
+
allow="fullscreen"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
## Source Code
|
|
23
|
+
|
|
24
|
+
```javascript title="focus-window.test.mjs" {23-25}
|
|
25
|
+
/**
|
|
26
|
+
* TestDriver SDK - Focus Window Test (Vitest)
|
|
27
|
+
* Converted from: testdriver/acceptance/focus-window.yaml
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, expect, it } from "vitest";
|
|
31
|
+
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
32
|
+
import { getDefaults } from "./config.mjs";
|
|
33
|
+
|
|
34
|
+
describe("Focus Window Test", () => {
|
|
35
|
+
it.skip(
|
|
36
|
+
"should click Microsoft Edge icon and focus Google Chrome",
|
|
37
|
+
async (context) => {
|
|
38
|
+
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
39
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Show desktop
|
|
43
|
+
await testdriver.pressKeys(["winleft", "d"]);
|
|
44
|
+
|
|
45
|
+
// Click on the Microsoft Edge icon
|
|
46
|
+
const edgeIcon = await testdriver.find(
|
|
47
|
+
"a blue and green swirl icon on the taskbar representing Microsoft Edge",
|
|
48
|
+
);
|
|
49
|
+
await edgeIcon.click();
|
|
50
|
+
|
|
51
|
+
// Focus Google Chrome
|
|
52
|
+
await testdriver.focusApplication("Google Chrome");
|
|
53
|
+
|
|
54
|
+
// Assert Chrome is focused (implicit through successful focus)
|
|
55
|
+
const result = await testdriver.assert(
|
|
56
|
+
"Google Chrome is the focused application",
|
|
57
|
+
);
|
|
58
|
+
expect(result).toBeTruthy();
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
sidebarTitle: "
|
|
2
|
+
title: "Hosted"
|
|
3
|
+
sidebarTitle: "Hosted"
|
|
4
4
|
description: "The fastest way to get started with TestDriver. Just set your API key and start testing."
|
|
5
5
|
icon: "cloud"
|
|
6
|
+
mode: "wide"
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Hosted pricing is based on **device-seconds**: the amount of time your tests run on **our infrastructure**.
|
|
9
10
|
|
|
10
11
|
- **Zero Setup** — Start testing immediately. No DevOps required.
|
|
11
12
|
- **Free Tier** — Get started with a limited preview at no cost.
|
|
12
13
|
- **Pay As You Go** — Only pay for the device-seconds you use.
|
|
13
14
|
|
|
15
|
+
## Hosted Plans
|
|
16
|
+
|
|
17
|
+
<CardGroup cols={3}>
|
|
18
|
+
<Card title="Free Trial" icon="gift" href="https://docs.testdriver.ai">
|
|
19
|
+
**$0/month**
|
|
20
|
+
|
|
21
|
+
- 1 Concurrent Sandbox
|
|
22
|
+
- 60 Minutes Included
|
|
23
|
+
- 1 Team User
|
|
24
|
+
- Community Support
|
|
25
|
+
</Card>
|
|
26
|
+
|
|
27
|
+
<Card title="Pro" icon="rocket" href="https://console.testdriver.ai/checkout/pro">
|
|
28
|
+
**$20/month**
|
|
29
|
+
|
|
30
|
+
- 2 Concurrent Sandboxes
|
|
31
|
+
- 600 Minutes Included
|
|
32
|
+
- Overage: $0.002/second
|
|
33
|
+
- 1 Team User
|
|
34
|
+
- Test Recordings
|
|
35
|
+
- Community Support
|
|
36
|
+
</Card>
|
|
37
|
+
|
|
38
|
+
<Card title="Team" icon="users" href="https://console.testdriver.ai/checkout/team">
|
|
39
|
+
**$600/month**
|
|
40
|
+
|
|
41
|
+
- 8 Concurrent Sandboxes
|
|
42
|
+
- 10,000 Minutes Included
|
|
43
|
+
- Overage: $0.001/second
|
|
44
|
+
- 5 Team Users
|
|
45
|
+
- Test Recordings
|
|
46
|
+
- Private Support
|
|
47
|
+
- Test Analytics
|
|
48
|
+
- CPU, RAM, & Network Profiles
|
|
49
|
+
</Card>
|
|
50
|
+
</CardGroup>
|
|
51
|
+
|
|
14
52
|
## Get Started
|
|
15
|
-
|
|
53
|
+
Hosted is the default when you follow the Quickstart guide.
|
|
16
54
|
<Card
|
|
17
55
|
title="Try the Quickstart"
|
|
18
56
|
icon="play"
|
|
@@ -102,7 +140,7 @@ To prevent tests from failing due to exceeding your license slot limit, we recom
|
|
|
102
140
|
|
|
103
141
|
## When to Consider Self-Hosted
|
|
104
142
|
|
|
105
|
-
|
|
143
|
+
Hosted is perfect for getting started and for teams that want zero infrastructure management. However, you might consider [Self-Hosted](/v7/self-hosted) if you:
|
|
106
144
|
|
|
107
145
|
- Want to escape per-second billing with a flat license fee
|
|
108
146
|
- Require greater concurrency than offered in Cloud plans
|