testdriverai 7.11.281-test → 7.11.283-test
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/ai/skills/testdriver-quickstart/SKILL.md +4 -9
- package/docs/GITHUB_COMMENTS.md +0 -1
- package/docs/GITHUB_COMMENTS_ANNOUNCEMENT.md +0 -1
- package/docs/_data/examples-manifest.json +44 -44
- package/docs/_drafts/faq.mdx +0 -13
- package/docs/_drafts/troubleshooting.mdx +2 -3
- package/docs/agent.mdx +1 -1
- package/docs/ci-cd.mdx +2 -2
- package/docs/copilot/auto-healing.mdx +1 -1
- package/docs/debugging-with-screenshots.mdx +1 -1
- package/docs/docs.json +10 -13
- package/docs/examples/add-to-cart.mdx +1 -1
- package/docs/examples/assert.mdx +1 -1
- package/docs/examples/click-button.mdx +1 -1
- package/docs/examples/click-icon.mdx +1 -1
- package/docs/examples/element-not-found.mdx +1 -1
- package/docs/examples/findall-coffee-icons.mdx +1 -1
- package/docs/examples/installer.mdx +1 -1
- package/docs/examples/launch-vscode-linux.mdx +1 -1
- package/docs/examples/navigate-and-scroll.mdx +1 -1
- package/docs/examples/parse.mdx +1 -1
- package/docs/examples/press-keys.mdx +1 -1
- package/docs/examples/scroll.mdx +1 -1
- package/docs/examples/type.mdx +1 -1
- package/docs/generating-tests.mdx +1 -1
- package/docs/github-integration-setup.md +0 -1
- package/docs/hosted.mdx +5 -26
- package/docs/images/content/icons/claude.svg +1 -0
- package/docs/mcp-setup.mdx +10 -32
- package/docs/quickstart-claude.mdx +3 -55
- package/docs/quickstart-github.mdx +1 -1
- package/docs/quickstart-manual.mdx +2 -2
- package/docs/quickstart.mdx +7 -16
- package/docs/self-hosted.mdx +1 -13
- package/docs/v6/getting-started/vscode.mdx +2 -2
- package/docs/v6/guide/protips.mdx +2 -2
- package/docs/what-is-testdriver.mdx +1 -0
- package/mcp-server/dist/plan-gate.d.ts +24 -0
- package/mcp-server/dist/plan-gate.js +36 -0
- package/mcp-server/dist/server.mjs +24 -1
- package/mcp-server/src/plan-gate.test.ts +38 -0
- package/mcp-server/src/plan-gate.ts +43 -0
- package/mcp-server/src/server.ts +30 -3
- package/package.json +1 -1
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testdriver:quickstart
|
|
3
|
-
description: Start with TestDriver from GitHub, Claude Code,
|
|
3
|
+
description: Start with TestDriver from GitHub, Claude Code, any MCP client, or your own project.
|
|
4
4
|
---
|
|
5
5
|
<!-- Generated from quickstart.mdx. DO NOT EDIT. -->
|
|
6
6
|
|
|
7
|
-
TestDriver writes and runs end-to-end tests for web apps, desktop apps, and browser extensions.
|
|
8
|
-
|
|
9
|
-
Each test describes what a user sees and does in plain language. It runs on a real machine in the cloud.
|
|
7
|
+
TestDriver writes and runs end-to-end tests for web apps, desktop apps, and browser extensions. Each test describes what a user sees and does in plain language. It runs on a real machine in the cloud.
|
|
10
8
|
|
|
11
9
|
Choose where you want to start:
|
|
12
10
|
|
|
@@ -14,13 +12,10 @@ Choose where you want to start:
|
|
|
14
12
|
<Card title="Add to GitHub" icon="github" href="/quickstart-github" arrow horizontal>
|
|
15
13
|
An AI code reviewer that tests every pull request in a real sandbox.
|
|
16
14
|
</Card>
|
|
17
|
-
<Card title="Claude
|
|
15
|
+
<Card title="Add Claude Connector" icon="terminal" href="/quickstart-claude" arrow horizontal>
|
|
18
16
|
Install the connector to write and run tests from Claude Code.
|
|
19
17
|
</Card>
|
|
20
|
-
<Card title="
|
|
21
|
-
Install the extension to write tests and watch them run live.
|
|
22
|
-
</Card>
|
|
23
|
-
<Card title="Connect your AI client" icon="plug" href="/mcp-setup" arrow horizontal>
|
|
18
|
+
<Card title="Connect via MCP" icon="plug" href="/mcp-setup" arrow horizontal>
|
|
24
19
|
Add one URL to any MCP client, such as Claude or ChatGPT.
|
|
25
20
|
</Card>
|
|
26
21
|
<Card title="Set up a project" icon="wrench" href="/quickstart-manual" arrow horizontal>
|
package/docs/GITHUB_COMMENTS.md
CHANGED
|
@@ -327,4 +327,3 @@ const customMarkdown = markdown.replace(
|
|
|
327
327
|
|
|
328
328
|
- **Documentation:** [testdriver.ai/docs](https://testdriver.ai/docs)
|
|
329
329
|
- **Issues:** [github.com/testdriverai/testdriverai/issues](https://github.com/testdriverai/testdriverai/issues)
|
|
330
|
-
- **Discord:** Join our [community Discord](https://discord.gg/testdriver)
|
|
@@ -160,7 +160,6 @@ GITHUB_TOKEN=ghp_xxx GITHUB_PR_NUMBER=123 npm run test:sdk
|
|
|
160
160
|
|
|
161
161
|
- **Documentation:** [testdriver.ai/docs](https://testdriver.ai/docs)
|
|
162
162
|
- **Issues:** [GitHub Issues](https://github.com/testdriverai/testdriverai/issues)
|
|
163
|
-
- **Discord:** [Join our community](https://discord.gg/testdriver)
|
|
164
163
|
|
|
165
164
|
---
|
|
166
165
|
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
"$schema": "./examples-manifest.schema.json",
|
|
3
3
|
"examples": {
|
|
4
4
|
"assert.test.mjs": {
|
|
5
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
6
|
-
"lastUpdated": "2026-11-
|
|
5
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062870c121f49ee8fe63",
|
|
6
|
+
"lastUpdated": "2026-11-12T00:19:52.316Z"
|
|
7
7
|
},
|
|
8
8
|
"drag-and-drop.test.mjs": {
|
|
9
9
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
|
|
10
10
|
"lastUpdated": "2026-03-03T00:32:25.275Z"
|
|
11
11
|
},
|
|
12
12
|
"exec-pwsh.test.mjs": {
|
|
13
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
14
|
-
"lastUpdated": "2026-11-
|
|
13
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5061fb2a40e1e66d152f3",
|
|
14
|
+
"lastUpdated": "2026-11-12T00:19:55.922Z"
|
|
15
15
|
},
|
|
16
16
|
"match-image.test.mjs": {
|
|
17
17
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c8738c14b73310c783941d",
|
|
@@ -22,58 +22,58 @@
|
|
|
22
22
|
"lastUpdated": "2026-03-03T00:32:25.282Z"
|
|
23
23
|
},
|
|
24
24
|
"windows-installer.test.mjs": {
|
|
25
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
26
|
-
"lastUpdated": "2026-11-
|
|
25
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5061fb2a40e1e66d152f2",
|
|
26
|
+
"lastUpdated": "2026-11-12T00:20:24.338Z"
|
|
27
27
|
},
|
|
28
28
|
"exec-output.test.mjs": {
|
|
29
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
30
|
-
"lastUpdated": "2026-11-
|
|
29
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50621b2a40e1e66d152f6",
|
|
30
|
+
"lastUpdated": "2026-11-12T00:20:09.516Z"
|
|
31
31
|
},
|
|
32
32
|
"chrome-extension.test.mjs": {
|
|
33
33
|
"url": "https://console-test.testdriver.ai/runs/6a87988fb7cddebeab9bfbea/6a8798913062df0fb9b85592",
|
|
34
34
|
"lastUpdated": "2026-08-21T00:15:47.131Z"
|
|
35
35
|
},
|
|
36
36
|
"launch-vscode-linux.test.mjs": {
|
|
37
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
38
|
-
"lastUpdated": "2026-11-
|
|
37
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062a70c121f49ee8fe64",
|
|
38
|
+
"lastUpdated": "2026-11-12T00:20:20.640Z"
|
|
39
39
|
},
|
|
40
40
|
"installer.test.mjs": {
|
|
41
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
42
|
-
"lastUpdated": "2026-11-
|
|
41
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062070c121f49ee8fe5f",
|
|
42
|
+
"lastUpdated": "2026-11-12T00:19:43.746Z"
|
|
43
43
|
},
|
|
44
44
|
"type.test.mjs": {
|
|
45
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
46
|
-
"lastUpdated": "2026-11-
|
|
45
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50623b2a40e1e66d152f9",
|
|
46
|
+
"lastUpdated": "2026-11-12T00:20:10.854Z"
|
|
47
47
|
},
|
|
48
48
|
"press-keys.test.mjs": {
|
|
49
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
50
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
51
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
52
|
-
"lastUpdated": "2026-11-
|
|
49
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50624b2a40e1e66d152fb",
|
|
50
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6af50704b2a40e1e66d15339?share=L7V7QMBvXTIbH5FIHcbP4A",
|
|
51
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6af50704b2a40e1e66d15339?share=L7V7QMBvXTIbH5FIHcbP4A&embed=true",
|
|
52
|
+
"lastUpdated": "2026-11-12T00:23:38.137Z"
|
|
53
53
|
},
|
|
54
54
|
"scroll.test.mjs": {
|
|
55
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
56
|
-
"lastUpdated": "2026-11-
|
|
55
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062770c121f49ee8fe62",
|
|
56
|
+
"lastUpdated": "2026-11-12T00:20:03.007Z"
|
|
57
57
|
},
|
|
58
58
|
"scroll-until-image.test.mjs": {
|
|
59
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
60
|
-
"lastUpdated": "2026-11-
|
|
59
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50624b2a40e1e66d152fc",
|
|
60
|
+
"lastUpdated": "2026-11-12T00:19:48.969Z"
|
|
61
61
|
},
|
|
62
62
|
"prompt.test.mjs": {
|
|
63
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
64
|
-
"lastUpdated": "2026-11-
|
|
63
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50625b2a40e1e66d152fd",
|
|
64
|
+
"lastUpdated": "2026-11-12T00:20:16.069Z"
|
|
65
65
|
},
|
|
66
66
|
"focus-window.test.mjs": {
|
|
67
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
68
|
-
"lastUpdated": "2026-11-
|
|
67
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50625b2a40e1e66d152fe",
|
|
68
|
+
"lastUpdated": "2026-11-12T00:20:17.902Z"
|
|
69
69
|
},
|
|
70
70
|
"captcha-api.test.mjs": {
|
|
71
71
|
"url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
|
|
72
72
|
"lastUpdated": "2026-02-13T19:55:05.951Z"
|
|
73
73
|
},
|
|
74
74
|
"element-not-found.test.mjs": {
|
|
75
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
76
|
-
"lastUpdated": "2026-11-
|
|
75
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50628b2a40e1e66d152ff",
|
|
76
|
+
"lastUpdated": "2026-11-12T00:20:04.388Z"
|
|
77
77
|
},
|
|
78
78
|
"formatted-logging.test.mjs": {
|
|
79
79
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c873a714b73310c7839450",
|
|
@@ -122,12 +122,12 @@
|
|
|
122
122
|
"lastUpdated": "2026-02-13T19:55:05.953Z"
|
|
123
123
|
},
|
|
124
124
|
"findall-coffee-icons.test.mjs": {
|
|
125
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
126
|
-
"lastUpdated": "2026-11-
|
|
125
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50624b2a40e1e66d152fa",
|
|
126
|
+
"lastUpdated": "2026-11-12T00:19:47.954Z"
|
|
127
127
|
},
|
|
128
128
|
"parse.test.mjs": {
|
|
129
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
130
|
-
"lastUpdated": "2026-11-
|
|
129
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062cb2a40e1e66d15301",
|
|
130
|
+
"lastUpdated": "2026-11-12T00:20:07.165Z"
|
|
131
131
|
},
|
|
132
132
|
"flake-diffthreshold-001.test.mjs": {
|
|
133
133
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
|
|
@@ -166,24 +166,24 @@
|
|
|
166
166
|
"lastUpdated": "2026-03-30T00:35:14.437Z"
|
|
167
167
|
},
|
|
168
168
|
"click-icon.test.mjs": {
|
|
169
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
170
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
171
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
172
|
-
"lastUpdated": "2026-11-
|
|
169
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50622b2a40e1e66d152f8",
|
|
170
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6af506d570c121f49ee8fe96?share=Z9zyWtMyrirP0AUn5Wiw",
|
|
171
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6af506d570c121f49ee8fe96?share=Z9zyWtMyrirP0AUn5Wiw&embed=true",
|
|
172
|
+
"lastUpdated": "2026-11-12T00:22:51.095Z"
|
|
173
173
|
},
|
|
174
174
|
"click-button.test.mjs": {
|
|
175
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
176
|
-
"lastUpdated": "2026-11-
|
|
175
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50628b2a40e1e66d15300",
|
|
176
|
+
"lastUpdated": "2026-11-12T00:20:18.608Z"
|
|
177
177
|
},
|
|
178
178
|
"add-to-cart.test.mjs": {
|
|
179
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
180
|
-
"lastUpdated": "2026-11-
|
|
179
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af5062170c121f49ee8fe60",
|
|
180
|
+
"lastUpdated": "2026-11-12T00:19:44.720Z"
|
|
181
181
|
},
|
|
182
182
|
"navigate-and-scroll.test.mjs": {
|
|
183
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
184
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
185
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
186
|
-
"lastUpdated": "2026-11-
|
|
183
|
+
"url": "https://console-test.testdriver.ai/runs/6af5061eb2a40e1e66d152f0/6af50622b2a40e1e66d152f7",
|
|
184
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6af5070e70c121f49ee8fea5?share=pnoT2NOqsmVPySORNOVw",
|
|
185
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6af5070e70c121f49ee8fea5?share=pnoT2NOqsmVPySORNOVw&embed=true",
|
|
186
|
+
"lastUpdated": "2026-11-12T00:23:48.268Z"
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
}
|
package/docs/_drafts/faq.mdx
CHANGED
|
@@ -341,7 +341,6 @@ Yes! Enterprise plans include self-hosting options. See [self-hosting guide](/gu
|
|
|
341
341
|
|
|
342
342
|
### Where can I get help?
|
|
343
343
|
|
|
344
|
-
- **Discord** - [discord.com/invite/cWDFW8DzPm](https://discord.com/invite/cWDFW8DzPm)
|
|
345
344
|
- **Docs** - [docs.testdriver.ai](https://docs.testdriver.ai)
|
|
346
345
|
- **GitHub** - [github.com/testdriverai/testdriverai](https://github.com/testdriverai/testdriverai)
|
|
347
346
|
- **Email** - support@testdriver.ai
|
|
@@ -356,14 +355,6 @@ Yes! Enterprise plans include self-hosting options. See [self-hosting guide](/gu
|
|
|
356
355
|
- Dashcam URL
|
|
357
356
|
- Minimal reproduction
|
|
358
357
|
|
|
359
|
-
### Is there a community?
|
|
360
|
-
|
|
361
|
-
Yes! Join our [Discord](https://discord.com/invite/cWDFW8DzPm) to:
|
|
362
|
-
- Ask questions
|
|
363
|
-
- Share tips
|
|
364
|
-
- See examples
|
|
365
|
-
- Get updates
|
|
366
|
-
|
|
367
358
|
## See Also
|
|
368
359
|
|
|
369
360
|
<CardGroup cols={2}>
|
|
@@ -378,8 +369,4 @@ Yes! Join our [Discord](https://discord.com/invite/cWDFW8DzPm) to:
|
|
|
378
369
|
<Card title="Best Practices" icon="star" href="/guides/best-practices">
|
|
379
370
|
Testing patterns
|
|
380
371
|
</Card>
|
|
381
|
-
|
|
382
|
-
<Card title="Discord Community" icon="discord" href="https://discord.com/invite/cWDFW8DzPm">
|
|
383
|
-
Join the community
|
|
384
|
-
</Card>
|
|
385
372
|
</CardGroup>
|
|
@@ -487,9 +487,8 @@ If issues persist:
|
|
|
487
487
|
|
|
488
488
|
1. **Check Dashcam recording** - See what actually happened
|
|
489
489
|
2. **Enable debug logging** - `verbosity: 2`
|
|
490
|
-
3. **
|
|
491
|
-
4. **
|
|
492
|
-
5. **Report bug** - [github.com/testdriverai/testdriverai/issues](https://github.com/testdriverai/testdriverai/issues)
|
|
490
|
+
3. **Check docs** - [docs.testdriver.ai](https://docs.testdriver.ai)
|
|
491
|
+
4. **Report bug** - [github.com/testdriverai/testdriverai/issues](https://github.com/testdriverai/testdriverai/issues)
|
|
493
492
|
|
|
494
493
|
### Information to Include
|
|
495
494
|
|
package/docs/agent.mdx
CHANGED
package/docs/ci-cd.mdx
CHANGED
|
@@ -289,8 +289,8 @@ TESTDRIVER_RUN_URL=https://console.testdriver.ai/runs/abc123
|
|
|
289
289
|
<Card title="Add TestDriver to GitHub" icon="github" href="/quickstart-github">
|
|
290
290
|
Get an AI code review on every pull request.
|
|
291
291
|
</Card>
|
|
292
|
-
<Card title="Hosted
|
|
293
|
-
|
|
292
|
+
<Card title="Hosted sandboxes" icon="cloud" href="/hosted">
|
|
293
|
+
How testing minutes and concurrency work.
|
|
294
294
|
</Card>
|
|
295
295
|
<Card title="Speed up reruns with caching" icon="brain" href="/caching">
|
|
296
296
|
Reuse element locations from passing runs.
|
|
@@ -100,7 +100,7 @@ The agent then:
|
|
|
100
100
|
2. Updates the test.
|
|
101
101
|
3. Opens a pull request with the fix for you to review.
|
|
102
102
|
|
|
103
|
-
You can make the same request from your editor through the [Claude Code
|
|
103
|
+
You can make the same request from your editor through the [Claude Code connector](/quickstart-claude) or the [VS Code extension](/quickstart-vscode).
|
|
104
104
|
|
|
105
105
|
<Warning>
|
|
106
106
|
Review the agent's fix before you merge it. A test updated to pass may no longer check what you meant. Confirm that the assertions still describe the behavior you want.
|
|
@@ -59,7 +59,7 @@ To save a screenshot at a specific point instead, call [`screenshot()`](/screens
|
|
|
59
59
|
|
|
60
60
|
## Inspect screenshots with your AI assistant
|
|
61
61
|
|
|
62
|
-
With the [
|
|
62
|
+
With the [TestDriver MCP server](/mcp-setup), your AI assistant can list and view these screenshots. Ask in plain language:
|
|
63
63
|
|
|
64
64
|
```text
|
|
65
65
|
The login test failed at line 42. Show me the screenshots from just before and after that line.
|
package/docs/docs.json
CHANGED
|
@@ -16,18 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"group": "Overview",
|
|
18
18
|
"pages": [
|
|
19
|
-
|
|
20
|
-
"group": "Get started",
|
|
21
|
-
"icon": "rocket",
|
|
22
|
-
"pages": [
|
|
23
|
-
"/quickstart",
|
|
24
|
-
"/quickstart-github",
|
|
25
|
-
"/quickstart-claude",
|
|
26
|
-
"/quickstart-vscode",
|
|
27
|
-
"/mcp-setup"
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
|
-
"/agent",
|
|
19
|
+
"/quickstart",
|
|
31
20
|
"/what-is-testdriver",
|
|
32
21
|
{
|
|
33
22
|
"group": "Examples",
|
|
@@ -50,6 +39,14 @@
|
|
|
50
39
|
}
|
|
51
40
|
]
|
|
52
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"group": "Quickstart",
|
|
44
|
+
"pages": [
|
|
45
|
+
"/agent",
|
|
46
|
+
"/quickstart-claude",
|
|
47
|
+
"/mcp-setup"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
53
50
|
{
|
|
54
51
|
"group": "Using TestDriver",
|
|
55
52
|
"pages": [
|
|
@@ -145,6 +142,7 @@
|
|
|
145
142
|
"/reusable-code",
|
|
146
143
|
"/cache",
|
|
147
144
|
"/customizing-devices",
|
|
145
|
+
"/quickstart-vscode",
|
|
148
146
|
"/guide/best-practices-polling"
|
|
149
147
|
]
|
|
150
148
|
}
|
|
@@ -173,7 +171,6 @@
|
|
|
173
171
|
"socials": {
|
|
174
172
|
"x": "https://x.com/testdriverai",
|
|
175
173
|
"github": "https://github.com/testdriverai",
|
|
176
|
-
"discord": "https://discord.com/invite/cWDFW8DzPm",
|
|
177
174
|
"youtube": "https://www.youtube.com/@testdriverai"
|
|
178
175
|
}
|
|
179
176
|
},
|
|
@@ -12,7 +12,7 @@ This test logs in to a demo store, adds a product to the cart, and asserts that
|
|
|
12
12
|
|
|
13
13
|
{/* add-to-cart.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062170c121f49ee8fe60/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="600"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/assert.mdx
CHANGED
|
@@ -12,7 +12,7 @@ This test opens a login page in Chrome, saves a screenshot, and asserts that the
|
|
|
12
12
|
|
|
13
13
|
{/* assert.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062870c121f49ee8fe63/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test clicks **Sign In** on an empty login form and asserts that a "fields r
|
|
|
12
12
|
|
|
13
13
|
{/* click-button.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af50628b2a40e1e66d15300/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test logs in to a demo store, clicks the shopping cart icon, and asserts th
|
|
|
12
12
|
|
|
13
13
|
{/* click-icon.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6af506d570c121f49ee8fe96?share=Z9zyWtMyrirP0AUn5Wiw&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test looks for an element that isn't on the page. Use this pattern when an
|
|
|
12
12
|
|
|
13
13
|
{/* element-not-found.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af50628b2a40e1e66d152ff/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test uses `findAll()` to find every coffee cup in an icon grid, clicks each
|
|
|
12
12
|
|
|
13
13
|
{/* findall-coffee-icons.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af50624b2a40e1e66d152fa/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="600"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -16,7 +16,7 @@ This file has three `provision.installer()` tests in a Linux sandbox:
|
|
|
16
16
|
|
|
17
17
|
{/* installer.test.mjs output */}
|
|
18
18
|
<iframe
|
|
19
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
19
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062070c121f49ee8fe5f/replay"
|
|
20
20
|
width="100%"
|
|
21
21
|
height="390"
|
|
22
22
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test launches VS Code with `provision.vscode()` and waits for its window to
|
|
|
12
12
|
|
|
13
13
|
{/* launch-vscode-linux.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062a70c121f49ee8fe64/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test types a URL into Chrome's address bar, scrolls down, and asserts that
|
|
|
12
12
|
|
|
13
13
|
{/* navigate-and-scroll.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6af5070e70c121f49ee8fea5?share=pnoT2NOqsmVPySORNOVw&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/parse.mdx
CHANGED
|
@@ -12,7 +12,7 @@ This test opens Airbnb in Chrome and calls `parse()` to detect every UI element
|
|
|
12
12
|
|
|
13
13
|
{/* parse.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062cb2a40e1e66d15301/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="600"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ This test drives Chrome with keyboard shortcuts to open tabs and DevTools, then
|
|
|
12
12
|
|
|
13
13
|
{/* press-keys.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6af50704b2a40e1e66d15339?share=L7V7QMBvXTIbH5FIHcbP4A&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/scroll.mdx
CHANGED
|
@@ -12,7 +12,7 @@ This test opens a page, scrolls down, and asserts that the page heading is no lo
|
|
|
12
12
|
|
|
13
13
|
{/* scroll.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af5062770c121f49ee8fe62/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
package/docs/examples/type.mdx
CHANGED
|
@@ -12,7 +12,7 @@ This file has two login form tests. One types a username and checks the field. T
|
|
|
12
12
|
|
|
13
13
|
{/* type.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6af50623b2a40e1e66d152f9/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -15,7 +15,7 @@ Describe a flow in plain language, and the TestDriver agent writes the test for
|
|
|
15
15
|
|
|
16
16
|
Install TestDriver in an AI coding assistant that can edit files in your repository:
|
|
17
17
|
|
|
18
|
-
- **Claude Code:** [install the
|
|
18
|
+
- **Claude Code:** [install the connector](/quickstart-claude).
|
|
19
19
|
- **VS Code or Cursor:** [install the extension](/quickstart-vscode).
|
|
20
20
|
- **Any other assistant:** run `npx testdriverai init` in your repository and choose your assistant. See [Set up a project](/quickstart-manual).
|
|
21
21
|
|
|
@@ -262,5 +262,4 @@ The GIF replays are automatically embedded so reviewers can see what happened wi
|
|
|
262
262
|
## Getting Help
|
|
263
263
|
|
|
264
264
|
- 📚 [Full Documentation](https://docs.testdriver.ai)
|
|
265
|
-
- 💬 [Discord Community](https://discord.gg/testdriver)
|
|
266
265
|
- 🐛 [Report Issues](https://github.com/testdriverai/testdriverai/issues)
|
package/docs/hosted.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Hosted
|
|
2
|
+
title: "Hosted sandboxes"
|
|
3
3
|
sidebarTitle: "Hosted"
|
|
4
|
-
description: "Run tests on TestDriver's cloud sandboxes,
|
|
4
|
+
description: "Run tests on TestDriver's cloud sandboxes, with no infrastructure to manage."
|
|
5
5
|
icon: "cloud"
|
|
6
6
|
mode: "wide"
|
|
7
7
|
---
|
|
@@ -11,32 +11,11 @@ import { EnterpriseBadge, EnterpriseCta } from '/snippets/enterprise.mdx';
|
|
|
11
11
|
By default, your tests run on TestDriver's cloud. Each test gets a fresh sandbox, and you don't manage any machines.
|
|
12
12
|
|
|
13
13
|
- Hosted is the default. When you follow the [Quickstart](/quickstart), you're using it.
|
|
14
|
-
-
|
|
14
|
+
- Usage is measured in **testing minutes**: the time a sandbox is running.
|
|
15
15
|
- Your plan sets how many sandboxes can run at the same time.
|
|
16
16
|
|
|
17
|
-
## Plans
|
|
18
|
-
|
|
19
|
-
| | Free | Pro | Team |
|
|
20
|
-
| --- | --- | --- | --- |
|
|
21
|
-
| **Price** | $0 | $24 per seat per month | $600 per month |
|
|
22
|
-
| **Testing minutes included** | 30 | 360 | 10,000 |
|
|
23
|
-
| **Extra minutes** | Not available | $0.08 per minute | $0.08 per minute |
|
|
24
|
-
| **Sandboxes at once** | 2 | 4 | 8 |
|
|
25
|
-
| **Users** | Unlimited | One per seat | 5 |
|
|
26
|
-
| **Test analytics** | No | Yes | Yes |
|
|
27
|
-
| **CPU, RAM, and network profiles** | No | Yes | Yes |
|
|
28
|
-
| **Support** | Community | Community | Private |
|
|
29
|
-
|
|
30
17
|
<EnterpriseCta feature="Windows sandboxes, custom screen resolutions, and self-hosting" plural />
|
|
31
18
|
|
|
32
|
-
Pro starts with a 14-day free trial. Start a plan from the [TestDriver console](https://console.testdriver.ai/checkout/pro).
|
|
33
|
-
|
|
34
|
-
On the Free plan, new sandboxes are refused once you've used your 30 minutes. Upgrade to keep testing.
|
|
35
|
-
|
|
36
|
-
<Note>
|
|
37
|
-
If you install TestDriver from GitHub Marketplace, GitHub bills you instead. The **Open Source** plan is free with 300 testing minutes per month. The **Team** plan is $20 per seat per month with 120 testing minutes per seat.
|
|
38
|
-
</Note>
|
|
39
|
-
|
|
40
19
|
## How testing minutes are counted
|
|
41
20
|
|
|
42
21
|
Testing minutes run while a sandbox is running. That includes sandboxes started by:
|
|
@@ -70,10 +49,10 @@ concurrency:
|
|
|
70
49
|
|
|
71
50
|
Consider [self-hosting](/self-hosted) when you need to:
|
|
72
51
|
|
|
73
|
-
- Run tests on your own infrastructure
|
|
52
|
+
- Run tests on your own infrastructure.
|
|
74
53
|
- Use your own AI provider keys.
|
|
75
54
|
- Run custom VM images with your software preinstalled.
|
|
76
|
-
- Run more tests in parallel than
|
|
55
|
+
- Run more tests in parallel than hosted allows.
|
|
77
56
|
|
|
78
57
|
## Next steps
|
|
79
58
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#D97757"><path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>
|
package/docs/mcp-setup.mdx
CHANGED
|
@@ -20,7 +20,7 @@ https://mcp.testdriver.ai/mcp
|
|
|
20
20
|
## Before you begin
|
|
21
21
|
|
|
22
22
|
- **An MCP client** that supports remote servers, such as Claude, Claude Code, Cursor, VS Code, or ChatGPT.
|
|
23
|
-
- **A TestDriver account.** [Sign up at console.testdriver.ai](https://console.testdriver.ai).
|
|
23
|
+
- **A TestDriver account.** [Sign up at console.testdriver.ai](https://console.testdriver.ai).
|
|
24
24
|
|
|
25
25
|
## Add the server
|
|
26
26
|
|
|
@@ -133,35 +133,6 @@ Each action also returns the SDK code for that step. The agent uses it to assemb
|
|
|
133
133
|
|
|
134
134
|
To turn that test into a file you run locally and in CI, [set up a project](/quickstart-manual).
|
|
135
135
|
|
|
136
|
-
## Run the server locally
|
|
137
|
-
|
|
138
|
-
Run the server as a local stdio process for headless automation, or to authenticate with an API key instead of a browser sign-in:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
npx -p testdriverai testdriverai-mcp
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
It reads your API key from the `TD_API_KEY` environment variable. Create a key at [console.testdriver.ai/team/api-keys](https://console.testdriver.ai/team/api-keys).
|
|
145
|
-
|
|
146
|
-
Then configure your client to start the server:
|
|
147
|
-
|
|
148
|
-
```json .mcp.json
|
|
149
|
-
{
|
|
150
|
-
"mcpServers": {
|
|
151
|
-
"testdriver": {
|
|
152
|
-
"type": "stdio",
|
|
153
|
-
"command": "npx",
|
|
154
|
-
"args": ["-p", "testdriverai", "testdriverai-mcp"],
|
|
155
|
-
"env": { "TD_API_KEY": "${TD_API_KEY}" }
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
<Tip>
|
|
162
|
-
`npx testdriverai init` writes this config for your client, along with the TestDriver agent, skills, an example test, and a GitHub Actions workflow. See [Set up a project](/quickstart-manual).
|
|
163
|
-
</Tip>
|
|
164
|
-
|
|
165
136
|
## Troubleshooting
|
|
166
137
|
|
|
167
138
|
<AccordionGroup>
|
|
@@ -174,7 +145,14 @@ Then configure your client to start the server:
|
|
|
174
145
|
|
|
175
146
|
Your client needs to support OAuth 2.1 with Dynamic Client Registration.
|
|
176
147
|
|
|
177
|
-
Clients that only support local (stdio) servers can't connect to the hosted URL.
|
|
148
|
+
Clients that only support local (stdio) servers can't connect to the hosted URL.
|
|
149
|
+
|
|
150
|
+
</Accordion>
|
|
151
|
+
<Accordion title="The agent says an upgrade is required">
|
|
152
|
+
|
|
153
|
+
Your team's plan doesn't include sandbox sessions. You can sign in and connect, but `session_start` returns an upgrade link instead of a sandbox.
|
|
154
|
+
|
|
155
|
+
Follow the link, then ask the agent to try again. You don't need to reconnect.
|
|
178
156
|
|
|
179
157
|
</Accordion>
|
|
180
158
|
<Accordion title="The sandbox expired in the middle of a session">
|
|
@@ -186,7 +164,7 @@ Then configure your client to start the server:
|
|
|
186
164
|
</Accordion>
|
|
187
165
|
<Accordion title="I ran out of testing minutes">
|
|
188
166
|
|
|
189
|
-
Sandbox time counts against your plan's testing minutes. Check your usage
|
|
167
|
+
Sandbox time counts against your plan's testing minutes. Check your usage at [console.testdriver.ai/usage](https://console.testdriver.ai/usage).
|
|
190
168
|
|
|
191
169
|
</Accordion>
|
|
192
170
|
</AccordionGroup>
|
|
@@ -12,7 +12,7 @@ The connector is the fastest way to get started: one click, a browser sign-in, a
|
|
|
12
12
|
## Before you begin
|
|
13
13
|
|
|
14
14
|
- **[Claude Code](https://code.claude.com)**, signed in with the same account you use on claude.ai.
|
|
15
|
-
- **A TestDriver account.** [Sign up at console.testdriver.ai](https://console.testdriver.ai).
|
|
15
|
+
- **A TestDriver account.** [Sign up at console.testdriver.ai](https://console.testdriver.ai).
|
|
16
16
|
|
|
17
17
|
<Steps>
|
|
18
18
|
<Step title="Install the connector">
|
|
@@ -41,61 +41,9 @@ The connector is the fastest way to get started: one click, a browser sign-in, a
|
|
|
41
41
|
|
|
42
42
|
Claude starts a sandbox, performs each step, and gets a screenshot after every action. Each tool call also returns the SDK code for that step, which Claude assembles into a runnable [Vitest](https://vitest.dev) test.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</Step>
|
|
47
|
-
</Steps>
|
|
48
|
-
|
|
49
|
-
## Alternative: install the plugin
|
|
50
|
-
|
|
51
|
-
Prefer everything to run locally, or want the `testdriver` agent and skills bundled in? Install the TestDriver plugin instead. It bundles three things:
|
|
52
|
-
|
|
53
|
-
- The TestDriver MCP server, run locally over stdio
|
|
54
|
-
- The `testdriver` agent, which builds tests step by step
|
|
55
|
-
- Skills that teach Claude the TestDriver SDK
|
|
56
|
-
|
|
57
|
-
The plugin requires **Node.js 20.19 or later** and a **TestDriver API key**. Create one at [console.testdriver.ai/team/api-keys](https://console.testdriver.ai/team/api-keys).
|
|
58
|
-
|
|
59
|
-
<Steps>
|
|
60
|
-
<Step title="Set your API key">
|
|
61
|
-
|
|
62
|
-
The plugin's MCP server reads your key from the `TD_API_KEY` environment variable. Add it to your shell profile (such as `~/.zshrc`) so every Claude Code session can use it:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
export TD_API_KEY="your_api_key"
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Open a new terminal, or run `source ~/.zshrc`, before you start Claude Code.
|
|
69
|
-
|
|
70
|
-
</Step>
|
|
71
|
-
<Step title="Install the plugin">
|
|
72
|
-
|
|
73
|
-
TestDriver is a community plugin. In Claude Code, run `/plugin`, open the **Discover** tab, search for **TestDriver**, and install it.
|
|
44
|
+
If Claude replies with an upgrade link instead, your team's plan doesn't include sandbox sessions. Follow the link, then ask again.
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```text
|
|
78
|
-
/plugin marketplace add testdriverai/testdriverai
|
|
79
|
-
/plugin install testdriver@testdriver
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
</Step>
|
|
83
|
-
<Step title="Check the connection">
|
|
84
|
-
|
|
85
|
-
Run `/mcp` in Claude Code. The `testdriver` server should be listed as connected.
|
|
86
|
-
|
|
87
|
-
If it isn't, check that `TD_API_KEY` is set in the terminal you started Claude Code from.
|
|
88
|
-
|
|
89
|
-
</Step>
|
|
90
|
-
<Step title="Write your first test">
|
|
91
|
-
|
|
92
|
-
Ask for the TestDriver agent by name:
|
|
93
|
-
|
|
94
|
-
```text
|
|
95
|
-
Use the testdriver agent to write a test that logs in to https://testdriver-sandbox.vercel.app/login and adds an item to the cart.
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The agent starts a sandbox and performs each step. It writes the code into a test file under `tests/`, then runs it with Vitest until it passes.
|
|
46
|
+
To save and run those tests locally and in CI, [set up a project](/quickstart-manual).
|
|
99
47
|
|
|
100
48
|
</Step>
|
|
101
49
|
</Steps>
|
|
@@ -17,7 +17,7 @@ There's nothing to install locally.
|
|
|
17
17
|
|
|
18
18
|
- **A GitHub repository** where you can install apps (you own it, or you're an admin of its organization).
|
|
19
19
|
- **A TestDriver account.** You create one or sign in during installation.
|
|
20
|
-
- **A
|
|
20
|
+
- **A plan that includes automatic reviews.** Without one, the agent still reviews a pull request when you @mention it.
|
|
21
21
|
|
|
22
22
|
<Steps>
|
|
23
23
|
<Step title="Install the GitHub app">
|
|
@@ -43,7 +43,7 @@ npx vitest run
|
|
|
43
43
|
<Steps>
|
|
44
44
|
<Step title="Add your API key">
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Copy your API key from [console.testdriver.ai/team/api-keys](https://console.testdriver.ai/team/api-keys).
|
|
47
47
|
|
|
48
48
|
Save the key in a `.env` file at your project root. The SDK loads it automatically:
|
|
49
49
|
|
|
@@ -142,7 +142,7 @@ To write tests with an AI assistant, add the TestDriver agent and MCP server to
|
|
|
142
142
|
npx testdriverai init --client cursor,claude-code,vscode --no-sample-test
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Your existing config and tests stay as they are. For Claude Code or VS Code, you can use the [
|
|
145
|
+
Your existing config and tests stay as they are. For Claude Code or VS Code, you can use the [connector](/quickstart-claude) or [extension](/quickstart-vscode) instead.
|
|
146
146
|
|
|
147
147
|
## Next steps
|
|
148
148
|
|
package/docs/quickstart.mdx
CHANGED
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Quickstart"
|
|
3
3
|
sidebarTitle: "Quickstart"
|
|
4
|
-
description: "Start with TestDriver from GitHub, Claude Code,
|
|
4
|
+
description: "Start with TestDriver from GitHub, Claude Code, any MCP client, or your own project."
|
|
5
5
|
icon: "rocket"
|
|
6
6
|
mode: "wide"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
TestDriver writes and runs end-to-end tests for web apps, desktop apps, and browser extensions.
|
|
10
|
-
|
|
11
|
-
Each test describes what a user sees and does in plain language. It runs on a real machine in the cloud.
|
|
9
|
+
TestDriver writes and runs end-to-end tests for web apps, desktop apps, and browser extensions. Each test describes what a user sees and does in plain language. It runs on a real machine in the cloud.
|
|
12
10
|
|
|
13
11
|
Choose where you want to start:
|
|
14
12
|
|
|
15
13
|
<CardGroup cols={1}>
|
|
16
|
-
<Card title="Add to GitHub" icon="github" href="/
|
|
14
|
+
<Card title="Add to GitHub" icon="github" href="/agent" arrow horizontal>
|
|
17
15
|
An AI code reviewer that tests every pull request in a real sandbox.
|
|
18
16
|
</Card>
|
|
19
|
-
<Card title="Claude
|
|
17
|
+
<Card title="Add Claude Connector" icon="/images/content/icons/claude.svg" href="/quickstart-claude" arrow horizontal>
|
|
20
18
|
Install the connector to write and run tests from Claude Code.
|
|
21
19
|
</Card>
|
|
22
|
-
<Card title="
|
|
23
|
-
Install the extension to write tests and watch them run live.
|
|
24
|
-
</Card>
|
|
25
|
-
<Card title="Connect your AI client" icon="plug" href="/mcp-setup" arrow horizontal>
|
|
20
|
+
<Card title="Connect via MCP" icon="plug" href="/mcp-setup" arrow horizontal>
|
|
26
21
|
Add one URL to any MCP client, such as Claude or ChatGPT.
|
|
27
22
|
</Card>
|
|
28
|
-
<Card title="Set up
|
|
23
|
+
<Card title="Set up via CLI" icon="terminal" href="/quickstart-manual" arrow horizontal>
|
|
29
24
|
Write and run Vitest tests in your own repository.
|
|
30
25
|
</Card>
|
|
31
26
|
</CardGroup>
|
|
32
27
|
|
|
33
28
|
Not sure? Start with GitHub. Your next pull request gets a review without any setup.
|
|
34
29
|
|
|
35
|
-
Set up
|
|
36
|
-
|
|
37
|
-
<Tip>
|
|
38
|
-
Questions along the way? Ask in the [TestDriver Discord](https://discord.com/invite/cWDFW8DzPm).
|
|
39
|
-
</Tip>
|
|
30
|
+
Set up via CLI when you want to write and run tests yourself.
|
package/docs/self-hosted.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Self-hosted (Enterprise)"
|
|
3
3
|
sidebarTitle: "Self-hosted"
|
|
4
|
-
description: "Run TestDriver sandboxes on your own infrastructure
|
|
4
|
+
description: "Run TestDriver sandboxes on your own infrastructure instead of TestDriver's cloud."
|
|
5
5
|
icon: "server"
|
|
6
6
|
mode: "wide"
|
|
7
7
|
---
|
|
@@ -12,7 +12,6 @@ On the Enterprise plan, your tests run on machines in your own infrastructure in
|
|
|
12
12
|
|
|
13
13
|
<EnterpriseCta feature="Self-hosting" />
|
|
14
14
|
|
|
15
|
-
- You pay a flat license fee per parallel test, with no testing minutes to meter.
|
|
16
15
|
- Your app and test data stay in your environment.
|
|
17
16
|
- You use your own AI provider key and your own VM images.
|
|
18
17
|
|
|
@@ -74,21 +73,10 @@ Self-hosted sandboxes run in your account, so you can connect to a running insta
|
|
|
74
73
|
|
|
75
74
|
For regulated environments, TestDriver can also deploy the API and console inside your network, on AWS, Azure, GCP, or on-premises. [Contact TestDriver](https://testdriver.ai/enterprise) to discuss it.
|
|
76
75
|
|
|
77
|
-
## Pricing
|
|
78
|
-
|
|
79
|
-
- A flat monthly fee that includes 2 parallel tests and 5 seats.
|
|
80
|
-
- Unlimited testing, with no per-minute fees.
|
|
81
|
-
- Extra parallel licenses are $250 per month each. Extra seats are $100 per month each.
|
|
82
|
-
- Setup help and support from TestDriver engineers in a shared Slack or Teams channel.
|
|
83
|
-
- A 30-day pilot with a money-back guarantee.
|
|
84
|
-
|
|
85
|
-
See [testdriver.ai/enterprise](https://testdriver.ai/enterprise) for current pricing and terms.
|
|
86
|
-
|
|
87
76
|
## Hosted or self-hosted
|
|
88
77
|
|
|
89
78
|
| | Hosted | Self-hosted |
|
|
90
79
|
| --- | --- | --- |
|
|
91
|
-
| **Billing** | Testing minutes | Flat license fee |
|
|
92
80
|
| **Sandboxes** | TestDriver's cloud | Your AWS account or machines |
|
|
93
81
|
| **AI key** | TestDriver's | Your own Gemini key |
|
|
94
82
|
| **VM images** | Standard | Your own |
|
|
@@ -25,8 +25,8 @@ The TestDriver VS Code extension is a powerful tool that integrates TestDriver's
|
|
|
25
25
|
|
|
26
26
|
<Tip>
|
|
27
27
|
The TestDriver VS Code Extension is currently in beta. If you encounter any
|
|
28
|
-
issues or have feedback, please let us know [
|
|
29
|
-
|
|
28
|
+
issues or have feedback, please let us know [on
|
|
29
|
+
GitHub](https://github.com/testdriverai/testdriverai/issues)
|
|
30
30
|
</Tip>
|
|
31
31
|
|
|
32
32
|
<Steps>
|
|
@@ -30,14 +30,14 @@ Assertions use the current state of the page to determine if the step passes or
|
|
|
30
30
|
|
|
31
31
|
## Reporting issues
|
|
32
32
|
|
|
33
|
-
If you encounter any issues while using TestDriver, please report them to us. You can do this by creating a new issue
|
|
33
|
+
If you encounter any issues while using TestDriver, please report them to us. You can do this by creating a new issue on [GitHub](https://github.com/testdriverai/testdriverai/issues). We appreciate your feedback and will work to resolve any problems as quickly as possible.
|
|
34
34
|
|
|
35
35
|
<Card
|
|
36
36
|
title="Report an Issue"
|
|
37
37
|
icon="arrow-turn-down-right"
|
|
38
38
|
iconType="duotone"
|
|
39
39
|
horizontal
|
|
40
|
-
href="https://
|
|
40
|
+
href="https://github.com/testdriverai/testdriverai/issues"
|
|
41
41
|
>
|
|
42
42
|
We'd love to hear from you!
|
|
43
43
|
</Card>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-gate.ts
|
|
3
|
+
*
|
|
4
|
+
* The hosted connector's paywall. Anyone can sign in and connect, but a team on
|
|
5
|
+
* the free plan can't start a sandbox: `session_start` returns an upgrade
|
|
6
|
+
* message with a checkout link instead. This mirrors the console, where a free
|
|
7
|
+
* team meets the same wall when it tries to reveal its API key.
|
|
8
|
+
*
|
|
9
|
+
* Only the hosted OAuth transport applies it. A local stdio server
|
|
10
|
+
* authenticates with an API key, which a team can only get once it has
|
|
11
|
+
* upgraded.
|
|
12
|
+
*/
|
|
13
|
+
/** Where a free team goes to start a trial. Overridable for staging. */
|
|
14
|
+
export declare function upgradeUrl(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether a team on `plan` must upgrade before starting a sandbox.
|
|
17
|
+
*
|
|
18
|
+
* `undefined` means the backend sent no plan at all (an older API, or a
|
|
19
|
+
* transport that never exchanged a token), so there is nothing to gate on. An
|
|
20
|
+
* empty or null plan is a brand-new signup, which is on free.
|
|
21
|
+
*/
|
|
22
|
+
export declare function planNeedsUpgrade(plan: string | null | undefined): boolean;
|
|
23
|
+
/** The text the agent relays to the user in place of a sandbox. */
|
|
24
|
+
export declare function upgradeRequiredMessage(): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-gate.ts
|
|
3
|
+
*
|
|
4
|
+
* The hosted connector's paywall. Anyone can sign in and connect, but a team on
|
|
5
|
+
* the free plan can't start a sandbox: `session_start` returns an upgrade
|
|
6
|
+
* message with a checkout link instead. This mirrors the console, where a free
|
|
7
|
+
* team meets the same wall when it tries to reveal its API key.
|
|
8
|
+
*
|
|
9
|
+
* Only the hosted OAuth transport applies it. A local stdio server
|
|
10
|
+
* authenticates with an API key, which a team can only get once it has
|
|
11
|
+
* upgraded.
|
|
12
|
+
*/
|
|
13
|
+
/** Where a free team goes to start a trial. Overridable for staging. */
|
|
14
|
+
export function upgradeUrl() {
|
|
15
|
+
return (process.env.TD_MCP_UPGRADE_URL ||
|
|
16
|
+
"https://console.testdriver.ai/checkout/pro?source=mcp_connector");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Whether a team on `plan` must upgrade before starting a sandbox.
|
|
20
|
+
*
|
|
21
|
+
* `undefined` means the backend sent no plan at all (an older API, or a
|
|
22
|
+
* transport that never exchanged a token), so there is nothing to gate on. An
|
|
23
|
+
* empty or null plan is a brand-new signup, which is on free.
|
|
24
|
+
*/
|
|
25
|
+
export function planNeedsUpgrade(plan) {
|
|
26
|
+
if (plan === undefined) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return !plan || plan === "free";
|
|
30
|
+
}
|
|
31
|
+
/** The text the agent relays to the user in place of a sandbox. */
|
|
32
|
+
export function upgradeRequiredMessage() {
|
|
33
|
+
return ("UPGRADE REQUIRED: This TestDriver team is on the Free plan, which doesn't include sandbox sessions through the connector. " +
|
|
34
|
+
`Start a free trial of TestDriver Pro to continue: ${upgradeUrl()}\n\n` +
|
|
35
|
+
"Show the user this link and stop. Don't retry session_start until they confirm they've upgraded.");
|
|
36
|
+
}
|
|
@@ -26,6 +26,7 @@ import * as core from "./core/actions.js";
|
|
|
26
26
|
import { NoActiveSessionError } from "./core/actions.js";
|
|
27
27
|
import { resolveE2bTemplateId, resolveOs } from "./env-utils.js";
|
|
28
28
|
import { authenticate, authorizationServerMetadata, authorizeRedirectUrl, isOAuthEnabled, protectedResourceMetadata, wwwAuthenticate } from "./http-auth.js";
|
|
29
|
+
import { planNeedsUpgrade, upgradeRequiredMessage, upgradeUrl } from "./plan-gate.js";
|
|
29
30
|
import { SessionStartInputSchema } from "./provision-types.js";
|
|
30
31
|
// =============================================================================
|
|
31
32
|
// Sentry
|
|
@@ -567,6 +568,18 @@ Debug mode (connect to existing sandbox):
|
|
|
567
568
|
}, async (params, extra) => {
|
|
568
569
|
const startTime = Date.now();
|
|
569
570
|
const progress = makeProgressReporter(extra);
|
|
571
|
+
// Hosted connector paywall: a free team can sign in and connect, but meets
|
|
572
|
+
// the upgrade wall here instead of getting a sandbox (see plan-gate.ts).
|
|
573
|
+
// `plan` is only set by the OAuth transport, so stdio is never gated.
|
|
574
|
+
const sessionPlan = core.getAdapterState().plan;
|
|
575
|
+
if (planNeedsUpgrade(sessionPlan)) {
|
|
576
|
+
logger.info("session_start: Blocked, team is on the free plan");
|
|
577
|
+
return createToolResult(false, upgradeRequiredMessage(), {
|
|
578
|
+
action: "session_start",
|
|
579
|
+
error: "UPGRADE_REQUIRED",
|
|
580
|
+
upgradeUrl: upgradeUrl(),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
570
583
|
// Resolve OS with priority: explicit param > TD_OS env var > "linux" default
|
|
571
584
|
// This mirrors the behavior of the Vitest hooks (hooks.mjs) which also reads TD_OS
|
|
572
585
|
const { os: resolvedOs, warning: osWarning } = resolveOs(params.os);
|
|
@@ -1746,7 +1759,7 @@ async function exchangeOAuthTokenForApiKey(token) {
|
|
|
1746
1759
|
if (!data.apiKey) {
|
|
1747
1760
|
return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
|
|
1748
1761
|
}
|
|
1749
|
-
return { ok: true, apiKey: data.apiKey };
|
|
1762
|
+
return { ok: true, apiKey: data.apiKey, plan: data.identity ? data.identity.plan ?? null : undefined };
|
|
1750
1763
|
}
|
|
1751
1764
|
catch (error) {
|
|
1752
1765
|
logger.error("http: exchange-oauth-token error", { error: String(error) });
|
|
@@ -1823,6 +1836,7 @@ async function startHttpServer() {
|
|
|
1823
1836
|
// disabled this is a no-op so local/trusted-network usage is unchanged.
|
|
1824
1837
|
let resolvedApiKey = null;
|
|
1825
1838
|
let resolvedToken = null;
|
|
1839
|
+
let resolvedPlan;
|
|
1826
1840
|
if (isOAuthEnabled()) {
|
|
1827
1841
|
const auth = await authenticate(req);
|
|
1828
1842
|
if (!auth.ok) {
|
|
@@ -1841,6 +1855,7 @@ async function startHttpServer() {
|
|
|
1841
1855
|
return;
|
|
1842
1856
|
}
|
|
1843
1857
|
resolvedApiKey = exchange.apiKey;
|
|
1858
|
+
resolvedPlan = exchange.plan;
|
|
1844
1859
|
}
|
|
1845
1860
|
const sessionId = req.headers["mcp-session-id"];
|
|
1846
1861
|
const sid = Array.isArray(sessionId) ? sessionId[0] : sessionId;
|
|
@@ -1848,6 +1863,11 @@ async function startHttpServer() {
|
|
|
1848
1863
|
// transport a pre-parsed body). GET (SSE) and DELETE carry no JSON body.
|
|
1849
1864
|
const body = req.method === "POST" ? await readJsonBody(req) : undefined;
|
|
1850
1865
|
let connection = sid ? connections.get(sid) : undefined;
|
|
1866
|
+
// Refresh the plan on every request, so a team that upgrades mid-session
|
|
1867
|
+
// gets past the session_start paywall without reconnecting.
|
|
1868
|
+
if (connection && isOAuthEnabled()) {
|
|
1869
|
+
connection.ctx.adapter.plan = resolvedPlan;
|
|
1870
|
+
}
|
|
1851
1871
|
// A brand-new session: only an `initialize` request may create one.
|
|
1852
1872
|
if (!connection) {
|
|
1853
1873
|
if (sid) {
|
|
@@ -1883,6 +1903,9 @@ async function startHttpServer() {
|
|
|
1883
1903
|
if (resolvedToken) {
|
|
1884
1904
|
ctx.adapter.oauthToken = resolvedToken;
|
|
1885
1905
|
}
|
|
1906
|
+
if (isOAuthEnabled()) {
|
|
1907
|
+
ctx.adapter.plan = resolvedPlan;
|
|
1908
|
+
}
|
|
1886
1909
|
const mcpServer = buildServer();
|
|
1887
1910
|
const transport = new StreamableHTTPServerTransport({
|
|
1888
1911
|
// Tag the owning machine into the id (multi-machine only) so later
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { planNeedsUpgrade, upgradeRequiredMessage, upgradeUrl } from "./plan-gate.js";
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
delete process.env.TD_MCP_UPGRADE_URL;
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe("planNeedsUpgrade", () => {
|
|
9
|
+
it("gates the free plan and a brand-new team with no plan set", () => {
|
|
10
|
+
expect(planNeedsUpgrade("free")).toBe(true);
|
|
11
|
+
expect(planNeedsUpgrade("")).toBe(true);
|
|
12
|
+
expect(planNeedsUpgrade(null)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("lets every other plan through", () => {
|
|
16
|
+
for (const plan of ["pro", "team", "enterprise", "oss", "github-team", "self-hosted"]) {
|
|
17
|
+
expect(planNeedsUpgrade(plan)).toBe(false);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("does not gate when the backend reported no plan at all", () => {
|
|
22
|
+
expect(planNeedsUpgrade(undefined)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("upgradeRequiredMessage", () => {
|
|
27
|
+
it("carries the checkout link", () => {
|
|
28
|
+
expect(upgradeRequiredMessage()).toContain(
|
|
29
|
+
"https://console.testdriver.ai/checkout/pro?source=mcp_connector",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("honours the staging override", () => {
|
|
34
|
+
process.env.TD_MCP_UPGRADE_URL = "https://console-dev.testdriver.ai/checkout/pro";
|
|
35
|
+
expect(upgradeUrl()).toBe("https://console-dev.testdriver.ai/checkout/pro");
|
|
36
|
+
expect(upgradeRequiredMessage()).toContain("console-dev.testdriver.ai");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan-gate.ts
|
|
3
|
+
*
|
|
4
|
+
* The hosted connector's paywall. Anyone can sign in and connect, but a team on
|
|
5
|
+
* the free plan can't start a sandbox: `session_start` returns an upgrade
|
|
6
|
+
* message with a checkout link instead. This mirrors the console, where a free
|
|
7
|
+
* team meets the same wall when it tries to reveal its API key.
|
|
8
|
+
*
|
|
9
|
+
* Only the hosted OAuth transport applies it. A local stdio server
|
|
10
|
+
* authenticates with an API key, which a team can only get once it has
|
|
11
|
+
* upgraded.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Where a free team goes to start a trial. Overridable for staging. */
|
|
15
|
+
export function upgradeUrl(): string {
|
|
16
|
+
return (
|
|
17
|
+
process.env.TD_MCP_UPGRADE_URL ||
|
|
18
|
+
"https://console.testdriver.ai/checkout/pro?source=mcp_connector"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether a team on `plan` must upgrade before starting a sandbox.
|
|
24
|
+
*
|
|
25
|
+
* `undefined` means the backend sent no plan at all (an older API, or a
|
|
26
|
+
* transport that never exchanged a token), so there is nothing to gate on. An
|
|
27
|
+
* empty or null plan is a brand-new signup, which is on free.
|
|
28
|
+
*/
|
|
29
|
+
export function planNeedsUpgrade(plan: string | null | undefined): boolean {
|
|
30
|
+
if (plan === undefined) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return !plan || plan === "free";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The text the agent relays to the user in place of a sandbox. */
|
|
37
|
+
export function upgradeRequiredMessage(): string {
|
|
38
|
+
return (
|
|
39
|
+
"UPGRADE REQUIRED: This TestDriver team is on the Free plan, which doesn't include sandbox sessions through the connector. " +
|
|
40
|
+
`Start a free trial of TestDriver Pro to continue: ${upgradeUrl()}\n\n` +
|
|
41
|
+
"Show the user this link and stop. Don't retry session_start until they confirm they've upgraded."
|
|
42
|
+
);
|
|
43
|
+
}
|
package/mcp-server/src/server.ts
CHANGED
|
@@ -32,6 +32,7 @@ import * as core from "./core/actions.js";
|
|
|
32
32
|
import { NoActiveSessionError, type ActionResult } from "./core/actions.js";
|
|
33
33
|
import { resolveE2bTemplateId, resolveOs } from "./env-utils.js";
|
|
34
34
|
import { authenticate, authorizationServerMetadata, authorizeRedirectUrl, isOAuthEnabled, protectedResourceMetadata, wwwAuthenticate } from "./http-auth.js";
|
|
35
|
+
import { planNeedsUpgrade, upgradeRequiredMessage, upgradeUrl } from "./plan-gate.js";
|
|
35
36
|
import { SessionStartInputSchema, type SessionStartInput } from "./provision-types.js";
|
|
36
37
|
import { type SessionState } from "./session.js";
|
|
37
38
|
|
|
@@ -733,6 +734,19 @@ Debug mode (connect to existing sandbox):
|
|
|
733
734
|
const startTime = Date.now();
|
|
734
735
|
const progress = makeProgressReporter(extra);
|
|
735
736
|
|
|
737
|
+
// Hosted connector paywall: a free team can sign in and connect, but meets
|
|
738
|
+
// the upgrade wall here instead of getting a sandbox (see plan-gate.ts).
|
|
739
|
+
// `plan` is only set by the OAuth transport, so stdio is never gated.
|
|
740
|
+
const sessionPlan = core.getAdapterState().plan as string | null | undefined;
|
|
741
|
+
if (planNeedsUpgrade(sessionPlan)) {
|
|
742
|
+
logger.info("session_start: Blocked, team is on the free plan");
|
|
743
|
+
return createToolResult(false, upgradeRequiredMessage(), {
|
|
744
|
+
action: "session_start",
|
|
745
|
+
error: "UPGRADE_REQUIRED",
|
|
746
|
+
upgradeUrl: upgradeUrl(),
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
736
750
|
// Resolve OS with priority: explicit param > TD_OS env var > "linux" default
|
|
737
751
|
// This mirrors the behavior of the Vitest hooks (hooks.mjs) which also reads TD_OS
|
|
738
752
|
const { os: resolvedOs, warning: osWarning } = resolveOs(params.os);
|
|
@@ -2116,7 +2130,9 @@ function readJsonBody(req: http.IncomingMessage): Promise<unknown> {
|
|
|
2116
2130
|
* catch-all `no_team`.
|
|
2117
2131
|
*/
|
|
2118
2132
|
type ExchangeResult =
|
|
2119
|
-
|
|
2133
|
+
// `plan` is undefined when the backend sent no identity, null when the team
|
|
2134
|
+
// has no plan set yet (a brand-new signup, which is on free).
|
|
2135
|
+
| { ok: true; apiKey: string; plan: string | null | undefined }
|
|
2120
2136
|
| { ok: false; status: number; error: string; description: string };
|
|
2121
2137
|
|
|
2122
2138
|
async function exchangeOAuthTokenForApiKey(token: string): Promise<ExchangeResult> {
|
|
@@ -2146,11 +2162,11 @@ async function exchangeOAuthTokenForApiKey(token: string): Promise<ExchangeResul
|
|
|
2146
2162
|
logger.warn("http: exchange-oauth-token failed", { status: resp.status, error, description });
|
|
2147
2163
|
return { ok: false, status: resp.status, error, description };
|
|
2148
2164
|
}
|
|
2149
|
-
const data = (await resp.json()) as { apiKey?: string };
|
|
2165
|
+
const data = (await resp.json()) as { apiKey?: string; identity?: { plan?: string | null } };
|
|
2150
2166
|
if (!data.apiKey) {
|
|
2151
2167
|
return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
|
|
2152
2168
|
}
|
|
2153
|
-
return { ok: true, apiKey: data.apiKey };
|
|
2169
|
+
return { ok: true, apiKey: data.apiKey, plan: data.identity ? data.identity.plan ?? null : undefined };
|
|
2154
2170
|
} catch (error) {
|
|
2155
2171
|
logger.error("http: exchange-oauth-token error", { error: String(error) });
|
|
2156
2172
|
return { ok: false, status: 502, error: "exchange_unreachable", description: "Could not reach the TestDriver API to exchange the token" };
|
|
@@ -2241,6 +2257,7 @@ async function startHttpServer() {
|
|
|
2241
2257
|
// disabled this is a no-op so local/trusted-network usage is unchanged.
|
|
2242
2258
|
let resolvedApiKey: string | null = null;
|
|
2243
2259
|
let resolvedToken: string | null = null;
|
|
2260
|
+
let resolvedPlan: string | null | undefined;
|
|
2244
2261
|
if (isOAuthEnabled()) {
|
|
2245
2262
|
const auth = await authenticate(req);
|
|
2246
2263
|
if (!auth.ok) {
|
|
@@ -2259,6 +2276,7 @@ async function startHttpServer() {
|
|
|
2259
2276
|
return;
|
|
2260
2277
|
}
|
|
2261
2278
|
resolvedApiKey = exchange.apiKey;
|
|
2279
|
+
resolvedPlan = exchange.plan;
|
|
2262
2280
|
}
|
|
2263
2281
|
|
|
2264
2282
|
const sessionId = req.headers["mcp-session-id"];
|
|
@@ -2270,6 +2288,12 @@ async function startHttpServer() {
|
|
|
2270
2288
|
|
|
2271
2289
|
let connection: HttpConnection | undefined = sid ? connections.get(sid) : undefined;
|
|
2272
2290
|
|
|
2291
|
+
// Refresh the plan on every request, so a team that upgrades mid-session
|
|
2292
|
+
// gets past the session_start paywall without reconnecting.
|
|
2293
|
+
if (connection && isOAuthEnabled()) {
|
|
2294
|
+
connection.ctx.adapter.plan = resolvedPlan;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2273
2297
|
// A brand-new session: only an `initialize` request may create one.
|
|
2274
2298
|
if (!connection) {
|
|
2275
2299
|
if (sid) {
|
|
@@ -2306,6 +2330,9 @@ async function startHttpServer() {
|
|
|
2306
2330
|
if (resolvedToken) {
|
|
2307
2331
|
ctx.adapter.oauthToken = resolvedToken;
|
|
2308
2332
|
}
|
|
2333
|
+
if (isOAuthEnabled()) {
|
|
2334
|
+
ctx.adapter.plan = resolvedPlan;
|
|
2335
|
+
}
|
|
2309
2336
|
const mcpServer = buildServer();
|
|
2310
2337
|
const transport = new StreamableHTTPServerTransport({
|
|
2311
2338
|
// Tag the owning machine into the id (multi-machine only) so later
|