@vizzly-testing/storybook
v0.8.0
Published
Storybook plugin for Vizzly - seamlessly integrate Storybook stories into your visual development workflow
Maintainers
Readme
@vizzly-testing/storybook
Seamlessly integrate your Storybook stories into Vizzly's visual development workflow. Iterate
locally with vizzly tdd, automatically create team builds from CI/CD, and collaborate on visual
changes with position-based comments and review rules.
Installation
pnpm install @vizzly-testing/storybookThe plugin is automatically discovered by the Vizzly CLI via the @vizzly-testing/* scope.
Storybook 10 Compatibility
The plugin works with the current stable Storybook 10 release. If you're upgrading an existing Storybook app to the latest major, keep these Storybook 10 requirements in mind:
.storybook/main.js|tsand related presets must be valid ESM- Storybook 10 requires Node
20.19+or22.12+ @storybook/addon-essentialsis zero-config in Storybook 10, so many apps no longer need to list it explicitly inaddons
Storybook's official migration guide: https://storybook.js.org/docs/releases/migration-guide
Quick Start
CLI Usage
# Capture screenshots from a static Storybook build
vizzly storybook ./storybook-static
# With custom viewports
vizzly storybook ./storybook-static \
--viewports "mobile:375x667,tablet:768x1024,desktop:1920x1080"
# With filtering
vizzly storybook ./storybook-static \
--include "components/**" \
--exclude "**/*.deprecated"
# With concurrency control
vizzly storybook ./storybook-static --concurrency 5Programmatic Usage
import { run } from '@vizzly-testing/storybook';
await run('./storybook-static', {
viewports: 'mobile:375x667,desktop:1920x1080',
concurrency: 4,
}, {
output: console,
});Configuration
Config File
Add a storybook section to your vizzly.config.js file:
// vizzly.config.js
export default {
comparison: {
threshold: 0.1,
minClusterSize: 2,
},
storybook: {
viewports: [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1920, height: 1080 },
],
browser: {
type: 'chromium',
headless: true,
args: ['--no-sandbox'],
},
screenshot: {
fullPage: true,
omitBackground: false,
timeout: 45000,
requestTimeout: 60000,
},
// Concurrency auto-detected from CPU cores (min 2, max 8)
// concurrency: 4,
include: 'components/**',
exclude: '**/*.test',
interactions: {
// Pattern-based hooks
'Button/*': async (page) => {
await page.hover('button');
},
'Tooltip/*': async (page) => {
await page.click('.tooltip-trigger');
},
},
},
};The shared top-level comparison section controls cloud/run thresholds. The
storybook section handles capture behavior, browser settings, viewports, and
per-story metadata.
Run vizzly init to generate a config file with sensible defaults.
Per-Story Configuration
You can configure specific stories by adding tags and vizzly parameters in your story files.
Use tags: ['vizzly-skip'] as the primary way to skip screenshot capture. The
parameters.vizzly.screenshot object can also carry custom properties, plus
threshold, minClusterSize, fullPage, omitBackground, timeout, and
requestTimeout when you need per-story overrides:
// Button.stories.js
export let Primary = {
args: { label: 'Click me' },
parameters: {
vizzly: {
viewports: [
{ name: 'mobile', width: 375, height: 667 },
],
beforeScreenshot: async (page) => {
await page.hover('button');
},
},
},
};
export let Disabled = {
args: { label: 'Disabled', disabled: true },
tags: ['vizzly-skip'], // Don't screenshot this story
};parameters.vizzly.skip is still supported for backwards compatibility.
Configuration Priority
Configuration is merged in this order (later overrides earlier):
- Default configuration (from plugin's
configSchema) - Config file (
storybookkey invizzly.config.js) - CLI options
- Per-story parameters
CLI Options
--viewports <list>- Comma-separated viewport definitions (format:name:WxH)--concurrency <n>- Number of parallel stories to process (default: auto-detected based on CPU cores, min 2, max 8)--browser <type>- Browser engine to use:chromium,firefox, orwebkit--include <pattern>- Include story pattern (glob)--exclude <pattern>- Exclude story pattern (glob)--config <path>- Path to custom config file--browser-args <args>- Additional Playwright browser arguments--headless- Run browser in headless mode (default: true)--no-headless- Run browser with a visible window--full-page- Capture full page screenshots (default: true)--no-full-page- Capture viewport-only screenshots--timeout <ms>- Screenshot capture timeout in milliseconds (default: 45000)--request-timeout <ms>- Vizzly screenshot request timeout in milliseconds
Interaction Hooks
Interaction hooks allow you to interact with stories before capturing screenshots.
Global Hooks (Pattern-Based)
// vizzly.config.js
export default {
storybook: {
interactions: {
'Button/*': async (page) => {
// Apply to all Button stories
await page.hover('button');
},
'Form/*': async (page) => {
// Apply to all Form stories
await page.fill('input[name="email"]', '[email protected]');
await page.click('button[type="submit"]');
},
'Dropdown/WithOptions': async (page) => {
// Specific story
await page.click('.dropdown-toggle');
},
},
},
};Per-Story Hooks
export let WithTooltip = {
parameters: {
vizzly: {
beforeScreenshot: async (page) => {
await page.hover('.info-icon');
await page.waitForSelector('.tooltip', { visible: true });
},
},
},
};Pattern Matching
Patterns support glob-like syntax:
*- Match any characters except/**- Match any characters including/Button/*- Match all Button storiescomponents/**- Match all stories under components**/*.deprecated- Match all deprecated stories
Screenshot Naming
Screenshots are named using the format:
ComponentName-StoryName@viewportNameExamples:
Button-Primary@mobileCard-WithImage@desktopComponents-Atoms-Input-Default@tablet
Visual Development Workflow
This plugin integrates Storybook into Vizzly's visual development workflow, enabling both local TDD iteration and seamless team collaboration. The plugin automatically detects which mode to use:
TDD Mode (Local Development)
When a TDD server is running, screenshots are compared locally for fast iteration:
# Start TDD server
vizzly tdd start
# Capture Storybook screenshots (automatically uses TDD mode)
vizzly storybook ./storybook-static
# View live results at http://localhost:47392Output:
ℹ 📍 TDD mode: Using local server
ℹ 📚 Found 5 stories in ./storybook-static
ℹ ✓ Components/Button/Primary@default
ℹ ✅ Captured 5 screenshots successfullyRun Mode (CI/CD & Cloud)
When a VIZZLY_TOKEN is set, screenshots are uploaded to the cloud for team review:
# Capture and upload to Vizzly cloud (automatically uses Run mode)
VIZZLY_TOKEN=your-token vizzly storybook ./storybook-staticOutput:
ℹ ☁️ Run mode: Uploading to cloud
ℹ 🔗 https://app.vizzly.dev/your-org/project/builds/...
ℹ 📚 Found 5 stories in ./storybook-static
ℹ ✓ Components/Button/Primary@default
ℹ ✅ Captured 5 screenshots successfully
ℹ 🔗 View results: https://app.vizzly.dev/your-org/project/builds/...Mode Detection
The plugin automatically chooses the mode:
- TDD mode - If a TDD server is running (
.vizzly/server.jsonfound) - Run mode - If
VIZZLY_TOKENenvironment variable is set - Warning - If neither is available, warns and skips screenshots
No need to wrap with vizzly run - the plugin handles everything!
Supported Storybook Versions
- Storybook v6.x through v10.x
Example Workflow
Build your Storybook:
pnpm run build-storybookCapture screenshots with Vizzly:
vizzly storybook ./storybook-staticReview in the Vizzly dashboard
Troubleshooting
Stories not found
Ensure your Storybook build has an index.json file:
ls storybook-static/index.jsonBrowser launch fails
Try adding browser arguments:
vizzly storybook ./storybook-static --browser-args "--no-sandbox,--disable-dev-shm-usage"Screenshots are blank
If your component needs time to render, wait for specific elements from your component to appear:
interactions: {
'**': async (page) => {
// Wait for your component's content to be visible
await page.waitForSelector('.your-component-class', { visible: true });
},
}License
MIT © Stubborn Mule Software
