Test Framework Integration
Vizzly works with any JavaScript test framework. You capture screenshots during your tests, and Vizzly handles the comparison and reporting.
How it Works
When you run vizzly run "npm test":
- Vizzly starts a local server
- Sets environment variables so your tests can find it
- Runs your test command
- Collects screenshots as tests call
vizzlyScreenshot() - Uploads everything for comparison
The key: your tests stay normal. You just add screenshot calls where you need them.
Environment Variables
The CLI sets these automatically. You don’t configure them yourself:
VIZZLY_SERVER_URL- Where to send screenshots (likehttp://localhost:47392)VIZZLY_BUILD_ID- Unique ID for this test runVIZZLY_ENABLED- Set totruewhen running undervizzly runVIZZLY_TDD- Set totruein TDD mode
In TDD mode, the client auto-discovers the server by looking for .vizzly/server.json in your project directory and parent directories. You can run tests directly without vizzly run and screenshots still work.
Playwright
Playwright’s screenshot API maps directly to Vizzly.
Basic Example
import { test, expect } from '@playwright/test';import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
test('homepage loads correctly', async ({ page, browserName }) => { await page.goto('/'); await expect(page.locator('h1')).toBeVisible();
const screenshot = await page.screenshot(); await vizzlyScreenshot('homepage', screenshot, { browser: browserName, viewport: page.viewportSize(), });});Properties like browser and viewport become part of the screenshot’s signature. Change the viewport size and Vizzly treats it as a different screenshot with its own baseline.
Helper Function
Create a wrapper to reduce repetition:
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
async function capturePlaywright(page, browserName, name, properties = {}) { const screenshot = await page.screenshot();
return vizzlyScreenshot(name, screenshot, { browser: browserName, viewport: page.viewportSize(), ...properties, });}
test('responsive homepage', async ({ page, browserName }) => { await page.goto('/');
// Desktop await page.setViewportSize({ width: 1920, height: 1080 }); await capturePlaywright(page, browserName, 'homepage-desktop', { device: 'desktop' });
// Mobile await page.setViewportSize({ width: 375, height: 667 }); await capturePlaywright(page, browserName, 'homepage-mobile', { device: 'mobile' });});Running Tests
# Basicvizzly run "npx playwright test"
# Custom build namevizzly run "npx playwright test" --build-name "PR #123"
# Wait for resultsvizzly run "npx playwright test" --waitCypress
Cypress works the same way but needs a custom command since it runs in the browser.
Setup
Add this to cypress/support/commands.js:
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';import { join } from 'path';
Cypress.Commands.add('vizzlyScreenshot', (name, options = {}) => { const testName = `${Cypress.spec.name}-${name}`;
cy.screenshot(testName, { capture: 'viewport', ...options.screenshotOptions });
const screenshotPath = join( Cypress.config('screenshotsFolder'), Cypress.spec.name, `${testName}.png` );
return cy.wrap( vizzlyScreenshot(name, screenshotPath, { browser: Cypress.browser.name, viewport: { width: Cypress.config('viewportWidth'), height: Cypress.config('viewportHeight') }, ...options.properties }) );});Pass a file path instead of a buffer. Vizzly reads the file for you.
Using the Command
describe('Homepage', () => { it('displays correctly on desktop', () => { cy.viewport(1920, 1080); cy.visit('/'); cy.get('h1').should('be.visible');
cy.vizzlyScreenshot('homepage-desktop', { properties: { page: 'homepage', device: 'desktop' } }); });
it('displays correctly on mobile', () => { cy.viewport(375, 812); cy.visit('/'); cy.get('h1').should('be.visible');
cy.vizzlyScreenshot('homepage-mobile', { properties: { page: 'homepage', device: 'mobile' } }); });});Running Cypress
vizzly run "npx cypress run"
# Specific browservizzly run "npx cypress run --browser chrome"
# With environmentvizzly run "npx cypress run" --environment "staging"Jest + Puppeteer
Combine Puppeteer’s automation with Jest’s test runner.
Basic Setup
import puppeteer from 'puppeteer';import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
describe('Visual Tests', () => { let browser; let page;
beforeAll(async () => { browser = await puppeteer.launch({ headless: 'new' }); });
beforeEach(async () => { page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); });
afterEach(async () => { await page.close(); });
afterAll(async () => { await browser.close(); });
test('homepage renders', async () => { await page.goto('http://localhost:3000'); await page.waitForSelector('h1');
const screenshot = await page.screenshot(); await vizzlyScreenshot('homepage', screenshot, { browser: 'chrome', viewport: page.viewport(), }); });});Puppeteer Helper
async function capturePuppeteer(page, name, properties = {}) { const screenshot = await page.screenshot();
return vizzlyScreenshot(name, screenshot, { browser: 'chrome', viewport: page.viewport(), ...properties, });}
test('responsive design', async () => { await page.goto('http://localhost:3000');
// Desktop await page.setViewport({ width: 1920, height: 1080 }); await capturePuppeteer(page, 'homepage-desktop', { device: 'desktop' });
// Mobile await page.setViewport({ width: 375, height: 812 }); await capturePuppeteer(page, 'homepage-mobile', { device: 'mobile' });});Running Jest
vizzly run "npm test"
# Specific filevizzly run "npx jest visual.test.js"
# Custom configvizzly run "npx jest --config jest.visual.config.js"Selenium WebDriver
Cross-browser testing with WebDriver.
import { Builder, By } from 'selenium-webdriver';import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
describe('Cross-Browser Tests', () => { let driver;
beforeEach(async () => { driver = await new Builder() .forBrowser('chrome') .build();
await driver.manage().window().setRect({ width: 1920, height: 1080 }); });
afterEach(async () => { await driver.quit(); });
test('homepage in Chrome', async () => { await driver.get('http://localhost:3000');
const screenshot = await driver.takeScreenshot();
await vizzlyScreenshot('homepage-chrome', screenshot, { browser: 'chrome', viewport: '1920x1080', }); });
test('specific element', async () => { await driver.get('http://localhost:3000');
const element = await driver.findElement(By.css('[data-testid="hero"]')); const screenshot = await element.takeScreenshot();
await vizzlyScreenshot('hero-component', screenshot, { browser: 'chrome', component: 'hero', }); });});WebDriver’s takeScreenshot() returns a base64 string. Pass it directly - Vizzly handles both buffers and base64.
WebdriverIO
Modern WebDriver with simpler syntax.
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
describe('Visual Tests', () => { it('captures homepage', async () => { await browser.url('/');
const screenshot = await browser.takeScreenshot();
await vizzlyScreenshot('homepage', screenshot, { browser: browser.capabilities.browserName, viewport: '1920x1080', }); });
it('captures component', async () => { await browser.url('/components');
const element = await $('[data-testid="hero"]'); const screenshot = await element.takeScreenshot();
await vizzlyScreenshot('hero-component', screenshot, { component: 'hero', browser: browser.capabilities.browserName, }); });});Best Practices
Naming Screenshots
Use consistent patterns that reflect what you’re testing:
// Pattern: page-component-stateawait vizzlyScreenshot('checkout-form-empty', screenshot, { page: 'checkout', component: 'form', state: 'empty',});
await vizzlyScreenshot('checkout-form-filled', screenshot, { page: 'checkout', component: 'form', state: 'filled',});Good names make diffs easier to understand.
Properties as Signatures
Properties become part of the screenshot’s identity. Same name but different viewport? Different screenshot.
// These are two separate screenshotsawait vizzlyScreenshot('homepage', screenshot, { viewport: { width: 1920, height: 1080 }});
await vizzlyScreenshot('homepage', screenshot, { viewport: { width: 375, height: 667 }});Keep properties consistent across test runs. If viewport changes between runs, Vizzly treats them as new screenshots.
Setting Thresholds
Use the threshold option to allow minor pixel differences:
await vizzlyScreenshot('dashboard', screenshot, { threshold: 2.0, // CIEDE2000 Delta E units browser: 'chrome',});Threshold uses CIEDE2000 color difference. Values around 1.0-3.0 work for most cases. Higher values allow more variation before flagging a diff.
Error Handling
Screenshots shouldn’t break your tests. The client disables itself after the first failure to prevent spam:
async function safeScreenshot(name, screenshot, properties = {}) { try { await vizzlyScreenshot(name, screenshot, properties); } catch (error) { console.warn(`Screenshot '${name}' failed:`, error.message); // Test continues }}In TDD mode, visual diffs log warnings but don’t throw errors. This lets you capture all screenshots before reviewing changes.
Creating Framework Helpers
Wrap vizzlyScreenshot with framework-specific defaults:
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
// Base wrapper with defaultsexport async function screenshot(name, imageBuffer, properties = {}) { return vizzlyScreenshot(name, imageBuffer, { threshold: 2.0, environment: process.env.NODE_ENV || 'test', ...properties, });}
// Playwright-specificexport async function playwrightScreenshot(page, browserName, name, properties = {}) { const imageBuffer = await page.screenshot();
return screenshot(name, imageBuffer, { browser: browserName, viewport: page.viewportSize(), ...properties, });}
// Puppeteer-specificexport async function puppeteerScreenshot(page, name, properties = {}) { const imageBuffer = await page.screenshot();
return screenshot(name, imageBuffer, { browser: 'chrome', viewport: page.viewport(), ...properties, });}Import from your helpers instead of using the client directly. Keeps things consistent.
Next Steps
- Client API Reference - Complete SDK documentation
- Parallel Builds - Sharding and parallel test execution
- CLI Commands - All CLI options and flags
- JavaScript SDK Overview - SDK architecture and concepts