testdriverai 7.3.12 → 7.3.13
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:ai/SKILL.md +204 -0
- package/.github/skills/testdriver:assert/SKILL.md +284 -0
- package/.github/skills/testdriver:aws-setup/SKILL.md +515 -0
- package/.github/skills/testdriver:caching/SKILL.md +124 -0
- package/.github/skills/testdriver:captcha/SKILL.md +159 -0
- package/.github/skills/testdriver:ci-cd/SKILL.md +602 -0
- package/.github/skills/testdriver:click/SKILL.md +286 -0
- package/.github/skills/testdriver:client/SKILL.md +339 -0
- package/.github/skills/testdriver:cloud/SKILL.md +119 -0
- package/.github/skills/testdriver:customizing-devices/SKILL.md +153 -0
- package/.github/skills/testdriver:dashcam/SKILL.md +418 -0
- package/.github/skills/testdriver:debugging-with-screenshots/SKILL.md +271 -0
- package/.github/skills/testdriver:device-config/SKILL.md +317 -0
- package/.github/skills/testdriver:double-click/SKILL.md +102 -0
- package/.github/skills/testdriver:elements/SKILL.md +605 -0
- package/.github/skills/testdriver:enterprise/SKILL.md +114 -0
- package/.github/skills/testdriver:examples/SKILL.md +7 -0
- package/.github/skills/testdriver:exec/SKILL.md +345 -0
- package/.github/skills/testdriver:find/SKILL.md +721 -0
- package/.github/skills/testdriver:focus-application/SKILL.md +293 -0
- package/.github/skills/testdriver:generating-tests/SKILL.md +36 -0
- package/.github/skills/testdriver:hover/SKILL.md +278 -0
- package/.github/skills/testdriver:locating-elements/SKILL.md +71 -0
- package/.github/skills/testdriver:making-assertions/SKILL.md +32 -0
- package/.github/skills/testdriver:mcp-workflow/SKILL.md +410 -0
- package/.github/skills/testdriver:mouse-down/SKILL.md +161 -0
- package/.github/skills/testdriver:mouse-up/SKILL.md +164 -0
- package/.github/skills/testdriver:performing-actions/SKILL.md +51 -0
- package/.github/skills/testdriver:press-keys/SKILL.md +348 -0
- package/.github/skills/testdriver:quickstart/SKILL.md +161 -0
- package/.github/skills/testdriver:reusable-code/SKILL.md +240 -0
- package/.github/skills/testdriver:right-click/SKILL.md +123 -0
- package/.github/skills/testdriver:running-tests/SKILL.md +181 -0
- package/.github/skills/testdriver:screenshot/SKILL.md +167 -0
- package/.github/skills/testdriver:scroll/SKILL.md +299 -0
- package/.github/skills/testdriver:secrets/SKILL.md +115 -0
- package/.github/skills/testdriver:self-hosted/SKILL.md +65 -0
- package/.github/skills/testdriver:test-writer/SKILL.md +451 -0
- package/.github/skills/testdriver:testdriver/SKILL.md +523 -0
- package/.github/skills/testdriver:testdriver-mechanic/SKILL.md +165 -0
- package/.github/skills/testdriver:type/SKILL.md +357 -0
- package/.github/skills/testdriver:variables/SKILL.md +111 -0
- package/.github/skills/testdriver:waiting-for-elements/SKILL.md +66 -0
- package/.github/skills/testdriver:what-is-testdriver/SKILL.md +54 -0
- package/.github/workflows/acceptance-windows-scheduled.yaml +6 -1
- package/.github/workflows/acceptance.yaml +0 -36
- package/.github/workflows/update-examples.yaml +53 -0
- package/CHANGELOG.md +4 -0
- package/agent/events.js +1 -0
- package/agent/index.js +8 -0
- package/agent/lib/commands.js +48 -29
- package/agent/lib/redraw.js +3 -1
- package/agent/lib/sandbox.js +166 -14
- package/agent/lib/sdk.js +142 -3
- package/agent/lib/system.js +4 -6
- package/ai/skills/testdriver:ai/SKILL.md +204 -0
- package/ai/skills/testdriver:assert/SKILL.md +315 -0
- package/ai/skills/testdriver:aws-setup/SKILL.md +448 -0
- package/ai/skills/testdriver:caching/SKILL.md +124 -0
- package/ai/skills/testdriver:captcha/SKILL.md +159 -0
- package/ai/skills/testdriver:ci-cd/SKILL.md +602 -0
- package/ai/skills/testdriver:click/SKILL.md +286 -0
- package/ai/skills/testdriver:client/SKILL.md +372 -0
- package/ai/skills/testdriver:cloud/SKILL.md +119 -0
- package/ai/skills/testdriver:customizing-devices/SKILL.md +153 -0
- package/ai/skills/testdriver:dashcam/SKILL.md +418 -0
- package/ai/skills/testdriver:debugging-with-screenshots/SKILL.md +401 -0
- package/ai/skills/testdriver:device-config/SKILL.md +317 -0
- package/ai/skills/testdriver:double-click/SKILL.md +102 -0
- package/ai/skills/testdriver:elements/SKILL.md +605 -0
- package/ai/skills/testdriver:enterprise/SKILL.md +114 -0
- package/ai/skills/testdriver:examples/SKILL.md +7 -0
- package/ai/skills/testdriver:exec/SKILL.md +345 -0
- package/ai/skills/testdriver:find/SKILL.md +745 -0
- package/ai/skills/testdriver:focus-application/SKILL.md +293 -0
- package/ai/skills/testdriver:generating-tests/SKILL.md +36 -0
- package/ai/skills/testdriver:hover/SKILL.md +278 -0
- package/ai/skills/testdriver:locating-elements/SKILL.md +71 -0
- package/ai/skills/testdriver:making-assertions/SKILL.md +32 -0
- package/ai/skills/testdriver:mcp-workflow/SKILL.md +410 -0
- package/ai/skills/testdriver:mouse-down/SKILL.md +161 -0
- package/ai/skills/testdriver:mouse-up/SKILL.md +164 -0
- package/ai/skills/testdriver:ocr/SKILL.md +235 -0
- package/ai/skills/testdriver:performing-actions/SKILL.md +51 -0
- package/ai/skills/testdriver:press-keys/SKILL.md +348 -0
- package/ai/skills/testdriver:quickstart/SKILL.md +146 -0
- package/ai/skills/testdriver:reusable-code/SKILL.md +240 -0
- package/ai/skills/testdriver:right-click/SKILL.md +123 -0
- package/ai/skills/testdriver:running-tests/SKILL.md +185 -0
- package/ai/skills/testdriver:screenshot/SKILL.md +248 -0
- package/ai/skills/testdriver:scroll/SKILL.md +335 -0
- package/ai/skills/testdriver:secrets/SKILL.md +115 -0
- package/ai/skills/testdriver:self-hosted/SKILL.md +65 -0
- package/ai/skills/testdriver:test-writer/SKILL.md +451 -0
- package/ai/skills/testdriver:testdriver/SKILL.md +631 -0
- package/ai/skills/testdriver:testdriver-mechanic/SKILL.md +165 -0
- package/ai/skills/testdriver:type/SKILL.md +357 -0
- package/ai/skills/testdriver:variables/SKILL.md +111 -0
- package/ai/skills/testdriver:waiting-for-elements/SKILL.md +66 -0
- package/ai/skills/testdriver:what-is-testdriver/SKILL.md +54 -0
- package/debugger/index.html +12 -2
- package/docs/v7/examples/scroll-keyboard.mdx +1 -1
- package/docs/v7/find.mdx +1 -0
- package/examples/config.mjs +1 -1
- package/examples/findall-coffee-icons.test.mjs +42 -0
- package/examples/flake-diffthreshold-001.test.mjs +9 -0
- package/examples/flake-diffthreshold-01.test.mjs +9 -0
- package/examples/flake-diffthreshold-05.test.mjs +9 -0
- package/examples/{z_flake-noredraw-cache.test.mjs → flake-noredraw-cache.test.mjs} +2 -2
- package/examples/{z_flake-noredraw-nocache.test.mjs → flake-noredraw-nocache.test.mjs} +2 -2
- package/examples/{z_flake-redraw-cache.test.mjs → flake-redraw-cache.test.mjs} +2 -2
- package/examples/{z_flake-redraw-nocache.test.mjs → flake-redraw-nocache.test.mjs} +2 -2
- package/examples/flake-rocket-match.test.mjs +30 -0
- package/examples/{z_flake-shared.mjs → flake-shared.mjs} +2 -2
- package/examples/parse.test.mjs +19 -0
- package/examples/scroll-keyboard.test.mjs +1 -1
- package/interfaces/cli/lib/base.js +6 -0
- package/interfaces/logger.js +51 -13
- package/interfaces/vitest-plugin.mjs +137 -0
- package/lib/core/index.d.ts +22 -0
- package/lib/init-project.js +105 -6
- package/lib/vitest/hooks.mjs +2 -5
- package/lib/vitest/setup-disable-defender.mjs +52 -0
- package/package.json +2 -1
- package/sdk-log-formatter.js +90 -0
- package/sdk.d.ts +88 -51
- package/sdk.js +126 -18
- package/setup/aws/disable-defender.sh +42 -0
- package/vitest.config.mjs +1 -3
- package/examples/z_flake-diffthreshold-001.test.mjs +0 -9
- package/examples/z_flake-diffthreshold-01.test.mjs +0 -9
- package/examples/z_flake-diffthreshold-05.test.mjs +0 -9
- /package/{examples → manual}/captcha-api.test.mjs +0 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testdriver:find
|
|
3
|
+
description: Locate UI elements using natural language
|
|
4
|
+
---
|
|
5
|
+
<!-- Generated from find.mdx. DO NOT EDIT. -->
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Locate UI elements on screen using AI-powered natural language descriptions. Returns an `Element` object that can be interacted with.
|
|
10
|
+
|
|
11
|
+
## Syntax
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const element = await testdriver.find(description)
|
|
15
|
+
const element = await testdriver.find(description, options)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Parameters
|
|
19
|
+
|
|
20
|
+
<ParamField path="description" type="string" required>
|
|
21
|
+
Natural language description of the element to find
|
|
22
|
+
</ParamField>
|
|
23
|
+
|
|
24
|
+
<ParamField path="options" type="object | number">
|
|
25
|
+
Optional configuration for finding and caching
|
|
26
|
+
|
|
27
|
+
<Expandable title="properties">
|
|
28
|
+
<ParamField path="cacheKey" type="string">
|
|
29
|
+
Custom cache key for storing element location. Use this to prevent cache pollution when using dynamic variables in prompts, or to share cache across tests.
|
|
30
|
+
</ParamField>
|
|
31
|
+
|
|
32
|
+
<ParamField path="cacheThreshold" type="number" default={0.05}>
|
|
33
|
+
Similarity threshold (0-1) for cache matching. Lower values require more similarity. Set to -1 to disable cache.
|
|
34
|
+
</ParamField>
|
|
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.
|
|
38
|
+
</ParamField>
|
|
39
|
+
|
|
40
|
+
<ParamField path="zoom" type="boolean" default={false}>
|
|
41
|
+
Enable two-phase zoom mode for better precision in crowded UIs with many similar elements.
|
|
42
|
+
</ParamField>
|
|
43
|
+
</Expandable>
|
|
44
|
+
</ParamField>
|
|
45
|
+
|
|
46
|
+
## Returns
|
|
47
|
+
|
|
48
|
+
`Promise<Element>` - Element instance that has been automatically located
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
### Basic Element Finding
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// Find by role
|
|
56
|
+
const button = await testdriver.find('submit button');
|
|
57
|
+
const input = await testdriver.find('email input field');
|
|
58
|
+
|
|
59
|
+
// Find by text content
|
|
60
|
+
const link = await testdriver.find('Contact Us link');
|
|
61
|
+
const heading = await testdriver.find('Welcome heading');
|
|
62
|
+
|
|
63
|
+
// Find by visual appearance
|
|
64
|
+
const icon = await testdriver.find('red warning icon');
|
|
65
|
+
const image = await testdriver.find('company logo image');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Finding with Context
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// Provide location context
|
|
72
|
+
const field = await testdriver.find('username input in the login form');
|
|
73
|
+
const button = await testdriver.find('delete button in the top right corner');
|
|
74
|
+
|
|
75
|
+
// Describe nearby elements
|
|
76
|
+
const input = await testdriver.find('input field below the email label');
|
|
77
|
+
const checkbox = await testdriver.find('checkbox next to "Remember me"');
|
|
78
|
+
|
|
79
|
+
// Describe visual position
|
|
80
|
+
const menu = await testdriver.find('hamburger menu icon in the top left');
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Interacting with Found Elements
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Find and click
|
|
87
|
+
const submitBtn = await testdriver.find('submit button');
|
|
88
|
+
await submitBtn.click();
|
|
89
|
+
|
|
90
|
+
// Find and verify
|
|
91
|
+
const message = await testdriver.find('success message');
|
|
92
|
+
if (message.found()) {
|
|
93
|
+
console.log('Success message appeared');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Find and extract info
|
|
97
|
+
const price = await testdriver.find('product price');
|
|
98
|
+
console.log('Price location:', price.coordinates);
|
|
99
|
+
console.log('Price text:', price.text);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Element Object
|
|
103
|
+
|
|
104
|
+
The returned `Element` object provides:
|
|
105
|
+
|
|
106
|
+
### Methods
|
|
107
|
+
|
|
108
|
+
- `found()` - Check if element was located
|
|
109
|
+
- `click(action)` - Click the element
|
|
110
|
+
- `hover()` - Hover over the element
|
|
111
|
+
- `doubleClick()` - Double-click the element
|
|
112
|
+
- `rightClick()` - Right-click the element
|
|
113
|
+
- `find(newDescription)` - Re-locate with optional new description
|
|
114
|
+
|
|
115
|
+
### Properties
|
|
116
|
+
|
|
117
|
+
- `coordinates` - Element position `{x, y, centerX, centerY}`
|
|
118
|
+
- `x`, `y` - Top-left coordinates
|
|
119
|
+
- `centerX`, `centerY` - Center coordinates
|
|
120
|
+
- `text` - Text content (if available)
|
|
121
|
+
- `screenshot` - Base64 screenshot (if available)
|
|
122
|
+
- `confidence` - AI confidence score
|
|
123
|
+
- `width`, `height` - Element dimensions
|
|
124
|
+
- `boundingBox` - Complete bounding box
|
|
125
|
+
|
|
126
|
+
See [Elements Reference](/v7/elements) for complete details.
|
|
127
|
+
|
|
128
|
+
### JSON Serialization
|
|
129
|
+
|
|
130
|
+
Elements can be safely serialized using `JSON.stringify()` for logging and debugging. Circular references are automatically removed:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
const element = await testdriver.find('login button');
|
|
134
|
+
|
|
135
|
+
// Safe to stringify - no circular reference errors
|
|
136
|
+
console.log(JSON.stringify(element, null, 2));
|
|
137
|
+
|
|
138
|
+
// Output includes useful debugging info:
|
|
139
|
+
// {
|
|
140
|
+
// "description": "login button",
|
|
141
|
+
// "coordinates": { "x": 100, "y": 200, "centerX": 150, "centerY": 225 },
|
|
142
|
+
// "found": true,
|
|
143
|
+
// "threshold": 0.01,
|
|
144
|
+
// "x": 100,
|
|
145
|
+
// "y": 200,
|
|
146
|
+
// "cache": {
|
|
147
|
+
// "hit": true,
|
|
148
|
+
// "strategy": "pixel-diff",
|
|
149
|
+
// "createdAt": "2025-12-09T10:30:00Z",
|
|
150
|
+
// "diffPercent": 0.0023,
|
|
151
|
+
// "imageUrl": "https://..."
|
|
152
|
+
// },
|
|
153
|
+
// "similarity": 0.98,
|
|
154
|
+
// "confidence": 0.95,
|
|
155
|
+
// "selector": "button#login",
|
|
156
|
+
// "aiResponse": "Found the blue login button..."
|
|
157
|
+
// }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This is useful for:
|
|
161
|
+
- Debugging element detection issues
|
|
162
|
+
- Logging test execution details
|
|
163
|
+
- Sharing element information across processes
|
|
164
|
+
- Analyzing cache performance
|
|
165
|
+
|
|
166
|
+
## Best Practices
|
|
167
|
+
|
|
168
|
+
<Check>
|
|
169
|
+
**Be specific in descriptions**
|
|
170
|
+
|
|
171
|
+
More specific descriptions improve accuracy:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// ✅ Good
|
|
175
|
+
await testdriver.find('blue submit button below the email field');
|
|
176
|
+
|
|
177
|
+
// ❌ Too vague
|
|
178
|
+
await testdriver.find('button');
|
|
179
|
+
```
|
|
180
|
+
</Check>
|
|
181
|
+
|
|
182
|
+
<Check>
|
|
183
|
+
**Always check if found**
|
|
184
|
+
|
|
185
|
+
Verify elements were located before interacting:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
const element = await testdriver.find('login button');
|
|
189
|
+
if (!element.found()) {
|
|
190
|
+
throw new Error('Login button not found');
|
|
191
|
+
}
|
|
192
|
+
await element.click();
|
|
193
|
+
```
|
|
194
|
+
</Check>
|
|
195
|
+
|
|
196
|
+
<Check>
|
|
197
|
+
**Include visual or positional context**
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// Include color
|
|
201
|
+
await testdriver.find('red error icon');
|
|
202
|
+
|
|
203
|
+
// Include position
|
|
204
|
+
await testdriver.find('search button in the top navigation bar');
|
|
205
|
+
|
|
206
|
+
// Include nearby text
|
|
207
|
+
await testdriver.find('checkbox next to "I agree to terms"');
|
|
208
|
+
```
|
|
209
|
+
</Check>
|
|
210
|
+
|
|
211
|
+
## Polling for Dynamic Elements
|
|
212
|
+
|
|
213
|
+
For elements that may not be immediately visible, use the `timeout` option to automatically poll:
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
// Poll for element (retries every 5 seconds until found or timeout)
|
|
217
|
+
const element = await testdriver.find('login button', { timeout: 30000 });
|
|
218
|
+
await element.click();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The `timeout` option:
|
|
222
|
+
- Retries finding the element every 5 seconds
|
|
223
|
+
- Stops when the element is found or the timeout expires
|
|
224
|
+
- Logs progress during polling
|
|
225
|
+
- Returns the element (check `element.found()` if not throwing on failure)
|
|
226
|
+
|
|
227
|
+
## Zoom Mode for Crowded UIs
|
|
228
|
+
|
|
229
|
+
When dealing with many similar icons or elements clustered together (like browser toolbars), enable `zoom` mode for better precision:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Enable zoom for better precision in crowded UIs
|
|
233
|
+
const extensionsBtn = await testdriver.find('extensions puzzle icon in Chrome toolbar', { zoom: true });
|
|
234
|
+
await extensionsBtn.click();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### How Zoom Mode Works
|
|
238
|
+
|
|
239
|
+
1. **Phase 1**: AI identifies the approximate location of the element
|
|
240
|
+
2. **Phase 2**: A 30% crop of the screen is created around that location
|
|
241
|
+
3. **Phase 3**: AI performs precise location on the zoomed/cropped image
|
|
242
|
+
4. **Result**: Coordinates are converted back to absolute screen position
|
|
243
|
+
|
|
244
|
+
This two-phase approach gives the AI a higher-resolution view of the target area, improving accuracy when multiple similar elements are close together.
|
|
245
|
+
|
|
246
|
+
<Tip>
|
|
247
|
+
Use `zoom: true` when:
|
|
248
|
+
- Clicking small icons in toolbars
|
|
249
|
+
- Selecting from a grid of similar items
|
|
250
|
+
- Targeting elements in dense UI areas
|
|
251
|
+
- The default locate is clicking the wrong similar element
|
|
252
|
+
- You get an AI verification rejection like "The crosshair is located in the empty space of the browser's tab bar/title bar area" — this means the initial locate was imprecise and zoom will help the AI pinpoint the correct element
|
|
253
|
+
</Tip>
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
// Without zoom - may click wrong icon in toolbar
|
|
257
|
+
const icon = await testdriver.find('settings icon');
|
|
258
|
+
|
|
259
|
+
// With zoom - better precision for crowded areas
|
|
260
|
+
const icon = await testdriver.find('settings icon', { zoom: true });
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Cache Options
|
|
264
|
+
|
|
265
|
+
Control caching behavior to optimize performance, especially when using dynamic variables in prompts.
|
|
266
|
+
|
|
267
|
+
### Custom Cache Key
|
|
268
|
+
|
|
269
|
+
Use `cacheKey` to prevent cache pollution when prompts contain variables:
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// ❌ Without cacheKey - creates new cache entry for each email value
|
|
273
|
+
const email = 'user@example.com';
|
|
274
|
+
await testdriver.find(`input for ${email}`); // Cache miss every time
|
|
275
|
+
|
|
276
|
+
// ✅ With cacheKey - reuses cache regardless of variable
|
|
277
|
+
const email = 'user@example.com';
|
|
278
|
+
await testdriver.find(`input for ${email}`, {
|
|
279
|
+
cacheKey: 'email-input'
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Also useful for dynamic IDs, names, or other changing data
|
|
283
|
+
const orderId = generateOrderId();
|
|
284
|
+
await testdriver.find(`order ${orderId} status`, {
|
|
285
|
+
cacheKey: 'order-status' // Same cache for all orders
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Cache Threshold
|
|
290
|
+
|
|
291
|
+
Control how similar a cached result must be to reuse it:
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// Default: 95% similarity required
|
|
295
|
+
await testdriver.find('submit button');
|
|
296
|
+
|
|
297
|
+
// Strict threshold - 99% similarity required
|
|
298
|
+
await testdriver.find('submit button', {
|
|
299
|
+
cacheThreshold: 0.01
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Disable cache entirely for this call
|
|
303
|
+
await testdriver.find('submit button', {
|
|
304
|
+
cacheThreshold: -1
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Combine cacheKey with threshold
|
|
308
|
+
await testdriver.find('submit button', {
|
|
309
|
+
cacheKey: 'submit-btn',
|
|
310
|
+
cacheThreshold: 0.01
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
<Tip>
|
|
315
|
+
By default, TestDriver auto-generates a cache key from the SHA-256 hash of your test file. When you modify your test file, the hash changes automatically, invalidating stale cache entries.
|
|
316
|
+
</Tip>
|
|
317
|
+
|
|
318
|
+
### Manual Polling (Alternative)
|
|
319
|
+
|
|
320
|
+
If you need custom polling logic:
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
async function waitForElement(testdriver, description, timeout = 30000) {
|
|
324
|
+
const startTime = Date.now();
|
|
325
|
+
|
|
326
|
+
while (Date.now() - startTime < timeout) {
|
|
327
|
+
const element = await testdriver.find(description);
|
|
328
|
+
if (element.found()) return element;
|
|
329
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
throw new Error(`Element "${description}" not found after ${timeout}ms`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Usage
|
|
336
|
+
const button = await waitForElement(testdriver, 'submit button', 10000);
|
|
337
|
+
await button.click();
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Use Cases
|
|
341
|
+
|
|
342
|
+
<AccordionGroup>
|
|
343
|
+
<Accordion title="Form Fields">
|
|
344
|
+
```javascript
|
|
345
|
+
const emailField = await testdriver.find('email input field');
|
|
346
|
+
await emailField.click();
|
|
347
|
+
await testdriver.type('user@example.com');
|
|
348
|
+
|
|
349
|
+
const passwordField = await testdriver.find('password input');
|
|
350
|
+
await passwordField.click();
|
|
351
|
+
await testdriver.type('MyP@ssw0rd');
|
|
352
|
+
```
|
|
353
|
+
</Accordion>
|
|
354
|
+
|
|
355
|
+
<Accordion title="Buttons and Links">
|
|
356
|
+
```javascript
|
|
357
|
+
const submitBtn = await testdriver.find('submit button');
|
|
358
|
+
await submitBtn.click();
|
|
359
|
+
|
|
360
|
+
const cancelLink = await testdriver.find('cancel link');
|
|
361
|
+
await cancelLink.click();
|
|
362
|
+
|
|
363
|
+
const menuIcon = await testdriver.find('hamburger menu icon');
|
|
364
|
+
await menuIcon.click();
|
|
365
|
+
```
|
|
366
|
+
</Accordion>
|
|
367
|
+
|
|
368
|
+
<Accordion title="Dynamic Content">
|
|
369
|
+
```javascript
|
|
370
|
+
// Wait for loading to complete
|
|
371
|
+
let content;
|
|
372
|
+
for (let i = 0; i < 30; i++) {
|
|
373
|
+
content = await testdriver.find('results table');
|
|
374
|
+
if (content.found()) break;
|
|
375
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Interact with loaded content
|
|
379
|
+
const firstRow = await testdriver.find('first row in the results table');
|
|
380
|
+
await firstRow.click();
|
|
381
|
+
```
|
|
382
|
+
</Accordion>
|
|
383
|
+
|
|
384
|
+
<Accordion title="Complex UI Elements">
|
|
385
|
+
```javascript
|
|
386
|
+
// Modals and dialogs
|
|
387
|
+
const modal = await testdriver.find('confirmation dialog');
|
|
388
|
+
if (modal.found()) {
|
|
389
|
+
const confirmBtn = await testdriver.find('confirm button in the dialog');
|
|
390
|
+
await confirmBtn.click();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Dropdown menus
|
|
394
|
+
const dropdown = await testdriver.find('country dropdown');
|
|
395
|
+
await dropdown.click();
|
|
396
|
+
|
|
397
|
+
const option = await testdriver.find('United States option');
|
|
398
|
+
await option.click();
|
|
399
|
+
```
|
|
400
|
+
</Accordion>
|
|
401
|
+
</AccordionGroup>
|
|
402
|
+
|
|
403
|
+
## Complete Example
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
|
|
407
|
+
import TestDriver from 'testdriverai';
|
|
408
|
+
|
|
409
|
+
describe('Element Finding', () => {
|
|
410
|
+
let testdriver;
|
|
411
|
+
|
|
412
|
+
beforeAll(async () => {
|
|
413
|
+
client = new TestDriver(process.env.TD_API_KEY);
|
|
414
|
+
await testdriver.auth();
|
|
415
|
+
await testdriver.connect();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
afterAll(async () => {
|
|
419
|
+
await testdriver.disconnect();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should find and interact with elements', async () => {
|
|
423
|
+
await testdriver.focusApplication('Google Chrome');
|
|
424
|
+
|
|
425
|
+
// Find login form elements
|
|
426
|
+
const usernameField = await testdriver.find('username input field');
|
|
427
|
+
expect(usernameField.found()).toBe(true);
|
|
428
|
+
|
|
429
|
+
await usernameField.click();
|
|
430
|
+
await testdriver.type('testuser');
|
|
431
|
+
|
|
432
|
+
// Find with context
|
|
433
|
+
const passwordField = await testdriver.find('password input below username');
|
|
434
|
+
await passwordField.click();
|
|
435
|
+
await testdriver.type('password123');
|
|
436
|
+
|
|
437
|
+
// Find button
|
|
438
|
+
const submitBtn = await testdriver.find('green submit button');
|
|
439
|
+
expect(submitBtn.found()).toBe(true);
|
|
440
|
+
|
|
441
|
+
console.log('Button location:', submitBtn.centerX, submitBtn.centerY);
|
|
442
|
+
|
|
443
|
+
await submitBtn.click();
|
|
444
|
+
|
|
445
|
+
// Wait for success message
|
|
446
|
+
let successMsg;
|
|
447
|
+
for (let i = 0; i < 10; i++) {
|
|
448
|
+
successMsg = await testdriver.find('success notification');
|
|
449
|
+
if (successMsg.found()) break;
|
|
450
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
expect(successMsg.found()).toBe(true);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Related Methods
|
|
459
|
+
|
|
460
|
+
- [`click()`](/v7/click) - Click on found elements
|
|
461
|
+
- [`hover()`](/v7/hover) - Hover over elements
|
|
462
|
+
- [`assert()`](/v7/assert) - Verify element states
|
|
463
|
+
- [Elements Reference](/v7/elements) - Complete Element API
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## findAll()
|
|
468
|
+
|
|
469
|
+
Locate **all elements** matching a description, rather than just one.
|
|
470
|
+
|
|
471
|
+
### Syntax
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
const elements = await testdriver.findAll(description, options)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Parameters
|
|
478
|
+
|
|
479
|
+
<ParamField path="description" type="string" required>
|
|
480
|
+
Natural language description of elements to find
|
|
481
|
+
</ParamField>
|
|
482
|
+
|
|
483
|
+
<ParamField path="options" type="object | number">
|
|
484
|
+
Optional cache options (same as `find()`)
|
|
485
|
+
|
|
486
|
+
<Expandable title="properties">
|
|
487
|
+
<ParamField path="cacheKey" type="string">
|
|
488
|
+
Cache key for storing element location
|
|
489
|
+
</ParamField>
|
|
490
|
+
|
|
491
|
+
<ParamField path="cacheThreshold" type="number" default={-1}>
|
|
492
|
+
Similarity threshold (0-1) for cache matching. Set to -1 to disable cache.
|
|
493
|
+
</ParamField>
|
|
494
|
+
</Expandable>
|
|
495
|
+
</ParamField>
|
|
496
|
+
|
|
497
|
+
### Returns
|
|
498
|
+
|
|
499
|
+
`Promise<Element[]>` - Array of Element instances
|
|
500
|
+
|
|
501
|
+
### Examples
|
|
502
|
+
|
|
503
|
+
#### Basic Usage
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
// Find all matching elements
|
|
507
|
+
const buttons = await testdriver.findAll('button');
|
|
508
|
+
console.log(`Found ${buttons.length} buttons`);
|
|
509
|
+
|
|
510
|
+
// Interact with specific element
|
|
511
|
+
if (buttons.length > 0) {
|
|
512
|
+
await buttons[0].click(); // Click first button
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Iterate over all
|
|
516
|
+
for (const button of buttons) {
|
|
517
|
+
console.log(`Button at (${button.x}, ${button.y})`);
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
#### Finding Multiple Items
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
// Find all list items
|
|
525
|
+
const items = await testdriver.findAll('list item');
|
|
526
|
+
|
|
527
|
+
// Find specific item by index
|
|
528
|
+
const thirdItem = items[2];
|
|
529
|
+
await thirdItem.click();
|
|
530
|
+
|
|
531
|
+
// Check all items
|
|
532
|
+
for (let i = 0; i < items.length; i++) {
|
|
533
|
+
console.log(`Item ${i + 1}: ${items[i].text || 'No text'}`);
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
#### With Caching
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
// Cache element locations for faster subsequent runs
|
|
541
|
+
const menuItems = await testdriver.findAll('menu item', {
|
|
542
|
+
cacheKey: 'main-menu-items'
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// First run: ~2-3 seconds (AI call)
|
|
546
|
+
// Subsequent runs: ~100ms (cache hit)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### Empty Results
|
|
550
|
+
|
|
551
|
+
```javascript
|
|
552
|
+
// Returns empty array if nothing found (doesn't throw error)
|
|
553
|
+
const errors = await testdriver.findAll('error message');
|
|
554
|
+
|
|
555
|
+
if (errors.length === 0) {
|
|
556
|
+
console.log('No errors found - test passed!');
|
|
557
|
+
} else {
|
|
558
|
+
console.log(`Found ${errors.length} errors`);
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Differences from find()
|
|
563
|
+
|
|
564
|
+
| Feature | find() | findAll() |
|
|
565
|
+
|---------|--------|-----------|
|
|
566
|
+
| Return type | Single `Element` | Array of `Element[]` |
|
|
567
|
+
| If nothing found | Throws `ElementNotFoundError` | Returns empty array `[]` |
|
|
568
|
+
| Chainable | ✅ Yes: `await find('button').click()` | ❌ No (returns array) |
|
|
569
|
+
| Use case | One specific element | Multiple similar elements |
|
|
570
|
+
| Cache support | ✅ Yes | ✅ Yes |
|
|
571
|
+
|
|
572
|
+
### Use Cases
|
|
573
|
+
|
|
574
|
+
<AccordionGroup>
|
|
575
|
+
<Accordion title="Table Rows">
|
|
576
|
+
```javascript
|
|
577
|
+
// Find all rows in a table
|
|
578
|
+
const rows = await testdriver.findAll('table row');
|
|
579
|
+
|
|
580
|
+
// Click every row
|
|
581
|
+
for (const row of rows) {
|
|
582
|
+
await row.click();
|
|
583
|
+
await new Promise(r => setTimeout(r, 500)); // Wait between clicks
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Or click specific row
|
|
587
|
+
await rows[2].click(); // Click third row
|
|
588
|
+
```
|
|
589
|
+
</Accordion>
|
|
590
|
+
|
|
591
|
+
<Accordion title="Checkboxes/Radio Buttons">
|
|
592
|
+
```javascript
|
|
593
|
+
// Find all checkboxes
|
|
594
|
+
const checkboxes = await testdriver.findAll('checkbox');
|
|
595
|
+
|
|
596
|
+
// Check all boxes
|
|
597
|
+
for (const checkbox of checkboxes) {
|
|
598
|
+
await checkbox.click();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Or select first unchecked
|
|
602
|
+
const unchecked = checkboxes[0];
|
|
603
|
+
await unchecked.click();
|
|
604
|
+
```
|
|
605
|
+
</Accordion>
|
|
606
|
+
|
|
607
|
+
<Accordion title="Navigation Links">
|
|
608
|
+
```javascript
|
|
609
|
+
// Find all navigation links
|
|
610
|
+
const navLinks = await testdriver.findAll('navigation link');
|
|
611
|
+
|
|
612
|
+
// Validate all are present
|
|
613
|
+
expect(navLinks.length).toBeGreaterThan(0);
|
|
614
|
+
|
|
615
|
+
// Click specific link by text
|
|
616
|
+
const homeLink = navLinks.find(link =>
|
|
617
|
+
link.text?.toLowerCase().includes('home')
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
if (homeLink) {
|
|
621
|
+
await homeLink.click();
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
</Accordion>
|
|
625
|
+
|
|
626
|
+
<Accordion title="Conditional Interactions">
|
|
627
|
+
```javascript
|
|
628
|
+
// Check if any error messages exist
|
|
629
|
+
const errors = await testdriver.findAll('error message');
|
|
630
|
+
|
|
631
|
+
if (errors.length > 0) {
|
|
632
|
+
console.log(`Found ${errors.length} validation errors`);
|
|
633
|
+
|
|
634
|
+
// Log each error location
|
|
635
|
+
errors.forEach((error, i) => {
|
|
636
|
+
console.log(`Error ${i + 1} at (${error.x}, ${error.y})`);
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
console.log('Form validation passed!');
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
</Accordion>
|
|
643
|
+
</AccordionGroup>
|
|
644
|
+
|
|
645
|
+
### Complete Example
|
|
646
|
+
|
|
647
|
+
```javascript
|
|
648
|
+
import { test, expect } from 'vitest';
|
|
649
|
+
import { chrome } from 'testdriverai/presets';
|
|
650
|
+
|
|
651
|
+
test('select multiple items from list', async (context) => {
|
|
652
|
+
const { testdriver } = await chrome(context, {
|
|
653
|
+
url: 'https://example.com/products'
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Find all product cards
|
|
657
|
+
const products = await testdriver.findAll('product card');
|
|
658
|
+
|
|
659
|
+
expect(products.length).toBeGreaterThan(0);
|
|
660
|
+
console.log(`Found ${products.length} products`);
|
|
661
|
+
|
|
662
|
+
// Click first 3 products
|
|
663
|
+
const productsToSelect = Math.min(3, products.length);
|
|
664
|
+
|
|
665
|
+
for (let i = 0; i < productsToSelect; i++) {
|
|
666
|
+
await products[i].click();
|
|
667
|
+
console.log(`Selected product ${i + 1}`);
|
|
668
|
+
await new Promise(r => setTimeout(r, 500)); // Brief pause
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Verify selections
|
|
672
|
+
const selectedBadges = await testdriver.findAll('selected badge');
|
|
673
|
+
expect(selectedBadges.length).toBe(productsToSelect);
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Best Practices
|
|
678
|
+
|
|
679
|
+
<Check>
|
|
680
|
+
**Handle empty arrays gracefully**
|
|
681
|
+
|
|
682
|
+
```javascript
|
|
683
|
+
// ✅ Good - check length first
|
|
684
|
+
const items = await testdriver.findAll('list item');
|
|
685
|
+
if (items.length > 0) {
|
|
686
|
+
await items[0].click();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// ❌ Bad - may throw error
|
|
690
|
+
const items = await testdriver.findAll('list item');
|
|
691
|
+
await items[0].click(); // Error if array is empty!
|
|
692
|
+
```
|
|
693
|
+
</Check>
|
|
694
|
+
|
|
695
|
+
<Check>
|
|
696
|
+
**Use find() for single elements**
|
|
697
|
+
|
|
698
|
+
```javascript
|
|
699
|
+
// ✅ Use find() when you need exactly one
|
|
700
|
+
const submitBtn = await testdriver.find('submit button');
|
|
701
|
+
await submitBtn.click();
|
|
702
|
+
|
|
703
|
+
// ❌ Unnecessary - findAll() returns array
|
|
704
|
+
const buttons = await testdriver.findAll('submit button');
|
|
705
|
+
await buttons[0].click(); // Extra array handling
|
|
706
|
+
```
|
|
707
|
+
</Check>
|
|
708
|
+
|
|
709
|
+
<Check>
|
|
710
|
+
**Cache for performance**
|
|
711
|
+
|
|
712
|
+
```javascript
|
|
713
|
+
// First run - slow (AI call)
|
|
714
|
+
const items = await testdriver.findAll('menu item', {
|
|
715
|
+
cacheKey: 'menu-items'
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Subsequent runs - fast (cache hit)
|
|
719
|
+
// ~10-20x faster than without cache
|
|
720
|
+
```
|
|
721
|
+
</Check>
|