Troubleshooting
This page covers common issues you may encounter when testing React Server Components with Scenarist, along with debugging tips.
Common Pitfalls
Section titled “Common Pitfalls”Pitfall 1: Missing Header Forwarding
Section titled “Pitfall 1: Missing Header Forwarding”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:
// ✅ Correctconst headersList = await headers();const response = await fetch(url, { headers: { ...getScenaristHeadersFromReadonlyHeaders(headersList), },});
// ❌ Wrong - missing header forwardingconst 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' }, });});Pitfall 3: Next.js Caching
Section titled “Pitfall 3: Next.js Caching”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});Pitfall 4: Sequence Not Advancing
Section titled “Pitfall 4: Sequence Not Advancing”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 patternawait fetch(`http://localhost:3001/github/jobs/${jobId}`, { // ✅ Matches pattern headers: { ...getScenaristHeadersFromReadonlyHeaders(headersList) },});Debugging Tips
Section titled “Debugging Tips”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.
1. Enable Scenarist Logging
Section titled “1. Enable Scenarist Logging”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=premiumUser12:34:56.801 INF 🎯 [test-abc-123] matching | mock_selected mockIndex=2 specificity=52. Debug Mock Matching Issues
Section titled “2. Debug Mock Matching Issues”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 matchmock_match_evaluated- Each mock’s evaluation resultmock_selected- Which mock was chosen and whymock_no_match- When no mock matched (with the URL that failed)
3. Environment Variable Pattern
Section titled “3. Environment Variable Pattern”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:
SCENARIST_LOG=1 pnpm test4. Check for URL Mismatches
Section titled “4. Check for URL Mismatches”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'); // ✅ Matchesawait fetch('http://localhost:3001/products/'); // ❌ Trailing slashawait fetch('http://localhost:3001/Products'); // ❌ Case mismatchawait fetch('/products'); // ❌ Relative URL5. 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, }, });
// ...}6. Debug State in Playwright Tests
Section titled “6. Debug State in Playwright Tests”The @scenarist/playwright-helpers package provides fixtures to inspect test state directly from your Playwright tests:
// Import from your configured fixtures fileimport { 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');});Quick Reference: Common Issues
Section titled “Quick Reference: Common Issues”| Issue | Likely Cause | Solution |
|---|---|---|
| ”No mock matched” | Missing header forwarding | Add getScenaristHeadersFromReadonlyHeaders() |
| Wrong scenario response | page.request missing test ID | Explicitly pass x-scenarist-test-id header |
| Stale responses | Next.js caching | Add cache: 'no-store' to fetch |
| Sequence stuck | URL mismatch or wrong test ID | Verify URL pattern and header forwarding |
| Intermittent failures | Parallel test isolation | Ensure each test uses unique test ID |
Next Steps
Section titled “Next Steps”- Testing RSC Overview - Return to the main RSC testing guide
- Data Fetching Patterns - Core patterns for RSC testing
- Debugging with Logs - Detailed logging configuration