Mobile web
Testing responsive pages
Why a browser at a phone-sized viewport tests responsiveness properly, what emulation cannot tell you, and where real devices actually belong.
Your page has to work on a phone. That is not a separate application, it is the same application at a different width — and a browser at that width is a faithful test of it.
This chapter is about testing that in automation: viewports, device descriptors, touch, permissions and location. It starts with the part people get wrong, which is deciding what needs a real device.
Emulation is enough, until it is not
A responsive layout is CSS reacting to viewport width, and occasionally to pointer type and device pixel ratio. All of those are inputs you can set. When you set them, the browser lays the page out exactly as it would on the device — because it is the same layout engine doing the same work.
So these are properly testable in automation, and cheaply:
- Does the navigation collapse into a menu below the breakpoint?
- Does the table become cards, or does it scroll sideways off the screen?
- Is the primary action still reachable without scrolling?
- Do tap targets keep a sane size?
- Does anything overflow horizontally? (This one is worth asserting on every page you have.)
And these are not things emulation can tell you:
| Emulation cannot judge | Because |
|---|---|
| Whether it feels fast on a four-year-old phone | Your CI machine is not that phone. CPU throttling approximates it; it does not reproduce it. |
| Whether the on-screen keyboard covers the field you just focused | There is no on-screen keyboard. |
| Whether the thumb can actually reach the button | Ergonomics are physical. |
| Whether Safari on iOS 17.2 has a bug | WebKit is the same engine, not the same browser build. |
| Whether the page is legible outdoors | Screens and eyes are involved. |
That list is the case for real devices, and it is a real case. Note what it has in common: every item is a human judgement. Which is exactly why it belongs in manual exploratory testing on real hardware, done by people, and not in an automated suite pretending to be a person.
The viewport is the input
Browser's default viewport is 1280×720. Every context can have its own, and
it is the main lever — deviceScaleFactor, isMobile and hasTouch sit
alongside it on the same keyword.
Contexts are cheap — a fresh, isolated profile, far cheaper than a new browser — so a viewport per context is the natural unit. Nothing leaks between them: no cookies, no storage, no permissions.
To change the size within a test, for instance to watch a layout cross a breakpoint, set it on the page instead:
Test the breakpoints, not the phones
The temptation is to write one test per popular handset. Resist it: you end up with fifteen tests that all exercise the same three CSS rules, and they go stale every time a manufacturer changes a screen.
Your CSS has breakpoints. Those are the real boundaries, and there are usually three or four of them. Test either side of each, and name the tests after the layout rather than the hardware:
When a test fails, "the checkout button is hidden at tablet width" locates the bug. "It failed on the Galaxy S21" does not.
The assertion worth having everywhere
Horizontal overflow is the most common responsive defect, one of the most annoying, and trivially detectable — an element wider than the viewport makes the whole page scroll sideways:
Two keywords, no JavaScript. Get Client Size is how much room the page has,
Get Scroll Size is how much it wants; when the second is larger, the page
scrolls sideways. Neither needs a selector — left out, both measure the page
itself — and the ${None} is there only because selector comes first in the
signature and the assertion arguments follow it.
Letting the assertion compare, rather than Should Not Be True, buys two
things. The failure names the numbers:
which is the difference between "something is too wide" and "there is a 1400px block on this page". And an assertion retries, so a layout still settling — a webfont, a late image — is not reported as a defect it is not.
Call it after loading each page at your narrowest layout. It costs nothing and
it catches the long unbreakable string, the fixed-width table and the image
without max-width — every time, before anyone has to notice it by hand.
Where to go next
- Emulating a device — descriptors,
isMobile, pixel ratio, and what emulation actually changes. - Touch, permissions and location —
Tap, granting camera or notifications, and faking where the user is.