How Can We Help?

< All Topics

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:

  1. Fix the browser window size (e.g., 1920Γ—1080)
  2. Disable all animations and transitions
  3. Suppress interactivity like hover effects
  4. Ensure lazy-loaded images or content are fully loaded
  5. 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

StepDescription
βœ… Fix viewport sizeUse 1920×1080 consistently for all tests
βœ… Disable animationsAvoid flickers and layout shifts
βœ… Disable pointer interactivityPrevent mouse-based changes
βœ… Load all contentEspecially lazy images or infinite scroll
βœ… Use consistent timingWait 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.