Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build TestDriver tests iteratively using MCP tools with visual feedback
.claude/skills/testdriverai-testdriver-mcp-workflow/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 256% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 82% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 112% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 98% | 0% |
Build automated tests by directly controlling a sandbox through MCP tools. Every action returns a screenshot AND the generated code to add to your test file.
Use this skill when:
session_start, find, click, etc.)Use MCP tools to:
session_start({ type: "chrome", url: "https://your-app.com" })This provisions a sandbox with Chrome and navigates to your URL. You'll see a screenshot and the provision code:
Add to test file:
await testdriver.provision.chrome({ url: "https://your-app.com" });For local development (pointing to a custom API endpoint):
session_start({
type: "chrome",
url: "https://your-app.com",
apiRoot: "https://your-ngrok-url.ngrok.io"
})For self-hosted AWS instances (your own Windows EC2):
session_start({
type: "chrome",
url: "https://your-app.com",
os: "windows",
ip: "1.2.3.4" // IP from your AWS instance
})See AWS Setup Guide to deploy your own infrastructure.
Find elements and interact with them. Each action returns a screenshot AND generated code:
find_and_click({ description: "Sign In button" })
→ Returns: screenshot with element highlighted
→ Add to test file: await testdriver.find("Sign In button").click();
type({ text: "user@example.com" })
→ Returns: screenshot showing typed text
→ Add to test file: await testdriver.type("user@example.com");After performing actions, use check to verify they worked:
check({ task: "Was the text entered into the field?" })
→ Returns: AI analysis of whether the task completed, with screenshot
check({ task: "Did the button click navigate to a new page?" })
→ Returns: AI compares previous screenshot to current stateUse assert for boolean pass/fail conditions that get recorded in test files:
assert({ assertion: "the login form is visible" })
→ Returns: pass/fail with screenshot
→ Add to test file:
const assertResult = await testdriver.assert("the login form is visible");
expect(assertResult).toBeTruthy();As you perform actions, append the generated code to your test file:
javascript/** * Login Flow test */ import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs"; describe("Login Flow", () => { it("should complete login", async (context) => { const testdriver = TestDriver(context); // Append generated code here as you go: await testdriver.provision.chrome({ url: "https://app.example.com" }); await testdriver.find("email input field").click(); await testdriver.type("user@example.com"); // ... more code as you perform actions }); });
Run the test from scratch to validate it works:
verify({ testFile: "tests/login.test.mjs" })| Tool | Description | |------|-------------| | session_start | Start sandbox with browser/app, returns screenshot + provision code | | session_status | Check session health and time remaining | | session_extend | Add more time before session expires |
Each tool returns a screenshot AND the generated code to add to your test file.
| Tool | Description | |------|-------------| | find | Locate element by description, returns ref for later use | | click | Click on element ref | | find_and_click | Find and click in one action | | type | Type text into focused field | | press_keys | Press keyboard shortcuts (e.g., ["ctrl", "a"]) | | scroll | Scroll page (up/down/left/right) |
| Tool | Description | |------|-------------| | check | For AI to understand screen state. Analyzes current screen and tells you (the AI) whether a task/condition is met. Use this after actions to verify they worked. | | assert | AI-powered boolean assertion for test files (pass/fail for CI). Returns generated code. | | screenshot | For showing the user the screen. Captures and displays a screenshot. Does NOT return analysis to you (the AI). | | exec | Execute JavaScript, shell, or PowerShell in sandbox. Returns generated code. |
| Tool | Description | |------|-------------| | verify | Run test file from scratch to validate it works |
Every tool returns a screenshot showing:
Don't try to build the entire test at once:
# Step 1: Get to login page
session_start({ url: "https://app.com" })
→ Add to test: await testdriver.provision.chrome({ url: "https://app.com" });
# Step 2: Verify you're on the right page
check({ task: "Is this the login page?" })
# Step 3: Fill in email
find_and_click({ description: "email input field" })
→ Add to test: await testdriver.find("email input field").click();
type({ text: "user@example.com" })
→ Add to test: await testdriver.type("user@example.com");
# Step 4: Check if email was entered
check({ task: "Was the email entered correctly?" })
# Step 5: Continue with password...After each action, use check to verify it worked:
find_and_click({ description: "Submit button" })
check({ task: "Was the form submitted?" })The check tool compares the previous screenshot (from before your action) with the current state, giving you AI analysis of what changed and whether the action succeeded.
For AI understanding: Use check to analyze the screen:
check({ task: "Did the form submit successfully?" })
→ Returns AI analysis you can read and understandFor user visibility: Use screenshot to show the user:
screenshot()
→ Displays to user, no analysis returned to youAction tools (find, click, find_and_click) return screenshots automatically, which the user can see. But if you need to understand the state, use check.
If elements take time to appear, use find with timeout:
find({ description: "Loading complete indicator", timeout: 30000 })Sessions expire after 5 minutes by default. Use session_status to check time remaining and session_extend to add more time:
session_status()
→ "Time remaining: 45s"
session_extend({ additionalMs: 60000 })
→ "New expiry: 105s"After each successful action, append the generated code to your test file. This ensures you don't lose progress and makes the test easier to debug.
If find fails:
find({ description: "...", timeout: 10000 })scroll({ direction: "down" })If the session expires:
session_start again with the same URLverify to get back to last stateIf verify fails:
When creating a new test project, use these exact dependencies:
package.json:
json{ "type": "module", "devDependencies": { "testdriverai": "canary", "vitest": "^4.0.0" }, "scripts": { "test": "vitest" } }
Important: The package is testdriverai (NOT @testdriverai/sdk). Always install from the canary tag.
Create test files using this standard format. Append generated code inside the test function:
javascript/** * Login Flow test */ import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs"; describe("Login Flow", () => { it("should complete Login Flow", async (context) => { const testdriver = TestDriver(context); // Append generated code from each action here: await testdriver.provision.chrome({ url: "https://app.example.com" }); await testdriver.find("email input field").click(); await testdriver.type("user@example.com"); await testdriver.find("password field").click(); await testdriver.type("secret123"); await testdriver.find("Sign In button").click(); const assertResult = await testdriver.assert("dashboard is visible"); expect(assertResult).toBeTruthy(); }); });
You can use your own AWS-hosted Windows instances instead of TestDriver cloud. This gives you:
bash AWS_REGION=us-east-2 \ AMI_ID=ami-0504bf50fad62f312 \ AWS_LAUNCH_TEMPLATE_ID=lt-xxx \ bash setup/aws/spawn-runner.sh Output: PUBLIC_IP=1.2.3.4
session_start({ type: "chrome", url: "https://example.com", os: "windows", ip: "1.2.3.4" })
bash aws ec2 terminate-instances --instance-ids i-xxx --region us-east-2
You can also set TD_IP environment variable in your MCP config instead of passing ip to each session:
json{ "mcpServers": { "testdriver": { "env": { "TD_API_KEY": "your-key", "TD_IP": "1.2.3.4" } } } }
check to understand the screen - This is how you (the AI) see and analyze the current statescreenshot to show the user - This displays the screen to the user, but does NOT return analysis to youcheck after every action - Verify your actions succeeded before moving oncheck for verification, assert for test files - check gives detailed AI analysis, assert gives boolean pass/fail for CI| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 12,072 | 3,785 | -69% | 1 | 1 | 0% | 2,501 | 3,463 | +38% | 0 | 0 | — |
case-02 | fail→fail | 9,565 | 8,537 | -11% | 1 | 1 | 0% | 1,795 | 3,509 | +95% | 0 | 0 | — |
case-03 | fail→fail | 7,119 | 5,635 | -21% | 1 | 1 | 0% | 571 | 3,586 | +528% | 0 | 0 | — |
case-04 | fail→pass | 9,187 | 3,749 | -59% | 1 | 1 | 0% | 1,689 | 3,943 | +133% | 0 | 0 | — |
case-05 | fail→pass | 5,378 | 2,715 | -50% | 1 | 1 | 0% | 1,045 | 3,724 | +256% | 0 | 0 | — |
case-06 | pass→pass | 9,738 | 3,140 | -68% | 1 | 1 | 0% | 1,607 | 3,761 | +134% | 0 | 0 | — |
case-07 | fail→pass | 14,586 | 6,232 | -57% | 1 | 1 | 0% | 2,334 | 4,257 | +82% | 0 | 0 | — |
case-08 | fail→fail | 12,215 | 3,480 | -72% | 1 | 1 | 0% | 2,016 | 3,771 | +87% | 0 | 0 | — |
case-09 | fail→pass | 9,961 | 3,234 | -68% | 1 | 1 | 0% | 1,820 | 3,852 | +112% | 0 | 0 | — |
case-10 | pass→pass | 9,791 | 6,315 | -36% | 1 | 1 | 0% | 1,736 | 4,189 | +141% | 0 | 0 | — |
case-11 | fail→fail | 4,896 | 3,519 | -28% | 1 | 1 | 0% | 793 | 3,744 | +372% | 0 | 0 | — |
case-12 | fail→pass | 12,880 | 5,417 | -58% | 1 | 1 | 0% | 2,060 | 4,088 | +98% | 0 | 0 | — |
case-13 | fail→pass | 8,886 | 3,145 | -65% | 1 | 1 | 0% | 1,601 | 3,773 | +136% | 0 | 0 | — |
case-14 | fail→pass | 7,429 | 2,607 | -65% | 1 | 1 | 0% | 1,461 | 3,706 | +154% | 0 | 0 | — |
case-15 | fail→fail | 15,099 | 2,239 | -85% | 1 | 1 | 0% | 2,439 | 3,546 | +45% | 0 | 0 | — |
case-16 | fail→pass | 12,607 | 6,725 | -47% | 1 | 1 | 0% | 1,718 | 4,351 | +153% | 0 | 0 | — |
case-17 | pass→pass | 6,571 | 2,284 | -65% | 1 | 1 | 0% | 1,212 | 3,594 | +197% | 0 | 0 | — |
case-18 | pass→pass | 8,038 | 1,585 | -80% | 1 | 1 | 0% | 1,259 | 3,371 | +168% | 0 | 0 | — |
case-19 | fail→pass | 9,120 | 3,995 | -56% | 1 | 1 | 0% | 1,710 | 3,699 | +116% | 0 | 0 | — |
case-20 | pass→pass | 5,510 | 2,140 | -61% | 1 | 1 | 0% | 970 | 3,508 | +262% | 0 | 0 | — |
case-21 | pass→pass | 13,180 | 7,103 | -46% | 1 | 1 | 0% | 2,606 | 4,742 | +82% | 0 | 0 | — |
case-22 | pass→pass | 7,575 | 7,567 | -0% | 1 | 1 | 0% | 1,585 | 4,343 | +174% | 0 | 0 | — |
case-23 | pass→pass | 3,816 | 4,237 | +11% | 1 | 1 | 0% | 750 | 3,776 | +403% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +39 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.