Percy
01 / 02

Integration, Snapshots & Cloud Rendering

Percy: Integration, Snapshots & Cloud Rendering

Percy (now part of BrowserStack) is a cloud-hosted visual testing service that integrates INTO an existing test suite (Cypress, Playwright, Selenium) rather than replacing it -- functional tests verify behavior, Percy catches the purely-visual regressions those tests structurally can't detect.

Adding Percy to an Existing Suite

// cypress/support/e2e.js
import '@percy/cypress';

// cypress/e2e/dashboard.cy.js -- existing functional test, plus
// visual checks layered in at meaningful UI states
describe('Dashboard', () => {
  it('renders correctly for a logged-in user', () => {
    cy.login('user@example.com');
    cy.visit('/dashboard');
    cy.contains('Welcome back').should('be.visible'); // functional assertion

    // Visual check -- catches a CSS regression a functional
    // assertion structurally can't (button still "exists", but
    // might be invisible/overlapping due to a broken style)
    cy.percySnapshot('Dashboard - logged in');
  });

  it('shows the error state on a failed form submit', () => {
    cy.visit('/settings');
    cy.get('form').submit();
    cy.percySnapshot('Settings - validation errors', {
      widths: [375, 1280], // one call, multiple viewport comparisons
    });
  });
});

Running with the Percy CLI/Agent

# Wraps the actual test command -- starts a local agent that
# intercepts percySnapshot() calls and uploads the captured data
PERCY_TOKEN=xxx percy exec -- cypress run

# Percy is NOT literally taking a local screenshot -- the SDK
# captures the page's DOM structure + CSS at snapshot time, then
# uploads that to Percy's cloud, which renders it through whichever
# target browsers/widths are configured. This DOM-capture approach
# is exactly what enables ONE snapshot call to produce MULTIPLE
# cloud-rendered comparisons (cross-browser, cross-viewport).

Handling Non-Deterministic Content

// Percy captures whatever's in the DOM at snapshot time -- like
// any visual testing tool, eliminating non-determinism is the
// TEST's responsibility, not something Percy auto-detects/ignores.

// BAD: live timestamp, changes every run
cy.visit('/orders');
cy.percySnapshot('Orders list'); // false positive every time

// GOOD: mock data, deterministic snapshot
cy.intercept('GET', '/api/orders', { fixture: 'orders.json' });
cy.visit('/orders');
cy.percySnapshot('Orders list');

// For content outside the team's control (a third-party ad
// embed), MASK the region instead of trying to mock it
cy.percySnapshot('Homepage', {
  percyCSS: '.ad-slot { visibility: hidden; }',
});

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free