Responsive layouts can pass desktop Selenium tests while failing under phone-sized viewports, touch input, or mobile-only navigation. Chrome mobile emulation lets a browser test exercise those client-side branches without moving the whole test run to a physical device farm.
ChromeDriver applies mobile emulation when the browser session starts through the mobileEmulation entry in ChromeOptions. Custom device metrics keep the test independent of Chrome DevTools' named-device list, while the user-agent string and client hints keep server-side and browser-side mobile checks aligned.
Emulation is still desktop Chrome running with mobile viewport behavior. It does not reproduce device GPU performance, browser address-bar behavior, mobile OS dialogs, sensors, or every hardware API, so keep real-device coverage for release paths that depend on those boundaries.
Use explicit width, height, pixelRatio, mobile, and touch values when CI runners may use different ChromeDriver builds. Named devices such as Nexus 5 depend on the device list bundled with the active driver.
import shutil from urllib.parse import quote from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Selenium mobile emulation demo</title> </head> <body> <h1>Mobile layout ready</h1> </body> </html> """ mobile_emulation = { "deviceMetrics": { "width": 390, "height": 844, "pixelRatio": 3.0, "mobile": True, "touch": True, }, "userAgent": ( "Mozilla/5.0 (Linux; Android 14; Pixel 8 Build/AP2A.240905.003) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/148.0.0.0 Mobile Safari/537.36" ), "clientHints": {"platform": "Android", "mobile": True}, } options = Options() options.add_argument("--headless=new") options.add_experimental_option("mobileEmulation", mobile_emulation) for browser_name in ("google-chrome", "chromium", "chromium-browser"): browser_path = shutil.which(browser_name) if browser_path: options.binary_location = browser_path break driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() driver = webdriver.Chrome(service=service, options=options) try: driver.get("data:text/html;charset=utf-8," + quote(HTML)) metrics = driver.execute_script( """ return { title: document.title, viewport: `${window.innerWidth}x${window.innerHeight}`, screen: `${screen.width}x${screen.height}`, pixelRatio: window.devicePixelRatio, maxTouchPoints: navigator.maxTouchPoints, mobileUA: navigator.userAgent.includes("Mobile") }; """ ) print(f"title: {metrics['title']}") print(f"viewport: {metrics['viewport']}") print(f"screen: {metrics['screen']}") print(f"device_pixel_ratio: {metrics['pixelRatio']}") print(f"max_touch_points: {metrics['maxTouchPoints']}") print(f"user_agent_mobile: {metrics['mobileUA']}") finally: driver.quit()
The mobileEmulation option must be added before webdriver.Chrome() creates the session. Changing it later requires a new browser session.
$ python3 selenium-mobile-emulation-use.py title: Selenium mobile emulation demo viewport: 390x844 screen: 390x844 device_pixel_ratio: 3 max_touch_points: 1 user_agent_mobile: True
options = Options() options.add_argument("--headless=new") options.add_experimental_option("mobileEmulation", mobile_emulation) driver = webdriver.Chrome(options=options)
Keep the option in the shared fixture so every mobile-layout test starts with the same viewport, touch, user-agent, and client-hints behavior.
driver.get("https://app.example.com/") assert driver.execute_script("return window.innerWidth") == 390 assert driver.execute_script("return navigator.maxTouchPoints") > 0 assert "Mobile" in driver.execute_script("return navigator.userAgent")
Viewport checks prove the browser session is emulated. Application checks should still assert the real mobile menu, responsive component, or page branch the test depends on.
Mobile emulation does not validate device GPU performance, OS-level permission prompts, camera behavior, virtual keyboard effects, or Safari-on-iOS behavior.
$ rm selenium-mobile-emulation-use.py