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.
@@ -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,
@@ -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", "trackInteraction"];
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="windows">
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" default="https://testdriver-api.onrender.com">
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
- reconnect: false,
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
- ## Keepalive
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
- By default, sandboxes terminate immediately when the test finishes. Set this value to keep the sandbox alive for reconnection.
220
+ ### Redraw Detection
111
221
 
112
- The `keepAlive` param enables you to keep the sandbox running after the test completes for debugging or reconnection. This will allow you to use the debugger to inspect the state of the device after the test has finished.
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
- For elements that may not be immediately visible, use the `timeout` option to automatically poll:
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
- // Poll for element (retries every 5 seconds until found or timeout)
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
- Use the `timeout` option with `find()` to wait for elements that appear after async operations:
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
- // Wait up to 30 seconds for element to appear (polls every 5 seconds)
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
- // Short timeout for quick checks
21
- const toast = await testdriver.find('notification toast', { timeout: 5000 });
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
- For elements that may not be immediately visible, use the `timeout` option to automatically poll:
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
- // Poll for element (retries every 5 seconds until found or timeout)
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
- Use the `timeout` option with `find()` to wait for elements that appear after async operations:
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
- // Wait up to 30 seconds for element to appear (polls every 5 seconds)
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
- // Short timeout for quick checks
21
- const toast = await testdriver.find('notification toast', { timeout: 5000 });
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.3.44",
3
+ "version": "7.4.1",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",