Quick Start

Get your first persona test running in under 5 minutes.

1. Install the package

Terminal bash
npm install personaspec @playwright/test
npx playwright install chromium

2. Initialize your project

Terminal bash
npx personaspec init

This creates a playwright.config.ts and an example test file in tests/personas/.

3. Run your first test

Terminal bash
npx playwright test

4. Generate a report

Terminal bash
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:

tests/personas/dev-dana.spec.ts TypeScript
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:

tests/personas/marcus-frustrated.spec.ts TypeScript
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:

  1. Start task timing with collector.startTask()
  2. Initialize success as false (prove success, don't assume)
  3. Navigate and screenshot the starting point
  4. Attempt the task as the persona would
  5. Evaluate and observe outcomes
  6. Capture result screenshot with context
  7. Record the task with collector.recordTask()
Task Test Example TypeScript
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:

Free Exploration Test TypeScript
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.

Terminal bash
npx personaspec init [options]

# Options:
  --force    Overwrite existing files

Creates:

  • playwright.config.ts - Configured for serial persona tests
  • tests/personas/first-visitor.spec.ts - Example test file
  • tests/utils/observation-collector.ts - Re-export wrapper

personaspec report

Generate an HTML report from test results.

Terminal bash
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.

Terminal bash
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.

Constructor TypeScript
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

definePersona(config)

Create a validated persona definition.

Signature TypeScript
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:

Presets Reference

PersonaSpec provides preset configurations for Behavioral Realism attributes. Use these as starting points.

interactionPatternDefaults

Human-scale timing presets for different user types:

emotionalBaselineDefaults

Starting emotional states for different scenarios:

cognitionDefaults

How different user types process information:

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:

Using Templates TypeScript
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'],
});

See the Personas page for detailed descriptions of each template.

Output Format

Test results are saved as JSON with this structure:

alex-observations.json JSON
{
  "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..."
    }
  ]
}

See It In Action

View real test results from 6 personas tested against a live site.

View Case Study