Skip to content

TypeScript Patterns

Type-safe scenario definitions that provide autocomplete in tests and catch errors at compile time.

Use cases:

  • Autocomplete: switchScenario(page, '...') suggests valid scenario IDs
  • Compile-time errors: Typos in scenario names caught immediately
  • Structure validation: Missing required fields caught at compile time
  • Refactoring safety: Rename scenarios confidently

Organize scenarios in a typed object:

import type { ScenaristScenarios } from '@scenarist/express-adapter';
export const scenarios = {
default: defaultScenario, // Required: 'default' key
success: successScenario,
error: errorScenario,
premiumUser: premiumUserScenario,
} as const satisfies ScenaristScenarios;

The as const satisfies ScenaristScenarios pattern is essential for type safety. Each part serves a distinct purpose:

// WITHOUT as const - scenario IDs become generic 'string'
const scenarios = {
default: defaultScenario,
premiumUser: premiumUserScenario,
};
type ScenarioId = keyof typeof scenarios;
// Result: string (not useful for autocomplete)
// WITH as const - scenario IDs are preserved as literal types
const scenarios = {
default: defaultScenario,
premiumUser: premiumUserScenario,
} as const;
type ScenarioId = keyof typeof scenarios;
// Result: 'default' | 'premiumUser' (enables autocomplete!)

satisfies ScenaristScenarios - Validates Structure

Section titled “satisfies ScenaristScenarios - Validates Structure”
// Using : type annotation WIDENS the type (loses literals)
const scenarios: ScenaristScenarios = {
default: defaultScenario,
premiumUser: premiumUserScenario,
};
type ScenarioId = keyof typeof scenarios;
// Result: string (annotation widened the type)
// Using satisfies VALIDATES without widening
const scenarios = {
default: defaultScenario,
premiumUser: premiumUserScenario,
} as const satisfies ScenaristScenarios;
type ScenarioId = keyof typeof scenarios;
// Result: 'default' | 'premiumUser' (validated AND preserved!)
  1. Autocomplete in tests: switchScenario(page, '...') suggests valid scenario IDs
  2. Compile-time errors: switchScenario(page, 'typo') fails immediately
  3. Structure validation: Missing fields caught at compile time
// In your Playwright tests:
await switchScenario(page, 'premiumUser'); // ✅ Autocomplete works
await switchScenario(page, 'typo'); // ❌ TypeScript error
PatternAutocompleteValidation
as const satisfies ScenaristScenarios
as const only
satisfies only
: ScenaristScenarios annotation

Create a type for valid scenario IDs:

export const scenarios = {
default: defaultScenario,
success: successScenario,
error: errorScenario,
} as const satisfies ScenaristScenarios;
// Extract scenario ID type
export type ScenarioId = keyof typeof scenarios;
// 'default' | 'success' | 'error'
// Use in helper functions
export function getScenario(id: ScenarioId) {
return scenarios[id];
}

Types are re-exported from all adapter packages:

// Express
import type { ScenaristScenario, ScenaristScenarios } from '@scenarist/express-adapter';
// Next.js App Router
import type { ScenaristScenario, ScenaristScenarios } from '@scenarist/nextjs-adapter/app';
// Next.js Pages Router
import type { ScenaristScenario, ScenaristScenarios } from '@scenarist/nextjs-adapter/pages';
lib/scenarios.ts
import type {
ScenaristScenario,
ScenaristScenarios,
} from '@scenarist/express-adapter';
// Define individual scenarios
const defaultScenario: ScenaristScenario = {
id: 'default',
name: 'Happy Path',
description: 'All external APIs succeed',
mocks: [
{
method: 'GET',
url: 'https://api.example.com/user',
response: { status: 200, body: { name: 'Test User' } },
},
],
};
const errorScenario: ScenaristScenario = {
id: 'error',
name: 'API Error',
description: 'External API returns error',
mocks: [
{
method: 'GET',
url: 'https://api.example.com/user',
response: { status: 500, body: { error: 'Server Error' } },
},
],
};
const premiumUserScenario: ScenaristScenario = {
id: 'premium-user',
name: 'Premium User',
description: 'User has premium tier',
mocks: [
{
method: 'GET',
url: 'https://api.example.com/user',
response: { status: 200, body: { name: 'Premium User', tier: 'premium' } },
},
],
};
// Export typed scenarios object
export const scenarios = {
default: defaultScenario,
error: errorScenario,
premiumUser: premiumUserScenario,
} as const satisfies ScenaristScenarios;
// Export scenario ID type for use in tests
export type ScenarioId = keyof typeof scenarios;
tests/example.spec.ts
import { test, expect } from '@playwright/test';
import type { ScenarioId } from '../lib/scenarios';
// Type-safe fixture
test.extend<{ switchScenario: (id: ScenarioId) => Promise<void> }>({
switchScenario: async ({ page }, use) => {
await use(async (id: ScenarioId) => {
await page.request.post('/__scenario__', {
data: { scenario: id },
});
});
},
});
test('handles premium user', async ({ page, switchScenario }) => {
await switchScenario('premiumUser'); // ✅ Autocomplete
await switchScenario('typo'); // ❌ TypeScript error
});

For many scenarios, organize by feature:

scenarios/auth.ts
export const authScenarios = {
loggedIn: loggedInScenario,
loggedOut: loggedOutScenario,
sessionExpired: sessionExpiredScenario,
};
// scenarios/payment.ts
export const paymentScenarios = {
paymentSuccess: paymentSuccessScenario,
paymentDeclined: paymentDeclinedScenario,
paymentPending: paymentPendingScenario,
};
// scenarios/index.ts
import { authScenarios } from './auth';
import { paymentScenarios } from './payment';
export const scenarios = {
default: defaultScenario,
...authScenarios,
...paymentScenarios,
} as const satisfies ScenaristScenarios;
export type ScenarioId = keyof typeof scenarios;
const scenarios = {
success: successScenario,
// ❌ Error: Property 'default' is missing
} as const satisfies ScenaristScenarios;
const badScenario: ScenaristScenario = {
id: 'bad',
// ❌ Error: Property 'name' is missing
// ❌ Error: Property 'description' is missing
mocks: [],
};
const scenario: ScenaristScenario = {
id: 'test',
name: 'Test',
description: 'Test scenario',
mocks: [
{
method: 'GET',
url: '/api/test',
// ❌ Error: Must have 'response' or 'sequence'
},
],
};