Quick Start
Get your first persona test running in under 5 minutes.
1. Install the package
npm install personaspec @playwright/test
npx playwright install chromium
2. Initialize your project
npx personaspec init
This creates a playwright.config.ts and an example test file in tests/personas/.
3. Run your first test
npx playwright test
4. Generate a report
npx personaspec report test-results/alex-observations.json
AI Analysis (Optional)
Get AI-powered UX insights by running:
ANTHROPIC_API_KEY=sk-ant-... npx personaspec analyze test-results/alex-observations.json
Writing Tests
PersonaSpec tests follow a structured pattern that captures meaningful user journey data.
Define Your Persona
Start by defining who is using your site. Use definePersona() or one of the built-in templates:
import { test } from '@playwright/test';
import { ObservationCollector, definePersona } from 'personaspec';
const persona = definePersona({
name: 'Dana',
role: 'Developer Evaluating',
background: 'Senior frontend dev, skeptical of new tools, has 15 minutes',
goals: [
'Understand if worth the learning curve',
'Find runnable code examples',
'Assess implementation effort',
],
behaviors: [
'Skims headings, jumps to code',
'Looks for quickstart guides',
],
});
const collector = new ObservationCollector({
outputDir: './test-results',
persona,
});
Behavioral Realism (Optional)
For human-realistic testing, add interaction patterns, emotional state, and cognitive profiles using built-in presets:
import {
definePersona,
interactionPatternDefaults,
emotionalBaselineDefaults,
cognitionDefaults,
} from 'personaspec';
const persona = definePersona({
name: 'Marcus',
role: 'Frustrated Customer',
background: 'Has been trying to resolve an issue for days...',
goals: ['Get immediate help', 'Talk to a real person'],
behaviors: ['Impatient with slow responses', 'Will abandon if frustrated'],
// Human-scale timing (even impatient users don't click 100x/sec)
interactionPatterns: interactionPatternDefaults.impatient,
// Emotional state
emotionalBaseline: {
frustrationLevel: 60, // Already frustrated
frustrationEscalation: 'volatile',
trustLevel: 'skeptical',
urgency: 'urgent',
},
// How they process information
cognition: cognitionDefaults.scanner,
// What they compare against
priorExperience: {
referenceProducts: ['Zendesk', 'Intercom'],
expectedPatterns: ['Live chat indicator', 'Response time estimate'],
delighters: ['Instant response', 'No queue'],
petPeeves: ['Long wait times', 'Generic responses'],
},
// Session context
sessionContext: {
isReturning: true,
priorActivity: 'Failed email attempts',
distractionLevel: 'low',
timeContext: 'midday-busy',
},
});
Write Task Tests
Each task test follows the 7-step pattern:
- Start task timing with
collector.startTask() - Initialize success as false (prove success, don't assume)
- Navigate and screenshot the starting point
- Attempt the task as the persona would
- Evaluate and observe outcomes
- Capture result screenshot with context
- Record the task with
collector.recordTask()
test.describe.configure({ mode: 'serial' });
test.describe(`${persona.name} - ${persona.role}`, () => {
test.afterAll(async () => {
await collector.save();
});
test('find code examples quickly', async ({ page }) => {
// 1. Start timing
collector.startTask();
// 2. Initialize success as false
let success = false;
// 3. Navigate and screenshot
await page.goto('https://example.com');
collector.trackPageLoad();
await collector.screenshot(page, 'homepage',
'Dana lands on homepage, scanning for code');
// 4. Attempt the task
const codeBlock = page.locator('pre code').first();
// 5. Evaluate and observe
if (await codeBlock.isVisible({ timeout: 5000 })) {
success = true;
collector.observe('success', 'Code visible on homepage', 'Homepage');
} else {
collector.observe('frustration', 'No code examples found', 'Homepage');
}
// 6. Result screenshot
await collector.screenshot(page, 'code-search-result',
'After searching for code examples');
// 7. Record the task
collector.recordTask('find code examples quickly', success,
success ? 'Found on homepage' : 'Code not visible');
});
});
Add Free Exploration
End each persona with an exploration test that catches issues task-focused tests miss:
test('free exploration', async ({ page }) => {
collector.startTask();
await page.goto('https://example.com');
collector.trackPageLoad();
// Browse naturally based on persona behaviors
const links = await page.locator('nav a').all();
for (const link of links.slice(0, 3)) {
try {
await link.click();
collector.trackClick();
collector.trackPageLoad();
await collector.screenshot(page, `explore-${await link.textContent()}`,
'Exploring navigation');
} catch (e) {
collector.observe('note', 'Navigation click failed', page.url());
}
}
// Free exploration always "succeeds" - observations are the value
collector.recordTask('free exploration', true, 'Browsed site naturally');
});
CLI Reference
PersonaSpec includes three CLI commands for scaffolding, reporting, and AI analysis.
personaspec init
Scaffold a new PersonaSpec project with example files.
npx personaspec init [options]
# Options:
--force Overwrite existing files
Creates:
playwright.config.ts- Configured for serial persona teststests/personas/first-visitor.spec.ts- Example test filetests/utils/observation-collector.ts- Re-export wrapper
personaspec report
Generate an HTML report from test results.
npx personaspec report <results.json> [options]
# Options:
--output <file> Output filename (default: persona-report.html)
personaspec analyze
Send results to Claude for AI-powered UX analysis with vision.
npx personaspec analyze <results.json> [options]
# Options:
--api-key <key> Anthropic API key (or set ANTHROPIC_API_KEY env var)
--output <file> Output filename (default: analysis-report.md)
--model <model> Claude model (default: claude-sonnet-4-20250514)
--max-screenshots <n> Max screenshots to analyze (default: 10)
API Key Setup
Set your Anthropic API key as an environment variable to avoid passing it each time:
export ANTHROPIC_API_KEY=sk-ant-...
API Reference
ObservationCollector
The main class for collecting test data during persona-driven tests.
new ObservationCollector({
outputDir: string, // Where to save results
persona: PersonaDefinition, // From definePersona()
includeBase64?: boolean, // Include base64 screenshots (default: true)
screenshotFormat?: 'png' | 'jpeg', // Screenshot format (default: 'png')
})
Methods
| Method | Description |
|---|---|
screenshot(page, name, context) |
Capture screenshot with context for AI analysis |
observe(type, description, location) |
Record an observation (success, note, confusion, frustration) |
startTask() |
Start timing a task (call at beginning of each task test) |
recordTask(name, success, notes) |
Record task completion with results |
trackPageLoad() |
Track a page navigation event |
trackClick() |
Track a click interaction |
trackSearch() |
Track a search action |
trackBackNav() |
Track back navigation (high counts indicate confusion) |
addConsoleError(message) |
Log a console error from the page |
save() |
Save all collected data to JSON file |
reset() |
Clear collected data (keeps persona) |
definePersona(config)
Create a validated persona definition.
definePersona({
// Required - Core attributes
name: string,
role: string,
background: string,
goals: string[],
behaviors: string[],
// Optional - Behavioral Realism attributes
interactionPatterns?: InteractionPatterns,
emotionalBaseline?: EmotionalBaseline,
cognition?: CognitionProfile,
priorExperience?: PriorExperience,
sessionContext?: SessionContext,
}): PersonaDefinition
Observation Types
Use these consistently across all tests:
| Type | When to Use |
|---|---|
| ✓ success | Goal achieved smoothly, good UX discovered |
| ○ note | Neutral observation, suggestion for improvement |
| ! confusion | Unclear what to do next, feedback ambiguous |
| ✗ frustration | Goal blocked, feature missing, error encountered |
Presets Reference
PersonaSpec provides preset configurations for Behavioral Realism attributes. Use these as starting points.
interactionPatternDefaults
Human-scale timing presets for different user types:
| Preset | Scan Time | Retry Delay | Best For |
|---|---|---|---|
careful |
3-8 sec | 5-10 sec | First-time visitors, hesitant users |
normal |
1.5-4 sec | 3-6 sec | Average users, returning customers |
impatient |
0.5-2 sec | 2-4 sec | Frustrated customers, time-pressured users |
exploratory |
2-5 sec | 3-8 sec | Users learning a new interface |
expert |
0.2-1 sec | 1.5-3 sec | Power users who know the interface |
monitoring |
1-3 sec | 5-15 sec | Supervisors, periodic checkers |
emotionalBaselineDefaults
Starting emotional states for different scenarios:
| Preset | Frustration | Escalation | Trust | Urgency |
|---|---|---|---|---|
calm |
10 | patient | trusting | relaxed |
neutral |
30 | moderate | neutral | moderate |
frustrated |
60 | volatile | skeptical | urgent |
crisis |
80 | volatile | skeptical | crisis |
cognitionDefaults
How different user types process information:
| Preset | Reading | Decision Style | Focus | Scan Pattern |
|---|---|---|---|---|
careful |
slow | deliberate | 15 min | F-pattern |
balanced |
average | balanced | 10 min | F-pattern |
scanner |
scanner | impulsive | 5 min | center-first |
expert |
fast | impulsive | 60 min | F-pattern |
learner |
average | deliberate | 20 min | Z-pattern |
Mix and Match
You can combine presets or override individual properties. For example, use interactionPatternDefaults.impatient but set a custom maxRetries.
Persona Templates
Pre-built personas you can use directly or customize:
import { personaTemplates } from 'personaspec';
// Use default template
const visitor = personaTemplates.firstTimeVisitor();
// Customize the template
const custom = personaTemplates.firstTimeVisitor({
name: 'Jordan',
goals: ['Find pricing', 'Compare with competitors'],
});
| Template | Default Name | Focus |
|---|---|---|
firstTimeVisitor() |
Alex | New user evaluating the site |
powerUser() |
Sam | Experienced user valuing efficiency |
accessibilityAuditor() |
Jordan | Testing WCAG compliance |
designReviewer() |
Casey | Checking visual consistency |
skepticalEvaluator() |
Morgan | Needs proof before committing |
supportSeeker() |
Riley | Looking for help with an issue |
mobileUser() |
Taylor | Mobile-first, limited patience |
See the Personas page for detailed descriptions of each template.
Output Format
Test results are saved as JSON with this structure:
{
"persona": "Alex - First-Time Visitor",
"background": "New to the site with no prior context...",
"goals": ["Understand what this does within 10 seconds"],
"behaviors": ["Skims headings before reading"],
"session": {
"startTime": "2024-01-15T10:30:00Z",
"endTime": "2024-01-15T10:32:45Z",
"pagesVisited": 4,
"clickCount": 5,
"backNavCount": 1,
"consoleErrors": []
},
"tasks": [
{
"name": "understand site purpose",
"success": true,
"duration": 4500,
"notes": "Clear headline"
}
],
"observations": [
{
"type": "success",
"description": "Value prop clear in hero",
"location": "Homepage hero",
"timestamp": "2024-01-15T10:30:15Z"
}
],
"screenshots": [
{
"name": "homepage-initial",
"context": "First view of homepage",
"url": "https://example.com/",
"base64": "iVBORw0KGgo..."
}
]
}