How to Capture Mobile Website Screenshots with Selenium on Safari
Learn when a mobile-sized desktop Safari window is enough, when you need iOS Safari, and how to save reliable screenshots with Selenium.
Short answer: Selenium can save screenshots from Safari, but first choose what “mobile Safari” means. A narrow window in macOS Safari checks a responsive layout; it is still desktop Safari. To capture actual iOS Safari, use an iOS Simulator or a connected iPhone/iPad with an iOS WebDriver route. Selenium’s Safari guide points iOS automation users to Appium, while WebKit also documents native Safari WebDriver sessions on iOS. Selenium’s Safari guidance · WebKit’s iOS WebDriver guide
1. Choose the Safari target
| Target | What the screenshot represents | Choose it when |
|---|---|---|
| macOS Safari, narrow window | Safari on macOS with a constrained window or viewport | You need a quick responsive-layout check, not device-specific iOS behavior. |
| Safari on iOS Simulator | Safari running in an iOS simulator | You need iOS rendering without a physical device. WebKit documents simulator sessions for iOS 13 or later runtimes, subject to the runtimes and tools installed on your Mac. |
| Safari on iPhone or iPad | A real iOS device browser session | You need device-specific behavior or a real-device capture. This needs a Mac host and device setup. |
Selenium’s own Safari page directs people automating Safari on iOS to Appium. WebKit separately documents native iOS Safari WebDriver sessions. These routes are related, but their client setup and capabilities are not interchangeable. This guide shows the simple desktop Safari path in runnable Selenium Python, then explains the requirements and screenshot pattern for iOS sessions without presenting a desktop driver as an iPhone launcher.
2. Capture a mobile-sized page in desktop Safari
Use this when your question is “does the layout respond at this width?” It does not reproduce iOS Safari. Install Selenium with python -m pip install selenium, use a Mac with Safari, and enable Safari’s remote automation if it is not already enabled by running safaridriver --enable in Terminal.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
url = "https://example.com"
output = Path("mobile-layout.png").resolve()
driver = webdriver.Safari()
try:
# This sets the desktop Safari window size. It is a responsive check,
# not an iPhone viewport or an iOS Safari session.
driver.set_window_size(390, 844)
driver.get(url)
WebDriverWait(driver, 20).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
# Replace this with a page-specific condition when a key component
# loads asynchronously.
WebDriverWait(driver, 10).until(
lambda d: d.find_element("tag name", "body").is_displayed()
)
if not driver.save_screenshot(str(output)):
raise RuntimeError(f"Could not save screenshot to {output}")
print(f"Saved {output}")
finally:
driver.quit()
The screenshot method captures the current browser view as PNG. Selenium documents both save_screenshot(filename) and get_screenshot_as_file(filename); the latter returns False if saving fails. Use an absolute path and a .png extension. Selenium Python Safari API
About viewport dimensions
set_window_size(390, 844) sets a browser window size, not a guaranteed 390-by-844 CSS-pixel content area. Browser chrome and window-manager behavior affect the available page area. For precise responsive checks, inspect window.innerWidth and window.innerHeight in the page, or use Safari’s Responsive Design Mode to choose a viewport. A mobile viewport also does not create iOS-only browser behavior.
3. Capture actual iOS Safari
For a real iOS browser capture, run the WebDriver client against an iOS-capable route. Selenium’s guidance recommends Appium for iOS Safari automation. WebKit documents a native Safari WebDriver route using /usr/bin/safaridriver, with platformName: ios to request an iOS host. WebKit’s 2019 article introduced that support; consult the current man safaridriver and installed tool documentation for supported capabilities in your environment.
Physical device prerequisites
- Use a Mac host with a sufficiently recent Safari WebDriver.
- Enable host automation if needed with
safaridriver --enable. - On the iPhone or iPad, enable Remote Automation in Safari’s Advanced settings.
- Connect and trust the device. Unlock it when starting the session.
- Create a WebDriver session that identifies iOS, for example with
platformName: ios, and the device details required by your chosen route.
Simulator prerequisites
WebKit documents simulator sessions with the safari:useSimulator: true capability and iOS 13-or-later simulator runtimes. Install the needed runtime using your Apple development tools, then check the current safaridriver manual for accepted simulator/device capabilities. The exact session creation endpoint and options depend on whether you use Appium or the native driver and on their installed versions.
Screenshot code shared by either iOS route
Once your selected route has created a valid iOS Safari WebDriver session, navigation, page-specific waiting, screenshot saving, and cleanup look like this. The session creation line is intentionally left to your chosen route; a plain webdriver.Safari() call creates desktop Safari and does not launch an iPhone session.
from selenium.webdriver.support.ui import WebDriverWait
# driver = ... create an iOS Safari session using your Appium or
# native safaridriver setup and its currently supported capabilities.
try:
driver.get("https://example.com")
WebDriverWait(driver, 20).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
# Add a condition for the content that must appear in your capture.
WebDriverWait(driver, 15).until(
lambda d: d.find_element("css selector", "main").is_displayed()
)
if not driver.save_screenshot("ios-safari.png"):
raise RuntimeError("Screenshot could not be saved")
finally:
driver.quit()
WebKit’s historical introduction says: “Starting in iOS 13, Safari now includes native support for the W3C WebDriver standard.” That describes when the feature was introduced, not a statement about the minimum version supported by every current route. Brian Burg, WebKit, July 8, 2019
4. Make the capture deterministic
- Wait for the content that matters.
document.readyState == "complete"does not mean lazy images, animations, or app data are ready. Wait for a page-specific selector or state. - Control dynamic content. If your app supports a test mode, use stable data and disable rotating banners or animations there. Avoid arbitrary long sleeps when a meaningful condition is available.
- Scroll when the target is lazy-loaded. Scroll the relevant section into view, wait for its image/content, then capture. A regular WebDriver screenshot generally represents the current visible browser view; do not assume it captures the entire page.
- Keep the same target configuration. Record whether the capture was macOS Safari, Simulator, or a physical device, along with OS/runtime and viewport details. A desktop viewport and an iPhone capture answer different questions.
- Write output to a known location. Resolve paths or use an absolute path, create the destination directory first, and check the Boolean return value from the save method.
5. iOS Safari limits to plan around
WebKit documents platform-specific differences for iOS WebDriver. In particular, Set Window Rect, Minimize Window, and Maximize Window are unsupported for iOS sessions. The software keyboard is suppressed in WebDriver sessions. So do not rely on desktop window-resizing commands to establish an iPhone viewport after starting an iOS session, and do not expect typing to display the normal on-screen keyboard. Configure the device or simulator through the supported route and verify the resulting page dimensions.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
webdriver.Safari() opens macOS Safari, not an iPhone |
That constructor starts desktop Safari. | Choose Appium or WebKit’s native iOS WebDriver route and create an iOS session using its supported capabilities. |
| SafariDriver cannot start or reports automation is disabled | Host-side remote automation is disabled or Safari/driver setup is incomplete. | On the Mac, run safaridriver --enable when required; check Safari and Selenium versions and the current Safari driver instructions. |
| iOS device is missing or session creation fails | The device may be locked, untrusted, disconnected, missing Remote Automation permission, or addressed with unsupported capabilities. | Unlock and reconnect it, accept the trust prompt, enable Remote Automation in Safari Advanced settings, and compare the session options with the installed driver’s manual. |
| Simulator session cannot find a device | The needed iOS runtime may not be installed, or simulator support/capabilities differ in the installed driver. | Install an available iOS Simulator runtime with Apple’s tools; inspect installed devices and man safaridriver. Confirm that the selected route supports simulator sessions. |
| Screenshot is blank or missing expected sections | The capture ran before app content appeared, the page has lazy loading, or the screenshot covers only the current viewport. | Wait for the actual content, scroll the relevant area into view, and capture after its images or data are ready. For a full-page artifact, use a route that explicitly supports it. |
save_screenshot returns False or raises an I/O error |
The directory does not exist, the path is not writable, or the filename is unsuitable. | Create the directory, use an absolute writable path ending in .png, and check the return value. |
| Responsive desktop capture differs from iPhone | A narrow macOS browser window does not reproduce iOS Safari or device-specific rendering. | Use an iOS Simulator for iOS rendering or a connected device when real hardware behavior is required. |
7. Reliability, runtime, and cost considerations
Desktop Safari is usually the lightest path to a quick responsive screenshot because it avoids device-session setup. Simulator and physical-device sessions add device discovery, runtime, unlock, and session-start steps; account for these in automation scheduling and retries. The supplied official material gives no comparable timing or price benchmarks, so treat these as workflow tradeoffs rather than performance measurements.
For repeatable captures, keep sessions isolated, always call quit() in a finally block, use bounded waits, and save diagnostic information when navigation or a required selector times out. A transient failure should be retried only after checking whether the device or simulator session is still healthy; indiscriminate retries can hide a broken setup. Use a simulator for routine visual checks and reserve physical-device runs for cases where device fidelity matters.
8. Or skip the browser setup
If the goal is to get a website screenshot rather than exercise Safari interactions, ScreenshotNeo provides a website screenshot API and MCP server. Its one-request API returns an image or PDF; see the ScreenshotNeo API documentation.
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,
)
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 banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed; response headers identify the page verdict and billing status. The MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. This is a website capture service, not a replacement for validating iOS-only behavior in a real Safari session.
Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.
FAQ
Can Selenium take a screenshot in iPhone Safari?
Yes, through an iOS-capable route such as Appium or WebKit’s native Safari WebDriver, with the Mac and device or simulator setup that route requires. Desktop webdriver.Safari() alone is not an iPhone session.
Does setting a 390-pixel Safari window make it an iPhone screenshot?
No. It is a desktop Safari responsive-layout capture. Use iOS Simulator or a physical device for iOS Safari rendering.
Can I use Selenium to capture the whole page?
The documented Safari screenshot methods save a PNG of the current browser view. Full-page capture is not guaranteed by that call; use a specifically supported full-page method or capture page sections separately.
Which option should I use for visual regression?
Use the same target, OS/runtime, viewport, content state, and wait condition for each run. Choose desktop Safari for responsive breakpoints, Simulator for iOS rendering, and a physical device when hardware behavior is part of the regression.


