Natural language E2E tests that run on every PR. Get video recordings of failures automatically.
import { MorphClient } from '@morphllm/morphsdk';const morph = new MorphClient({ apiKey: "YOUR_API_KEY" });async function runE2ETests(previewUrl: string, commitSha: string) { const tests = [ "Test user can sign up with email and password", "Test user can login with valid credentials", "Test checkout flow with test credit card", "Test settings page loads and can update profile" ]; const results = await Promise.all( tests.map(task => morph.browser.execute({ task, url: previewUrl, maxSteps: 15, recordVideo: true }) ) ); const failed = results.filter(r => !r.success); if (failed.length > 0) { // Get recordings and embed videos in PR const failureReports = await Promise.all( failed.map(async (r, i) => { if (r.recordingId) { const rec = await morph.browser.getRecording(r.recordingId); return { test: tests[i], videoUrl: rec.videoUrl, error: r.error }; } return { test: tests[i], error: r.error }; }) ); // Post to GitHub PR with embedded videos const { Octokit } = require('@octokit/rest'); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); const prBody = `## ❌ ${failed.length} Test${failed.length > 1 ? 's' : ''} Failed${failureReports.map(f => `### ${f.test}${f.error ? `**Error:** ${f.error}` : ''}${f.videoUrl ? `<video src="${f.videoUrl}" controls width="100%"> <a href="${f.videoUrl}">Watch video</a></video>` : ''}`).join('\n---\n')}`; await octokit.issues.createComment({ owner: process.env.GITHUB_REPOSITORY_OWNER, repo: process.env.GITHUB_REPOSITORY?.split('/')[1], issue_number: parseInt(process.env.PR_NUMBER), body: prBody }); throw new Error(`${failed.length} tests failed - see PR comment for videos`); } console.log('✅ All tests passed!'); return results;}// Vercel preview integrationawait runE2ETests( process.env.VERCEL_URL, process.env.VERCEL_GIT_COMMIT_SHA);
Cost: ~$0.10 per test suite run. Videos auto-delete after 7 days. Contact support for higher concurrency limits.
Example PR Comment with Embedded Video
When tests fail, the video is embedded directly in the PR comment:
## ❌ 1 Test Failed### Test checkout flow with test credit card**Error:** Checkout button not found after 15 steps<video src="https://morph-recordings.s3.amazonaws.com/..." controls width="100%"> <a href="https://morph-recordings.s3.amazonaws.com/...">Watch video</a></video>
GitHub renders this as a playable video directly in the PR. No need to click links.
Send each request to the right model for its difficulty — Opus for hard prompts, Haiku for easy ones — using the router’s classify endpoint. You keep the mapping; the router just reads the prompt.
import Anthropic from '@anthropic-ai/sdk';const anthropic = new Anthropic();// Map each difficulty tier to the model you wantconst MODEL: Record<string, string> = { hard: "claude-opus-4-8", medium: "claude-sonnet-4-6", easy: "claude-haiku-4-5-20251001",};const FALLBACK = "claude-sonnet-4-6"; // when the prompt is too ambiguous to sizeasync function ask(prompt: string) { // 1. Classify the prompt's difficulty const res = await fetch("https://api.morphllm.com/v1/router/classify", { method: "POST", headers: { Authorization: `Bearer ${process.env.MORPH_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ input: prompt, classes: ["difficulty"] }), }); const { difficulty } = (await res.json()).classifications; // 2. Pick your model (fall back if the router isn't confident) const model = difficulty.meets_threshold ? MODEL[difficulty.label] ?? FALLBACK : FALLBACK; // 3. Call it return anthropic.messages.create({ model, max_tokens: 1024, messages: [{ role: "user", content: prompt }], });}await ask("Fix this typo"); // easy -> Haikuawait ask("Design an event-sourcing system"); // hard -> Opus
What it does: One classify call reads the prompt’s difficulty, then you route to the model you picked for that tier — cheap models for simple work, your strongest model only when it’s needed.