Skip to content

Troubleshooting

This page covers common issues you may encounter when testing React Server Components with Scenarist, along with debugging tips.

Symptom: Tests fail with “no mock matched” or default responses instead of scenario-specific ones.

Cause: Server Component fetches don’t include the test ID header.

Fix: Always forward headers:

// ✅ Correct
const headersList = await headers();
const response = await fetch(url, {
headers: {
...getScenaristHeadersFromReadonlyHeaders(headersList),
},
});
// ❌ Wrong - missing header forwarding
const response = await fetch(url);

Pitfall 2: page.request Doesn’t Include Test ID

Section titled “Pitfall 2: page.request Doesn’t Include Test ID”

Symptom: API calls from page.request.post() don’t use the correct scenario.

Cause: page.request uses a separate HTTP context from the browser page.

Fix: Explicitly include the test ID header:

test('adds item to cart', async ({ page, switchScenario }) => {
const testId = await switchScenario(page, 'cartWithState'); // ✅ Capture testId
await page.request.post('http://localhost:3002/api/cart/add', {
headers: {
'Content-Type': 'application/json',
'x-scenarist-test-id': testId, // ✅ Explicitly include
},
data: { productId: 'prod-1' },
});
});

Symptom: Same response returned despite scenario changes.

Cause: Next.js caches fetch responses by default.

Fix: Disable caching for testable fetches:

const response = await fetch(url, {
headers: { ... },
cache: 'no-store', // ✅ Disable caching
});

Symptom: Same response returned on every request.

Cause: Requests going to a different mock or different test ID.

Fix: Ensure URL matches exactly and headers are forwarded:

// Scenario mock
{ method: 'GET', url: 'http://localhost:3001/github/jobs/:id', sequence: {...} }
// Component fetch - must match URL pattern
await fetch(`http://localhost:3001/github/jobs/${jobId}`, { // ✅ Matches pattern
headers: { ...getScenaristHeadersFromReadonlyHeaders(headersList) },
});

Scenarist has built-in logging that shows exactly what’s happening with scenario switching, mock matching, and state management. This is far more useful than generic network debugging.

Add a console logger to your Scenarist setup:

import { createScenarist, createConsoleLogger } from '@scenarist/nextjs-adapter/app';
const scenarist = createScenarist({
enabled: process.env.NODE_ENV === 'test',
scenarios,
// Enable logging at info level
logger: createConsoleLogger({ level: 'info' }),
});

This shows key events like scenario switches and mock selections:

12:34:56.789 INF 🎬 [test-abc-123] scenario | scenario_switched scenarioId=premiumUser
12:34:56.801 INF 🎯 [test-abc-123] matching | mock_selected mockIndex=2 specificity=5

When a mock isn’t being selected, enable debug logging for the matching category:

const logger = createConsoleLogger({
level: 'debug',
categories: ['matching'],
});

You’ll see:

  • mock_candidates_found - How many mocks could potentially match
  • mock_match_evaluated - Each mock’s evaluation result
  • mock_selected - Which mock was chosen and why
  • mock_no_match - When no mock matched (with the URL that failed)

For easy toggling without code changes:

import {
createScenarist,
createConsoleLogger,
noOpLogger,
} from '@scenarist/nextjs-adapter/app';
const scenarist = createScenarist({
enabled: process.env.NODE_ENV === 'test',
scenarios,
// Enable via SCENARIST_LOG=1
logger: process.env.SCENARIST_LOG
? createConsoleLogger({ level: 'debug' })
: noOpLogger,
});

Then run tests with logging:

Terminal window
SCENARIST_LOG=1 pnpm test

Ensure your mock URLs exactly match what your components are fetching:

// Mock definition
{
method: 'GET',
url: 'http://localhost:3001/products', // Must match exactly
// ...
}
// Component fetch - common mistakes:
await fetch('http://localhost:3001/products'); // ✅ Matches
await fetch('http://localhost:3001/products/'); // ❌ Trailing slash
await fetch('http://localhost:3001/Products'); // ❌ Case mismatch
await fetch('/products'); // ❌ Relative URL

5. Verify Header Forwarding in Server Components

Section titled “5. Verify Header Forwarding in Server Components”

Add temporary logging to verify headers are being forwarded:

export default async function MyPage() {
const headersList = await headers();
const scenaristHeaders = getScenaristHeadersFromReadonlyHeaders(headersList);
// Temporary debug logging
console.log('Scenarist headers:', scenaristHeaders);
const response = await fetch('http://localhost:3001/api/data', {
headers: {
...scenaristHeaders,
},
});
// ...
}

The @scenarist/playwright-helpers package provides fixtures to inspect test state directly from your Playwright tests:

// Import from your configured fixtures file
import { test, expect } from './fixtures';
test('checkout flow updates cart state', async ({ page, switchScenario, debugState }) => {
await switchScenario(page, 'cartWithState');
// Check initial state
const initialState = await debugState(page);
console.log('Initial state:', initialState);
await page.goto('/cart');
await page.getByRole('button', { name: 'Checkout' }).click();
// Verify state after action
const updatedState = await debugState(page);
console.log('Updated state:', updatedState);
expect(updatedState.checkoutStarted).toBe(true);
});

For async flows where state changes after a delay, use waitForDebugState:

test('polling updates job status', async ({ page, switchScenario, waitForDebugState }) => {
await switchScenario(page, 'jobPolling');
await page.goto('/jobs/123');
// Wait for state to indicate job completion
const state = await waitForDebugState(
page,
(s) => s.jobStatus === 'completed',
{ timeout: 10000 } // Wait up to 10 seconds
);
expect(state.jobStatus).toBe('completed');
});

IssueLikely CauseSolution
”No mock matched”Missing header forwardingAdd getScenaristHeadersFromReadonlyHeaders()
Wrong scenario responsepage.request missing test IDExplicitly pass x-scenarist-test-id header
Stale responsesNext.js cachingAdd cache: 'no-store' to fetch
Sequence stuckURL mismatch or wrong test IDVerify URL pattern and header forwarding
Intermittent failuresParallel test isolationEnsure each test uses unique test ID