How to Find and Fix Visual Bugs on Websites
Find the element and condition behind a visual bug, fix its cause, and add screenshot checks to catch regressions before they reach production.
A visual bug is a difference between how a page should look and how it renders under a particular browser, viewport, or interaction state. To find its cause, reproduce the same conditions, inspect the affected element and its computed styles in browser DevTools, then change the source rule responsible and check the original case again. To prevent the bug from returning, add a reviewed screenshot comparison to your test workflow.
1. Make the visual bug reproducible
Before changing CSS, write down the conditions under which the defect appears. A screenshot is useful evidence, but by itself it may not show whether the issue depends on a viewport, browser, delayed asset, scroll position, or interaction.
- Page: record the full URL or route and whether the page requires authentication or seeded data.
- Browser: record the browser and version, plus the operating system if the issue may be environment-specific.
- Viewport: note the viewport width and height and browser zoom. Distinguish the viewport from the full page dimensions.
- State: record relevant clicks, form values, open menus, scroll position, and whether the issue occurs before or after assets load.
- Expected and actual: capture both when possible, under the same conditions, and describe the visible difference precisely.
Try to reproduce the issue from a clean page load. If it is intermittent, repeat the same steps and note what changes: timing, network activity, viewport, or state. This narrows the investigation before you start editing.
2. Find the element and inspect its styles
Use your browser’s developer tools to connect the visible defect to the live DOM and CSS. In Chrome, open DevTools and select the Inspect tool, then move over the faulty area and select the element. The highlighted node and its tooltip help identify dimensions, spacing, color, and typography. DevTools shows the runtime HTML and styles applied to the page, which can differ from what you expect from the source files. See [Chrome’s Inspect documentation](https://developer.chrome.com/docs/devtools/inspect-mode?hl=en) and [MDN’s guide to browser developer tools](https://developer.mozilla.org/en-US/docs/Learn_web_development/Howto/Tools_and_setup/What_are_browser_developer_tools).
- Select the smallest element that visibly exhibits the problem. If the wrong position or size belongs to its container, inspect the parent too.
- Review the element’s computed dimensions, margin, padding, border, font, line height, colors, and positioning.
- Check the parent and neighboring elements. A child may be correct on its own but constrained or displaced by a grid, flex container, overflow rule, or parent size.
- Look through active CSS rules for overrides, specificity conflicts, media queries, inherited properties, and styles applied by scripts.
- Temporarily edit a declaration in DevTools. If the page matches the expected result, you have a useful hypothesis to verify in the source.
Make one temporary change at a time. Changing several declarations together makes it harder to know which one explained the symptom. A DevTools edit is only a diagnostic; make the lasting fix in your source code or design system.
3. Check layout geometry and responsive rules
For a static mismatch, compare the faulty element’s box with the intended dimensions and its surrounding layout. Check the content box and border box, margins and padding, min/max constraints, overflow, positioning, and the parent layout mode. Confirm that the media query active at the reproduced viewport is the one you intended.
Common root causes include a width or height constraint, an unexpected margin, a breakpoint that does not cover the tested width, an overriding selector, or content that wraps differently because of font metrics. These are possibilities to investigate, not diagnoses: use the computed styles and a controlled DevTools edit to find the cause in your page.
Test nearby widths after identifying a responsive issue. A fix that corrects one exact width may create a gap or overlap just above or below a breakpoint.
4. Investigate bugs that move during loading
If content jumps as a page loads or after an interaction, investigate the rendering sequence rather than relying only on a still screenshot. In Chrome DevTools, open the Rendering panel, enable Layout Shift Regions, and refresh the page to highlight regions that shift. Rendering overlays such as layer borders can provide additional clues. Consult [Chrome’s Rendering performance documentation](https://developer.chrome.com/docs/devtools/rendering/performance).
Observe when the movement happens and what appears at that moment. For example, an image, font, or asynchronously inserted component may change the available layout space. Treat those as leads to check in the DOM and network timeline, not as a conclusion from the highlight alone.
5. Fix the source and verify the original case
Once a DevTools experiment supports a cause, make the corresponding change in the source stylesheet, component, or asset configuration. Then repeat the original reproduction steps with the original browser, viewport, zoom, and page state. Confirm that the symptom is gone and that the change has not introduced a related layout problem at other relevant viewport sizes or browsers.
- Check the element and its parent after the fix, not just the broad page impression.
- Re-test the interaction or load timing that triggered the issue.
- Inspect other breakpoints that use the changed rule.
- Keep the before-and-after capture with the bug report or change review when it helps explain the fix.
6. Add screenshot regression checks with Playwright
A visual regression check compares a rendered page with a reviewed reference screenshot. Playwright Test provides expect(page).toHaveScreenshot(): an initial run creates reference screenshots, and later runs compare the current render against them. Review the image diff before updating a baseline; accepting a new screenshot without review can encode an unintended change as the expected result. See [Playwright’s screenshot comparison documentation](https://playwright.dev/docs/test-snapshots).
Here is a runnable minimal setup for a page that is available without authentication:
npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium
Create playwright.config.js:
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests',
use: {
browserName: 'chromium',
viewport: { width: 1280, height: 800 },
},
reporter: 'list',
});
Create tests/homepage.spec.js:
const { test, expect } = require('@playwright/test');
test('homepage visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
animations: 'disabled',
});
});
Run the test:
npx playwright test
On the first run, Playwright creates a reference screenshot. Review that artifact to confirm the page is in the intended state. Subsequent runs compare the render to the reference. If a deliberate design change updates the expected appearance, inspect the diff and then update the baseline using the update workflow documented by Playwright.
Make the capture represent a stable page state
- Wait for the condition that matters. Navigate to the page and wait for a meaningful selector or app-ready state when the page renders asynchronously. Avoid arbitrary sleeps unless a real time-dependent behavior is what you intend to test.
- Control state. Set consistent data, authentication, locale, viewport, and interactions. A test that captures a different account or open menu on each run cannot provide a useful baseline.
- Handle animation deliberately. Disabling animations can reduce motion-related noise, but preserve animation when the animation itself is the behavior under test.
- Use tolerance carefully. Playwright offers screenshot comparison options for pixel differences. Tolerance can help with small rendering variation, but a setting broad enough to hide a real defect weakens the check.
- Mask only volatile content when justified. A stylesheet or other screenshot options can suppress dynamic content. Do not hide the part of the page you need to protect from regressions.
- Keep baselines under review. A diff is evidence that renders changed, not proof that users see a bug. Decide whether the difference is expected and meaningful before changing the reference.
Playwright notes that screenshot output can vary with operating system, browser version, browser settings, hardware, power source, and headless mode. Keep the test environment consistent, including browser and operating system versions, to reduce unrelated differences. See [Playwright’s best practices](https://playwright.dev/docs/best-practices).
Debug an automated visual failure
When a screenshot assertion fails, inspect the test’s captured artifacts and the actual-versus-expected diff. If the failure depends on a preceding action, use Playwright Inspector to step through the test and examine locator behavior and actionability logs. For CI failures, Playwright recommends Trace Viewer, which shows the action timeline and DOM snapshots. UI Mode can also expose browser DevTools, network activity, DOM snapshots, and screenshot comparisons; its next documentation may change. See [Playwright debugging](https://playwright.dev/docs/debug), [best practices](https://playwright.dev/docs/best-practices), and [UI Mode](https://playwright.dev/docs/next/test-ui-mode).
7. Capture a reference screenshot for diagnosis
A screenshot can preserve the appearance of a route and viewport for a bug report, a before-and-after review, or a manual comparison. For a one-off local diagnosis, browser DevTools remains useful because it lets you inspect the live DOM and styles while reproducing interactions. A static image does not replace that inspection, especially when the defect moves or appears only after a particular action.
If you capture pages in an automated workflow, record the URL, viewport, browser environment, and state alongside the image. This makes a later comparison more informative. Avoid treating a changed screenshot as a defect until you have checked whether the difference is expected.
Or skip the browser setup
For a quick reference capture, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; see the API documentation for parameters and options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with the response identifying page verdict and billing status in headers. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up for 1,000 free screenshots a month, with no card required.
Troubleshooting common visual bug checks
| Symptom | Likely investigation | What to do |
|---|---|---|
| You cannot reproduce the bug | The original viewport, zoom, browser, data, or interaction state may be missing. | Re-check the bug report conditions and replay the same sequence from a fresh load. |
| The element looks correct in source CSS but wrong in the browser | A runtime override, inherited rule, media query, or script-applied style may be active. | Inspect computed styles and active rules on the rendered element and its parent. |
| The page looks right in DevTools but shifts after refresh | The defect may depend on load order or changing content dimensions. | Observe the page while it loads and use Layout Shift Regions to locate movement. |
| A screenshot test fails only in CI | The rendering environment, browser build, OS, or page state may differ. | Compare the environment and test data; inspect the trace and image diff before changing the baseline. |
| Visual tests fail intermittently | Dynamic content, animation, timing, or inconsistent state can change pixels. | Wait for a meaningful ready condition, stabilize test data, and suppress only irrelevant volatility. |
| Many screenshot differences are small and scattered | Browser or OS rendering differences can affect screenshot output. | Align browser and OS versions and review whether the difference is meaningful before adjusting tolerance. |
| A baseline update makes the failure disappear | The changed image may have been accepted without determining whether the change was intended. | Review the diff and confirm the UI change is desired before updating the reference. |
Performance, reliability, and cost considerations
For a single bug, the fastest path is usually a focused reproduction and inspection of the affected element; broad visual test suites are most useful when repeated changes make regressions costly to find manually. Keep automated coverage focused on important routes and states, and make each capture deterministic enough to diagnose.
Screenshot comparisons depend on stable inputs and rendering environments. Pin browser and operating system versions where practical, control viewport and test data, and retain failure artifacts so a mismatch can be investigated. A baseline is useful only while it reflects an intentionally reviewed state.
Playwright is the code-driven option described here: the dossier establishes its screenshot assertions and debugging features but does not establish a comparative price or performance ranking against other tools. Consider the engineering cost of maintaining baselines, test data, and CI environments as part of the workflow. For hosted reference captures, ScreenshotNeo’s pricing is $0 for 1,000 shots per month, $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000; yearly billing gives two months free. Every feature is on every plan.
Frequently asked questions
Can a screenshot alone tell me which CSS rule is wrong?
No. A screenshot shows rendered output, while DevTools connects that output to the live element and its applied styles. Use the image to locate and describe the symptom, then inspect the runtime page.
Should every visual difference fail a test?
A comparison can report a difference, but a person should review whether it is an unintended, user-visible change before treating it as a bug or updating the reference.
Why does the same page render differently on another machine?
Browser version, operating system, settings, hardware, and headless mode can change screenshot rendering. Keep the environment consistent when comparing baselines.
When should I use a layout shift overlay?
Use it when the defect involves movement during loading or interaction. For a stable but incorrect size, color, or spacing, inspect the element’s computed styles and layout first.


