@testdriverai/agent 7.9.103-canary โ†’ 7.9.104-canary

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.
@@ -10,7 +10,13 @@ function createCommandDefinitions(agent) {
10
10
  init: {
11
11
  description: "Initialize a new TestDriver project with Vitest SDK examples",
12
12
  args: {},
13
- flags: {},
13
+ flags: {
14
+ client: Flags.string({
15
+ description:
16
+ "AI client(s) to install into (comma-separated, or 'all'). e.g. --client claude-code,cursor. Omit for an interactive picker.",
17
+ multiple: false,
18
+ }),
19
+ },
14
20
  handler: async () => {
15
21
  // This handler is special - it doesn't need an agent instance
16
22
  // It just scaffolds files, so it will be handled by the CLI command
@@ -302,10 +302,12 @@ const createCommands = (
302
302
  `๐Ÿ” assert() threshold: ${threshold} (cache ${threshold < 0 ? "DISABLED" : "ENABLED"}${cacheKey ? `, cacheKey: ${cacheKey.substring(0, 8)}...` : ""})`,
303
303
  );
304
304
 
305
- // Use v7 endpoint for assert with caching support
305
+ // Use v7 endpoint for assert with caching support.
306
+ // captureScreenImage returns { imageKey } (fast S3-key path) or { image }
307
+ // (base64 fallback) โ€” see system.captureScreenImage.
306
308
  let response = await sdk.req("assert", {
307
309
  expect: assertion,
308
- image: await system.captureScreenBase64(),
310
+ ...(await system.captureScreenImage()),
309
311
  threshold,
310
312
  cacheKey,
311
313
  os,
@@ -815,7 +817,7 @@ const createCommands = (
815
817
 
816
818
  let response = await sdk.req("find", {
817
819
  element: description,
818
- image: await system.captureScreenBase64(),
820
+ ...(await system.captureScreenImage()),
819
821
  });
820
822
 
821
823
  if (!response || !response.coordinates) {
@@ -855,7 +857,7 @@ const createCommands = (
855
857
 
856
858
  let response = await sdk.req("find", {
857
859
  element: description,
858
- image: await system.captureScreenBase64(),
860
+ ...(await system.captureScreenImage()),
859
861
  });
860
862
 
861
863
  if (!response || !response.coordinates) {
@@ -1211,7 +1213,7 @@ const createCommands = (
1211
1213
  while (durationPassed < timeout && !passed) {
1212
1214
  const response = await sdk.req("find", {
1213
1215
  element: text,
1214
- image: await system.captureScreenBase64(),
1216
+ ...(await system.captureScreenImage()),
1215
1217
  });
1216
1218
 
1217
1219
  passed = !!(response && response.coordinates);
@@ -1304,7 +1306,7 @@ const createCommands = (
1304
1306
  while (scrollDistance <= maxDistance && !passed) {
1305
1307
  const response = await sdk.req("find", {
1306
1308
  element: text,
1307
- image: await system.captureScreenBase64(),
1309
+ ...(await system.captureScreenImage()),
1308
1310
  });
1309
1311
 
1310
1312
  passed = !!(response && response.coordinates);
@@ -53,15 +53,28 @@ const createSystem = (emitter, sandbox, config) => {
53
53
  return Buffer.from(imageResponse.data).toString("base64");
54
54
  };
55
55
 
56
- const screenshot = async (options) => {
56
+ // Capture a screenshot from the runner. Returns the raw runner response,
57
+ // which is one of:
58
+ // { s3Key, width, height } โ€” runner uploaded to S3 (Ably 64KB limit)
59
+ // { base64 } โ€” direct/local connection, bytes inline
60
+ const captureRaw = async () => {
61
+ return await sandbox.send({
62
+ type: "system.screenshot",
63
+ });
64
+ };
65
+
66
+ const screenshot = async (options, rawResponse) => {
57
67
  const MAX_RETRIES = 3;
58
68
  let lastError;
59
69
 
60
70
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
61
71
  try {
62
- let response = await sandbox.send({
63
- type: "system.screenshot",
64
- });
72
+ // Reuse a response captured by the caller (so the key path and the
73
+ // base64 path don't each trigger a separate runner capture); otherwise
74
+ // capture fresh.
75
+ let response = attempt === 0 && rawResponse
76
+ ? rawResponse
77
+ : await captureRaw();
65
78
 
66
79
  let base64;
67
80
 
@@ -112,7 +125,7 @@ const createSystem = (emitter, sandbox, config) => {
112
125
  return path.join(os.tmpdir(), `td-${Date.now()}-${randomUUID().slice(0, 8)}-${countImages}.png`);
113
126
  };
114
127
 
115
- const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
128
+ const captureAndResize = async (scale = 1, silent = false, mouse = false, rawResponse = null) => {
116
129
  try {
117
130
  if (!silent) {
118
131
  emitter.emit(events.screenCapture.start, {
@@ -125,7 +138,7 @@ const createSystem = (emitter, sandbox, config) => {
125
138
  let step1 = tmpFilename();
126
139
  let step2 = tmpFilename();
127
140
 
128
- await screenshot({ filename: step1, format: "png" });
141
+ await screenshot({ filename: step1, format: "png" }, rawResponse);
129
142
 
130
143
  // Load the screenshot image with Jimp
131
144
  let image = await Jimp.read(step1);
@@ -187,6 +200,46 @@ const createSystem = (emitter, sandbox, config) => {
187
200
  return await captureAndResize(scale, silent, mouse);
188
201
  };
189
202
 
203
+ // Build the image payload to send to the API for a command (find/assert/etc).
204
+ //
205
+ // Fast path: when the runner uploaded the screenshot to S3 and it was already
206
+ // captured at the requested resolution, return { imageKey } so the API reads
207
+ // the bytes straight from S3 by key. This skips the redundant round-trip the
208
+ // base64 path pays per command โ€” SDK download from S3, Jimp re-encode, then a
209
+ // re-upload on the API side.
210
+ //
211
+ // Slow path (fallback): when bytes are inline (local/direct connection), when
212
+ // a mouse cursor must be composited, when scale != 1, or when the captured
213
+ // size differs from TD_RESOLUTION (so a resize is actually required), fall
214
+ // back to capturing + resizing locally and return { image } (base64).
215
+ const captureScreenImage = async (scale = 1, silent = false, mouse = false) => {
216
+ const raw = await captureRaw();
217
+
218
+ const [targetW, targetH] = config.TD_RESOLUTION || [];
219
+ const canUseKey =
220
+ raw &&
221
+ raw.s3Key &&
222
+ !mouse &&
223
+ scale === 1 &&
224
+ typeof raw.width === "number" &&
225
+ typeof raw.height === "number" &&
226
+ raw.width === targetW &&
227
+ raw.height === targetH;
228
+
229
+ if (canUseKey) {
230
+ if (!silent) {
231
+ emitter.emit(events.screenCapture.start, { scale, silent, display: primaryDisplay });
232
+ emitter.emit(events.screenCapture.end, { scale, silent, display: primaryDisplay });
233
+ }
234
+ return { imageKey: raw.s3Key };
235
+ }
236
+
237
+ // Fallback: download/resize locally and send base64. Pass the already
238
+ // captured runner response through so we don't capture the screen twice.
239
+ const step2 = await captureAndResize(scale, silent, mouse, raw);
240
+ return { image: fs.readFileSync(step2, "base64") };
241
+ };
242
+
190
243
  const platform = () => {
191
244
  return "windows";
192
245
  };
@@ -213,6 +266,7 @@ const createSystem = (emitter, sandbox, config) => {
213
266
  return {
214
267
  captureScreenBase64,
215
268
  captureScreenPNG,
269
+ captureScreenImage,
216
270
  getMousePosition,
217
271
  primaryDisplay,
218
272
  activeWin,
package/docs/docs.json CHANGED
@@ -99,10 +99,25 @@
99
99
  }
100
100
  ]
101
101
  },
102
+ {
103
+ "group": "AI",
104
+ "icon": "robot",
105
+ "pages": [
106
+ "/v7/ai/agent",
107
+ "/v7/ai/skills",
108
+ "/v7/ai/mcp"
109
+ ]
110
+ },
102
111
  {
103
112
  "group": "Actions",
104
113
  "pages": [
105
- "/v7/ai",
114
+ {
115
+ "group": "ai",
116
+ "tag": "Beta",
117
+ "pages": [
118
+ "/v7/ai"
119
+ ]
120
+ },
106
121
  "/v7/assert",
107
122
  "/v7/captcha",
108
123
  "/v7/click",
@@ -0,0 +1,72 @@
1
+ ---
2
+ title: "Agent"
3
+ sidebarTitle: "Agent"
4
+ description: "The TestDriver test-creator agent that writes and debugs end-to-end tests for you"
5
+ icon: "robot"
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ The **TestDriver agent** is an expert test-creator that runs inside your AI client (Claude Code, Cursor, VS Code, and others). It writes, runs, and debugs real end-to-end tests by driving your app the same way a person would โ€” using AI vision to find elements, click, type, and assert โ€” through the [TestDriver MCP Server](/v7/ai/mcp).
11
+
12
+ Unlike a chat assistant that only suggests code, the agent works **iteratively against a live sandbox**: it starts a session, performs each action, writes the generated code to your test file, verifies the result with a screenshot, and reruns the test until it passes.
13
+
14
+ ## What it does
15
+
16
+ - **Builds tests from scratch** using TestDriver [skills](/v7/ai/skills) and best practices.
17
+ - **Drives a live sandbox** via MCP tools (`session_start`, `find`, `click`, `type`, `assert`, `check`, โ€ฆ), getting a screenshot and generated code after every action.
18
+ - **Writes code immediately** to the test file after each successful step โ€” never all at once at the end.
19
+ - **Verifies visually** with `check` to confirm each action did what was intended.
20
+ - **Runs the test itself** with `vitest run` and iterates until it passes reliably.
21
+ - **Shares the run report** โ€” after each run it surfaces the `TESTDRIVER_RUN_URL` so you can watch the recording.
22
+
23
+ ## Installation
24
+
25
+ The agent is installed automatically by `testdriverai init`, alongside the [skills](/v7/ai/skills) and the [MCP server](/v7/ai/mcp):
26
+
27
+ ```bash
28
+ npx testdriverai init
29
+ ```
30
+
31
+ During init you'll be asked which AI client(s) to install into. The agent is written to the location each client expects:
32
+
33
+ | Client | Agent location |
34
+ | --- | --- |
35
+ | Claude Code | `.claude/agents/testdriver.md` |
36
+ | VS Code (Copilot) | `.github/agents/testdriver.agent.md` |
37
+ | Cursor | `.cursor/rules/testdriver.mdc` |
38
+ | Windsurf | `.windsurf/rules/testdriver.md` |
39
+ | Codex | `AGENTS.md` |
40
+ | Zed | `.rules` |
41
+
42
+ To install for a specific client without the interactive picker:
43
+
44
+ ```bash
45
+ npx testdriverai init --client claude-code
46
+ # or several at once
47
+ npx testdriverai init --client claude-code,cursor,vscode
48
+ # or everything
49
+ npx testdriverai init --client all
50
+ ```
51
+
52
+ See the [MCP Server](/v7/ai/mcp) page for the full client matrix, including web-based clients (Lovable, Replit, v0) that require a few manual steps.
53
+
54
+ ## Prerequisites
55
+
56
+ You need a TestDriver API key. Create one at [console.testdriver.ai/team](https://console.testdriver.ai/team) and `init` will save it to `.env` as `TD_API_KEY`.
57
+
58
+ ## Using the agent
59
+
60
+ Once installed, invoke it from your client's chat:
61
+
62
+ ```text
63
+ @testdriver write a test that logs in and verifies the dashboard loads
64
+ ```
65
+
66
+ The agent will spin up a sandbox, perform the steps live, write them into a test file under `tests/`, and run it for you.
67
+
68
+ ## Related
69
+
70
+ - [Skills](/v7/ai/skills) โ€” the building blocks the agent composes tests from
71
+ - [MCP Server](/v7/ai/mcp) โ€” the tools the agent uses to drive your app
72
+ - [Generating tests](/v7/generating-tests) โ€” how test generation works end-to-end
@@ -0,0 +1,228 @@
1
+ ---
2
+ title: "MCP Server"
3
+ sidebarTitle: "MCP Server"
4
+ description: "Install the TestDriver MCP server in any AI client โ€” Claude Code, Cursor, VS Code, Windsurf, Codex, Zed, and more"
5
+ icon: "plug"
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ The **TestDriver MCP server** exposes TestDriver's computer-use tools โ€” `session_start`, `find`, `click`, `type`, `assert`, `check`, `screenshot`, and more โ€” over the [Model Context Protocol](https://modelcontextprotocol.io). Any MCP-capable AI client can use it to drive a live sandbox and write real end-to-end tests.
11
+
12
+ It runs as a local stdio process:
13
+
14
+ ```bash
15
+ npx -p testdriverai testdriverai-mcp
16
+ ```
17
+
18
+ and authenticates with your `TD_API_KEY` (from [console.testdriver.ai/team](https://console.testdriver.ai/team)).
19
+
20
+ ## Quick install (recommended)
21
+
22
+ `testdriverai init` wires up the MCP server, the [agent](/v7/ai/agent), and the [skills](/v7/ai/skills) for you, writing each client's config in the exact format and location it expects:
23
+
24
+ ```bash
25
+ # interactive โ€” pick your client(s)
26
+ npx testdriverai init
27
+
28
+ # one client
29
+ npx testdriverai init --client claude-code
30
+
31
+ # several
32
+ npx testdriverai init --client claude-code,cursor,vscode
33
+
34
+ # everything
35
+ npx testdriverai init --client all
36
+ ```
37
+
38
+ <Info>
39
+ `init` detects clients already present in your project and pre-selects them in the picker. Re-running `init` is safe โ€” it merges the TestDriver entry into existing config without overwriting your other servers.
40
+ </Info>
41
+
42
+ ## Client support matrix
43
+
44
+ | Client | Auto-install | MCP config file | Config key |
45
+ | --- | --- | --- | --- |
46
+ | Claude Code | โœ… | `.mcp.json` | `mcpServers` |
47
+ | Claude Desktop | โœ… | OS-specific (see below) | `mcpServers` |
48
+ | Cursor | โœ… | `.cursor/mcp.json` | `mcpServers` |
49
+ | VS Code (Copilot) | โœ… | `.vscode/mcp.json` | `servers` |
50
+ | Windsurf | โœ… | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` |
51
+ | Codex | โœ… | `~/.codex/config.toml` | `[mcp_servers]` |
52
+ | Zed | โœ… | `.zed/settings.json` | `context_servers` |
53
+ | Lovable | โš™๏ธ partial | GitHub `AGENTS.md` + UI | โ€” |
54
+ | Replit | โš™๏ธ partial | `replit.md` + UI | โ€” |
55
+ | v0 (Vercel) | ๐Ÿ“ manual | web UI only | โ€” |
56
+
57
+ <Note>
58
+ Each client uses a **different top-level key** for MCP servers. The most common mistake when configuring by hand is using `mcpServers` for VS Code (it wants `servers`), Codex (TOML `[mcp_servers]`), or Zed (`context_servers`).
59
+ </Note>
60
+
61
+ ## Manual installation
62
+
63
+ If you'd rather configure by hand, use the snippets below. The stdio command is the same everywhere; only the wrapper key and file location change.
64
+
65
+ <Tabs>
66
+ <Tab title="Claude Code">
67
+ Add to `.mcp.json` at your project root (or `~/.claude.json` for all projects):
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "testdriver": {
73
+ "type": "stdio",
74
+ "command": "npx",
75
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
76
+ "env": { "TD_API_KEY": "${TD_API_KEY}" }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+ </Tab>
82
+
83
+ <Tab title="Claude Desktop">
84
+ Edit the Claude Desktop config file:
85
+
86
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
87
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
88
+ - **Linux:** `~/.config/Claude/claude_desktop_config.json`
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "testdriver": {
94
+ "command": "npx",
95
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
96
+ "env": { "TD_API_KEY": "your_api_key" }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ Restart Claude Desktop after saving.
103
+ </Tab>
104
+
105
+ <Tab title="Cursor">
106
+ Add to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
107
+
108
+ ```json
109
+ {
110
+ "mcpServers": {
111
+ "testdriver": {
112
+ "type": "stdio",
113
+ "command": "npx",
114
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
115
+ "env": { "TD_API_KEY": "${TD_API_KEY}" }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+ </Tab>
121
+
122
+ <Tab title="VS Code">
123
+ Add to `.vscode/mcp.json`. VS Code uses the `servers` key and an `inputs` prompt for secrets:
124
+
125
+ ```json
126
+ {
127
+ "servers": {
128
+ "testdriver": {
129
+ "type": "stdio",
130
+ "command": "npx",
131
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
132
+ "env": { "TD_API_KEY": "${input:testdriver-api-key}" }
133
+ }
134
+ },
135
+ "inputs": [
136
+ {
137
+ "type": "promptString",
138
+ "id": "testdriver-api-key",
139
+ "description": "TestDriver API Key From https://console.testdriver.ai/team",
140
+ "password": true
141
+ }
142
+ ]
143
+ }
144
+ ```
145
+ </Tab>
146
+
147
+ <Tab title="Windsurf">
148
+ Windsurf reads MCP config globally. Add to `~/.codeium/windsurf/mcp_config.json`:
149
+
150
+ ```json
151
+ {
152
+ "mcpServers": {
153
+ "testdriver": {
154
+ "command": "npx",
155
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
156
+ "env": { "TD_API_KEY": "${TD_API_KEY}" }
157
+ }
158
+ }
159
+ }
160
+ ```
161
+ </Tab>
162
+
163
+ <Tab title="Codex">
164
+ Codex uses TOML. Add to `~/.codex/config.toml`:
165
+
166
+ ```toml
167
+ [mcp_servers.testdriver]
168
+ command = "npx"
169
+ args = ["-p", "testdriverai", "testdriverai-mcp"]
170
+ env = { TD_API_KEY = "${TD_API_KEY}" }
171
+ ```
172
+ </Tab>
173
+
174
+ <Tab title="Zed">
175
+ Zed calls them "context servers". Add to `.zed/settings.json` (project) or `~/.config/zed/settings.json` (global):
176
+
177
+ ```json
178
+ {
179
+ "context_servers": {
180
+ "testdriver": {
181
+ "command": "npx",
182
+ "args": ["-p", "testdriverai", "testdriverai-mcp"],
183
+ "env": { "TD_API_KEY": "${TD_API_KEY}" }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+ </Tab>
189
+ </Tabs>
190
+
191
+ ## Web-based clients
192
+
193
+ Lovable, Replit, and v0 run in the browser, so the MCP server can't be launched as a local process. Configure them through each product's UI.
194
+
195
+ <AccordionGroup>
196
+ <Accordion title="Lovable">
197
+ 1. Connect your GitHub repo and run `npx testdriverai init --client lovable` โ€” this writes `AGENTS.md` and the skills into the repo so Lovable's agent picks them up.
198
+ 2. In Lovable, open **Settings โ†’ MCP** and add the TestDriver server.
199
+ </Accordion>
200
+
201
+ <Accordion title="Replit">
202
+ 1. Run `npx testdriverai init --client replit` to write `replit.md` with the TestDriver agent guidance.
203
+ 2. In Replit, open **Tools โ†’ Integrations โ†’ MCP** and add a custom MCP server.
204
+ </Accordion>
205
+
206
+ <Accordion title="v0 (Vercel)">
207
+ v0 is fully UI-driven and does not read repo files.
208
+
209
+ 1. Open **[v0.app/chat/settings/mcp-connections](https://v0.app/chat/settings/mcp-connections)** and add the TestDriver MCP connection.
210
+ 2. Paste the agent guidance into **Instructions** (the **+** in the prompt bar).
211
+ </Accordion>
212
+ </AccordionGroup>
213
+
214
+ ## Verifying the install
215
+
216
+ After installing, open your client's chat and ask the agent to write a test:
217
+
218
+ ```text
219
+ @testdriver write a test that opens the homepage and asserts the title
220
+ ```
221
+
222
+ If the MCP server is wired up correctly, the agent will start a session and you'll see screenshots come back as it works. If tools don't appear, check that `TD_API_KEY` is set and restart the client.
223
+
224
+ ## Related
225
+
226
+ - [Agent](/v7/ai/agent) โ€” the test-creator that uses these tools
227
+ - [Skills](/v7/ai/skills) โ€” instruction files describing each tool
228
+ - [CI/CD](/v7/ci-cd) โ€” running the generated tests in your pipeline
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: "Skills"
3
+ sidebarTitle: "Skills"
4
+ description: "Composable instruction files that teach any AI client how to use TestDriver"
5
+ icon: "puzzle-piece"
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ **Skills** are small, focused instruction files โ€” one per TestDriver capability โ€” that teach your AI client exactly how to use each part of the TestDriver SDK and MCP tools. They follow the [Anthropic `SKILL.md` format](https://code.claude.com/docs/en/skills): a folder per skill, each containing a `SKILL.md` with YAML frontmatter and a markdown body.
11
+
12
+ There are **106 skills**, generated directly from the TestDriver documentation, covering every action and concept: `find`, `click`, `type`, `assert`, `check`, `scroll`, `press-keys`, `provision`, caching, secrets, CI/CD, and more.
13
+
14
+ ```text
15
+ .claude/skills/
16
+ โ”œโ”€โ”€ testdriver-click/
17
+ โ”‚ โ””โ”€โ”€ SKILL.md
18
+ โ”œโ”€โ”€ testdriver-find/
19
+ โ”‚ โ””โ”€โ”€ SKILL.md
20
+ โ”œโ”€โ”€ testdriver-assert/
21
+ โ”‚ โ””โ”€โ”€ SKILL.md
22
+ โ””โ”€โ”€ โ€ฆ (103 more)
23
+ ```
24
+
25
+ Each `SKILL.md` looks like:
26
+
27
+ ```markdown
28
+ ---
29
+ name: testdriver:click
30
+ description: Click at specific coordinates or on elements
31
+ ---
32
+
33
+ ## Element Click
34
+
35
+ When called on an Element object, clicks on the located element.
36
+ โ€ฆ
37
+ ```
38
+
39
+ ## Why skills
40
+
41
+ The [agent](/v7/ai/agent) is general; skills are specific. When the agent needs to perform an action โ€” say, locate an element โ€” it pulls in the `testdriver:find` skill, which contains the exact syntax, options, return shape, and gotchas for that one method. This keeps the agent accurate without bloating its base prompt, and lets clients load only the skills relevant to the current step.
42
+
43
+ ## Installation
44
+
45
+ Skills are installed by `testdriverai init` along with the [agent](/v7/ai/agent) and [MCP server](/v7/ai/mcp):
46
+
47
+ ```bash
48
+ npx testdriverai init
49
+ ```
50
+
51
+ They're written to the skills directory each client expects:
52
+
53
+ | Client | Skills location |
54
+ | --- | --- |
55
+ | Claude Code | `.claude/skills/<name>/SKILL.md` |
56
+ | Zed | `.agents/skills/<name>/SKILL.md` |
57
+ | Codex | referenced from `AGENTS.md` |
58
+ | VS Code ยท Cursor ยท Windsurf | folded into the agent rules/instructions |
59
+
60
+ Clients without a native skills concept (Cursor, VS Code, Windsurf) still get the agent definition, which references the same guidance inline.
61
+
62
+ ## Authoring & regenerating
63
+
64
+ Skills are **generated, not hand-edited** โ€” each is built from a `.mdx` page in the docs. Do not edit `SKILL.md` files directly (they carry a `DO NOT EDIT` marker). To change a skill, edit the corresponding documentation page and regenerate:
65
+
66
+ ```bash
67
+ node docs/_scripts/generate-skills.js
68
+ ```
69
+
70
+ ## Related
71
+
72
+ - [Agent](/v7/ai/agent) โ€” composes skills into working tests
73
+ - [MCP Server](/v7/ai/mcp) โ€” the tools the skills describe how to use
package/docs/v7/find.mdx CHANGED
@@ -402,6 +402,8 @@ await element.click();
402
402
 
403
403
  ## Cache Options
404
404
 
405
+ When a test completes successfully, the result of each `find()` is cached. On later runs, TestDriver reuses the cached match instead of making a fresh AI call, which significantly speeds up locating the same element. The cache lives in your [dashboard](https://console.testdriver.ai/cache) and is shared across runs โ€” see the [Cache](/v7/cache) page for how matching, thresholds, and invalidation work.
406
+
405
407
  Control caching behavior to optimize performance, especially when using dynamic variables in prompts.
406
408
 
407
409
  ### Custom Cache Key