How to Fix Blurry Text in Selenium Webpage Screenshots
Diagnose blurry Selenium screenshots by checking viewport size, device pixel ratio, image dimensions, scaling, and font loading—with runnable Python examples.
Start by checking the browser viewport, device pixel ratio (DPR), and the saved PNG’s actual dimensions. Selenium captures the browser’s rendered output; it does not have one setting that sharpens every screenshot. If the PNG is clear at its native size but looks soft when displayed larger, investigate resizing or resampling. If characters are missing or shaped differently, investigate font loading and differences in the browser environment.
This guide uses Python with Selenium for the main workflow, then shows equivalent screenshot checks in cURL, Python, and Node.js through ScreenshotNeo. Selenium’s official documentation describes window sizing and page or element screenshots; Chrome’s documentation explains DPR and CSS pixels. Selenium window and screenshot documentation · Chrome device mode and DPR.
1. Diagnose the kind of blur first
“Blurry text” can describe different problems. Identify which one you see before changing browser flags or image settings.
| What you see | First checks |
|---|---|
| Letters look soft or have fuzzy edges | Open the original PNG at native scale; compare its pixel dimensions with the viewport and DPR; check whether it was enlarged or recompressed. |
| Text looks too small after capture | Check the CSS viewport, the image’s display size, and whether a downstream process resized it. |
| Characters are missing, substituted, or shaped differently | Check web-font loading, installed fonts, browser version, and operating-system or container differences. |
| Only one component looks wrong | Capture that element separately and compare it with the full-window capture. |
| Results vary between runs | Hold browser, driver, operating system, headless mode, viewport, font environment, and capture timing constant. |
DPR is the ratio between physical pixels and CSS pixels. A CSS viewport does not by itself tell you the PNG dimensions: record both the page’s CSS dimensions and the saved file’s pixel dimensions. This relationship helps diagnose softness, but does not establish that a particular DPR or browser option will always produce sharper text.
2. Set a repeatable viewport and inspect DPR
Use the same browser window size on every run. Then query window.innerWidth, window.innerHeight, and window.devicePixelRatio immediately before capture. Selenium’s window API sets the browser window size; the page’s reported inner dimensions are useful for checking the CSS viewport actually available to content.
from pathlib import Path
from selenium import webdriver
URL = "https://example.com"
OUTPUT = Path("page.png")
options = webdriver.ChromeOptions()
# Add browser-specific options only when they are needed in your environment.
driver = webdriver.Chrome(options=options)
try:
driver.set_window_size(1440, 1000)
driver.get(URL)
metrics = driver.execute_script(
"return {width: window.innerWidth, height: window.innerHeight, "
"dpr: window.devicePixelRatio};"
)
print("Page metrics:", metrics)
saved = driver.save_screenshot(str(OUTPUT))
if not saved:
raise RuntimeError("Selenium did not save the screenshot")
print("Saved:", OUTPUT.resolve(), "bytes:", OUTPUT.stat().st_size)
finally:
driver.quit()
Install Selenium in the Python environment with python -m pip install selenium. A configured browser and compatible driver are also required. Selenium’s current driver management may handle driver setup for supported environments; follow the documentation for the browser and Selenium versions in use. This example fixes a window size and reports the page’s resulting viewport rather than assuming that the requested outer window dimensions equal the CSS viewport.
3. Check the PNG’s real dimensions and display scale
Inspect the original file rather than judging only a preview in a web page, editor, or chat client. Compare PNG pixel dimensions with the CSS viewport and DPR. A difference can be expected depending on the browser and capture path, so treat the measurements as diagnostic evidence rather than a universal formula.
For example, the following uses Pillow to report dimensions:
python -m pip install pillow
python -c "from PIL import Image; im=Image.open('page.png'); print(im.size, im.mode)"
View the PNG at 100% or native scale. If it appears sharp there but soft when placed in a larger layout, the display is resampling it. Keep the original capture, and avoid repeatedly enlarging, shrinking, or converting it through lossy formats when crisp text matters. PNG is lossless; converting a screenshot to JPEG can introduce compression artifacts around text.
4. Separate page rendering from capture scope
Selenium supports screenshots of the current browsing context and screenshots of individual elements. Capture a suspect element to determine whether the softness is already present in the browser-rendered region or appears only in a full-page display or later processing step.
from selenium.webdriver.common.by import By
# After navigating and waiting for the target page:
driver.save_screenshot("window.png")
element = driver.find_element(By.CSS_SELECTOR, "main article")
element.screenshot("article.png")
If both files show the same softness at native scale, focus on viewport, DPR, font loading, and environment consistency. If the element image looks clear but the full-window image does not, inspect how the full capture is being displayed, stitched, resized, or processed downstream. Element screenshots are also useful when a long page or a particular component is the only area under investigation.
5. Wait for fonts and page content before capture
A screenshot taken before a web font finishes loading may capture fallback text, which can have different shapes, widths, and line breaks. That often looks like a rendering mismatch rather than uniformly low resolution. Wait for the page’s relevant content and fonts, then check the result in the same browser environment on each run.
# Navigate first, then wait for the page's font set to finish loading.
driver.get("https://example.com")
driver.execute_async_script("""
const done = arguments[arguments.length - 1];
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(() => done());
} else {
done();
}
""")
# Add an application-specific readiness check when the page renders
# important content asynchronously.
driver.save_screenshot("page.png")
Font readiness alone does not guarantee that every image, chart, or asynchronously rendered component is ready. When needed, wait for a meaningful selector or application-specific ready condition. Avoid relying on a short fixed sleep if the page’s load time varies.
6. Use DPR changes as a controlled comparison
Chrome DevTools device mode allows DPR selection and distinguishes mobile and desktop rendering methods. Use it to compare controlled environments and record the browser version, viewport, DPR, and rendering mode. DevTools’ interface is a diagnostic aid; it is not a Selenium code recipe, and emulating a device does not automatically sharpen raster text.
Change one variable at a time. First capture with your stable baseline, then compare a different DPR or rendering mode while holding the page, browser version, viewport, and downstream image handling constant. Keep the setting that matches the environment you intend to reproduce. Avoid adding browser-specific flags without verifying their behavior for your installed browser and Selenium versions.
7. Troubleshooting common problems
| Symptom or error | Likely cause | What to do |
|---|---|---|
| Requested window size differs from the page’s inner size | Outer window dimensions include browser chrome, and headless or container setups can behave differently. | Print innerWidth and innerHeight after setting the size. Adjust the requested size for the target environment and keep the measured viewport consistent. |
| PNG looks blurry only in a document or preview | The viewer is scaling the image beyond its captured pixel dimensions. | Open the original at native scale and inspect the actual dimensions before changing Selenium. |
| PNG dimensions seem unexpectedly small | The browser’s viewport or device scale differs from the assumed values, or a later step resized the file. | Record viewport and DPR in the page, inspect the raw file, and trace any image-processing or upload pipeline. |
| Text differs between local and CI captures | Browser, operating system, installed fonts, font availability, or headless environment differs. | Compare and pin the environment details needed for reproducibility; confirm web fonts have loaded. |
| Some characters are replaced or line wrapping changes | A fallback font may be used, or fonts/content may not be ready at capture time. | Wait for document.fonts.ready, confirm the intended font is available, and wait for app-specific content. |
save_screenshot returns false or no file appears |
The save failed, the path is unwritable, or the script is checking a different working directory. | Check the Boolean return value, use an explicit path, and verify the output directory exists and is writable. |
| Element screenshot fails or captures the wrong area | The selector matched no element, matched a hidden element, or content has not rendered. | Wait for the intended selector, confirm it is visible, and inspect the matched element before capturing. |
| Text becomes jagged after conversion | A lossy format or repeated resizing introduced artifacts. | Preserve and compare the original PNG; avoid unnecessary resampling and lossy conversion. |
8. Make captures more reliable and efficient
- Keep a capture manifest: record browser and driver versions, operating system or container image, headless or headed mode, window size, measured viewport, DPR, URL, and capture scope.
- Wait on conditions: use a selector, font readiness, or app-specific signal for content that renders asynchronously. Capture timing should be deterministic.
- Save the original: preserve the first PNG before cropping, resizing, or converting. This separates browser output from pipeline changes.
- Compare like with like: use the same page state, viewport, and environment when evaluating a DPR or browser change.
- Control output size: very large full-page captures use more memory and storage. Capture only the needed element or viewport when that meets the task.
There is no evidence here for a universal performance benchmark or one best DPR. Higher pixel dimensions can increase image size and processing work; choose dimensions appropriate to the viewing or testing task, then verify the result. A repeatable environment and saved diagnostics are more useful than repeatedly changing settings without recording the effect.
9. Or skip the browser setup
If the task is simply to obtain a page screenshot, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF, and its documented options include viewport and device presets, retina scale, full-page or selector capture, waits, custom CSS and JavaScript, and output formats. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.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://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await 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, and failed loads are never billed, and cache hits cost nothing; response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
10. Frequently asked questions
Why are my Selenium screenshots blurry?
There is no single cause. Check the original PNG’s dimensions, viewport, DPR, and any downstream scaling. If character shapes differ, check font loading and environment consistency.
How do I increase screenshot resolution in Selenium?
First measure the existing viewport, DPR, and PNG dimensions. Then choose a deliberate viewport or browser-specific scale configuration for your target environment and compare the raw output. Selenium’s screenshot API captures rendered browser output; it does not guarantee that a setting will sharpen every page.
Why is text blurry in headless Chrome screenshots?
Headless mode is one environment detail to record, not a universal explanation. Compare captures with the same browser version, viewport, DPR, fonts, and image handling before attributing the difference to headless rendering.
How do I set device pixel ratio in Selenium?
DPR controls are browser-specific. Chrome DevTools device mode can be used for a controlled comparison, but its UI is not Selenium code. Verify the appropriate configuration for your browser and version, and read the page’s actual window.devicePixelRatio after setup.
Should I capture an element or the whole page?
Use an element screenshot to isolate a component or compare capture scopes. Use a page or full-window capture when surrounding layout is part of the result. Selenium documents current-context and element screenshot methods.


