testdriverai 7.4.4 → 7.4.5

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.
@@ -670,7 +670,7 @@ await testdriver.screenshot(1, false, true);
670
670
  3. **⚠️ SHARE THE TEST REPORT URL** - After EVERY test run, find `TESTDRIVER_RUN_URL=https://console.testdriver.ai/runs/...` in the output and share it with the user. This is CRITICAL - users need to view the recording to understand what happened.
671
671
  3. **Screenshots are automatic** - TestDriver captures screenshots before/after every command by default. Each screenshot filename includes the line number (e.g., `001-click-before-L42-submit-button.png`) making it easy to trace issues.
672
672
  4. **⚠️ USE SCREENSHOT VIEWING FOR DEBUGGING** - When tests fail, use `list_local_screenshots` and `view_local_screenshot` MCP commands to see exactly what the UI looked like. The filenames tell you which line of code triggered each screenshot.
673
- 5. **⚠️ NEVER USE `.wait()`** - Do NOT use any `.wait()` method. Instead, use `find()` with a `timeout` option to poll for elements, or use `assert()` / `check()` to verify state. Explicit waits are flaky and slow.
673
+ 5. **Use `wait()` for simple delays** - Use `await testdriver.wait(ms)` when you need a pause (e.g., after actions, for animations). For waiting for specific elements, prefer `find()` with a `timeout` option.
674
674
  6. **Use MCP tools for development** - Build tests interactively with visual feedback
675
675
  7. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
676
676
  8. **Look at test samples** in `node_modules/testdriverai/test` for working examples
@@ -29,6 +29,9 @@ await testdriver.find('dropdown menu').hover();
29
29
  await testdriver.scroll('down', 500);
30
30
  await testdriver.scrollUntilText('Footer content');
31
31
 
32
+ // Waiting
33
+ await testdriver.wait(2000); // Wait 2 seconds for animation/state change
34
+
32
35
  // Extracting information from screen
33
36
  const price = await testdriver.extract('the total price');
34
37
  const orderNumber = await testdriver.extract('the order confirmation number');
@@ -70,3 +70,19 @@ const testdriver = TestDriver(context, {
70
70
  }
71
71
  });
72
72
  ```
73
+
74
+ ## Simple Delays with `wait()`
75
+
76
+ For simple pauses — waiting for animations, transitions, or state changes after an action — use `wait()`:
77
+
78
+ ```javascript
79
+ // Wait for an animation to complete
80
+ await testdriver.find('menu toggle').click();
81
+ await testdriver.wait(2000);
82
+
83
+ // Wait for a page transition to settle
84
+ await testdriver.find('next page button').click();
85
+ await testdriver.wait(1000);
86
+ ```
87
+
88
+ For waiting for specific **elements** to appear, prefer `find()` with a `timeout` option. Use `wait()` only for simple time-based pauses.
@@ -38,16 +38,9 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
38
38
  this.os = null; // Store OS value to send with every message
39
39
  this.sessionInstance = sessionInstance; // Store session instance to include in messages
40
40
  this.traceId = null; // Sentry trace ID for debugging
41
- this.reconnectAttempts = 0;
42
- this.maxReconnectAttempts = 10;
43
- this.intentionalDisconnect = false;
44
41
  this.apiRoot = null;
45
42
  this.apiKey = null;
46
- this.reconnectTimer = null; // Track reconnect setTimeout
47
- this.reconnecting = false; // Prevent duplicate reconnection attempts
48
- this.pendingTimeouts = new Map(); // Track per-message timeouts
49
- this.pendingRetryQueue = []; // Queue of requests to retry after reconnection
50
- this._lastConnectParams = null; // Connection params for reconnection (per-instance, not shared)
43
+ this._lastConnectParams = null; // Connection params for sandboxId injection
51
44
  }
52
45
 
53
46
  /**
@@ -118,7 +111,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
118
111
 
119
112
  // Set up timeout to prevent hanging requests
120
113
  const timeoutId = setTimeout(() => {
121
- this.pendingTimeouts.delete(requestId);
122
114
  if (this.ps[requestId]) {
123
115
  delete this.ps[requestId];
124
116
  rejectPromise(
@@ -133,19 +125,14 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
133
125
  timeoutId.unref();
134
126
  }
135
127
 
136
- // Track timeout so close() can clear it
137
- this.pendingTimeouts.set(requestId, timeoutId);
138
-
139
128
  this.ps[requestId] = {
140
129
  promise: p,
141
130
  resolve: (result) => {
142
131
  clearTimeout(timeoutId);
143
- this.pendingTimeouts.delete(requestId);
144
132
  resolvePromise(result);
145
133
  },
146
134
  reject: (error) => {
147
135
  clearTimeout(timeoutId);
148
- this.pendingTimeouts.delete(requestId);
149
136
  rejectPromise(error);
150
137
  },
151
138
  message,
@@ -199,16 +186,9 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
199
186
  }
200
187
 
201
188
  /**
202
- * Set connection params for reconnection logic and sandboxId injection.
203
- * Use this instead of directly assigning this._lastConnectParams from
204
- * external code. Keeps the shape consistent and avoids stale state
205
- * leaking across concurrent test runs.
189
+ * Set connection params for sandboxId injection.
206
190
  * @param {Object|null} params
207
- * @param {string} [params.type] - 'direct' for IP-based connections
208
- * @param {string} [params.ip] - IP address for direct connections
209
191
  * @param {string} [params.sandboxId] - Sandbox/instance ID
210
- * @param {boolean} [params.persist] - Whether to persist the sandbox
211
- * @param {number|null} [params.keepAlive] - Keep-alive TTL in ms
212
192
  */
213
193
  setConnectionParams(params) {
214
194
  this._lastConnectParams = params ? { ...params } : null;
@@ -238,163 +218,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
238
218
  }
239
219
  }
240
220
 
241
- /**
242
- * Reconnect to a direct IP-based sandbox after connection loss.
243
- * Sends a 'direct' message instead of 'connect' to avoid the API
244
- * treating the IP as an AWS instance ID.
245
- */
246
- async reconnectDirect(ip) {
247
- let reply = await this.send({
248
- type: "direct",
249
- ip,
250
- });
251
-
252
- if (reply.success) {
253
- this.instanceSocketConnected = true;
254
- emitter.emit(events.sandbox.connected);
255
- return reply;
256
- } else {
257
- throw new Error(reply.errorMessage || "Failed to reconnect to direct sandbox");
258
- }
259
- }
260
-
261
- async handleConnectionLoss() {
262
- if (this.intentionalDisconnect) return;
263
-
264
- // Prevent duplicate reconnection attempts (both 'error' and 'close' fire)
265
- if (this.reconnecting) return;
266
- this.reconnecting = true;
267
-
268
- // Remove listeners from the old socket to prevent "No pending promise found" warnings
269
- // when late responses arrive on the dying connection
270
- if (this.socket) {
271
- try {
272
- this.socket.removeAllListeners("message");
273
- } catch (e) {
274
- // Ignore errors removing listeners from closed socket
275
- }
276
- }
277
-
278
- // Queue pending requests for retry after reconnection
279
- // (they were sent on the old socket and will never receive responses)
280
- const pendingRequestIds = Object.keys(this.ps);
281
- if (pendingRequestIds.length > 0) {
282
- console.log(`[Sandbox] Queuing ${pendingRequestIds.length} pending request(s) for retry after reconnection`);
283
- for (const requestId of pendingRequestIds) {
284
- const pending = this.ps[requestId];
285
- if (pending) {
286
- // Clear the timeout - we'll set a new one when we retry
287
- const timeoutId = this.pendingTimeouts.get(requestId);
288
- if (timeoutId) {
289
- clearTimeout(timeoutId);
290
- this.pendingTimeouts.delete(requestId);
291
- }
292
- // Queue for retry (store message and promise handlers)
293
- this.pendingRetryQueue.push({
294
- message: pending.message,
295
- resolve: pending.resolve,
296
- reject: pending.reject,
297
- });
298
- }
299
- }
300
- this.ps = {};
301
- }
302
-
303
- // Cancel any existing reconnect timer
304
- if (this.reconnectTimer) {
305
- clearTimeout(this.reconnectTimer);
306
- this.reconnectTimer = null;
307
- }
308
-
309
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
310
- const errorMsg =
311
- "Unable to reconnect to TestDriver sandbox after multiple attempts. Please check your internet connection.";
312
- emitter.emit(events.error.sandbox, errorMsg);
313
- console.error(errorMsg);
314
-
315
- // Reject all queued requests since reconnection failed
316
- if (this.pendingRetryQueue.length > 0) {
317
- console.log(`[Sandbox] Rejecting ${this.pendingRetryQueue.length} queued request(s) - reconnection failed`);
318
- for (const queued of this.pendingRetryQueue) {
319
- queued.reject(new Error("Sandbox reconnection failed after multiple attempts"));
320
- }
321
- this.pendingRetryQueue = [];
322
- }
323
-
324
- this.reconnecting = false;
325
- return;
326
- }
327
-
328
- this.reconnectAttempts++;
329
- const delay = Math.min(1000 * 2 ** (this.reconnectAttempts - 1), 60000);
330
-
331
- console.log(
332
- `[Sandbox] Connection lost. Reconnecting in ${delay}ms... (Attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
333
- );
334
-
335
- this.reconnectTimer = setTimeout(async () => {
336
- this.reconnectTimer = null;
337
- try {
338
- await this.boot(this.apiRoot);
339
- if (this.apiKey) {
340
- await this.auth(this.apiKey);
341
- }
342
- // Re-establish sandbox connection on the new API instance
343
- // Without this, the new API instance has no connection.desktop
344
- // and all Linux operations will fail with "sandbox not initialized"
345
- if (this._lastConnectParams) {
346
- if (this._lastConnectParams.type === 'direct') {
347
- // Direct IP connections must reconnect via 'direct' message, not 'connect'
348
- const { ip, persist, keepAlive } = this._lastConnectParams;
349
- console.log(`[Sandbox] Re-establishing direct connection (${ip})...`);
350
- await this.reconnectDirect(ip);
351
- } else {
352
- const { sandboxId, persist, keepAlive } = this._lastConnectParams;
353
- console.log(`[Sandbox] Re-establishing sandbox connection (${sandboxId})...`);
354
- await this.connect(sandboxId, persist, keepAlive);
355
- }
356
- }
357
- console.log("[Sandbox] Reconnected successfully.");
358
-
359
- // Retry queued requests
360
- await this._retryQueuedRequests();
361
- } catch (e) {
362
- // boot's close handler will trigger handleConnectionLoss again
363
- } finally {
364
- this.reconnecting = false;
365
- }
366
- }, delay);
367
- // Don't let reconnect timer prevent Node process from exiting
368
- if (this.reconnectTimer.unref) {
369
- this.reconnectTimer.unref();
370
- }
371
- }
372
-
373
- /**
374
- * Retry queued requests after successful reconnection
375
- * @private
376
- */
377
- async _retryQueuedRequests() {
378
- if (this.pendingRetryQueue.length === 0) return;
379
-
380
- console.log(`[Sandbox] Retrying ${this.pendingRetryQueue.length} queued request(s)...`);
381
-
382
- // Take all queued requests and clear the queue
383
- const toRetry = this.pendingRetryQueue.splice(0);
384
-
385
- for (const queued of toRetry) {
386
- try {
387
- // Re-send the message and resolve/reject the original promise
388
- const result = await this.send(queued.message);
389
- queued.resolve(result);
390
- } catch (err) {
391
- queued.reject(err);
392
- }
393
- }
394
-
395
- console.log(`[Sandbox] Finished retrying queued requests.`);
396
- }
397
-
398
221
  async boot(apiRoot) {
399
222
  if (apiRoot) this.apiRoot = apiRoot;
400
223
  return new Promise((resolve, reject) => {
@@ -424,12 +247,7 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
424
247
  this.socket.on("close", () => {
425
248
  clearInterval(this.heartbeat);
426
249
  this.apiSocketConnected = false;
427
- // Also mark instance socket as disconnected to prevent sending messages
428
- // to a closed connection (e.g., when sandbox is killed due to test failure)
429
250
  this.instanceSocketConnected = false;
430
- // Reset reconnecting flag so handleConnectionLoss can run for this new disconnection
431
- this.reconnecting = false;
432
- this.handleConnectionLoss();
433
251
  reject();
434
252
  });
435
253
 
@@ -439,14 +257,10 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
439
257
  clearInterval(this.heartbeat);
440
258
  emitter.emit(events.error.sandbox, err);
441
259
  this.apiSocketConnected = false;
442
- // Don't call handleConnectionLoss here - the 'close' event always fires
443
- // after 'error', so let 'close' handle reconnection to avoid duplicate attempts
444
260
  reject(err);
445
261
  });
446
262
 
447
263
  this.socket.on("open", async () => {
448
- this.reconnectAttempts = 0;
449
- this.reconnecting = false;
450
264
  this.apiSocketConnected = true;
451
265
 
452
266
  this.heartbeat = setInterval(() => {
@@ -475,18 +289,15 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
475
289
  }
476
290
 
477
291
  if (!this.ps[message.requestId]) {
478
- // This can happen during reconnection (ps was cleared) or after timeout
479
- // (promise was deleted). Expected during polling loops (e.g. Chrome
480
- // debugger readiness checks) where short-timeout exec calls regularly
481
- // expire before the sandbox responds. Only log in debug/verbose mode.
482
- if (!this.reconnecting) {
483
- const debugMode = process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
484
- if (debugMode) {
485
- console.warn(
486
- "No pending promise found for requestId:",
487
- message.requestId,
488
- );
489
- }
292
+ // Can happen after timeout (promise was deleted). Expected during
293
+ // polling loops where short-timeout exec calls regularly expire
294
+ // before the sandbox responds. Only log in debug/verbose mode.
295
+ const debugMode = process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
296
+ if (debugMode) {
297
+ console.warn(
298
+ "No pending promise found for requestId:",
299
+ message.requestId,
300
+ );
490
301
  }
491
302
  return;
492
303
  }
@@ -514,27 +325,12 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
514
325
  * Close the WebSocket connection and clean up resources
515
326
  */
516
327
  close() {
517
- this.intentionalDisconnect = true;
518
- this.reconnecting = false;
519
- // Cancel any pending reconnect timer
520
- if (this.reconnectTimer) {
521
- clearTimeout(this.reconnectTimer);
522
- this.reconnectTimer = null;
523
- }
524
-
525
328
  if (this.heartbeat) {
526
329
  clearInterval(this.heartbeat);
527
330
  this.heartbeat = null;
528
331
  }
529
332
 
530
- // Clear all pending message timeouts to prevent timers keeping the process alive
531
- for (const timeoutId of this.pendingTimeouts.values()) {
532
- clearTimeout(timeoutId);
533
- }
534
- this.pendingTimeouts.clear();
535
-
536
333
  if (this.socket) {
537
- // Remove all listeners before closing to prevent reconnect attempts
538
334
  this.socket.removeAllListeners();
539
335
  try {
540
336
  this.socket.close();
@@ -549,11 +345,7 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
549
345
  this.authenticated = false;
550
346
  this.instance = null;
551
347
  this._lastConnectParams = null;
552
-
553
- // Silently clear pending promises and retry queue without rejecting
554
- // (rejecting causes unhandled promise rejections during cleanup)
555
348
  this.ps = {};
556
- this.pendingRetryQueue = [];
557
349
  }
558
350
  }
559
351
 
@@ -621,7 +621,7 @@ await testdriver.screenshot(1, false, true);
621
621
  3. **⚠️ SHARE THE TEST REPORT URL** - After EVERY test run, find `TESTDRIVER_RUN_URL=https://console.testdriver.ai/runs/...` in the output and share it with the user. This is CRITICAL - users need to view the recording to understand what happened.
622
622
  3. **Screenshots are automatic** - TestDriver captures screenshots before/after every command by default. Each screenshot filename includes the line number (e.g., `001-click-before-L42-submit-button.png`) making it easy to trace issues.
623
623
  4. **⚠️ USE SCREENSHOT VIEWING FOR DEBUGGING** - When tests fail, use `list_local_screenshots` and `view_local_screenshot` MCP commands to see exactly what the UI looked like. The filenames tell you which line of code triggered each screenshot.
624
- 5. **⚠️ NEVER USE `.wait()`** - Do NOT use any `.wait()` method. Instead, use `find()` with a `timeout` option to poll for elements, or use `assert()` / `check()` to verify state. Explicit waits are flaky and slow.
624
+ 5. **Use `wait()` for simple delays** - Use `await testdriver.wait(ms)` when you need a pause (e.g., after actions, for animations). For waiting for specific elements, prefer `find()` with a `timeout` option.
625
625
  6. **Use MCP tools for development** - Build tests interactively with visual feedback
626
626
  7. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
627
627
  8. **Look at test samples** in `node_modules/testdriverai/test` for working examples
@@ -29,6 +29,9 @@ await testdriver.find('dropdown menu').hover();
29
29
  await testdriver.scroll('down', 500);
30
30
  await testdriver.scrollUntilText('Footer content');
31
31
 
32
+ // Waiting
33
+ await testdriver.wait(2000); // Wait 2 seconds for animation/state change
34
+
32
35
  // Extracting information from screen
33
36
  const price = await testdriver.extract('the total price');
34
37
  const orderNumber = await testdriver.extract('the order confirmation number');
@@ -611,7 +611,7 @@ await testdriver.screenshot(1, false, true);
611
611
  3. **⚠️ SHARE THE TEST REPORT URL** - After EVERY test run, find `TESTDRIVER_RUN_URL=https://console.testdriver.ai/runs/...` in the output and share it with the user. This is CRITICAL - users need to view the recording to understand what happened.
612
612
  3. **Screenshots are automatic** - TestDriver captures screenshots before/after every command by default. Each screenshot filename includes the line number (e.g., `001-click-before-L42-submit-button.png`) making it easy to trace issues.
613
613
  4. **⚠️ USE SCREENSHOT VIEWING FOR DEBUGGING** - When tests fail, use `list_local_screenshots` and `view_local_screenshot` MCP commands to see exactly what the UI looked like. The filenames tell you which line of code triggered each screenshot.
614
- 5. **⚠️ NEVER USE `.wait()`** - Do NOT use any `.wait()` method. Instead, use `find()` with a `timeout` option to poll for elements, or use `assert()` / `check()` to verify state. Explicit waits are flaky and slow.
614
+ 5. **Use `wait()` for simple delays** - Use `await testdriver.wait(ms)` when you need a pause (e.g., after actions, for animations). For waiting for specific elements, prefer `find()` with a `timeout` option.
615
615
  6. **Use MCP tools for development** - Build tests interactively with visual feedback
616
616
  7. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
617
617
  8. **Look at test samples** in `node_modules/testdriverai/test` for working examples
@@ -70,3 +70,19 @@ const testdriver = TestDriver(context, {
70
70
  }
71
71
  });
72
72
  ```
73
+
74
+ ## Simple Delays with `wait()`
75
+
76
+ For simple pauses — waiting for animations, transitions, or state changes after an action — use `wait()`:
77
+
78
+ ```javascript
79
+ // Wait for an animation to complete
80
+ await testdriver.find('menu toggle').click();
81
+ await testdriver.wait(2000);
82
+
83
+ // Wait for a page transition to settle
84
+ await testdriver.find('next page button').click();
85
+ await testdriver.wait(1000);
86
+ ```
87
+
88
+ For waiting for specific **elements** to appear, prefer `find()` with a `timeout` option. Use `wait()` only for simple time-based pauses.
@@ -1,10 +1,30 @@
1
1
  # Best Practices: Element Polling
2
2
 
3
- **⚠️ CRITICAL: Never use `wait()` for waiting for elements to appear.**
3
+ **When waiting for elements to appear, prefer `find()` with a `timeout` option over `wait()`.**
4
4
 
5
- ## Why Avoid `wait()`?
5
+ ## When to Use `wait()` vs `find()` with Timeout
6
6
 
7
- Arbitrary waits with `wait()` have several problems:
7
+ `wait()` is useful for **simple pauses** — after actions, for animations, or for state changes to settle:
8
+
9
+ ```javascript
10
+ await testdriver.find('submit button').click();
11
+ await testdriver.wait(2000); // Wait for animation to complete
12
+ ```
13
+
14
+ However, **don't use `wait()` to wait for elements to appear**. For that, use `find()` with a `timeout`:
15
+
16
+ ```javascript
17
+ // ✅ GOOD: Polls until the element appears (up to 30s)
18
+ const element = await testdriver.find('success message', { timeout: 30000 });
19
+
20
+ // ❌ BAD: Arbitrary wait then hope the element is there
21
+ await testdriver.wait(5000);
22
+ const element = await testdriver.find('success message');
23
+ ```
24
+
25
+ ## Why Prefer `find()` with Timeout for Element Waiting?
26
+
27
+ Using arbitrary waits for element detection has problems:
8
28
 
9
29
  1. **Brittle**: Fixed timeouts may be too short (causing flaky tests) or too long (wasting time)
10
30
  2. **Slow**: You always wait the full duration, even if the element appears sooner
@@ -147,8 +167,8 @@ await waitForElement(testdriver, "Processing complete indicator", 10, 2000);
147
167
  | Pattern | Use Case |
148
168
  |---------|----------|
149
169
  | **Polling with `find()`** | ✅ Waiting for UI elements to appear or disappear |
150
- | **`wait()`** | ❌ NEVER use for element waiting |
170
+ | **`wait()`** | ✅ Simple delays (animations, state changes) — Don't use for element waiting |
151
171
  | **Helper function** | ✅ Recommended for cleaner, reusable code |
152
172
  | **Conditional polling** | ✅ For optional elements (dialogs, notifications) |
153
173
 
154
- Remember: **If you're waiting for something to appear on screen, use `find()` in a polling loop, not `wait()`.**
174
+ Remember: **If you're waiting for something to appear on screen, use `find()` with a `timeout` option, not `wait()`. Use `wait()` for simple pauses between actions.**
@@ -29,6 +29,9 @@ await testdriver.find('dropdown menu').hover();
29
29
  await testdriver.scroll('down', 500);
30
30
  await testdriver.scrollUntilText('Footer content');
31
31
 
32
+ // Waiting
33
+ await testdriver.wait(2000); // Wait 2 seconds for animation/state change
34
+
32
35
  // Extracting information from screen
33
36
  const price = await testdriver.extract('the total price');
34
37
  const orderNumber = await testdriver.extract('the order confirmation number');
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: "wait"
3
+ sidebarTitle: "wait"
4
+ description: "Pause the execution of the script for a specified duration."
5
+ icon: "clock"
6
+ "mode": "wide"
7
+ ---
8
+
9
+ ## Description
10
+
11
+ The `wait` method pauses test execution for a specified number of milliseconds before continuing. This is useful for adding delays between actions, waiting for animations to complete, or pausing for state changes to settle.
12
+
13
+ ## Syntax
14
+
15
+ ```javascript
16
+ await testdriver.wait(timeout);
17
+ ```
18
+
19
+ ## Arguments
20
+
21
+ | Argument | Type | Default | Description |
22
+ | --------- | -------- | ------- | ------------------------------------- |
23
+ | `timeout` | `number` | `3000` | The duration in milliseconds to wait. |
24
+
25
+ ## Examples
26
+
27
+ ```javascript
28
+ // Wait 2 seconds for an animation to complete
29
+ await testdriver.find('submit button').click();
30
+ await testdriver.wait(2000);
31
+
32
+ // Wait 5 seconds
33
+ await testdriver.wait(5000);
34
+
35
+ // Wait with default timeout (3 seconds)
36
+ await testdriver.wait();
37
+ ```
38
+
39
+ ## Best Practices
40
+
41
+ - **Use for simple delays** — waiting for animations, transitions, or state changes after an action.
42
+ - **Avoid for element waiting** — if you're waiting for a specific element to appear, use `find()` with a `timeout` option instead:
43
+ ```javascript
44
+ // ✅ Better for waiting for elements
45
+ const element = await testdriver.find('success message', { timeout: 30000 });
46
+
47
+ // ❌ Don't do this for element waiting
48
+ await testdriver.wait(5000);
49
+ const element = await testdriver.find('success message');
50
+ ```
51
+ - Avoid excessively long timeouts to keep tests efficient.
52
+ - Use sparingly — TestDriver's [redraw detection](/v7/waiting-for-elements) automatically waits for screen and network stability after each action.
@@ -70,3 +70,21 @@ const testdriver = TestDriver(context, {
70
70
  }
71
71
  });
72
72
  ```
73
+
74
+ ## Simple Delays with `wait()`
75
+
76
+ For simple pauses — waiting for animations, transitions, or state changes after an action — use `wait()`:
77
+
78
+ ```javascript
79
+ // Wait for an animation to complete
80
+ await testdriver.find('menu toggle').click();
81
+ await testdriver.wait(2000);
82
+
83
+ // Wait for a page transition to settle
84
+ await testdriver.find('next page button').click();
85
+ await testdriver.wait(1000);
86
+ ```
87
+
88
+ <Note>
89
+ For waiting for specific **elements** to appear, prefer `find()` with a `timeout` option. Use `wait()` only for simple time-based pauses.
90
+ </Note>
@@ -60,6 +60,8 @@ describe("Chrome Extension Test", () => {
60
60
  const helloExtension = await testdriver.find("Hello Extensions extension in the extensions dropdown");
61
61
  await helloExtension.click();
62
62
 
63
+ await testdriver.wait(2000); // wait for the popup to open
64
+
63
65
  // Verify the extension popup shows "Hello Extensions" text
64
66
  const popupResult = await testdriver.assert("a popup shows with the text 'Hello Extensions'");
65
67
  expect(popupResult).toBeTruthy();
@@ -10,6 +10,8 @@ import { getDefaults } from "./config.mjs";
10
10
  describe("Assert Test", () => {
11
11
  it("should assert the testdriver login page shows", async (context) => {
12
12
  const testdriver = TestDriver(context, { ...getDefaults(context) });
13
+
14
+ await testdriver.wait(10000)
13
15
 
14
16
  // Assert the TestDriver.ai Sandbox login page is displayed
15
17
  const result = await testdriver.assert(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.4.4",
3
+ "version": "7.4.5",
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",
package/sdk.d.ts CHANGED
@@ -1408,10 +1408,22 @@ export default class TestDriverSDK {
1408
1408
  parse(): Promise<ParseResult>;
1409
1409
 
1410
1410
  /**
1411
- * Wait for specified time
1412
- * @deprecated Consider using element polling with find() instead of arbitrary waits
1411
+ * Wait for specified time. Useful for adding delays between actions, waiting for
1412
+ * animations to complete, or pausing for state changes to settle.
1413
+ *
1414
+ * For waiting for specific elements to appear, prefer `find()` with a `timeout` option instead.
1415
+ *
1413
1416
  * @param timeout - Time to wait in milliseconds (default: 3000)
1414
1417
  * @param options - Additional options (reserved for future use)
1418
+ *
1419
+ * @example
1420
+ * // Wait 2 seconds for an animation to complete
1421
+ * await testdriver.wait(2000);
1422
+ *
1423
+ * @example
1424
+ * // Wait after a click for state to settle
1425
+ * await testdriver.find('submit button').click();
1426
+ * await testdriver.wait(1000);
1415
1427
  */
1416
1428
  wait(timeout?: number, options?: object): Promise<void>;
1417
1429