How Can We Help?
Visual testing is one of the most powerful methods for catching unexpected UI regressions. But it’s only effective if your screenshots are stable and repeatable. If you’ve ever faced random visual diffs even when no code has changed, you’re not alone.
This article explains why screenshot inconsistencies happen, and how to fix them using Cypress, Selenium (Java), and Playwright β with ready-to-use code snippets.
π€ Why Do We Need Stable Screenshots?
Imagium excels at highlighting true visual regressions, but it depends on clean, stable screenshots unaffected by layout shifts, animations, or inconsistent rendering.
Unfortunately, many factors can cause subtle shifts:
- CSS animations or transitions still playing
- Hover effects triggered during scrolling
- Lazy-loaded images not rendering fast enough
- Sticky headers and elements behaving differently
- Browser sizes being inconsistent across environments
These factors lead to false positives, wasting time and eroding trust in your tests.
β What Makes Screenshots Stable?
To get truly consistent visual snapshots, your test setup must do the following:
- Fix the browser window size (e.g., 1920Γ1080)
- Disable all animations and transitions
- Suppress interactivity like hover effects
- Ensure lazy-loaded images or content are fully loaded
- Scroll the page to trigger content if necessary
Letβs explore how to do this in Cypress, Selenium, and Playwright.
Cypress Stable Screenshots
// Set browser size (must be consistent) cy.viewport(1920, 1080); // Call once at test start // Disable Animations cy.document().then((doc) => { const style = doc.createElement('style'); style.innerHTML = `* { animation: none !important; transition: none !important; }`; doc.head.appendChild(style); }); // Disable Interactivity cy.document().then((doc) => { const style = doc.createElement('style'); style.innerHTML = `body { pointer-events: none !important; }`; doc.head.appendChild(style); }); // Option 1: Force load lazy images cy.document().then((doc) => { doc.querySelectorAll('img[loading="lazy"]').forEach(img => { img.loading = 'eager'; const src = img.getAttribute('src'); if (src && !img.complete) { const temp = new Image(); temp.src = src; } }); }); // Option 2: Scroll to load content cy.scrollTo('bottom'); cy.wait(1000); cy.scrollTo('top');
Selenium Stable Screenshots
// Set fixed window size driver.manage().window().setSize(new Dimension(1920, 1080)); // Disable Animations ((JavascriptExecutor) driver).executeScript( "var s = document.createElement('style'); s.innerHTML = '* { animation: none !important; transition: none !important; }'; document.head.appendChild(s);" ); // Disable Interactivity ((JavascriptExecutor) driver).executeScript( "var s = document.createElement('style'); s.innerHTML = 'body { pointer-events: none !important; }'; document.head.appendChild(s);" ); // Option 1: Force load lazy images ((JavascriptExecutor) driver).executeScript( "document.querySelectorAll('img[loading=\\\"lazy\\\"]').forEach(function(img) {" + "img.loading = 'eager';" + "var src = img.getAttribute('src');" + "if (src && !img.complete) { var temp = new Image(); temp.src = src; } });" ); // Option 2: Scroll to load content JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); Thread.sleep(1000); js.executeScript("window.scrollTo(0, 0);");
Playwright Stable Screenshots
// Set consistent viewport size await page.setViewportSize({ width: 1920, height: 1080 }); // Disable Animations await page.addStyleTag({ content: `* { animation: none !important; transition: none !important; }` }); // Disable Interactivity await page.addStyleTag({ content: `body { pointer-events: none !important; }` }); // Option 1: Force load lazy images await page.evaluate(() => { document.querySelectorAll('img[loading="lazy"]').forEach(img => { img.loading = 'eager'; const src = img.getAttribute('src'); if (src && !img.complete) { const temp = new Image(); temp.src = src; } }); }); // Option 2: Scroll to load content await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.waitForTimeout(1000); await page.evaluate(() => window.scrollTo(0, 0));
π Best Practices Checklist
Step | Description |
---|---|
β Fix viewport size | Use 1920×1080 consistently for all tests |
β Disable animations | Avoid flickers and layout shifts |
β Disable pointer interactivity | Prevent mouse-based changes |
β Load all content | Especially lazy images or infinite scroll |
β Use consistent timing | Wait appropriately after scrolling |
π§ Final Thoughts
Stable screenshots are the foundation of trustworthy visual testing. Without a clean, consistent rendering environment, even the best visual diff tools will mislead you.
By applying the above steps in Cypress, Selenium, or Playwright, you ensure your screenshots reflect real UI changes β not test noise.
Note: While these JavaScript tweaks help ensure consistent and stable screenshots for visual testing, they may interfere with manual interactions during exploratory testing or debugging sessions. Disabling pointer events or animations can block mouse-based actions like clicking, hovering, or scrolling in real time. However, these changes are temporary and easily reversible β either by removing the injected styles manually or by refreshing the page, which will restore the original interactivity. In automated scripts (especially with Cypress), these limitations usually donβt apply, as interactions are programmatic and not dependent on pointer events.