Skip to main content
Visual assertions are the flakiest part of Playwright test suites. Stably augments the Playwright runner with AI-powered screenshot prompts that understand intent, tolerate dynamic content, and explain failures—no golden baseline required.
Why not vanilla Playwright? expect(page).toHaveScreenshot() excels for static pages with reliable baselines. For highly dynamic UIs, Stably’s prompt assertions cover intent-based validation that Playwright’s pixel diffing cannot reliably express.

Installation

Install the Stably Playwright test integration and import it in place of the Playwright test runner:
import { test, expect } from '@stablyai/playwright-test';
The API surface stays the same as Playwright, but you gain access to AI assertions.

toMatchScreenshotPrompt

Use prompt-based visual assertions to validate your UI without maintaining golden screenshots.

Basic Usage

import { test, expect } from '@stablyai/playwright-test';

test('personalized dashboard renders key metrics', async ({ page }) => {
  await page.goto('/dashboard');

  await expect(page).toMatchScreenshotPrompt(
    'Shows revenue trend chart for at least 6 months and spotlight card for this account',
    {
      fullPage: true, // optional, defaults to false
    }
  );
});

How It Works

toMatchScreenshotPrompt performs intelligent visual validation in three steps:
  1. Stabilization: Waits for the page to stabilize and captures the screenshot.
  2. AI Analysis: Evaluates the screenshot against your prompt using Stably’s vision model.
  3. Assertion Outcome: If the prompt is satisfied, the assertion resolves. Otherwise it throws with a natural-language explanation describing what the AI found missing or inconsistent.
Each assertion performs one AI call that completes in a few seconds, and failures surface the AI reasoning in the thrown Playwright assertion error.

Method Signature

await expect(page).toMatchScreenshotPrompt(
  prompt: string,
  options?: ScreenshotOptions
): Promise<void>
Parameters:
  • prompt - Human-readable description of what should appear on screen
  • options - Standard Playwright screenshot options (fullPage, animations, etc.)
The assertion resolves when the UI matches the intent described in the prompt. On failure it throws a Playwright assertion error with the AI-generated reasoning.

Common Use Cases

Validate Personalization
await expect(page).toMatchScreenshotPrompt(
  'Hero advertises "Welcome back, Taylor" with a check-in button below'
);
Check Conditional UI States
await expect(page.locator('.toast')).toMatchScreenshotPrompt(
  'Displays success toast with green icon and copy "Changes saved"'
);
Verify Data Visualizations
await expect(page.locator('#chart')).toMatchScreenshotPrompt(
  'Line chart trends upward with three milestone markers labeled M1, M2, M3'
);
These assertions stay resilient even as layout, colors, or supporting content evolve, as long as the core intent aligns with the prompt.

Advanced Options

All standard Playwright screenshot options are supported:
await expect(page).toMatchScreenshotPrompt(
  'Left nav shows an orange "Live" badge and the promo banner highlights AI upgrades',
  {
    animations: 'disabled',
    fullPage: true,
    clip: { x: 0, y: 0, width: 1280, height: 720 },
    timeout: 30000,
  }
);
Common options:
  • fullPage - Capture the full scrollable page
  • animations - Set to 'disabled' to disable CSS animations
  • clip - Capture a specific rectangular area
  • timeout - Maximum time to wait for stabilization

Scoping with Locators

Combine with Playwright locators to scope assertions to specific components:
// Assert on a specific element
await expect(page.locator('.header')).toMatchScreenshotPrompt(
  'Navigation bar with user profile and notifications icon'
);

// Assert on the entire page
await expect(page).toMatchScreenshotPrompt(
  'Dashboard with sidebar, header, and main content area'
);

When to Use

  • Use toMatchScreenshotPrompt when:
    • The page can change materially between runs while maintaining semantic intent
    • You need to validate invariants (e.g., “shows revenue chart”) even when the UI layout or styling changes
    • You have dynamic content (personalized data, A/B tests, real-time updates)
    • Layout and styling evolve frequently and describing intent is easier than maintaining golden snapshots
  • Use expect(page).toHaveScreenshot() when:
    • You have a stable, non-dynamic screen
    • Minor rendering differences are acceptable (Stably’s auto-heal handles font rendering, anti-aliasing, and subtle visual variations)
    • Pixel-perfect matching is important and the page structure rarely changes
Stably offers auto-heal for toHaveScreenshot() that handles minor rendering differences like font anti-aliasing and subtle layout shifts. However, for pages where major structural changes are acceptable as long as semantic invariants remain valid, use toMatchScreenshotPrompt instead. Prompt-driven assertions tolerate variance, provide contextual reasoning, and continue to work even when UI components are re-arranged or re-styled.

Upcoming Features

Model Configuration Select the vision model per project or per suite (coming soon). Prompt Result Caching Avoid repeat AI calls when the captured screenshot matches a stored cache key—AI evaluation will only run when the image actually changes (coming soon).
Future versions will let you provide a cache name or enable caching globally. When cached, the SDK will only call AI if the newly captured frame differs from the previous cached copy.

toMatchAriaPrompt (Coming Soon)

Stably is expanding prompt assertions beyond screenshots to support semantic validation directly on accessibility trees.

Method Signature

await expect(page).toMatchAriaPrompt(
  prompt: string,
  options?: {
    cache?: {
      name: string;
    } | true;
  }
): Promise<{ success: boolean; reason?: string }>
This method will validate UI semantics and structure without requiring visual screenshots, enabling faster and more accessible test assertions.

Best Practices

  • Write intent-focused prompts: Mention critical UI elements (primary CTA, key metrics, legal copy) rather than pixel-perfect descriptions.
  • Stabilize before capture: Pair assertions with network and animation stabilization waits to reduce UI churn.
  • Scope assertions: Use Playwright locators to focus on components that matter.
  • Be specific: Provide enough detail in prompts for the AI to differentiate success states.

Troubleshooting

Prompt is too vague Refine the description with salient visual cues. The AI needs enough detail to differentiate success states.
// ❌ Too vague
await expect(page).toMatchScreenshotPrompt('Shows dashboard');

// ✅ Specific
await expect(page).toMatchScreenshotPrompt(
  'Dashboard with revenue chart trending up, user count of 1,234, and green success banner'
);
Assertion feels slow Keep assertions scoped using locators, and watch for upcoming caching updates to accelerate reruns.
// Faster: scope to specific component
await expect(page.locator('.metric-card')).toMatchScreenshotPrompt(
  'Revenue: $12,345 with +15% indicator'
);
Unexpected failure reasoning Review the assertion error message—it often reveals missing UI elements or structural changes worth addressing.
try {
  await expect(page).toMatchScreenshotPrompt('Dashboard shows revenue card and usage chart');
} catch (error) {
  console.error('AI assertion failed:', error.message);
  // Example: "Expected spotlight card with user quota, but found subscription upsell banner instead."
  throw error;
}

References

I