QA How-To
Cypress visual testing: Examples
Copy each cypress visual testing example for Percy setup, page and component snapshots, API states, responsive widths, themes, stable data, and CI runs.
27 min read | 2,592 words
TL;DR
The most useful Cypress visual testing example creates a known UI state, waits for data, fonts, and images, and then calls `cy.percySnapshot()` with a stable name and deliberate widths or scope. Build separate examples for meaningful states, and review every resulting diff before changing the baseline.
Key Takeaways
- Every snapshot example should establish deterministic state and prove readiness before capture.
- Use semantic snapshot names that identify feature, state, theme, and viewport intent.
- Scope component snapshots to reduce unrelated differences and review cost.
- Cover populated, empty, validation, and error states with explicit API fixtures rather than live data.
- Exercise representative responsive widths and long-content cases that create distinct layouts.
- Apply Percy-specific CSS narrowly to stabilize an unavoidable region, not to conceal product content.
- Run the same locked command locally and in CI, with the project token stored only as a secret.
A practical Cypress visual testing example must be reproducible, not merely produce a screenshot. The test needs controlled data, a defined viewport or set of widths, an observable ready condition, a semantic snapshot name, and a comparison service or plugin that manages the baseline.
This guide builds a runnable Percy-based Cypress suite and then expands it through page, component, responsive, modal, API-state, theme, long-content, font, and CI examples. Percy is used because its current Cypress SDK exposes a documented cy.percySnapshot() command. The test-design patterns also apply to other maintained integrations, although their command names and options differ.
Cypress's built-in cy.screenshot() captures an artifact but does not perform visual comparison. Do not substitute an image file for a baseline and review workflow.
TL;DR
cy.intercept('GET', '/api/projects', { fixture: 'projects.json' }).as('projects')
cy.visit('/projects')
cy.wait('@projects')
cy.get('[data-cy=project-card]').should('have.length', 4)
cy.get('[data-cy=loading]').should('not.exist')
cy.percySnapshot('Projects, populated', {
widths: [375, 768, 1280],
})
| Example | Main control | Snapshot intent |
|---|---|---|
| Responsive page | Fixed scenario data plus selected widths | Layout changes at breakpoints |
| Component | Explicit props plus scope |
One owned visual contract |
| Modal | Functional open-state assertion | Overlay, focus surface, and composition |
| Empty and error | Stubbed response per state | Non-happy-path UI |
| Time-dependent page | Frozen clock and fixed timestamps | Stable dates and countdowns |
| Theme | Theme set before page initialization | Token and contrast presentation |
1. Install and Configure the Example Suite
Install the documented Percy packages beside the existing Cypress dependency:
npm install --save-dev cypress @percy/cli @percy/cypress
Load the custom command from the end-to-end support entry:
// cypress/support/e2e.ts
import '@percy/cypress'
A Cypress configuration for the examples can use a local application on port 3000:
import { defineConfig } from 'cypress'
export default defineConfig({
viewportWidth: 1280,
viewportHeight: 800,
defaultCommandTimeout: 6000,
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.ts',
specPattern: 'cypress/e2e/**/*.cy.ts',
},
})
Create .percy.yml for project-wide snapshot defaults:
version: 2
snapshot:
widths:
- 375
- 1280
min-height: 1024
discovery:
network-idle-timeout: 100
The configuration keys above are part of Percy's version 2 configuration format. Per-snapshot options can override relevant defaults. Keep the token out of this file. Export it from a local secret manager or inject it in CI:
export PERCY_TOKEN=your_project_token
npx percy exec -- cypress run --spec 'cypress/e2e/visual/**/*.cy.ts'
Commit the dependency lockfile. A baseline created with one browser and dependency set should not be compared casually with a different local stack.
2. Cypress Visual Testing Example for a Stable Page
The first example captures a populated account page. It intercepts the data before visiting, waits for the matched request, asserts meaningful content, waits for the loading state to disappear, and only then captures.
describe('account visual states', () => {
it('captures the populated overview', () => {
cy.intercept('GET', '/api/account', {
statusCode: 200,
body: {
id: 'acct_101',
owner: 'Asha Rao',
plan: 'Team',
seatsUsed: 8,
seatsTotal: 10,
},
}).as('getAccount')
cy.visit('/account')
cy.wait('@getAccount').its('response.statusCode').should('eq', 200)
cy.get('[data-cy=account-owner]').should('have.text', 'Asha Rao')
cy.get('[data-cy=seat-usage]').should('contain.text', '8 of 10')
cy.get('[data-cy=loading]').should('not.exist')
cy.percySnapshot('Account overview, populated')
})
})
The functional assertions are not redundant. They establish that the intended state exists before Percy serializes it. Without them, an early snapshot of a skeleton could become an accepted baseline.
The API values are deliberately fixed. A live owner name or changing usage count would create a valid content diff on every run. The sample verifies visual handling of one controlled response. Separate API or end-to-end integration coverage should verify the real account service.
Name the snapshot for the user state rather than the test file. If this test moves during refactoring, Account overview, populated still maps to a product contract.
3. Responsive Cypress Visual Testing Example
A managed DOM snapshot can be rendered at several widths. Choose widths that exercise distinct layouts, such as collapsed navigation, stacked cards, and a desktop grid.
it('captures responsive pricing layouts', () => {
cy.intercept('GET', '/api/plans', { fixture: 'plans.json' }).as('plans')
cy.visit('/pricing')
cy.wait('@plans')
cy.get('[data-cy=pricing-card]').should('have.length', 3)
cy.get('[data-cy=pricing-grid]').should('be.visible')
cy.percySnapshot('Pricing, three plans', {
widths: [375, 768, 1280, 1440],
})
})
The service renders the captured state at those widths. This is different from writing four Cypress tests that each call cy.viewport(), and it can be efficient when page content responds entirely through CSS.
If the application fetches different data at load time based on viewport, sets device state in JavaScript only during initialization, or uses server-side user-agent rendering, create separate Cypress flows with the desired viewport set before visit. Confirm that each flow has a unique snapshot name.
Widths should come from the product's supported breakpoints, not from this sample. Add just below or above a breakpoint when edge behavior is important. Four widths that produce four distinct compositions are valuable; four adjacent desktop widths with identical layout often add review cost.
Functionally test responsive controls too. A screenshot can show a hamburger icon but cannot prove that it opens, receives focus, or traps focus correctly.
4. Scoped Component Cypress Visual Testing Example
Percy's scope snapshot option limits capture to a matching selector. This is useful when the surrounding test harness or unrelated page content should not participate in a component baseline.
The following React example assumes Cypress Component Testing and cy.mount() are already configured:
import { PricingCard } from '../../src/components/PricingCard'
describe('<PricingCard /> visual states', () => {
it('captures the recommended plan', () => {
cy.mount(
<div style={{ padding: 24, width: 420 }}>
<PricingCard
name="Team"
price="$49"
features={[
'10 projects',
'Unlimited test runs',
'Priority support',
]}
recommended
/>
</div>,
)
cy.get('[data-cy=pricing-card]').should('be.visible')
cy.get('[data-cy=recommended-badge]').should('have.text', 'Recommended')
cy.percySnapshot('Pricing card, recommended', {
scope: '[data-cy=pricing-card]',
widths: [420],
})
})
})
The wrapper gives the component deliberate breathing room and width. The scope keeps the result focused. Use production theme and font providers in the component mount command so isolation does not render a fake environment.
Add separate examples for visually distinct contracts, such as disabled, selected, validation error, and long-content states. Avoid snapshotting every boolean combination when several combinations produce identical appearance.
The Cypress component testing example guide covers framework mounting in more depth. Component snapshots and page snapshots have different jobs: one verifies the card, the other verifies how multiple cards compose at breakpoints.
5. Capture an Interactive Modal State
Visual suites should cover important states reached through interaction, not only initial page loads. Open the modal through the real control, prove its accessible state, then capture.
it('captures the delete confirmation modal', () => {
cy.intercept('GET', '/api/projects/prj_101', {
body: { id: 'prj_101', name: 'Payments API' },
}).as('project')
cy.visit('/projects/prj_101/settings')
cy.wait('@project')
cy.get('[data-cy=delete-project]').click()
cy.get('[role=dialog]')
.should('be.visible')
.and('have.attr', 'aria-modal', 'true')
.within(() => {
cy.contains('h2', 'Delete Payments API?').should('be.visible')
cy.get('[data-cy=confirm-delete]').should('be.enabled')
})
cy.percySnapshot('Project settings, delete confirmation')
})
The snapshot covers the dialog, overlay, background relationship, destructive action styling, and text wrapping. Functional assertions prove that it is represented as a modal and contains the expected controls. A separate keyboard test should verify focus placement, tab containment, Escape behavior, and focus restoration.
Do not call the snapshot immediately after .click() if the modal has an entering transition. Wait for the final product state. If animations prevent stable rendering, prefer an application test mode or narrowly targeted Percy CSS that places the modal in its final visual state.
Test critical variants separately, such as a long project name or a server error inside the dialog, when they change layout meaningfully.
6. Cover Populated, Empty, and Error API States
One route can have several visually important outcomes. Define each response explicitly so a reviewer knows exactly what a diff represents.
describe('orders visual states', () => {
it('captures the populated state', () => {
cy.intercept('GET', '/api/orders', { fixture: 'orders.json' }).as('orders')
cy.visit('/orders')
cy.wait('@orders')
cy.get('[data-cy=order-row]').should('have.length', 5)
cy.percySnapshot('Orders, populated')
})
it('captures the empty state', () => {
cy.intercept('GET', '/api/orders', { statusCode: 200, body: [] }).as('orders')
cy.visit('/orders')
cy.wait('@orders')
cy.get('[data-cy=empty-orders]').should('be.visible')
cy.get('[data-cy=order-row]').should('not.exist')
cy.percySnapshot('Orders, empty')
})
it('captures the service error state', () => {
cy.intercept('GET', '/api/orders', {
statusCode: 503,
body: { code: 'ORDERS_UNAVAILABLE' },
}).as('orders')
cy.visit('/orders')
cy.wait('@orders').its('response.statusCode').should('eq', 503)
cy.get('[role=alert]').should('contain.text', 'Orders are temporarily unavailable')
cy.percySnapshot('Orders, service unavailable')
})
})
These are genuinely different baselines. The empty state verifies spacing, illustration, message, and call to action. The error state verifies alert prominence and recovery affordance. Happy-path-only visual coverage misses the screens users most need to understand.
Keep response fixtures aligned with the published service schema. When the contract changes, update the adapter and visual scenario together through review. The Cypress cy.intercept examples explain precise route matching and response control.
7. Freeze Time and Date-Dependent Content
Clocks, relative dates, trial countdowns, and calendar selections change without code changes. Freeze browser time before the page initializes, and return fixed server timestamps in the response.
it('captures a trial banner with seven days remaining', () => {
cy.clock(Date.UTC(2026, 6, 13, 9, 0, 0))
cy.intercept('GET', '/api/subscription', {
body: {
plan: 'Trial',
trialEndsAt: '2026-07-20T09:00:00.000Z',
},
}).as('subscription')
cy.visit('/dashboard')
cy.wait('@subscription')
cy.get('[data-cy=trial-banner]').should('contain.text', '7 days remaining')
cy.percySnapshot('Dashboard, trial ending in seven days')
})
The browser clock controls client-side date calculations. The response fixes the server timestamp. If the backend renders the string into HTML before Cypress loads, browser clock control alone cannot stabilize it. Seed or stub the server boundary.
Set locale and timezone consistently in the test environment when formatted dates are visible. Decide whether different locale layouts deserve dedicated snapshots, especially for right-to-left languages and month names that change width.
Avoid hiding all dates with CSS. The date's layout may be the defect surface. Deterministic time preserves coverage while eliminating expected daily diffs.
8. Test Long Content and Localization Pressure
Happy-path text often fits neatly. Long names, translated labels, large amounts, and unbroken identifiers reveal overflow and truncation defects. Use deliberately challenging but realistic fixed content.
it('captures a table with long customer content', () => {
cy.intercept('GET', '/api/customers', {
body: [
{
id: 'cust_101',
name: 'Northwestern Regional Testing and Reliability Cooperative',
email: 'quality-automation-owner@example.test',
plan: 'Enterprise',
},
],
}).as('customers')
cy.visit('/customers')
cy.wait('@customers')
cy.get('[data-cy=customer-row]').should('have.length', 1)
cy.get('[data-cy=customer-name]').should('contain.text', 'Northwestern')
cy.percySnapshot('Customers, long content', {
widths: [375, 768, 1280],
})
})
Review whether the mobile state wraps, truncates, or changes column presentation according to design. The test should not decide visual policy through fragile CSS assertions. It provides controlled pressure and lets the approved baseline express the design outcome.
For localization, load the actual translation bundle and set locale through the supported application boundary. Do not replace English text with arbitrary repeated characters, since real languages have different word breaks, glyphs, and direction. Include at least one right-to-left snapshot if the product supports it.
Keep names and addresses fictional and safe for repository and service storage. Visual fixtures should never be copied from production customer records.
9. Capture Light and Dark Themes Correctly
Set the theme before application initialization to avoid capturing a transition or a flash of the default theme. The exact storage key is an application contract in this example.
const openDashboardWithTheme = (theme: 'light' | 'dark') => {
cy.visit('/dashboard', {
onBeforeLoad(win) {
win.localStorage.setItem('theme', theme)
},
})
cy.get('html').should('have.attr', 'data-theme', theme)
cy.get('[data-cy=dashboard]').should('be.visible')
}
it('captures the light dashboard', () => {
openDashboardWithTheme('light')
cy.percySnapshot('Dashboard, light theme')
})
it('captures the dark dashboard', () => {
openDashboardWithTheme('dark')
cy.percySnapshot('Dashboard, dark theme')
})
If the application follows prefers-color-scheme rather than storage, configure the browser or application test seam accordingly. Do not invent a local storage switch that production never reads.
Visual comparisons can catch missing token mappings, light icons on light backgrounds, and a component that remains in the wrong theme. They do not calculate accessibility contrast against a standard. Pair these snapshots with automated accessibility checks and design-token tests.
Keep both themes only if the product supports and maintains both. Duplicate snapshots without a support requirement double the review burden.
10. Stabilize Animation With Narrow Percy CSS
Percy supports percyCSS, CSS applied in its rendering environment. Use it to put a known animated element into a defined final state or hide a genuinely uncontrollable third-party region. Keep it narrow and explain why.
it('captures the final success animation state', () => {
cy.visit('/payment/success')
cy.get('[data-cy=success-panel]').should('be.visible')
cy.get('[data-cy=receipt-number]').should('have.text', 'RCT-2026-101')
cy.percySnapshot('Payment, success', {
percyCSS: `
[data-cy=success-icon] {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
`,
})
})
The CSS targets one application element and defines the state the snapshot intends to verify. It does not hide the payment panel or all animation globally. If the success animation itself is a product contract, test key frames at a lower level with a dedicated strategy rather than freezing it away.
Document per-snapshot CSS during review. A rule such as [data-cy=ad] { visibility: hidden } can be reasonable for a third-party ad outside team control. A rule that hides every chart, notification, and user name would erase valuable coverage.
Prefer deterministic application state first. Percy CSS is a surgical tool, not the primary stability mechanism.
11. Wait for Images and Fonts Before Capture
Missing fonts and late images can change every downstream pixel. Prove that visual assets needed by the snapshot are ready.
it('captures loaded product imagery', () => {
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('products')
cy.visit('/catalog')
cy.wait('@products')
cy.get('[data-cy=product-image]').should(($images) => {
const elements = $images.toArray() as HTMLImageElement[]
expect(elements, 'product images').to.have.length(4)
for (const image of elements) {
expect(image.complete, image.alt).to.eq(true)
expect(image.naturalWidth, image.alt).to.be.greaterThan(0)
}
})
cy.document().its('fonts.status').should('eq', 'loaded')
cy.percySnapshot('Catalog, four products')
})
The retryable callback reads image properties without adding Cypress commands. It fails until all four images are complete and have decoded enough to report a natural width. The document assertion checks the font set where the browser supports that API.
Use stable, reachable fixture assets. If Percy renders DOM in its own environment, it must be able to discover the required asset hosts and authorization. Configure allowed hosts or request headers according to the selected service's security guidance rather than placing credentials in URLs.
A broken image is often a real defect. Do not hide it as visual noise. Distinguish an inaccessible test asset from a product path failure through request logs and local reproduction.
12. Run the Visual Examples in GitHub Actions
Create one script so local and CI users invoke the same visual command:
{
"scripts": {
"test:visual": "percy exec -- cypress run --spec 'cypress/e2e/visual/**/*.cy.ts'"
}
}
The CI workflow installs locked dependencies, starts the app, waits for readiness, and injects the token from secrets:
name: Cypress visual examples
on:
pull_request:
jobs:
visual:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Start application
run: npm run dev -- --host 0.0.0.0 &
- name: Wait for application
run: npx wait-on http://127.0.0.1:3000
- name: Run visual suite
run: npm run test:visual
- name: Upload Cypress failure evidence
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-visual-failures
path: |
cypress/screenshots
cypress/videos
if-no-files-found: ignore
Install wait-on as a development dependency so npx uses the locked package instead of fetching an unreviewed tool at runtime. Match the app port and Node version to the repository. Protect secrets from untrusted fork workflows and avoid printing environment values.
The visual service contains comparison evidence, while Cypress artifacts explain failures that happen before capture. Keep both links accessible from the pull request.
13. Review the Example Portfolio as a Test Suite
A set of working examples can still become an unmaintainable portfolio. Review it with a simple matrix:
| Snapshot | State source | Widths | Scope | Owner |
|---|---|---|---|---|
| Account overview, populated | Stubbed account response | Default | Page | Account team |
| Pricing card, recommended | Component props | 420 | Card | Design system |
| Orders, empty | Stubbed empty array | Default | Page | Orders team |
| Dashboard, dark theme | Preloaded theme | Default | Page | Platform UI |
| Customers, long content | Stubbed boundary data | 375, 768, 1280 | Page | Customer team |
Delete obsolete snapshots and consolidate duplicates. Ensure each remaining capture has a reason, deterministic state, meaningful name, and owner. Reviewers should be able to connect a changed region to source code and product intent.
When a baseline changes, inspect every width and state. A desktop improvement can create a mobile overflow. An intentional color-token update can reveal a component that did not adopt the token. The diff is evidence to evaluate, not merely a gate to clear.
Use the Applitools vs Percy guide if the team is reassessing platform needs. The test scenarios remain valuable even if the snapshot provider changes.
Interview Questions and Answers
Q: Show the minimum Percy Cypress example.
Import @percy/cypress in the support file, reach a stable page state, and call cy.percySnapshot('Feature, state'). Run the test through npx percy exec -- cypress run with PERCY_TOKEN supplied securely. The stable state and review process are as important as the command.
Q: Why assert content before a visual snapshot?
The assertion proves that the intended scenario is ready and prevents a loading skeleton from becoming the baseline. It also produces a clearer functional failure when setup is wrong. The snapshot then focuses on appearance.
Q: When should you use the scope option?
Use it when a component or region is the owned visual contract and surrounding content would create unrelated diffs. The scope selector must be stable and uniquely match the intended region. Keep page-level tests for composition.
Q: How do you cover responsive states efficiently?
I select widths that produce distinct layouts and pass them to the snapshot integration when CSS drives responsiveness. If initial viewport changes application logic or data, I create separate flows with viewport set before navigation.
Q: What is a good use of percyCSS?
It can place one animated icon in its defined final state or hide one uncontrollable third-party region. The rule should be narrow, reviewed, and documented. It should not remove large areas simply to make the test pass.
Q: Why stub populated, empty, and error responses separately?
They are distinct user experiences with different layout and recovery controls. Explicit responses make each state reproducible and give each baseline a clear meaning. Live shared data cannot guarantee those states.
Q: How do you protect sensitive information in visual tests?
I use fictional fixtures, test accounts, and least-privilege CI secrets. I review what images, DOM, headers, and assets the service receives. Customer data, tokens, and private URLs never belong in snapshots or logs.
Q: What evidence do you retain when the test fails before snapshot upload?
I retain Cypress screenshots, video according to policy, command logs, and relevant request identifiers. Those artifacts distinguish application setup failures from visual comparison changes. The visual dashboard alone cannot explain a snapshot that was never captured.
Common Mistakes
- Taking the snapshot immediately after navigation without proving readiness.
- Using live names, counts, dates, or images that change between builds.
- Giving snapshots generic names such as
home 1or adding timestamps. - Assuming per-snapshot widths reproduce JavaScript behavior that only runs at initial load.
- Scoping so narrowly that overflow into neighboring layout cannot be observed.
- Hiding all dynamic content with Percy CSS.
- Capturing only populated happy paths and ignoring empty, validation, and error states.
- Using a fake theme switch that the real application does not support.
- Checking screenshot appearance without functional or accessibility assertions.
- Fetching unlocked CI helper packages at runtime.
- Putting the Percy token in repository configuration or exposing customer data in fixtures.
- Accepting a new baseline without inspecting every state and width.
Conclusion
Every Cypress visual testing example in this guide follows one pattern: declare the state, control its inputs, prove it is ready, capture it with a semantic name, and review the result. Different examples vary the surface, data, viewport, theme, or interaction, but they do not relax determinism.
Start with the populated, empty, and error states of one high-risk feature. Add one responsive page snapshot and focused component snapshots for its reusable pieces. Run them in the locked CI path, assign an owner, and expand only when reviewers continue to trust every diff.
Interview Questions and Answers
Walk through one complete visual test example.
I intercept the page's data with a scenario fixture, visit, wait for the alias, and assert the expected rows and absence of loading UI. Then I call a semantically named snapshot with deliberate widths. CI publishes the comparison for owner review.
Why use separate snapshots for empty and error states?
They have different messages, illustrations, actions, and accessibility needs. Separate fixtures make each state deterministic and separate names give the baseline a clear contract. Combining them would weaken diagnosis.
How would you visually test a React component?
I mount it with production providers and explicit props, assert its meaningful state, and capture a scoped snapshot. I add cases only for visually distinct variants and long-content boundaries. Page snapshots cover composition separately.
What is the risk of capturing before fonts load?
The fallback font can change glyph widths, line wrapping, and every element below the text. That can create widespread false differences or an incorrect baseline. I verify font readiness and asset access first.
How do you test a modal visually?
I open it through the real user control, assert role, visibility, content, and enabled actions, then capture the page state. Functional keyboard tests separately verify focus trapping, Escape, and restoration.
When is percyCSS acceptable?
It is acceptable for a narrow, documented rendering adjustment, such as fixing one animation at its intended endpoint. I prefer controlling application state first. Broad hiding rules reduce coverage and are rejected in review.
How do you make local and CI runs comparable?
I commit the lockfile, use the same package script, pin the application and capture environment, and inject only secrets at runtime. For local pixel tools, baseline and comparison run in the same container. Managed rendering reduces but does not eliminate state-control needs.
What should a reviewer inspect in a responsive diff?
The reviewer checks every affected width, breakpoint transitions, wrapping, overflow, alignment, hidden controls, and neighboring regions. A correct desktop change can still break mobile. Approval records why the new rendering is expected.
Frequently Asked Questions
What is a basic Cypress visual testing example with Percy?
Import `@percy/cypress`, load deterministic application state, assert readiness, and call `cy.percySnapshot('Feature, state')`. Execute Cypress through `npx percy exec -- cypress run` with a secure project token.
How do I take responsive Percy snapshots in Cypress?
Pass a `widths` array in the snapshot options, such as `{ widths: [375, 768, 1280] }`. Use product breakpoints and create separate initial-load flows when viewport changes JavaScript behavior or data.
How do I capture only one component with Percy and Cypress?
Pass a stable CSS selector through the `scope` snapshot option. Mount or navigate to a controlled component state first and assert that the scoped element is visible.
How can I visually test API error states in Cypress?
Stub the precise route with the intended error status and body, wait for the alias, assert the error UI, and capture a uniquely named snapshot. Keep it separate from populated and empty baselines.
How do I stabilize dates in Cypress visual tests?
Call `cy.clock()` before page initialization for client-side time and return fixed server timestamps from a fixture or stub. Also keep locale and timezone consistent when formatted values are visible.
What is percyCSS used for?
Percy-specific CSS changes rendering only in the Percy environment. Use it narrowly to set a defined animation state or handle one unavoidable dynamic region, and review the rule as test code.
Why should Cypress wait for images before a visual snapshot?
Late or broken images alter layout and create misleading baselines. Assert that required images are complete with a positive natural width, and verify that external assets are reachable by the rendering environment.