testdriverai 7.3.44 → 7.4.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.
- package/.github/skills/testdriver:find/SKILL.md +13 -4
- package/.github/skills/testdriver:waiting-for-elements/SKILL.md +10 -4
- package/agent/lib/commands.js +172 -232
- package/agent/lib/debugger-server.js +33 -0
- package/agent/lib/sandbox.js +9 -1
- package/agent/lib/sdk.js +67 -0
- package/ai/skills/testdriver:client/SKILL.md +107 -2
- package/ai/skills/testdriver:customizing-devices/SKILL.md +170 -4
- package/ai/skills/testdriver:find/SKILL.md +13 -4
- package/ai/skills/testdriver:waiting-for-elements/SKILL.md +10 -4
- package/docs/v7/find.mdx +13 -4
- package/docs/v7/waiting-for-elements.mdx +10 -4
- package/examples/hover-image.test.mjs +11 -0
- package/package.json +1 -1
- package/sdk.js +8 -4
|
@@ -84,6 +84,13 @@ function createDebuggerServer(config = {}) {
|
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
const actualPort = address.port;
|
|
87
|
+
|
|
88
|
+
// Don't let the debugger server prevent Node process from exiting
|
|
89
|
+
// This ensures tests can exit even if debugger cleanup fails
|
|
90
|
+
if (server.unref) {
|
|
91
|
+
server.unref();
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
resolve({ port: actualPort, server, wss });
|
|
88
95
|
});
|
|
89
96
|
|
|
@@ -110,6 +117,9 @@ function broadcastEvent(event, data) {
|
|
|
110
117
|
|
|
111
118
|
async function startDebugger(config = {}, emitter) {
|
|
112
119
|
try {
|
|
120
|
+
// Register exit handler to ensure cleanup on process exit
|
|
121
|
+
registerExitHandler();
|
|
122
|
+
|
|
113
123
|
const { port } = await createDebuggerServer(config);
|
|
114
124
|
const url = `http://localhost:${port}`;
|
|
115
125
|
|
|
@@ -190,6 +200,29 @@ function stopDebugger() {
|
|
|
190
200
|
forceStopDebugger();
|
|
191
201
|
}
|
|
192
202
|
|
|
203
|
+
// Ensure debugger server is cleaned up on process exit
|
|
204
|
+
// This prevents zombie processes when tests crash or cleanup fails
|
|
205
|
+
let exitHandlerRegistered = false;
|
|
206
|
+
function registerExitHandler() {
|
|
207
|
+
if (exitHandlerRegistered) return;
|
|
208
|
+
exitHandlerRegistered = true;
|
|
209
|
+
|
|
210
|
+
process.on("exit", () => {
|
|
211
|
+
if (server || wss) {
|
|
212
|
+
// Synchronous cleanup - can't await in exit handler
|
|
213
|
+
if (wss) {
|
|
214
|
+
try { wss.close(); } catch (e) { /* ignore */ }
|
|
215
|
+
wss = null;
|
|
216
|
+
}
|
|
217
|
+
if (server) {
|
|
218
|
+
try { server.close(); } catch (e) { /* ignore */ }
|
|
219
|
+
server = null;
|
|
220
|
+
}
|
|
221
|
+
clients.clear();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
193
226
|
module.exports = {
|
|
194
227
|
startDebugger,
|
|
195
228
|
stopDebugger,
|
package/agent/lib/sandbox.js
CHANGED
|
@@ -128,6 +128,10 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
|
|
|
128
128
|
);
|
|
129
129
|
}
|
|
130
130
|
}, timeout);
|
|
131
|
+
// Don't let pending timeouts prevent Node process from exiting
|
|
132
|
+
if (timeoutId.unref) {
|
|
133
|
+
timeoutId.unref();
|
|
134
|
+
}
|
|
131
135
|
|
|
132
136
|
// Track timeout so close() can clear it
|
|
133
137
|
this.pendingTimeouts.set(requestId, timeoutId);
|
|
@@ -150,7 +154,7 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
|
|
|
150
154
|
|
|
151
155
|
// Fire-and-forget message types: attach .catch() to prevent
|
|
152
156
|
// unhandled promise rejections if nobody awaits the result
|
|
153
|
-
const fireAndForgetTypes = ["output"
|
|
157
|
+
const fireAndForgetTypes = ["output"];
|
|
154
158
|
if (fireAndForgetTypes.includes(message.type)) {
|
|
155
159
|
p.catch(() => {});
|
|
156
160
|
}
|
|
@@ -360,6 +364,10 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
|
|
|
360
364
|
this.reconnecting = false;
|
|
361
365
|
}
|
|
362
366
|
}, delay);
|
|
367
|
+
// Don't let reconnect timer prevent Node process from exiting
|
|
368
|
+
if (this.reconnectTimer.unref) {
|
|
369
|
+
this.reconnectTimer.unref();
|
|
370
|
+
}
|
|
363
371
|
}
|
|
364
372
|
|
|
365
373
|
/**
|
package/agent/lib/sdk.js
CHANGED
|
@@ -346,6 +346,73 @@ const createSDK = (emitter, config, sessionInstance) => {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
// ── S3 upload: replace large inline base64 images with S3 keys ──────
|
|
350
|
+
// If data.image is a large base64 string (>50KB), upload the raw PNG
|
|
351
|
+
// to S3 via a presigned URL and send only the imageKey instead.
|
|
352
|
+
// This reduces JSON body size from ~1.3MB to ~60 bytes.
|
|
353
|
+
const MIN_IMAGE_SIZE = 50_000; // 50KB base64 chars
|
|
354
|
+
if (
|
|
355
|
+
data &&
|
|
356
|
+
typeof data.image === "string" &&
|
|
357
|
+
data.image.length > MIN_IMAGE_SIZE
|
|
358
|
+
) {
|
|
359
|
+
try {
|
|
360
|
+
const apiRoot = config["TD_API_ROOT"];
|
|
361
|
+
const uploadUrlEndpoint = [apiRoot, "api", version, "testdriver", "upload-url"].join("/");
|
|
362
|
+
|
|
363
|
+
// Step 1: Get presigned upload URL from API
|
|
364
|
+
const uploadRes = await axios(uploadUrlEndpoint, {
|
|
365
|
+
method: "post",
|
|
366
|
+
headers: {
|
|
367
|
+
"Content-Type": "application/json",
|
|
368
|
+
...(token && { Authorization: `Bearer ${token}` }),
|
|
369
|
+
},
|
|
370
|
+
timeout: 15000,
|
|
371
|
+
data: {
|
|
372
|
+
session: sessionInstance.get(),
|
|
373
|
+
contentType: "image/png",
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const { uploadUrl, imageKey } = uploadRes.data;
|
|
378
|
+
|
|
379
|
+
if (uploadUrl && imageKey) {
|
|
380
|
+
// Step 2: Upload raw PNG bytes to S3 via presigned PUT URL
|
|
381
|
+
const base64Data = data.image.replace(/^data:image\/\w+;base64,/, "");
|
|
382
|
+
const pngBuffer = Buffer.from(base64Data, "base64");
|
|
383
|
+
|
|
384
|
+
await axios(uploadUrl, {
|
|
385
|
+
method: "put",
|
|
386
|
+
headers: {
|
|
387
|
+
"Content-Type": "image/png",
|
|
388
|
+
"Content-Length": pngBuffer.length,
|
|
389
|
+
},
|
|
390
|
+
data: pngBuffer,
|
|
391
|
+
timeout: 30000,
|
|
392
|
+
maxBodyLength: Infinity,
|
|
393
|
+
maxContentLength: Infinity,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Step 3: Replace image with imageKey in the request data
|
|
397
|
+
const savedKB = (data.image.length / 1024).toFixed(0);
|
|
398
|
+
delete data.image;
|
|
399
|
+
data.imageKey = imageKey;
|
|
400
|
+
emitter.emit(events.log?.debug || events.sdk.request, {
|
|
401
|
+
path,
|
|
402
|
+
message: `[sdk] uploaded screenshot to S3 (saved ${savedKB}KB inline), imageKey=${imageKey}`,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} catch (uploadErr) {
|
|
406
|
+
// Non-fatal: fall back to sending base64 inline
|
|
407
|
+
// This ensures old API servers without the upload-url endpoint still work
|
|
408
|
+
emitter.emit(events.log?.debug || events.sdk.request, {
|
|
409
|
+
path,
|
|
410
|
+
message: `[sdk] S3 upload failed, falling back to inline base64: ${uploadErr.message}`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// ── End S3 upload ───────────────────────────────────────────────────
|
|
415
|
+
|
|
349
416
|
emitter.emit(events.sdk.request, {
|
|
350
417
|
path,
|
|
351
418
|
});
|
|
@@ -24,7 +24,7 @@ const testdriver = new TestDriver(apiKey, options)
|
|
|
24
24
|
Configuration options for the client
|
|
25
25
|
|
|
26
26
|
<Expandable title="properties">
|
|
27
|
-
<ParamField path="os" type="string" default="
|
|
27
|
+
<ParamField path="os" type="string" default="linux">
|
|
28
28
|
Operating system for the sandbox: `'windows'` or `'linux'`
|
|
29
29
|
</ParamField>
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ const testdriver = new TestDriver(apiKey, options)
|
|
|
32
32
|
Screen resolution for the sandbox (e.g., `'1920x1080'`, `'1366x768'`)
|
|
33
33
|
</ParamField>
|
|
34
34
|
|
|
35
|
-
<ParamField path="apiRoot" type="string"
|
|
35
|
+
<ParamField path="apiRoot" type="string">
|
|
36
36
|
API endpoint URL (typically only changed for self-hosted deployments)
|
|
37
37
|
</ParamField>
|
|
38
38
|
|
|
@@ -48,6 +48,111 @@ const testdriver = new TestDriver(apiKey, options)
|
|
|
48
48
|
Automatically capture screenshots before and after each command. Screenshots are saved to `.testdriver/screenshots/<test>/` with descriptive filenames that include the line number and action name. Format: `<seq>-<action>-<phase>-L<line>-<description>.png`
|
|
49
49
|
</ParamField>
|
|
50
50
|
|
|
51
|
+
<ParamField path="newSandbox" type="boolean" default="true">
|
|
52
|
+
Force creation of a new sandbox instead of reusing an existing one
|
|
53
|
+
</ParamField>
|
|
54
|
+
|
|
55
|
+
<ParamField path="reconnect" type="boolean" default="false">
|
|
56
|
+
Reconnect to the last used sandbox instead of creating a new one. When `true`, provision methods (`chrome`, `vscode`, `installer`, etc.) will be skipped since the application is already running. Throws error if no previous sandbox exists.
|
|
57
|
+
</ParamField>
|
|
58
|
+
|
|
59
|
+
<ParamField path="keepAlive" type="number" default="60000">
|
|
60
|
+
Keep sandbox alive for the specified number of milliseconds after disconnect. Set to `0` to terminate immediately on disconnect. Useful for debugging or reconnecting to the same sandbox.
|
|
61
|
+
</ParamField>
|
|
62
|
+
|
|
63
|
+
<ParamField path="preview" type="string" default="browser">
|
|
64
|
+
Preview mode for live test visualization:
|
|
65
|
+
- `"browser"` — Opens debugger in default browser (default)
|
|
66
|
+
- `"ide"` — Opens preview in IDE panel (VSCode, Cursor - requires TestDriver extension)
|
|
67
|
+
- `"none"` — Headless mode, no visual preview
|
|
68
|
+
</ParamField>
|
|
69
|
+
|
|
70
|
+
<ParamField path="headless" type="boolean" default="false">
|
|
71
|
+
**Deprecated**: Use `preview: "none"` instead. Run in headless mode without opening the debugger.
|
|
72
|
+
</ParamField>
|
|
73
|
+
|
|
74
|
+
<ParamField path="debugOnFailure" type="boolean" default="false">
|
|
75
|
+
Keep the sandbox alive when a test fails so you can reconnect and debug interactively. The sandbox ID is printed to the console.
|
|
76
|
+
</ParamField>
|
|
77
|
+
|
|
78
|
+
<ParamField path="ip" type="string">
|
|
79
|
+
Direct IP address to connect to a running sandbox instance (for self-hosted deployments)
|
|
80
|
+
</ParamField>
|
|
81
|
+
|
|
82
|
+
<ParamField path="sandboxAmi" type="string">
|
|
83
|
+
Custom AMI ID for the sandbox instance (AWS deployments, e.g., `'ami-1234'`)
|
|
84
|
+
</ParamField>
|
|
85
|
+
|
|
86
|
+
<ParamField path="sandboxInstance" type="string">
|
|
87
|
+
EC2 instance type for the sandbox (AWS deployments, e.g., `'i3.metal'`)
|
|
88
|
+
</ParamField>
|
|
89
|
+
|
|
90
|
+
<ParamField path="cache" type="boolean | object" default="true">
|
|
91
|
+
Enable or disable element caching, or provide advanced threshold configuration.
|
|
92
|
+
|
|
93
|
+
<Expandable title="advanced config">
|
|
94
|
+
<ParamField path="enabled" type="boolean" default="true">
|
|
95
|
+
Enable or disable caching
|
|
96
|
+
</ParamField>
|
|
97
|
+
|
|
98
|
+
<ParamField path="thresholds" type="object">
|
|
99
|
+
Fine-tune cache matching
|
|
100
|
+
|
|
101
|
+
<Expandable title="properties">
|
|
102
|
+
<ParamField path="find" type="object">
|
|
103
|
+
Thresholds for `find()` operations
|
|
104
|
+
|
|
105
|
+
<Expandable title="properties">
|
|
106
|
+
<ParamField path="screen" type="number" default="0.05">
|
|
107
|
+
Pixel diff threshold for screen comparison (0-1). `0.05` = 5% diff allowed.
|
|
108
|
+
</ParamField>
|
|
109
|
+
|
|
110
|
+
<ParamField path="element" type="number" default="0.8">
|
|
111
|
+
OpenCV template match threshold for element matching (0-1). `0.8` = 80% correlation.
|
|
112
|
+
</ParamField>
|
|
113
|
+
</Expandable>
|
|
114
|
+
</ParamField>
|
|
115
|
+
|
|
116
|
+
<ParamField path="assert" type="number" default="0.05">
|
|
117
|
+
Pixel diff threshold for `assert()` operations (0-1). `0.05` = 5% diff allowed.
|
|
118
|
+
</ParamField>
|
|
119
|
+
</Expandable>
|
|
120
|
+
</ParamField>
|
|
121
|
+
</Expandable>
|
|
122
|
+
</ParamField>
|
|
123
|
+
|
|
124
|
+
<ParamField path="cacheKey" type="string">
|
|
125
|
+
Cache key for element finding operations. If provided, enables caching tied to this key.
|
|
126
|
+
</ParamField>
|
|
127
|
+
|
|
128
|
+
<ParamField path="dashcam" type="boolean" default="true">
|
|
129
|
+
Enable or disable Dashcam video recording
|
|
130
|
+
</ParamField>
|
|
131
|
+
|
|
132
|
+
<ParamField path="redraw" type="boolean | object" default="true">
|
|
133
|
+
Enable or disable screen-change (redraw) detection, or provide advanced configuration.
|
|
134
|
+
|
|
135
|
+
<Expandable title="advanced config">
|
|
136
|
+
<ParamField path="enabled" type="boolean" default="true">
|
|
137
|
+
Enable or disable redraw detection
|
|
138
|
+
</ParamField>
|
|
139
|
+
|
|
140
|
+
<ParamField path="thresholds" type="object">
|
|
141
|
+
Threshold configuration
|
|
142
|
+
|
|
143
|
+
<Expandable title="properties">
|
|
144
|
+
<ParamField path="screen" type="number | false" default="0.05">
|
|
145
|
+
Pixel diff threshold (0-1). Set to `false` to disable screen redraw detection.
|
|
146
|
+
</ParamField>
|
|
147
|
+
|
|
148
|
+
<ParamField path="network" type="boolean" default="false">
|
|
149
|
+
Enable or disable network activity monitoring
|
|
150
|
+
</ParamField>
|
|
151
|
+
</Expandable>
|
|
152
|
+
</ParamField>
|
|
153
|
+
</Expandable>
|
|
154
|
+
</ParamField>
|
|
155
|
+
|
|
51
156
|
<ParamField path="environment" type="object">
|
|
52
157
|
Additional environment variables to pass to the sandbox
|
|
53
158
|
</ParamField>
|
|
@@ -10,7 +10,64 @@ Configure TestDriver behavior with options passed to the `TestDriver()` function
|
|
|
10
10
|
|
|
11
11
|
```javascript
|
|
12
12
|
const testdriver = TestDriver(context, {
|
|
13
|
-
|
|
13
|
+
// === Sandbox & Connection ===
|
|
14
|
+
newSandbox: true, // Force creation of a new sandbox (default: true)
|
|
15
|
+
reconnect: false, // Reconnect to last sandbox (default: false)
|
|
16
|
+
keepAlive: 60000, // Keep sandbox alive after disconnect in ms (default: 60000)
|
|
17
|
+
os: "linux", // 'linux' | 'windows' (default: 'linux')
|
|
18
|
+
resolution: "1366x768", // Sandbox resolution (e.g., '1920x1080')
|
|
19
|
+
ip: "203.0.113.42", // Direct IP for self-hosted sandbox
|
|
20
|
+
sandboxAmi: "ami-1234", // Custom AMI ID (AWS deployments)
|
|
21
|
+
sandboxInstance: "i3.metal", // EC2 instance type (AWS deployments)
|
|
22
|
+
|
|
23
|
+
// === Preview & Debugging ===
|
|
24
|
+
preview: "browser", // "browser" | "ide" | "none" (default: "browser")
|
|
25
|
+
headless: false, // @deprecated - use preview: "none" instead
|
|
26
|
+
debugOnFailure: false, // Keep sandbox alive on test failure for debugging
|
|
27
|
+
|
|
28
|
+
// === Caching ===
|
|
29
|
+
cache: true, // Enable element caching (default: true)
|
|
30
|
+
// Or use advanced caching config:
|
|
31
|
+
// cache: {
|
|
32
|
+
// enabled: true,
|
|
33
|
+
// thresholds: {
|
|
34
|
+
// find: { screen: 0.05, element: 0.8 },
|
|
35
|
+
// assert: 0.05
|
|
36
|
+
// }
|
|
37
|
+
// },
|
|
38
|
+
cacheKey: "my-test", // Cache key for element finding operations
|
|
39
|
+
|
|
40
|
+
// === Recording & Screenshots ===
|
|
41
|
+
dashcam: true, // Enable/disable Dashcam video recording (default: true)
|
|
42
|
+
autoScreenshots: true, // Capture screenshots before/after each command (default: true)
|
|
43
|
+
|
|
44
|
+
// === AI Configuration ===
|
|
45
|
+
ai: { // Global AI sampling configuration
|
|
46
|
+
temperature: 0, // 0 = deterministic, higher = more creative
|
|
47
|
+
top: {
|
|
48
|
+
p: 0.9, // Top-P nucleus sampling (0-1)
|
|
49
|
+
k: 40, // Top-K sampling (1 = most likely, 0 = disabled)
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// === Screen Change Detection ===
|
|
54
|
+
redraw: true, // Enable redraw detection (default: true)
|
|
55
|
+
// Or use advanced redraw config:
|
|
56
|
+
// redraw: {
|
|
57
|
+
// enabled: true,
|
|
58
|
+
// thresholds: {
|
|
59
|
+
// screen: 0.05, // Pixel diff threshold (0-1), false to disable
|
|
60
|
+
// network: false, // Monitor network activity (default: false)
|
|
61
|
+
// }
|
|
62
|
+
// },
|
|
63
|
+
|
|
64
|
+
// === Logging & Analytics ===
|
|
65
|
+
logging: true, // Enable console logging output (default: true)
|
|
66
|
+
analytics: true, // Enable analytics tracking (default: true)
|
|
67
|
+
|
|
68
|
+
// === Advanced ===
|
|
69
|
+
apiRoot: "https://...", // API endpoint URL (for self-hosted deployments)
|
|
70
|
+
environment: {}, // Additional environment variables for the sandbox
|
|
14
71
|
});
|
|
15
72
|
```
|
|
16
73
|
|
|
@@ -54,6 +111,16 @@ const testdriver = TestDriver(context, {
|
|
|
54
111
|
The legacy `headless: true` option still works for backward compatibility and maps to `preview: "none"`.
|
|
55
112
|
</Note>
|
|
56
113
|
|
|
114
|
+
### Debug on Failure
|
|
115
|
+
|
|
116
|
+
Keep the sandbox alive when a test fails so you can reconnect and debug interactively. The sandbox ID is printed to the console along with instructions for reconnecting via MCP.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const testdriver = TestDriver(context, {
|
|
120
|
+
debugOnFailure: true,
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
57
124
|
### IP Target
|
|
58
125
|
|
|
59
126
|
If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
|
|
@@ -105,11 +172,102 @@ steps:
|
|
|
105
172
|
- run: TD_OS=${{ matrix.os }} vitest run
|
|
106
173
|
```
|
|
107
174
|
|
|
108
|
-
|
|
175
|
+
### Dashcam Recording
|
|
176
|
+
|
|
177
|
+
Dashcam video recording is enabled by default. Disable it to skip recording:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
const testdriver = TestDriver(context, {
|
|
181
|
+
dashcam: false,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Automatic Screenshots
|
|
186
|
+
|
|
187
|
+
Screenshots are automatically captured before and after every command (click, type, find, assert, etc.) by default. Each screenshot filename includes the line number from your test file.
|
|
188
|
+
|
|
189
|
+
Disable automatic screenshots:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
const testdriver = TestDriver(context, {
|
|
193
|
+
autoScreenshots: false,
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Caching
|
|
198
|
+
|
|
199
|
+
Element caching speeds up repeated `find()` and `assert()` calls. Enabled by default.
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Disable caching
|
|
203
|
+
const testdriver = TestDriver(context, {
|
|
204
|
+
cache: false,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Advanced: custom thresholds
|
|
208
|
+
const testdriver = TestDriver(context, {
|
|
209
|
+
cache: {
|
|
210
|
+
enabled: true,
|
|
211
|
+
thresholds: {
|
|
212
|
+
find: { screen: 0.05, element: 0.8 },
|
|
213
|
+
assert: 0.05,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
cacheKey: "my-test",
|
|
217
|
+
});
|
|
218
|
+
```
|
|
109
219
|
|
|
110
|
-
|
|
220
|
+
### Redraw Detection
|
|
111
221
|
|
|
112
|
-
|
|
222
|
+
Redraw detection waits for the screen to stabilize before taking actions. Enabled by default.
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// Disable redraw detection
|
|
226
|
+
const testdriver = TestDriver(context, {
|
|
227
|
+
redraw: false,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Advanced: custom thresholds with network monitoring
|
|
231
|
+
const testdriver = TestDriver(context, {
|
|
232
|
+
redraw: {
|
|
233
|
+
enabled: true,
|
|
234
|
+
thresholds: {
|
|
235
|
+
screen: 0.05,
|
|
236
|
+
network: true,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### AI Configuration
|
|
243
|
+
|
|
244
|
+
Control how the AI model generates responses for `find()` verification and `assert()` calls:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
const testdriver = TestDriver(context, {
|
|
248
|
+
ai: {
|
|
249
|
+
temperature: 0, // 0 = deterministic
|
|
250
|
+
top: { p: 0.9, k: 40 },
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Environment Variables
|
|
256
|
+
|
|
257
|
+
Pass additional environment variables to the sandbox:
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const testdriver = TestDriver(context, {
|
|
261
|
+
environment: {
|
|
262
|
+
MY_VAR: "value",
|
|
263
|
+
DEBUG: "true",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Keepalive
|
|
269
|
+
|
|
270
|
+
By default, sandboxes stay alive for 60 seconds after disconnect. Customize this with `keepAlive`:
|
|
113
271
|
|
|
114
272
|
```javascript
|
|
115
273
|
const testdriver = TestDriver(context, {
|
|
@@ -117,6 +275,14 @@ const testdriver = TestDriver(context, {
|
|
|
117
275
|
});
|
|
118
276
|
```
|
|
119
277
|
|
|
278
|
+
Set to `0` to terminate immediately:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
const testdriver = TestDriver(context, {
|
|
282
|
+
keepAlive: 0, // Terminate sandbox immediately on disconnect
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
120
286
|
### Reconnecting to Existing Sandbox
|
|
121
287
|
|
|
122
288
|
Speed up test development by reconnecting to an existing sandbox instead of starting fresh each time. This lets you iterate quickly on failing steps without re-running the entire test from the beginning.
|
|
@@ -33,8 +33,8 @@ const element = await testdriver.find(description, options)
|
|
|
33
33
|
Similarity threshold (0-1) for cache matching. Lower values require more similarity. Set to -1 to disable cache.
|
|
34
34
|
</ParamField>
|
|
35
35
|
|
|
36
|
-
<ParamField path="timeout" type="number">
|
|
37
|
-
Maximum time in milliseconds to poll for the element. Retries every 5 seconds until found or timeout expires.
|
|
36
|
+
<ParamField path="timeout" type="number" default={10000}>
|
|
37
|
+
Maximum time in milliseconds to poll for the element. Retries every 5 seconds until found or timeout expires. Defaults to `10000` (10 seconds). Set to `0` to disable polling and make a single attempt.
|
|
38
38
|
</ParamField>
|
|
39
39
|
|
|
40
40
|
<ParamField path="confidence" type="number">
|
|
@@ -309,19 +309,28 @@ const el = await testdriver.find('the blue submit button', { type: 'any' });
|
|
|
309
309
|
</Tip>
|
|
310
310
|
## Polling for Dynamic Elements
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
By default, `find()` polls for up to 10 seconds (retrying every 5 seconds) until the element is found. You can customize this with the `timeout` option:
|
|
313
313
|
|
|
314
314
|
```javascript
|
|
315
|
-
//
|
|
315
|
+
// Uses default 10s timeout - polls every 5 seconds
|
|
316
|
+
const element = await testdriver.find('login button');
|
|
317
|
+
await element.click();
|
|
318
|
+
|
|
319
|
+
// Custom timeout - wait up to 30 seconds
|
|
316
320
|
const element = await testdriver.find('login button', { timeout: 30000 });
|
|
317
321
|
await element.click();
|
|
322
|
+
|
|
323
|
+
// Disable polling - single attempt only
|
|
324
|
+
const element = await testdriver.find('login button', { timeout: 0 });
|
|
318
325
|
```
|
|
319
326
|
|
|
320
327
|
The `timeout` option:
|
|
328
|
+
- Defaults to `10000` (10 seconds)
|
|
321
329
|
- Retries finding the element every 5 seconds
|
|
322
330
|
- Stops when the element is found or the timeout expires
|
|
323
331
|
- Logs progress during polling
|
|
324
332
|
- Returns the element (check `element.found()` if not throwing on failure)
|
|
333
|
+
- Set to `0` to disable polling and make a single attempt
|
|
325
334
|
|
|
326
335
|
## Zoom Mode for Crowded UIs
|
|
327
336
|
|
|
@@ -6,10 +6,16 @@ description: Handle async operations and prevent flaky tests
|
|
|
6
6
|
|
|
7
7
|
## Waiting for Elements
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
By default, `find()` automatically polls for up to 10 seconds, retrying every 5 seconds until the element is found. This means most elements that appear after short async operations will be found without any extra configuration.
|
|
10
|
+
|
|
11
|
+
For longer operations, increase the `timeout`:
|
|
10
12
|
|
|
11
13
|
```javascript
|
|
12
|
-
//
|
|
14
|
+
// Default behavior - polls for up to 10 seconds automatically
|
|
15
|
+
const element = await testdriver.find('Loading complete indicator');
|
|
16
|
+
await element.click();
|
|
17
|
+
|
|
18
|
+
// Wait up to 30 seconds for slower operations
|
|
13
19
|
const element = await testdriver.find('Loading complete indicator', { timeout: 30000 });
|
|
14
20
|
await element.click();
|
|
15
21
|
|
|
@@ -17,8 +23,8 @@ await element.click();
|
|
|
17
23
|
await testdriver.find('submit button').click();
|
|
18
24
|
await testdriver.find('success message', { timeout: 15000 });
|
|
19
25
|
|
|
20
|
-
//
|
|
21
|
-
const toast = await testdriver.find('notification toast', { timeout:
|
|
26
|
+
// Disable polling for instant checks
|
|
27
|
+
const toast = await testdriver.find('notification toast', { timeout: 0 });
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
## Flake Prevention
|
package/docs/v7/find.mdx
CHANGED
|
@@ -34,8 +34,8 @@ const element = await testdriver.find(description, options)
|
|
|
34
34
|
Similarity threshold (0-1) for cache matching. Lower values require more similarity. Set to -1 to disable cache.
|
|
35
35
|
</ParamField>
|
|
36
36
|
|
|
37
|
-
<ParamField path="timeout" type="number">
|
|
38
|
-
Maximum time in milliseconds to poll for the element. Retries every 5 seconds until found or timeout expires.
|
|
37
|
+
<ParamField path="timeout" type="number" default={10000}>
|
|
38
|
+
Maximum time in milliseconds to poll for the element. Retries every 5 seconds until found or timeout expires. Defaults to `10000` (10 seconds). Set to `0` to disable polling and make a single attempt.
|
|
39
39
|
</ParamField>
|
|
40
40
|
|
|
41
41
|
<ParamField path="confidence" type="number">
|
|
@@ -310,19 +310,28 @@ const el = await testdriver.find('the blue submit button', { type: 'any' });
|
|
|
310
310
|
</Tip>
|
|
311
311
|
## Polling for Dynamic Elements
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
By default, `find()` polls for up to 10 seconds (retrying every 5 seconds) until the element is found. You can customize this with the `timeout` option:
|
|
314
314
|
|
|
315
315
|
```javascript
|
|
316
|
-
//
|
|
316
|
+
// Uses default 10s timeout - polls every 5 seconds
|
|
317
|
+
const element = await testdriver.find('login button');
|
|
318
|
+
await element.click();
|
|
319
|
+
|
|
320
|
+
// Custom timeout - wait up to 30 seconds
|
|
317
321
|
const element = await testdriver.find('login button', { timeout: 30000 });
|
|
318
322
|
await element.click();
|
|
323
|
+
|
|
324
|
+
// Disable polling - single attempt only
|
|
325
|
+
const element = await testdriver.find('login button', { timeout: 0 });
|
|
319
326
|
```
|
|
320
327
|
|
|
321
328
|
The `timeout` option:
|
|
329
|
+
- Defaults to `10000` (10 seconds)
|
|
322
330
|
- Retries finding the element every 5 seconds
|
|
323
331
|
- Stops when the element is found or the timeout expires
|
|
324
332
|
- Logs progress during polling
|
|
325
333
|
- Returns the element (check `element.found()` if not throwing on failure)
|
|
334
|
+
- Set to `0` to disable polling and make a single attempt
|
|
326
335
|
|
|
327
336
|
## Zoom Mode for Crowded UIs
|
|
328
337
|
|
|
@@ -6,10 +6,16 @@ icon: "clock"
|
|
|
6
6
|
|
|
7
7
|
## Waiting for Elements
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
By default, `find()` automatically polls for up to 10 seconds, retrying every 5 seconds until the element is found. This means most elements that appear after short async operations will be found without any extra configuration.
|
|
10
|
+
|
|
11
|
+
For longer operations, increase the `timeout`:
|
|
10
12
|
|
|
11
13
|
```javascript
|
|
12
|
-
//
|
|
14
|
+
// Default behavior - polls for up to 10 seconds automatically
|
|
15
|
+
const element = await testdriver.find('Loading complete indicator');
|
|
16
|
+
await element.click();
|
|
17
|
+
|
|
18
|
+
// Wait up to 30 seconds for slower operations
|
|
13
19
|
const element = await testdriver.find('Loading complete indicator', { timeout: 30000 });
|
|
14
20
|
await element.click();
|
|
15
21
|
|
|
@@ -17,8 +23,8 @@ await element.click();
|
|
|
17
23
|
await testdriver.find('submit button').click();
|
|
18
24
|
await testdriver.find('success message', { timeout: 15000 });
|
|
19
25
|
|
|
20
|
-
//
|
|
21
|
-
const toast = await testdriver.find('notification toast', { timeout:
|
|
26
|
+
// Disable polling for instant checks
|
|
27
|
+
const toast = await testdriver.find('notification toast', { timeout: 0 });
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
## Flake Prevention
|
|
@@ -13,11 +13,20 @@ import { getDefaults } from "./config.mjs";
|
|
|
13
13
|
* @param {string} username - Username (default: 'standard_user')
|
|
14
14
|
*/
|
|
15
15
|
async function performLogin(client, username = "standard_user") {
|
|
16
|
+
|
|
17
|
+
console.log('Performing login with username:', username);
|
|
16
18
|
await client.focusApplication("Google Chrome");
|
|
19
|
+
|
|
20
|
+
console.log('Extracting password from page');
|
|
17
21
|
const password = await client.extract("the password");
|
|
22
|
+
|
|
23
|
+
console.log('Password extracted:', password ? '***' : 'not found');
|
|
24
|
+
|
|
18
25
|
const usernameField = await client.find(
|
|
19
26
|
"username input",
|
|
20
27
|
);
|
|
28
|
+
|
|
29
|
+
console.log('Clicking on username field and entering credentials');
|
|
21
30
|
await usernameField.click();
|
|
22
31
|
await client.type(username);
|
|
23
32
|
await client.pressKeys(["tab"]);
|
|
@@ -35,6 +44,8 @@ describe("Hover Image Test", () => {
|
|
|
35
44
|
url: 'http://testdriver-sandbox.vercel.app/login'
|
|
36
45
|
});
|
|
37
46
|
|
|
47
|
+
console.log('starting login')
|
|
48
|
+
|
|
38
49
|
// Perform login first
|
|
39
50
|
await performLogin(testdriver);
|
|
40
51
|
|