How to Capture Website Screenshots at Multiple Viewport Widths with Selenium
Resize a Selenium browser, wait for each page state, and save a separate screenshot at every target viewport width. Includes Python code, mobile emulation, troubleshooting, and an API alternative.
Use Selenium’s set_window_size(width, height) to resize the browser, wait until the page state you need is ready, then call save_screenshot() with a unique filename for each size. Selenium’s window dimensions describe the browser window; if you need an exact CSS viewport width, measure window.innerWidth and adjust or use a browser emulation profile. Selenium window documentation · Python WebDriver API.
1. Install Selenium and prepare a browser
This example uses Python and Chrome. Install Selenium in your environment:
python -m pip install selenium
Recent Selenium versions can manage compatible browser drivers through Selenium Manager. If your environment cannot download or locate a driver, install a matching browser driver or configure a remote Selenium server. The script below opens one browser session and reuses it across all requested sizes.
2. Capture a screenshot at each window size
Set a width and height for every capture. The values below are examples; choose dimensions that match your layout breakpoints or test cases.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
url = "https://example.com"
viewports = [(375, 812), (768, 1024), (1280, 900)]
out = Path("screenshots")
out.mkdir(parents=True, exist_ok=True)
driver = webdriver.Chrome()
try:
for width, height in viewports:
driver.set_window_size(width, height)
actual_window = driver.get_window_size()
driver.get(url)
# Replace this with a page-specific readiness condition if needed.
WebDriverWait(driver, 20).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
path = out / f"page-{width}x{height}.png"
if not driver.save_screenshot(str(path)):
raise OSError(f"Could not save screenshot to {path}")
css_viewport = driver.execute_script(
"return {width: window.innerWidth, height: window.innerHeight}"
)
print(
f"Saved {path}; requested window {width}x{height}; "
f"reported window {actual_window}; CSS viewport {css_viewport}"
)
finally:
driver.quit()
The order is deliberate: resize, navigate, wait for the state relevant to the capture, then save. A unique filename prevents later iterations from overwriting earlier screenshots. For a page whose responsive layout reacts immediately to resizing without navigation, you can navigate once before the loop, resize on each iteration, and wait for the layout change before saving.
3. Choose dimensions and verify the viewport
set_window_size() sets the browser window’s width and height in pixels. Do not assume those values equal the page’s CSS content viewport: browser chrome, operating system behavior, and driver details can affect the rendered area. Selenium’s API provides get_window_size() for the window dimensions, while JavaScript’s window.innerWidth reports the page’s current inner viewport width.
driver.set_window_size(1280, 900)
print("Window:", driver.get_window_size())
print("CSS viewport:", driver.execute_script(
"return {width: window.innerWidth, height: window.innerHeight}"
))
If your acceptance criterion is “the page must render at exactly 1280 CSS pixels wide,” compare window.innerWidth with 1280, then adjust the window dimensions and check again. Keep the check in your automation if exact width is important; a requested outer window size alone does not establish that condition.
| Goal | Use | Check |
|---|---|---|
| Compare ordinary responsive desktop layouts | Resize with set_window_size() |
Record window.innerWidth if the width must be exact |
| Test a device-style Chromium profile | Configure mobile emulation before creating the driver | Confirm screen dimensions and pixel ratio match the intended profile |
| Capture a page with dynamic content | Wait for a page-specific condition after navigation or resize | Check the element or state that must appear in the image |
4. Wait for the page state you actually need
A completed navigation does not guarantee every image, client-rendered component, or delayed widget is ready. The example waits for document.readyState == "complete", but the right condition depends on the page. Prefer a specific observable state when possible, such as a key element becoming visible:
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "main .hero"))
)
For a short animation or delayed transition, a fixed delay can be appropriate, but it is usually less reliable than waiting for a meaningful condition. If resizing triggers client-side layout work, wait after resizing as well as after navigation.
5. When to use Chromium mobile emulation
Window resizing is suitable for checking responsive layouts at different desktop widths. For device-style Chromium tests that also need emulated screen metrics and a pixel ratio, Selenium’s Chrome options support mobile emulation. Configure it before building the driver:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("mobileEmulation", {
"deviceMetrics": {
"width": 390,
"height": 844,
"pixelRatio": 3.0,
}
})
driver = webdriver.Chrome(options=options)
Use a device profile when the test calls for emulated device metrics. It is a different setup from changing the desktop browser window; choose based on what the test is intended to represent. See Selenium’s Chromium Options reference for the mobile emulation configuration.
6. Useful capture variations
Navigate once and resize the same page
If the page is already loaded and responds to viewport changes, avoid reloading it for every size. Resize, wait for the responsive state if necessary, and save each image:
driver.get(url)
WebDriverWait(driver, 20).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
for width, height in viewports:
driver.set_window_size(width, height)
WebDriverWait(driver, 10).until(
lambda d: d.execute_script("return window.innerWidth") > 0
)
path = out / f"page-{width}x{height}.png"
if not driver.save_screenshot(str(path)):
raise OSError(f"Could not save {path}")
Capture fresh navigation at each size
Keep driver.get(url) inside the loop when page initialization, server responses, or JavaScript behavior may differ based on the initial viewport. This costs more time but makes each capture start from a new navigation.
Use additional widths
Add any widths relevant to the project to the viewports list. Keep heights explicit because fixed-height content, sticky headers, and visible fold position can change what appears in a viewport screenshot.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot width differs from the requested width | The requested dimensions are browser-window dimensions, not a guarantee of CSS viewport dimensions. | Measure window.innerWidth after resizing and tune the window dimensions. Use mobile emulation when device metrics are required. |
| Every output file contains the same screenshot | The loop reused one filename. | Include width and, when needed, height or a sequence number in each filename. |
| The screenshot is blank or missing page content | Capture happened before the relevant content was rendered, or navigation did not reach the expected state. | Wait for a page-specific element or state and check the browser’s current URL and page content before saving. |
save_screenshot() returns False |
The image could not be written to the requested path. | Create the output directory, check path permissions, use a writable path, and treat the Boolean result as an error. |
| Driver creation fails | The browser or driver is unavailable, incompatible, or cannot be resolved in the environment. | Install a supported browser, allow Selenium Manager to resolve its driver where available, or configure the matching driver or remote endpoint. |
| A screenshot misses lazy-loaded images or delayed content | The page has not loaded those resources at capture time. | Wait for the relevant images or content, or trigger the page behavior that loads them before capturing. |
| The page changes between viewport captures | Repeated navigation can expose changing content, or the page depends on time, session, or network state. | Reuse one loaded page when appropriate, control the page state in your test, and keep the same session for the capture set. |
8. Performance, reliability, and cost
The main time cost is usually browser startup, page navigation, and waiting for content. Reusing one driver session avoids starting a browser for every width. Navigating once can save time when only responsive layout changes; fresh navigation is more representative when initial load behavior matters. Use explicit waits tied to the page instead of a long universal sleep to reduce unnecessary delay while retaining a clear readiness condition.
For repeatable output, use stable viewport lists, deterministic page state where your test permits it, distinct filenames, and a finally block that always quits the driver. On shared or remote runners, check that the screenshot directory is writable and preserve the browser and driver logs when investigating intermittent failures. Selenium itself is software; browser execution and infrastructure costs depend on where and how you run it. The Selenium API references do not specify a universal performance or cost figure.
9. Or skip the browser setup
If you need image captures at several dimensions without managing Selenium, ScreenshotNeo is a website screenshot API and MCP server. Its API accepts viewport settings and returns an image; see the ScreenshotNeo API documentation for the supported parameters. For multiple widths, make one request per width and set the requested width and height in each request.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d width=375 \
-d height=812 \
-o shot-375.png
Repeat the call with the next width and a distinct output filename. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots per month with no card.
10. FAQ
Does Selenium save screenshots as PNG?
Yes. Python’s save_screenshot(filename) saves the current window as a PNG image. Use a filename ending in .png, and check the returned Boolean.
Should I resize before or after loading the URL?
Resize before navigation when you want the page to initialize at that size. Resize after navigation when you want to test how an already loaded page responds. Choose based on the behavior under test.
Can one Selenium browser capture several widths?
Yes. A single driver session can be resized repeatedly. Use unique filenames and wait for any resize-triggered content changes before each capture.
Are the example widths required breakpoints?
No. They are illustrative dimensions. Use the widths your design or test plan needs.


