testdriverai 7.4.4 → 7.5.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.
Files changed (45) hide show
  1. package/.github/copilot-instructions.md +1 -1
  2. package/.github/skills/testdriver:performing-actions/SKILL.md +3 -0
  3. package/.github/skills/testdriver:waiting-for-elements/SKILL.md +16 -0
  4. package/CHANGELOG.md +4 -0
  5. package/agent/events.js +7 -0
  6. package/agent/index.js +24 -17
  7. package/agent/lib/sandbox.js +703 -428
  8. package/agent/lib/system.js +70 -1
  9. package/ai/agents/testdriver.md +1 -1
  10. package/ai/skills/testdriver:performing-actions/SKILL.md +3 -0
  11. package/ai/skills/testdriver:testdriver/SKILL.md +1 -1
  12. package/ai/skills/testdriver:waiting-for-elements/SKILL.md +16 -0
  13. package/docs/_data/examples-manifest.json +68 -68
  14. package/docs/guide/best-practices-polling.mdx +25 -5
  15. package/docs/v7/examples/ai.mdx +1 -1
  16. package/docs/v7/examples/assert.mdx +1 -1
  17. package/docs/v7/examples/chrome-extension.mdx +1 -1
  18. package/docs/v7/examples/drag-and-drop.mdx +1 -1
  19. package/docs/v7/examples/element-not-found.mdx +1 -1
  20. package/docs/v7/examples/hover-image.mdx +1 -1
  21. package/docs/v7/examples/hover-text.mdx +1 -1
  22. package/docs/v7/examples/installer.mdx +1 -1
  23. package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
  24. package/docs/v7/examples/match-image.mdx +1 -1
  25. package/docs/v7/examples/press-keys.mdx +1 -1
  26. package/docs/v7/examples/scroll-keyboard.mdx +1 -1
  27. package/docs/v7/examples/scroll-until-image.mdx +1 -1
  28. package/docs/v7/examples/scroll-until-text.mdx +1 -1
  29. package/docs/v7/examples/scroll.mdx +1 -1
  30. package/docs/v7/examples/type.mdx +1 -1
  31. package/docs/v7/examples/windows-installer.mdx +1 -1
  32. package/docs/v7/performing-actions.mdx +3 -0
  33. package/docs/v7/wait.mdx +52 -0
  34. package/docs/v7/waiting-for-elements.mdx +18 -0
  35. package/examples/chrome-extension.test.mjs +2 -0
  36. package/examples/no-provision.test.mjs +2 -0
  37. package/lib/vitest/hooks.mjs +9 -0
  38. package/lib/vitest/setup-aws.mjs +1 -0
  39. package/manual/packer-hover-image.test.mjs +176 -0
  40. package/package.json +2 -1
  41. package/sdk.d.ts +14 -2
  42. package/sdk.js +10 -0
  43. package/setup/aws/cloudformation.yaml +1 -8
  44. package/setup/aws/spawn-runner.sh +2 -37
  45. package/vitest.config.mjs +1 -1
@@ -7,11 +7,80 @@ const Jimp = require("jimp");
7
7
  const { events } = require("../events.js");
8
8
 
9
9
  const createSystem = (emitter, sandbox, config) => {
10
+
11
+ // Download a screenshot from S3 when the runner returns an s3Key
12
+ // (screenshots exceed Ably's 64KB message limit)
13
+ const downloadFromS3 = async (s3Key) => {
14
+ const https = require("https");
15
+ const http = require("http");
16
+ const apiRoot = config["TD_API_ROOT"] || sandbox.apiRoot;
17
+ const apiKey = sandbox.apiKey;
18
+
19
+ // Step 1: Get presigned download URL from API
20
+ const body = JSON.stringify({ apiKey, s3Key });
21
+ const url = new URL(apiRoot + "/api/v7/runner/download-url");
22
+ const transport = url.protocol === "https:" ? https : http;
23
+
24
+ const downloadUrl = await new Promise((resolve, reject) => {
25
+ const req = transport.request(url, {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ "Content-Length": Buffer.byteLength(body),
30
+ "Connection": "close",
31
+ },
32
+ }, (res) => {
33
+ let data = "";
34
+ res.on("data", (chunk) => { data += chunk; });
35
+ res.on("end", () => {
36
+ try {
37
+ const parsed = JSON.parse(data);
38
+ if (parsed.downloadUrl) {
39
+ resolve(parsed.downloadUrl);
40
+ } else {
41
+ reject(new Error("No downloadUrl in response: " + data));
42
+ }
43
+ } catch (e) {
44
+ reject(new Error("Failed to parse download-url response: " + data));
45
+ }
46
+ });
47
+ });
48
+ req.on("error", reject);
49
+ req.write(body);
50
+ req.end();
51
+ });
52
+
53
+ // Step 2: Download the image from S3
54
+ const imageUrl = new URL(downloadUrl);
55
+ const s3Transport = imageUrl.protocol === "https:" ? https : http;
56
+
57
+ const imageBuffer = await new Promise((resolve, reject) => {
58
+ s3Transport.get(downloadUrl, { headers: { "Connection": "close" } }, (res) => {
59
+ const chunks = [];
60
+ res.on("data", (chunk) => chunks.push(chunk));
61
+ res.on("end", () => resolve(Buffer.concat(chunks)));
62
+ res.on("error", reject);
63
+ }).on("error", reject);
64
+ });
65
+
66
+ return imageBuffer.toString("base64");
67
+ };
68
+
10
69
  const screenshot = async (options) => {
11
- let { base64 } = await sandbox.send({
70
+ let response = await sandbox.send({
12
71
  type: "system.screenshot",
13
72
  });
14
73
 
74
+ let base64;
75
+
76
+ // Runner returns { s3Key } for Ably (screenshots too large for 64KB limit)
77
+ // Runner returns { base64 } for direct/local connections
78
+ if (response.s3Key) {
79
+ base64 = await downloadFromS3(response.s3Key);
80
+ } else {
81
+ base64 = response.base64;
82
+ }
83
+
15
84
  if (!base64) {
16
85
  throw new Error("Failed to take screenshot: sandbox returned empty data");
17
86
  }
@@ -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.
@@ -2,104 +2,104 @@
2
2
  "$schema": "./examples-manifest.schema.json",
3
3
  "examples": {
4
4
  "assert.test.mjs": {
5
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf285bdc9b421c706da17",
6
- "lastUpdated": "2026-02-24T00:50:23.965Z"
5
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b6206a177a05bccd1cc",
6
+ "lastUpdated": "2026-03-03T00:32:25.279Z"
7
7
  },
8
8
  "drag-and-drop.test.mjs": {
9
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf08fbdc9b421c706d850",
10
- "lastUpdated": "2026-02-24T00:50:23.961Z"
9
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
10
+ "lastUpdated": "2026-03-03T00:32:25.275Z"
11
11
  },
12
12
  "exec-pwsh.test.mjs": {
13
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf090bdc9b421c706d857",
14
- "lastUpdated": "2026-02-24T00:50:23.961Z"
13
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42565d339e8065f180",
14
+ "lastUpdated": "2026-03-03T00:32:25.275Z"
15
15
  },
16
16
  "match-image.test.mjs": {
17
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf092bdc9b421c706d858",
18
- "lastUpdated": "2026-02-24T00:50:23.961Z"
17
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42258e8885264fc704",
18
+ "lastUpdated": "2026-03-03T00:32:25.275Z"
19
19
  },
20
20
  "scroll-until-text.test.mjs": {
21
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf11ebdc9b421c706d8da",
22
- "lastUpdated": "2026-02-24T00:50:23.961Z"
21
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b99dc33133fc0da9440",
22
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
23
23
  },
24
24
  "hover-text-with-description.test.mjs": {
25
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf120bdc9b421c706d8e1",
26
- "lastUpdated": "2026-02-24T00:50:23.962Z"
25
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b820d4a5265e44e7d89",
26
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
27
27
  },
28
28
  "windows-installer.test.mjs": {
29
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf122bdc9b421c706d8e9",
30
- "lastUpdated": "2026-02-24T00:50:23.962Z"
29
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42565d339e8065f17f",
30
+ "lastUpdated": "2026-03-03T00:32:25.275Z"
31
31
  },
32
32
  "exec-output.test.mjs": {
33
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf123bdc9b421c706d8ea",
34
- "lastUpdated": "2026-02-24T00:50:23.962Z"
33
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918c",
34
+ "lastUpdated": "2026-03-03T00:32:25.275Z"
35
35
  },
36
36
  "chrome-extension.test.mjs": {
37
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf1a9bdc9b421c706d96c",
38
- "lastUpdated": "2026-02-24T00:50:23.963Z"
37
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b91fc0ac3cc632a91a0",
38
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
39
39
  },
40
40
  "launch-vscode-linux.test.mjs": {
41
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf192bdc9b421c706d950",
42
- "lastUpdated": "2026-02-24T00:50:23.963Z"
41
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b91113035da665496a6",
42
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
43
43
  },
44
44
  "hover-image.test.mjs": {
45
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf19fbdc9b421c706d95f",
46
- "lastUpdated": "2026-02-24T00:50:23.963Z"
45
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b77fc0ac3cc632a9198",
46
+ "lastUpdated": "2026-03-03T00:32:25.279Z"
47
47
  },
48
48
  "installer.test.mjs": {
49
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf1febdc9b421c706d9ae",
50
- "lastUpdated": "2026-02-24T00:50:23.965Z"
49
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b570446888b52a4e1c9",
50
+ "lastUpdated": "2026-03-03T00:32:25.277Z"
51
51
  },
52
52
  "type.test.mjs": {
53
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf252bdc9b421c706d9eb",
54
- "lastUpdated": "2026-02-24T00:50:23.965Z"
53
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b8d49845ced0b71e2bf",
54
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
55
55
  },
56
56
  "press-keys.test.mjs": {
57
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf286bdc9b421c706da18",
58
- "lastUpdated": "2026-02-24T00:50:23.965Z"
57
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b800d4a5265e44e7d86",
58
+ "lastUpdated": "2026-03-03T00:32:25.281Z"
59
59
  },
60
60
  "scroll-keyboard.test.mjs": {
61
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf273bdc9b421c706da04",
62
- "lastUpdated": "2026-02-24T00:50:23.965Z"
61
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b82258e8885264fc728",
62
+ "lastUpdated": "2026-03-03T00:32:25.281Z"
63
63
  },
64
64
  "scroll.test.mjs": {
65
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf227bdc9b421c706d9cc",
66
- "lastUpdated": "2026-02-24T00:50:23.965Z"
65
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b83113035da665496a3",
66
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
67
67
  },
68
68
  "scroll-until-image.test.mjs": {
69
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf287bdc9b421c706da19",
70
- "lastUpdated": "2026-02-24T00:50:23.965Z"
69
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b4549845ced0b71e2b0",
70
+ "lastUpdated": "2026-03-03T00:32:25.276Z"
71
71
  },
72
72
  "prompt.test.mjs": {
73
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf288bdc9b421c706da1a",
74
- "lastUpdated": "2026-02-24T00:50:23.965Z"
73
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b45b9fa9df65266123f",
74
+ "lastUpdated": "2026-03-03T00:32:25.276Z"
75
75
  },
76
76
  "focus-window.test.mjs": {
77
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf289bdc9b421c706da1b",
78
- "lastUpdated": "2026-02-24T00:50:23.965Z"
77
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b4549845ced0b71e2b1",
78
+ "lastUpdated": "2026-03-03T00:32:25.276Z"
79
79
  },
80
80
  "captcha-api.test.mjs": {
81
81
  "url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
82
82
  "lastUpdated": "2026-02-13T19:55:05.951Z"
83
83
  },
84
84
  "element-not-found.test.mjs": {
85
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf2bbbdc9b421c706da37",
86
- "lastUpdated": "2026-02-24T00:50:23.966Z"
85
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b6f0d4a5265e44e7d7b",
86
+ "lastUpdated": "2026-03-03T00:32:25.279Z"
87
87
  },
88
88
  "formatted-logging.test.mjs": {
89
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf2cfbdc9b421c706da45",
90
- "lastUpdated": "2026-02-24T00:50:23.966Z"
89
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b60258e8885264fc711",
90
+ "lastUpdated": "2026-03-03T00:32:25.279Z"
91
91
  },
92
92
  "hover-text.test.mjs": {
93
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf34ebdc9b421c706daa9",
94
- "lastUpdated": "2026-02-24T00:50:23.968Z"
93
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b79dc33133fc0da9439",
94
+ "lastUpdated": "2026-03-03T00:32:25.280Z"
95
95
  },
96
96
  "no-provision.test.mjs": {
97
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf342bdc9b421c706daa0",
98
- "lastUpdated": "2026-02-24T00:50:23.967Z"
97
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7706a177a05bccd1cf",
98
+ "lastUpdated": "2026-03-03T00:32:25.279Z"
99
99
  },
100
100
  "ai.test.mjs": {
101
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf327bdc9b421c706da86",
102
- "lastUpdated": "2026-02-24T00:50:23.967Z"
101
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b8f06a177a05bccd1dc",
102
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
103
103
  },
104
104
  "popup-loading.test.mjs": {
105
105
  "url": "https://console.testdriver.ai/runs/698bc89f7140c3fa7daaca8d/698bca7f7140c3fa7daacbf7",
@@ -134,44 +134,44 @@
134
134
  "lastUpdated": "2026-02-13T19:55:05.953Z"
135
135
  },
136
136
  "findall-coffee-icons.test.mjs": {
137
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf306bdc9b421c706da70",
138
- "lastUpdated": "2026-02-24T00:50:23.967Z"
137
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7afc0ac3cc632a919b",
138
+ "lastUpdated": "2026-03-03T00:32:25.280Z"
139
139
  },
140
140
  "parse.test.mjs": {
141
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf361bdc9b421c706dab8",
142
- "lastUpdated": "2026-02-24T00:50:23.968Z"
141
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b80aa712ecd3dea731c",
142
+ "lastUpdated": "2026-03-03T00:32:25.281Z"
143
143
  },
144
144
  "flake-diffthreshold-001.test.mjs": {
145
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf496bdc9b421c706db6e",
146
- "lastUpdated": "2026-02-24T00:50:23.969Z"
145
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
146
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
147
147
  },
148
148
  "flake-noredraw-cache.test.mjs": {
149
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf54fbdc9b421c706dbb7",
150
- "lastUpdated": "2026-02-24T00:50:23.969Z"
149
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62be21658b3dab5240bfc",
150
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
151
151
  },
152
152
  "flake-noredraw-nocache.test.mjs": {
153
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf4f7bdc9b421c706db96",
154
- "lastUpdated": "2026-02-24T00:50:23.969Z"
153
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bd292ffa2dc646f1446",
154
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
155
155
  },
156
156
  "flake-diffthreshold-05.test.mjs": {
157
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf4dcbdc9b421c706db88",
158
- "lastUpdated": "2026-02-24T00:50:23.969Z"
157
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62c0efc0ac3cc632a91ae",
158
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
159
159
  },
160
160
  "flake-redraw-cache.test.mjs": {
161
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf5c5bdc9b421c706dbd0",
162
- "lastUpdated": "2026-02-24T00:50:23.969Z"
161
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62be8565d339e8065f196",
162
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
163
163
  },
164
164
  "flake-redraw-nocache.test.mjs": {
165
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf5a6bdc9b421c706dbca",
166
- "lastUpdated": "2026-02-24T00:50:23.969Z"
165
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bf3fc0ac3cc632a91ad",
166
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
167
167
  },
168
168
  "flake-rocket-match.test.mjs": {
169
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf332bdc9b421c706da90",
170
- "lastUpdated": "2026-02-24T00:50:23.967Z"
169
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b84258e8885264fc729",
170
+ "lastUpdated": "2026-03-03T00:32:25.282Z"
171
171
  },
172
172
  "flake-diffthreshold-01.test.mjs": {
173
- "url": "https://console.testdriver.ai/runs/699cf08dbdc9b421c706d84f/699cf425bdc9b421c706db43",
174
- "lastUpdated": "2026-02-24T00:50:23.968Z"
173
+ "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bd0348bcf90ca081079",
174
+ "lastUpdated": "2026-03-03T00:32:25.283Z"
175
175
  }
176
176
  }
177
177
  }
@@ -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.**
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* ai.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf327bdc9b421c706da86/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b8f06a177a05bccd1dc/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* assert.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf285bdc9b421c706da17/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b6206a177a05bccd1cc/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* chrome-extension.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf1a9bdc9b421c706d96c/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b91fc0ac3cc632a91a0/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* drag-and-drop.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf08fbdc9b421c706d850/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b42fc0ac3cc632a918b/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* element-not-found.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf2bbbdc9b421c706da37/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b6f0d4a5265e44e7d7b/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* hover-image.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf19fbdc9b421c706d95f/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b77fc0ac3cc632a9198/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* hover-text.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf34ebdc9b421c706daa9/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b79dc33133fc0da9439/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* installer.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf1febdc9b421c706d9ae/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b570446888b52a4e1c9/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* launch-vscode-linux.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf192bdc9b421c706d950/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b91113035da665496a6/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* match-image.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf092bdc9b421c706d858/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b42258e8885264fc704/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* press-keys.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf286bdc9b421c706da18/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b800d4a5265e44e7d86/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll-keyboard.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf273bdc9b421c706da04/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b82258e8885264fc728/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll-until-image.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf287bdc9b421c706da19/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b4549845ced0b71e2b0/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll-until-text.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf11ebdc9b421c706d8da/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b99dc33133fc0da9440/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf227bdc9b421c706d9cc/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b83113035da665496a3/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* type.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf252bdc9b421c706d9eb/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b8d49845ced0b71e2bf/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* windows-installer.test.mjs output */}
14
14
  <iframe
15
- src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/699cf122bdc9b421c706d8e9/replay"
15
+ src="https://testdriver-api.onrender.com/api/v1/testdriver/testcase/69a62b42565d339e8065f17f/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -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');