Beta Feature — Browser automation is currently in beta. Please report any issues to founders@morphllm.com .
Test your web apps with natural language. “Test checkout flow” or “Verify mobile menu works”—Morph runs it with a real browser.
Quick Start
Install and run your first browser test in 30 seconds:
import { MorphClient } from '@morphllm/morphsdk' ;
const morph = new MorphClient ({ apiKey: "YOUR_API_KEY" });
const result = await morph . browser . execute ({
task: "Verify the homepage loads and has a working navigation menu" ,
url: "https://example.com"
});
console . log ( result . success ? '✅ Passed' : '❌ Failed' );
console . log ( result . result );
Use remote URLs only (Vercel previews, e2b.dev tunnels, staging). Cannot access localhost—see why .
Why Morph Browser?
Built specifically for testing AI-generated code changes:
10x cheaper than Claude Sonnet (0.30 v s 0.30 vs 0.30 v s 3.00 per 1M input tokens)
250% faster inference (200 tok/s vs 60 tok/s)
Live session streaming - Watch tests execute in real-time
Rich debugging - URLs, actions, errors auto-captured
Agent self-assessment - Real success detection, not just completion
Model Input Output Speed Best For morph-computer-use-v1 $0.30 $1.50 200 tok/s Browser automation (default) morph-computer-use-v0 $0.30 $1.50 200 tok/s Browser automation (legacy) gemini-3-flash-preview $0.50 $3.00 90 tok/s External API (Google) claude-sonnet-4.5 $3.00 $15.00 60 tok/s General reasoning
Per 1M tokens. Morph models are optimized for browser automation and testing.
Common Patterns
Basic Testing
Test any web flow with natural language:
const result = await morph . browser . execute ({
task: "Test the login flow with test@example.com / password123" ,
url: "https://myapp.vercel.app"
});
if ( result . success ) {
console . log ( '✅ Test passed' );
} else {
console . error ( '❌ Test failed:' , result . error );
console . log ( 'Failed at:' , result . urls [ result . urls . length - 1 ]);
}
Watch Tests Live
Get live URL immediately, watch execution in real-time:
// Returns in ~2s with live URL
const task = await morph . browser . createTask ({
task: "Test checkout flow with cart persistence" ,
url: "https://shop.example.com"
});
console . log ( '👁️ Watch live:' , task . debugUrl );
// Open URL in browser to watch from start
// Wait for completion
const result = await task . complete ();
Test Responsive Layouts
Use built-in tools to test different screen sizes:
const result = await morph . browser . execute ({
task: "Resize to mobile (375x667) and verify the hamburger menu appears" ,
url: "https://myapp.com"
});
Site Authentication
Test sites that require login with the auth parameter:
const result = await morph . browser . execute ({
task: "Log in with x_user and x_pass, then verify the dashboard loads" ,
url: "https://myapp.com/login" ,
auth: {
username: "test@example.com" ,
password: "secret123"
}
});
The agent sees placeholders (x_user, x_pass) in the task. Real values are injected when filling forms.
Scope credentials to specific domains: const result = await morph . browser . execute ({
task: "Log in and test the integration" ,
url: "https://staging.myapp.com" ,
auth: {
"https://*.staging.myapp.com" : { username: "staging-user" , password: "staging-pass" },
"https://api.external.com" : { username: "api-user" , password: "api-key" }
}
});
Skip login entirely with pre-authenticated cookies: const result = await morph . browser . execute ({
task: "Verify the admin dashboard loads" ,
url: "https://myapp.com/admin" ,
auth: {
cookies: [
{ name: "session" , value: "abc123..." , domain: "myapp.com" },
{ name: "auth_token" , value: "xyz789..." , domain: "myapp.com" }
]
}
});
Export cookies from your browser’s DevTools or use a cookie manager.
Browser Profiles (Persistent Logins)
Profiles let you sign in once (manually) and reuse that authenticated state across test runs. Profiles are scoped to a repo.
List available repos
const repos = await morph . browser . profiles . listRepos ();
// Pick by full name
const repo = repos . find ( r => r . repoFullName === 'owner/repo' );
if ( ! repo ) throw new Error ( 'Repo not found' );
Create a profile (returns a live URL)
const setup = await morph . browser . profiles . createProfile ({
name: 'Staging' ,
repoId: repo . repoId
});
console . log ( 'Open to sign in:' , setup . session . debugUrl );
// User signs in, then persist the state:
await setup . save ();
console . log ( 'Profile ID:' , setup . profile . id );
Use a profile in browser tasks
const result = await morph . browser . execute ({
task: "Go to the dashboard and verify it loads" ,
url: "https://staging.myapp.com" ,
profileId: setup . profile . id
});
Update a profile (add more logins)
const edit = await morph . browser . profiles . updateProfile ( setup . profile . id );
console . log ( 'Open to sign in:' , edit . session . debugUrl );
await edit . save ();
List and delete profiles
const profiles = await morph . browser . profiles . listProfiles ( repo . repoId );
await morph . browser . profiles . deleteProfile ( profiles [ 0 ]. id );
Debug with Recordings
Enable video and logs for failed tests:
const result = await morph . browser . execute ({
task: "Complete the checkout flow" ,
url: "https://shop.example.com" ,
recordVideo: true ,
maxSteps: 20
});
if ( ! result . success && result . recordingId ) {
const rec = await morph . browser . getRecording ( result . recordingId );
console . log ( '📹 Video:' , rec . videoUrl );
console . log ( '🌐 Network:' , rec . networkUrl );
console . log ( '📊 Console:' , rec . consoleUrl );
}
CI/CD Integration
Track tests with reference IDs:
const result = await morph . browser . execute ({
task: "Verify homepage loads correctly" ,
url: process . env . PREVIEW_URL ,
externalId: process . env . GITHUB_PR_NUMBER ,
repoFullName: process . env . GITHUB_REPOSITORY , // "owner/repo"
commitId: process . env . GITHUB_SHA ,
recordVideo: true
});
// Link results back to your PR/build system
Live Sessions
Stream browser execution in real-time at 25 fps. Perfect for debugging, monitoring, or human-in-the-loop workflows.
Basic Live Streaming
const task = await morph . browser . createTask ({
task: "Test the payment flow" ,
url: "https://myapp.com"
});
// Available immediately (~2s)
console . log ( 'Watch live:' , task . debugUrl );
// Share with team, embed in dashboard, or monitor yourself
const result = await task . complete ();
Embed in Dashboard
const task = await morph . browser . createTask ({
task: "Monitor production health check" ,
url: "https://myapp.com"
});
// Read-only viewer
const viewer = task . getLiveIframe ?.( 'readonly' );
document . getElementById ( 'monitor' ). innerHTML = viewer ;
// Interactive control (human takeover)
const controller = task . getLiveIframe ?.({
interactive: true ,
height: '800px' ,
width: '100%'
});
Live URLs are unauthenticated . Anyone with the URL can view (and control if interactive=true) the session. Add your own auth for production use.
The agent automatically uses tools when needed based on your task description.
Responsive Testing
Test layouts at different screen sizes—just mention dimensions in your task:
const result = await morph . browser . execute ({
task: "Resize to iPhone 12 size (390x844) and verify the mobile nav works" ,
url: "https://myapp.com"
});
Common dimensions:
Desktop : 1920x1080, 1440x900, 1280x720
Tablet : 1024x768 (iPad), 768x1024 (iPad Portrait)
Mobile : 375x667 (iPhone SE), 414x896 (iPhone 11), 390x844 (iPhone 12/13)
More tools coming: file uploads, API interactions, human-in-the-loop. All tools are globally available.
Recordings
Enable recordVideo: true to capture full session details:
const result = await morph . browser . execute ({
task: "Test the checkout flow" ,
url: "https://shop.example.com" ,
recordVideo: true
});
if ( result . recordingId ) {
const recording = await morph . browser . getRecording ( result . recordingId );
console . log ( '📹 Video:' , recording . videoUrl ); // MP4/WebM playback
console . log ( '🔄 Interactive:' , recording . replayUrl ); // rrweb DOM replay
console . log ( '🌐 Network:' , recording . networkUrl ); // All HTTP requests
console . log ( '📊 Console:' , recording . consoleUrl ); // JS console logs
}
What you get:
Video file (MP4/WebM)
Interactive DOM replay (rrweb)
Network logs (all requests/responses)
Console logs (JS output)
Screenshots (per step)
Get Animated WebP
Convert recordings to animated WebP for embedding in PRs or dashboards:
const recording = await morph . browser . getRecording ( result . recordingId );
const { webpUrl } = await recording . getWebp ({
width: 780 ,
fps: 10 ,
quality: 65 ,
maxDuration: 15
});
console . log ( `` );
With file size budget:
const { webpUrl } = await recording . getWebp ({
maxSizeMb: 2.0 // Output guaranteed ≤ 2MB
});
When maxSizeMb is set, the output is guaranteed to stay under that size. For long recordings with tight budgets, the video is automatically sped up to fit.
Cached in S3—subsequent calls return instantly.
Get Errors with Screenshots
const recording = await morph . browser . getRecording ( result . recordingId );
const { errors , totalErrors } = await recording . getErrors ();
errors . forEach ( err => {
console . log ( `[ ${ err . type } ] ${ err . message } ` );
if ( err . screenshotUrl ) {
console . log ( `Screenshot: ${ err . screenshotUrl } ` );
}
});
Choosing a Model
Specify which model to use for browser automation with the model parameter:
const result = await morph . browser . execute ({
task: "Test the checkout flow" ,
url: "https://myapp.com" ,
model: "morph-computer-use-v1" // or "morph-computer-use-v0", "gemini-3-flash-preview"
});
Available Models
Model Description morph-computer-use-v1Default. Latest Morph model, optimized for browser automationmorph-computer-use-v0Legacy Morph model, stable fallback gemini-3-flash-previewGoogle Gemini 3 Flash, uses external Google API
morph-computer-use-v1 is the default and recommended for most use cases. Use gemini-3-flash-preview if you prefer Google’s Gemini model (requires GOOGLE_API_KEY on the server).
API Reference
execute()
Synchronous execution—waits for completion (~30-60s).
const result = await morph . browser . execute ({
// Required
task: string , // Natural language description
// Optional
url? : string , // Starting URL
model? : string , // See "Choosing a Model" below
maxSteps? : number , // 1-50 (default: 10)
recordVideo? : boolean , // Enable recording (default: false)
viewportWidth? : number , // Browser width (default: 1280)
viewportHeight? : number , // Browser height (default: 720)
externalId? : string , // Your tracking ID
repoFullName? : string , // Repository full name ("owner/repo")
repoId? : string , // Repository UUID (if you already have it)
commitId? : string , // Commit/version identifier
// Authentication
auth? : { // Global credentials
username? : string ,
password? : string ,
cookies? : any // Cookie array for pre-authenticated sessions
} | Record < string , AuthCredentials> // Per-domain credentials
});
Returns:
{
success : boolean , // Agent self-assessment
result ?: string , // Task result/findings
error ?: string , // Error message if failed
stepsTaken : number , // Actions executed
executionTimeMs : number ,
// Debugging
urls : ( string | null )[], // All URLs visited
actionNames : string [], // All actions taken
errors : ( string | null )[], // Per-step errors
// Recording
recordingId ?: string ,
recordingStatus ?: string , // PENDING | RUNNING | PROCESSING | COMPLETED | ERROR
debugUrl ?: string // Live session URL
}
createTask()
Async execution—returns immediately with live URL (~2s).
const task = await morph . browser . createTask ({
// Same parameters as execute()
task: "Test the login flow" ,
url: "https://myapp.com"
});
console . log ( task . debugUrl ); // Watch live
const result = await task . complete (); // Wait for completion
Writing Good Tasks
Be specific—the agent performs better with clear instructions.
✅ Good (specific):
“Navigate to pricing and verify all three tiers display”
“Add item to cart, go to checkout, verify subtotal matches”
“Test login with test@example.com / password123”
❌ Bad (vague):
“test the app”
“check if everything works”
“make sure there are no bugs”
Choosing maxSteps:
5-10 steps : Simple navigation and verification
10-15 steps : Form submissions, multi-step flows
15-30 steps : Complex journeys (checkout, onboarding)
Remote URLs Only
The browser runs on our infrastructure—it cannot access localhost .
✅ Use these:
https://3000-abc.e2b.dev (e2b tunnel)
https://preview-abc.vercel.app (Vercel preview)
https://staging.myapp.com (staging environment)
❌ Don’t use:
localhost:3000
127.0.0.1
192.168.x.x or any local IPs
Tunnel local servers with e2b.dev , ngrok , or deploy to Vercel for instant preview URLs.
Python SDK
Morph is OpenAI-compatible . Use with browser-use Python SDK:
from browser_use import Agent, ChatOpenAI
import os
llm = ChatOpenAI(
model = "morph-computer-use-v1" , # or "morph-computer-use-v0", "gemini-3-flash-preview"
api_key = "YOUR_API_KEY" ,
base_url = "https://api.morphllm.com/v1"
)
agent = Agent(
task = "Navigate to amazon.com and get the first product title" ,
llm = llm
)
result = await agent.run( max_steps = 10 )
Get browser-use’s Python interface with Morph’s 10x cheaper pricing. See the full guide .
Setting Up Test Accounts
Use a separate development environment for browser automation testing. This lets you freely disable 2FA, bot protection, and other security features without affecting production.
Most auth providers (Clerk, Auth0, Supabase, Firebase) support creating separate development/staging instances. Create your test accounts there, where you can:
Disable 2FA/MFA on test accounts
Turn off bot protection and CAPTCHA
Skip email verification
Add preview URL wildcards to allowed origins (e.g., https://*.vercel.app)
Quick Setup by Provider
Create a Development instance in Clerk Dashboard
Create test user: Dashboard → Users → Create user
Disable bot protection: Configure → Attack Protection → Bot Protection
Add allowed origins: Configure → Paths → add https://*.vercel.app
Use development keys in preview environment:
# Vercel Preview environment variables
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_test_xxx
CLERK_SECRET_KEY = sk_test_xxx
Create a Development tenant in Auth0
Create test user: User Management → Users → Create User
Disable MFA: Security → Multi-factor Auth → disable or add rule to skip for test emails
Add allowed origins: Applications → Settings → Allowed Web Origins
Use development tenant in preview:
AUTH0_ISSUER_BASE_URL = https://dev-xxx.auth0.com
AUTH0_CLIENT_ID = dev_client_id
Create a separate Supabase project for staging
Create test user via Dashboard or SQL
Disable email confirmation: Authentication → Providers → Email
Disable CAPTCHA: Authentication → Settings
Add redirect URLs: Authentication → URL Configuration → add https://*.vercel.app/**
# Vercel Preview environment
NEXT_PUBLIC_SUPABASE_URL = https://xxx-staging.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY = staging_anon_key
Create a separate Firebase project for development
Create test user and mark email as verified
Add authorized domains: Authentication → Settings → Authorized domains
For CI/CD, consider using Firebase Auth Emulator
FIREBASE_AUTH_EMULATOR_HOST = localhost:9099
Vercel Environment Variables
Configure different auth credentials per environment:
# Vercel Dashboard → Settings → Environment Variables
# Production: Use production auth instance
CLERK_SECRET_KEY = sk_live_xxx (Production only )
# Preview: Use development auth instance
CLERK_SECRET_KEY = sk_test_xxx (Preview only )
TEST_USER_EMAIL = test@yourdomain.com (Preview only )
TEST_USER_PASSWORD = xxx (Preview only )
Then use with Morph:
const result = await morph . browser . execute ({
task: "Log in with x_user and x_pass, verify dashboard" ,
url: "https://preview-abc.vercel.app/login" ,
auth: {
username: process . env . TEST_USER_EMAIL ,
password: process . env . TEST_USER_PASSWORD
}
});
FAQ
Why can't I access localhost?
The browser runs on our servers, not your machine. Won’t work: localhost:3000, 127.0.0.1, 192.168.x.xSolutions:
Deploy to Vercel/Netlify for instant preview URLs
Tunnel with e2b.dev or ngrok
Use a staging environment
execute() vs createTask()—which should I use?
execute() - Waits for completion (~30-60s)const result = await morph . browser . execute ({
task: "Test login" ,
url: "https://app.com"
});
// Returns complete result
createTask() - Returns immediately (~2s)const task = await morph . browser . createTask ({
task: "Test login" ,
url: "https://app.com"
});
console . log ( task . debugUrl ); // Watch live!
const result = await task . complete ();
Use createTask() to watch from the start or get the live URL for monitoring/debugging.
How do I debug failed tests?
Every task returns rich debugging data automatically: const result = await morph . browser . execute ({
task: "Test checkout" ,
url: "https://myapp.com" ,
recordVideo: true ,
maxSteps: 20
});
if ( ! result . success ) {
// Always available
console . log ( 'Error:' , result . error );
console . log ( 'URLs:' , result . urls );
console . log ( 'Actions:' , result . actionNames );
console . log ( 'Step errors:' , result . errors . filter ( e => e ));
// With recording
if ( result . recordingId ) {
const rec = await morph . browser . getRecording ( result . recordingId );
console . log ( 'Video:' , rec . videoUrl );
console . log ( 'Network:' , rec . networkUrl );
}
}
You get: agent self-assessment, all URLs visited, every action taken, per-step errors, and optional video/logs.
Each browser action = 1 step:
Click → 1 step
Fill form → 1 step
Navigate → 1 step
Wait → 1 step
Sizing:
5-10 steps : Simple tasks
10-15 steps : Forms, multi-step
15-30 steps : Complex flows
Hit the limit? Increase maxSteps or simplify the task.
Can I get structured output?
Yes—use Zod schemas with createTask(): import { z } from 'zod' ;
const task = await morph . browser . createTask ({
task: "Get the product price" ,
url: "https://store.com/product" ,
schema: z . object ({
price: z . number (),
inStock: z . boolean ()
})
});
const result = await task . complete ();
console . log ( result . parsed ); // { price: 29.99, inStock: true }
Enable recordVideo: true to get:
Video (MP4/WebM) - Visual playback
rrweb replay - Interactive DOM timeline
Network logs - All HTTP requests
Console logs - JS console output
Screenshots - Per step
Stored in S3 with 7-day presigned URLs.
How do live sessions work?
WebRTC streaming at 25 fps. Use cases: Monitoring: const iframe = task . getLiveIframe ?.( 'readonly' );
// Embed in dashboard
Human takeover: const control = task . getLiveUrl ?.({ interactive: true });
// Send to operator
Debugging: console . log ( task . debugUrl );
// Share with team
Compatibility: Chrome 90+, Firefox 90+, Safari 14.1+
Is Morph OpenAI-compatible?
Yes—works with any OpenAI SDK: import OpenAI from 'openai' ;
const client = new OpenAI ({
apiKey: "YOUR_API_KEY" ,
baseURL: 'https://api.morphllm.com/v1'
});
const completion = await client . chat . completions . create ({
model: 'morph-computer-use-v1' ,
messages: [{ role: 'user' , content: 'Test checkout' }]
});
Works with: browser-use, Browserbase, Browserless, Steel, and more.
Link tests to your systems with reference IDs: const result = await morph . browser . execute ({
task: "Test checkout" ,
url: "https://myapp.com" ,
externalId: "PR-1234" , // PR, Jira, CI/CD
repoFullName: "myorg/myrepo" , // Repository
commitId: "abc123def456" // Specific commit
});
All fields optional. Filter by these IDs in the dashboard.
How do I test authenticated pages?
Use the auth parameter with username/password or cookies: // Username/password login
const result = await morph . browser . execute ({
task: "Log in with x_user and x_pass, then verify dashboard" ,
url: "https://myapp.com/login" ,
auth: { username: "test@example.com" , password: "secret" }
});
// Skip login with cookies
const result = await morph . browser . execute ({
task: "Verify admin dashboard" ,
url: "https://myapp.com/admin" ,
auth: {
cookies: [{ name: "session" , value: "abc123" , domain: "myapp.com" }]
}
});
Reference credentials in your task as x_user and x_pass.
Agent can't sign into my app (Clerk, Auth0, etc.)
Third-party auth providers like Clerk, Auth0, and Supabase Auth often block requests from unfamiliar origins or automated browsers. Solutions:
Use cookie-based auth (recommended):
const result = await morph . browser . execute ({
task: "Verify dashboard loads" ,
url: "https://myapp.com/dashboard" ,
auth: {
cookies: [
{ name: "__session" , value: "your-session-cookie" , domain: "myapp.com" }
]
}
});
Export cookies from your browser’s DevTools after logging in manually.
Add preview URL to your auth provider’s allowed origins :
Clerk : Dashboard → API Keys → Allowed origins → Add your preview URL
Auth0 : Applications → Settings → Allowed Origins → Add preview URL
Supabase : Authentication → URL Configuration → Add preview URL
Example: Add https://your-preview-abc.vercel.app to allowed origins.
Cookie auth is more reliable since it bypasses the login flow entirely.
Need Help?