Skip to content

Quick Start

Modern frameworks like Next.js blur the lines between frontend and backend. A single Server Component might validate input, query a database, call Stripe, and render HTML—all in one request.

The testing dilemma:

  • Mock everything? You lose integration confidence—you’re not testing how your app actually works
  • Hit real external APIs? Slow, flaky, expensive, and impossible to test edge cases like payment failures

Scenarist’s approach: Run Playwright tests against your real application. Your Server Components render, your middleware executes, your validation runs—for real. Only external services (Stripe, Auth0, SendGrid) are mocked, and you control exactly what they return per test.

  1. Define scenarios — Declarative objects describing what external APIs should return
  2. Add middleware — One line to integrate with your framework
  3. Switch scenarios per test — Each test can use different API responses, running in parallel
import type { ScenaristScenarios } from '@scenarist/express-adapter';
// Or: import type { ScenaristScenarios } from '@scenarist/nextjs-adapter/app';
// Define scenarios as data (not functions)
const scenarios = {
default: {
id: 'default',
mocks: [
{
method: 'POST',
url: 'https://api.stripe.com/v1/charges',
response: { status: 200, body: { id: 'ch_123', status: 'succeeded' } },
},
],
},
cardDeclined: {
id: 'cardDeclined',
mocks: [
{
method: 'POST',
url: 'https://api.stripe.com/v1/charges',
response: { status: 402, body: { error: { code: 'card_declined' } } },
},
],
},
} as const satisfies ScenaristScenarios;

Pick your framework to get started with a complete, working setup:

Each framework has a complete, working example you can clone and run:

FrameworkExample App
Expressapps/express-example
Next.js App Routerapps/nextjs-app-router-example
Next.js Pages Routerapps/nextjs-pages-router-example

Each framework guide covers:

  • Installation — Package setup for your framework
  • Scenario definition — How to structure mocks with default fallbacks
  • App integration — Framework-specific middleware/endpoint setup
  • Test patterns — Real test examples using your framework’s tools
  • Header forwarding — How to propagate test IDs to external APIs
  • Production safety — Tree-shaking and deployment considerations

Before diving into your framework guide, here’s what makes Scenarist different:

Test Real Code

Your routes, middleware, and business logic execute normally. Only external HTTP calls are mocked.

Declarative Scenarios

Scenarios are data structures, not functions. Inspectable, composable, and versionable.

Parallel Execution

Each test gets isolated scenario state via test IDs. Run hundreds of tests simultaneously.

Zero Production Code

Conditional exports eliminate all Scenarist code from production builds.

When scenarios don’t match as expected, Scenarist’s built-in logging shows you exactly what’s happening:

Terminal window
# See which mocks are matching your requests
SCENARIST_LOG=1 pnpm test
09:49:09.715 DBG [test-checkout] 🎯 matching mock_candidates_found count=5 url="/api/cart"
09:49:09.716 INF [test-checkout] 🎯 matching mock_selected mockIndex=2 specificity=5

→ Full logging guide

When tests fail, inspect the current mock state directly from your Playwright tests using the debug fixtures:

import { test, expect } from './fixtures';
test('checkout flow', async ({ page, switchScenario, debugState }) => {
await switchScenario(page, 'checkout');
await page.goto('/cart');
await page.click('#add-item');
// Inspect current state captured by mocks
const state = await debugState(page);
console.log('Cart state:', state);
// → { 'cart.items': 1, 'cart.total': 29.99 }
});

For async workflows, wait for state to reach a condition:

test('approval flow', async ({ page, switchScenario, waitForDebugState }) => {
await switchScenario(page, 'approvalFlow');
await page.click('#submit-for-approval');
// Wait for backend state to update
const state = await waitForDebugState(
page,
(s) => s['approval.status'] === 'approved',
{ timeout: 10000 }
);
});

→ Full Playwright debug helpers guide

  1. Choose your framework — Follow the complete getting-started guide
  2. Read the philosophy — Understand the “test behavior, not implementation” approach
  3. Explore dynamic capabilities — Request matching, sequences, stateful mocks