Prerequisites

TypeScript is recommended but not required. Examples below use TypeScript.

1

Create the Observation Collector

A utility class to capture screenshots and observations

Create a file at tests/utils/ObservationCollector.ts that will track everything during test execution.

tests/utils/ObservationCollector.ts TypeScript
import { Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';

type ObservationType = 'success' | 'note' | 'confusion' | 'frustration';

interface Observation {
  type: ObservationType;
  description: string;
  location?: string;
  timestamp: string;
}

interface Screenshot {
  name: string;
  context: string;
  url: string;
  base64: string;
}

interface TaskResult {
  name: string;
  success: boolean;
  duration: number;
  notes?: string;
}

export class ObservationCollector {
  private persona: string;
  private outputDir: string;
  private observations: Observation[] = [];
  private screenshots: Screenshot[] = [];
  private tasks: TaskResult[] = [];
  private startTime: Date;
  private metrics = { pageLoads: 0, clicks: 0, backNavs: 0 };

  constructor(persona: string, outputDir = './test-results/personas') {
    this.persona = persona;
    this.outputDir = outputDir;
    this.startTime = new Date();
  }

  async screenshot(page: Page, name: string, context: string) {
    const buffer = await page.screenshot();
    this.screenshots.push({
      name,
      context,
      url: page.url(),
      base64: buffer.toString('base64')
    });
  }

  observe(type: ObservationType, description: string, location?: string) {
    this.observations.push({
      type,
      description,
      location,
      timestamp: new Date().toISOString()
    });
  }

  recordTask(name: string, success: boolean, duration: number, notes?: string) {
    this.tasks.push({ name, success, duration, notes });
  }

  trackPageLoad() { this.metrics.pageLoads++; }
  trackClick() { this.metrics.clicks++; }
  trackBackNav() { this.metrics.backNavs++; }

  async save() {
    const data = {
      persona: this.persona,
      session: {
        startTime: this.startTime.toISOString(),
        endTime: new Date().toISOString(),
        ...this.metrics
      },
      tasks: this.tasks,
      observations: this.observations,
      screenshots: this.screenshots
    };

    fs.mkdirSync(this.outputDir, { recursive: true });
    const filename = `${this.persona.toLowerCase().replace(/\s+/g, '-')}.json`;
    fs.writeFileSync(
      path.join(this.outputDir, filename),
      JSON.stringify(data, null, 2)
    );
  }
}

Tip

The base64 screenshots are embedded in the JSON output so you can send the entire file to a vision model for analysis without managing separate image files.

2

Define Your First Persona

Create a fictional user with specific goals and behaviors

Create a test file at tests/personas/dev-dana.spec.ts. We'll use "Dev Dana" - a developer evaluating PersonaSpec.

tests/personas/dev-dana.spec.ts TypeScript
import { test } from '@playwright/test';
import { ObservationCollector } from '../utils/ObservationCollector';

/**
 * Persona: Dev Dana - Developer Evaluating
 *
 * Background: Senior frontend developer at a Series B startup.
 *             Skeptical of new testing approaches, has 15 minutes
 *             between meetings to evaluate if this is worth adopting.
 *
 * Goals:
 *   - Understand if PersonaSpec is worth the learning curve
 *   - Find concrete, runnable code examples
 *   - Assess implementation effort for their team
 *
 * Behaviors:
 *   - Skims headings, jumps to code blocks
 *   - Looks for "getting started" or quickstart guides
 *   - Evaluates based on first impressions
 */

const collector = new ObservationCollector('Dev Dana - Developer Evaluating');

// Tests run in serial order (like a real user session)
test.describe.serial('Dev Dana evaluates PersonaSpec', () => {

  test.afterAll(async () => {
    await collector.save();
  });

  // Tasks defined in next step...
});

Good Persona Design

Make personas specific enough to guide test design. "A user" is too vague. "A skeptical senior developer with 15 minutes" tells you exactly how to structure the tests.

3

Write Task Tests

Each task represents something the persona wants to accomplish

Add task tests inside the describe.serial block. Each follows the 7-step pattern.

Task Test Example TypeScript
test('understand the core concept within 60 seconds', async ({ page }) => {
  // 1. Track start time
  const startTime = Date.now();

  // 2. Initialize success as false (prove it, don't assume)
  let success = false;
  let notes = '';

  // 3. Navigate and screenshot
  await page.goto('https://personaspec.dev');
  collector.trackPageLoad();
  await collector.screenshot(page, 'landing-first-look',
    'Dev Dana lands on homepage, scanning for value prop');

  // 4. Attempt the task as persona would
  const heroText = await page.locator('.hero').textContent();

  // 5. Evaluate outcome
  if (heroText?.includes('persona') && heroText?.includes('test')) {
    success = true;
    notes = 'Hero clearly mentions persona-driven testing';
    collector.observe('success', 'Value prop clear in hero section', 'Homepage');
  } else {
    notes = 'Could not quickly understand the core concept';
    collector.observe('confusion', 'Value prop not immediately clear', 'Homepage');
  }

  // 6. Capture result screenshot
  await collector.screenshot(page, 'landing-after-scan',
    'After scanning hero section for core concept');

  // 7. Record the task
  const duration = Date.now() - startTime;
  collector.recordTask('Understand core concept within 60 seconds', success, duration, notes);
});

test('find a runnable code example', async ({ page }) => {
  const startTime = Date.now();
  let success = false;
  let notes = '';

  // Look for "Get Started" link (developer instinct)
  const getStarted = page.getByRole('link', { name: /get started/i });

  if (await getStarted.isVisible({ timeout: 3000 })) {
    await getStarted.click();
    collector.trackClick();
    collector.trackPageLoad();
    await collector.screenshot(page, 'getting-started-page',
      'Navigated to Get Started, looking for code');

    // Look for code blocks
    const codeBlocks = page.locator('.code-block');
    if (await codeBlocks.count() > 0) {
      success = true;
      notes = `Found ${await codeBlocks.count()} code examples`;
      collector.observe('success', 'Code examples found on Get Started page');
    }
  } else {
    notes = 'Could not find Get Started link quickly';
    collector.observe('frustration', 'No obvious path to code examples');
  }

  await collector.screenshot(page, 'code-search-result',
    'Final state after searching for code examples');

  collector.recordTask('Find a runnable code example', success,
    Date.now() - startTime, notes);
});

The 7-step pattern ensures consistent, measurable results:

  • 1 Track start time for duration measurement
  • 2 Initialize success as false - prove success, don't assume it
  • 3 Navigate and screenshot the starting point
  • 4 Attempt the task as the persona would naturally try it
  • 5 Evaluate outcome and record observations
  • 6 Capture result screenshot with context
  • 7 Record the task with success, duration, notes
4

Add Free Exploration

Simulate browsing without a specific goal

End each persona file with an exploration test that catches issues task-focused tests miss.

Free Exploration Test TypeScript
test('free exploration - browse like a curious developer', async ({ page }) => {
  const startTime = Date.now();

  // Start on homepage
  await page.goto('https://personaspec.dev');
  collector.trackPageLoad();

  // Define exploration actions
  const actions = [
    async () => {
      await page.locator('a[href*="methodology"]').first().click();
      collector.trackClick();
      await collector.screenshot(page, 'explore-methodology',
        'Clicked methodology link to see details');
    },
    async () => {
      await page.goBack();
      collector.trackBackNav();
    },
    async () => {
      await page.locator('a[href*="personas"]').first().click();
      collector.trackClick();
      await collector.screenshot(page, 'explore-personas',
        'Checking out example personas');
    },
    async () => {
      await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
      await collector.screenshot(page, 'explore-scroll-bottom',
        'Scrolled to bottom to see all content');
    }
  ];

  // Execute each action with error handling
  for (const action of actions) {
    try {
      await action();
      await page.waitForTimeout(500); // Simulate human pace
    } catch (error) {
      collector.observe('note', `Exploration action failed: ${error}`);
    }
  }

  // Free exploration always "succeeds" - observations are the value
  collector.recordTask('Free exploration', true,
    Date.now() - startTime, 'Browsed homepage, methodology, and personas');
});
5

Run and Analyze

Execute tests and feed results to a vision model

Run your persona tests:

Terminal bash
npx playwright test tests/personas/dev-dana.spec.ts

This generates a JSON file at test-results/personas/dev-dana---developer-evaluating.json:

Sample Output

dev-dana---developer-evaluating.json JSON
{
  "persona": "Dev Dana - Developer Evaluating",
  "session": {
    "startTime": "2024-01-15T10:30:00Z",
    "endTime": "2024-01-15T10:32:45Z",
    "pageLoads": 4,
    "clicks": 5,
    "backNavs": 1
  },
  "tasks": [
    {
      "name": "Understand core concept within 60 seconds",
      "success": true,
      "duration": 4500,
      "notes": "Hero clearly mentions persona-driven testing"
    },
    {
      "name": "Find a runnable code example",
      "success": true,
      "duration": 8200,
      "notes": "Found 6 code examples"
    }
  ],
  "observations": [
    {
      "type": "success",
      "description": "Value prop clear in hero section",
      "location": "Homepage"
    },
    {
      "type": "success",
      "description": "Code examples found on Get Started page"
    }
  ],
  "screenshots": [
    {
      "name": "landing-first-look",
      "context": "Dev Dana lands on homepage, scanning for value prop",
      "url": "https://personaspec.dev/",
      "base64": "iVBORw0KGgoAAAANSUhEUgAA..."
    }
  ]
}

Vision Model Analysis

Feed the JSON file to Claude or another vision model with this prompt:

Vision Model Prompt
Here are screenshots and observations from a persona-driven test.

The persona is "Dev Dana - Developer Evaluating":
- Background: Senior frontend dev, skeptical of new approaches, 15 minutes to evaluate
- Goals: Understand if worth adopting, find code examples, assess implementation effort
- Behaviors: Skims headings, jumps to code blocks, looks for quickstart guides

Analyze each screenshot in context of this persona and their tasks.
Identify:
1. UX issues that would frustrate this specific developer
2. Accessibility problems visible in the screenshots
3. Design inconsistencies
4. Whether their goals appear achievable
5. Specific recommendations for improvement

The observations.json file is attached.

Next Steps