Cypress Visual Testing for Lazy Loaded Pages
The calculator for this page is switched off while it is checked. The formula is below, and the worked example in the article shows how to use it by hand.
Cypress visual testing for lazy loaded pages works by combining Cypress’s ability to wait for network and DOM conditions with a visual regression tool that captures screenshots after the content is fully rendered. You enter the page URL, configure the lazy-load triggers (scroll, Intersection Observer, or click), and the test outputs a baseline image plus a diff image when the current render differs from the expected one. This article explains how to set that up reliably, what pitfalls to avoid, and how to choose between the main visual testing approaches.
Formula
The calculator above estimates how many screenshots you’ll need and the total waiting time for a lazy-loaded page. The formula is:
Total wait (s) = (wait / 1000) × (scroll steps + 1)
Where:
- wait – the milliseconds you pause after each scroll action (or after the initial load) to let images and components render.
- scroll steps – the number of times you scroll to trigger lazy loading (each step typically scrolls to the next viewport height).
- +1 – accounts for the initial screenshot before any scrolling.
The output shots is simply the number of scroll steps plus one (the initial viewport). This helps you plan how many visual snapshots you’ll store and compare.
Worked Example
Using the default values from the calculator: URL is https://example.com, wait is 1500 ms, scroll steps is 5.
First, compute the number of screenshots:
shots = 5 + 1 = 6
Now compute the total wait time:
total = (1500 / 1000) × (5 + 1) = 1.5 × 6 = 9 seconds
So you’ll take 6 screenshots and spend about 9 seconds waiting (not counting the actual scrolling time). This matches the calculator’s output.
Why Lazy Loading Breaks Naive Visual Tests
Lazy loading is a performance technique where images, iframes, or components are not fetched until the user scrolls near them. A standard Cypress cy.screenshot() only captures the current viewport, and if the content hasn’t been triggered, the screenshot shows empty boxes or placeholder backgrounds. Even full-page screenshots in Cypress (which stitch together multiple viewport captures) can miss content that loads after the scroll event fires. The core problem is timing: the visual test must wait for the network request to complete and the DOM to update before taking the screenshot.
Another issue is that lazy loading often depends on an IntersectionObserver, which only fires when an element enters the viewport. If you take a screenshot before scrolling, the observer never triggers, so the image is blank. This is why a simple “wait for the page to load� command is insufficient.
How to Set Up a Reliable Cypress Visual Test for Lazy Loaded Pages
Here’s a step-by-step approach that works with most visual testing plugins (like Percy, Applitools, or Cypress Image Snapshot):
- Install a visual testing plugin – Choose one that integrates with Cypress. For example,
cypress-image-snapshotis a popular open-source option, while Percy and Applitools are cloud-based. - Disable lazy loading in the test environment – Many sites have a query parameter or a cookie to disable lazy loading (e.g.,
?lazy=false). If that’s available, use it. This is the simplest and most reliable method. - If you can’t disable it, simulate scrolling – Use
cy.scrollTo('bottom')or a loop that scrolls by viewport height. After each scroll, wait for a network idle state usingcy.intercept()to track image requests, then wait a small fixed delay (e.g., 500 ms) for rendering. - Use
cy.waitForNetworkIdle()if available – Some plugins provide a command that waits until no network requests have been made for a specified time. This is more robust than a fixed wait. - Take screenshots after each scroll – For a full-page visual, either stitch them together (if your tool supports it) or take separate screenshots per viewport and compare them individually.
- Set a generous timeout – Lazy-loaded images can be slow, especially on test environments. Set
defaultCommandTimeoutto 10000 ms or more.
Common Pitfalls and How to Avoid Them
- Fixed waits are flaky – A 3-second wait might work on your machine but fail on a slow CI server. Prefer waiting for specific network requests or elements to be visible.
- Scroll events don’t trigger IntersectionObserver – If you scroll too fast, the browser may skip the intersection. Use
cy.scrollTo(0, y)with a small duration, or usecy.get('selector').scrollIntoView()to ensure the element is in view. - Full-page screenshots miss lazy content – The built-in
cy.screenshot({ fullPage: true })scrolls automatically, but it may not wait for images to load after each scroll. Use a custom command that waits after each scroll step. - Baseline images become stale – When you change the lazy-load threshold or add new images, the baseline must be updated. Make it a habit to review and update baselines after intentional UI changes.
Comparing Cypress Visual Testing Tools
There are two main categories: open-source local tools and cloud-based services.
- Open-source (cypress-image-snapshot) – Stores images in your repo, free, but you must manage the baselines and handle minor pixel differences manually. It’s good for small projects.
- Cloud-based (Percy, Applitools, Chromatic) – They handle image diffing, provide a web dashboard, and offer features like layout diffing (ignoring text changes) and cross-browser testing. They cost money but save time on CI.
For lazy loaded pages, cloud tools often have built-in “scroll and capture� options that handle the waiting automatically. For example, Percy’s cy.percySnapshot() can be configured to scroll the full page, but you still need to ensure the lazy content is triggered before calling it.
Best Practices for Stable Visual Tests
- Use a fixed viewport size – Set
cy.viewport(1280, 720)before each test to avoid layout shifts. - Mock network requests – If possible, stub image responses with
cy.interceptto return a known placeholder. This makes tests faster and more deterministic. - Disable animations – CSS animations can cause false diffs. Add a style that disables transitions and animations in the test environment.
- Wait for the last image to load – If you know the number of images, you can wait for
cy.get('img').should('have.length', N)and then check that each is naturalWidth > 0. - Use a dedicated test environment – Don’t run visual tests against production data that changes frequently. Use a staging server with fixed content.
Frequently Asked Questions
Q: Can I use Cypress’s built-in screenshot for lazy loaded pages?
Yes, but you must scroll and wait manually. The built-in cy.screenshot() doesn’t wait for lazy content. Use cy.scrollTo('bottom') and then wait for a network idle before taking the screenshot.
Q: How do I wait for all images to load in Cypress?
You can use cy.intercept('GET', '**/*.jpg').as('image') and then wait for multiple aliases, or use a custom command that checks document.images and waits until all have complete === true.
Q: What is the difference between a full-page screenshot and a viewport screenshot?
A full-page screenshot captures the entire page by stitching multiple viewport captures together. A viewport screenshot only captures what is visible. For lazy loaded pages, a full-page screenshot may miss content that loads after the scroll, so you often need to take multiple viewport screenshots.
Q: Should I disable lazy loading in tests?
If possible, yes. It makes tests faster and more reliable. Many frameworks allow a query parameter to disable lazy loading. If not, you must simulate scrolling.
Q: Why does my visual test pass locally but fail on CI?
CI machines are often slower, so lazy content may not load in time. Increase timeouts, use network idle waits, and ensure your CI environment has a stable network connection.
Q: Can I use Cypress with Percy for lazy loaded pages?
Yes, but you need to trigger the lazy loading before calling cy.percySnapshot(). Percy’s full-page snapshot will scroll, but it may not wait for images. Use a custom command to scroll and wait first.
Related
Formula used
in url | Page URL | - | https://example.com | 0 in wait | Wait after load (ms) | - | 1500 | 0 in scroll | Scroll steps | - | 5 | 1 out shots | Screenshots taken | - | scroll + 1 | 0 out total | Total wait (s) | - | (wait / 1000) * (scroll + 1) | 1
Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.