Mobile Emulation — Testing the Multiverse
Over 60% of web traffic comes from mobile devices. If your tests only run on a 1920px desktop monitor, you're flying blind. Learn how to use Playwright's device descriptors to test touch, viewport, and network conditions.
📖 The Story: The Desktop Hubris
A SaaS company launched a highly anticipated promotional campaign. The QA team ran the Playwright suite across Chrome, Firefox, and WebKit on 24-inch desktop monitors. Everything was green. They deployed.
Two hours later, customer support was flooded. Mobile users couldn't close the promotional pop-up. The "X" button was positioned absolutely, and on screens narrower than 768px, it shifted off-screen behind a sticky header.
The QA team hadn't configured a mobile viewport. Their tests clicked the "X" perfectly at 1920x1080, completely missing the mobile CSS overflow bug. The campaign was pulled, and the company lost thousands in ad spend.
The next day, the team added a Mobile Safari project to their Playwright config using the built-in devices descriptor. The test immediately failed, revealing the hidden button. They fixed the CSS and re-launched safely.
Desktop testing is half the battle. If it doesn't work on mobile, it doesn't work.
🎯 Why Mobile Emulation?
Responsive Design
Catch CSS breakpoints and layout shifts before they reach real mobile devices.
Touch Events
Ensure buttons respond to taps, swipes, and long-presses, not just mouse clicks.
Geolocation
Mock GPS coordinates to test location-based features like store locators.
Slow Networks
Simulate 3G connections to ensure your loading spinners and timeouts behave correctly.
🌍 Real World Example
A food delivery app has a map that centers on the user's current GPS location. In standard tests, the browser prompts "Allow location access?". On mobile, this prompt often blocks the entire screen. By emulating an iPhone and pre-granting geolocation permissions with specific coordinates, you can test the map's UI without the browser prompt blocking the view.
🧒 Explain Like I'm 10
Imagine you're testing a new video game, but you only test it on a massive 80-inch TV. It looks great! But when your friend plays it on a tiny 5-inch handheld screen, the text is unreadable and the buttons are too small.
Mobile emulation is like putting a special mask over your browser. It shrinks the screen, changes the browser's ID card (User Agent), and turns your mouse into a finger. This way, you can test the "handheld screen" version without actually owning the device.
🎓 Professional Explanation
Playwright provides a devices object exported from @playwright/test. This object contains curated configurations for popular smartphones and tablets (e.g., iPhone 14, Pixel 7, iPad Pro). When applied to a BrowserContext, Playwright automatically configures the viewport, userAgent, deviceScaleFactor, and hasTouch.
This doesn't just resize the window; it tricks the web server into thinking the request is coming from an actual mobile device, triggering the correct SSR (Server-Side Rendering) or responsive CSS.
📊 The Device Matrix
Common Device Emulation Targets
Desktop Chrome
1920x1080
Standard Mouse
iPhone 14
390x844
Safari WebKit / Touch
Pixel 7
412x915
Chrome Android / Touch
📱 Using Device Descriptors
The easiest way to emulate a device is in your playwright.config.ts using a Project.
import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ projects: [ { name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 14'] }, } ] });
What does devices['iPhone 14'] do? It automatically sets viewport: { width: 390, height: 844 }, userAgent: '...iPhone...', deviceScaleFactor: 3, isMobile: true, and hasTouch: true. It's a complete mobile disguise.
👆 Touch Events and Interactions
When hasTouch is enabled, Playwright maps standard mouse events (click, hover) to touch events automatically. However, you can also use explicit touch methods.
// Standard click works, Playwright sends a touch event await page.getByRole('button').click(); // Explicit tap await page.getByRole('button').tap(); // Simulate a swipe (e.g., to dismiss a mobile notification) await page.getByTestId('notification').dragTo( page.locator('body'), { force: true } );
📍 Mocking Geolocation
Testing store locators or map apps requires GPS. You can pre-grant permissions and set coordinates.
test('finds nearest store', async ({ page, context }) => { // 1. Grant permission for geolocation so the browser prompt doesn't appear await context.grantPermissions(['geolocation']); // 2. Set the GPS coordinates (e.g., New York City) await context.setGeolocation({ latitude: 40.7128, longitude: -74.0060 }); await page.goto('/store-locator'); // 3. Assert the app thinks we are in New York await expect(page.getByText('New York Stores')).toBeVisible(); });
📶 Simulating Network Conditions
Mobile users often have slow connections. Test how your app handles latency.
test('shows loading spinner on slow 3G', async ({ page, context }) => { // Simulate Slow 3G await context.setOffline(false); // Ensure we are online // Playwright doesn't have a native setNetworkCondition, but we can mock delays await page.route('**/api/data', async route => { await new Promise(r => setTimeout(r, 3000)); // 3 second delay await route.continue(); }); await page.goto('/dashboard'); // Assert the spinner appears and stays visible during the delay await expect(page.getByTestId('loading-spinner')).toBeVisible(); });
⚠️ Common Mistakes
Mistake 1: Resizing the Window Instead of Emulating
// ❌ BAD: Only changes size. Server still thinks it's a desktop. await page.setViewportSize({ width: 390, height: 844 }); // ✅ GOOD: Changes size, user agent, and touch capabilities. await context.newPage({ ...devices['iPhone 14'] });
If you just resize the window, the backend won't serve the mobile HTML bundle because the User-Agent header still says "Windows". Always use device descriptors.
✅ Best Practices
- Use the built-in
devicesregistry. Don't hardcode user agents; they change frequently. - Test mobile interactions explicitly. If a menu requires a swipe to close, use
dragTorather than a click. - Grant permissions early. Grant geolocation in
beforeEachto avoid prompt flakiness. - Don't emulate every device. Test one iOS device (WebKit) and one Android device (Chromium) to cover 95% of bases.
🏗️ Senior Engineer Deep Dive
User Agent vs Client Hints
Historically, mobile detection relied on parsing the User-Agent string. Modern browsers are moving towards User-Agent Client Hints (Sec-CH-UA-Mobile). Playwright's device descriptors set both automatically, ensuring compatibility with both legacy server checks and modern frameworks.
Device Scale Factor (Retina Displays)
Why does deviceScaleFactor: 3 matter? On a real iPhone, one CSS pixel equals 3 physical pixels. This affects image rendering (srcset) and canvas resolution. If your app serves 1x images to a 3x screen, it will look blurry. Emulating the scale factor ensures you catch missing high-res assets.
💼 Interview Questions
Show Answer
devices from @playwright/test and create a project in the config file. I spread devices['iPhone 14'] into the project's use object. This configures the viewport, user agent, and touch events for that project.setViewportSize and a device descriptor?Show Answer
setViewportSize only changes the dimensions of the browser window. A device descriptor changes the dimensions, but also changes the User-Agent string, enables touch events (hasTouch), sets the isMobile flag, and configures the device scale factor. This is necessary for backend SSR to serve mobile templates.Show Answer
context.grantPermissions(['geolocation']) before navigating to the page. Then I use context.setGeolocation({ latitude, longitude }) to set the specific GPS coordinates. This bypasses the UI prompt and feeds the mock location directly to the Geolocation API.Show Answer
touchstart but an overlapping element is immediately triggering a touchend or click event on the body, closing it. I'd use page.tap() in slow motion to see the event sequence.🏋️ Practical Exercise
Add a Mobile Project
- Open
playwright.config.ts. - Add a new project named "Mobile" that uses
devices['iPhone 14']. - Run the suite using
--project=Mobile. - Watch the test run. Notice the browser window is now shaped like a phone.
- Take a screenshot in the test to see the mobile layout.
🚀 Mini Project
🏗️ The GPS Store Locator
- Write a test that navigates to a site with a map (e.g., Google Maps or a local store locator).
- Configure the context to emulate a Pixel 7.
- Grant geolocation permissions.
- Set the GPS coordinates to your actual home city.
- Assert that the map centers on those coordinates (or that the text "Stores near [Your City]" appears).
📝 Quiz
Test your understanding. Click an option to check your answer.
@playwright/test contains pre-configured mobile device settings?browsersphonesdevicesmobiledevices object exports configurations for popular phones and tablets.devices['iPhone 14'] automatically configure?hasTouch: true and isMobile: truepage.setGPS(lat, long)context.setGeolocation({ latitude, longitude })page.mockLocation()devices.setLocation()setGeolocation method sets the coordinates.setViewportSize not enough for mobile testing?context.grantPermissions(['geolocation'])page.route to block the prompthasTouch is enabled, what happens when you call page.click()?tap().click() works seamlessly on touch devices.deviceScaleFactor: 3 mean?context.setNetworkCondition('Slow 3G')page.route and adding a setTimeout before fulfillingroute.continue().playwright.config.ts as a Project--project=Mobile.page.swipe()element.dragTo(target) or mouse drag with coordinatespage.click() with force: true❓ FAQ
Q: Does Playwright mobile emulation replace testing on real devices (BrowserStack)?
A: No. Emulation mimics the viewport and user agent, but it doesn't run the actual iOS Safari engine or Android Chrome engine perfectly. It uses the desktop engine disguised as mobile. For 100% accuracy, you still need real device clouds for final regression.
Q: Can I emulate dark mode for mobile?
A: Yes. Use colorScheme: 'dark' in the use object alongside the device descriptor.
📦 Summary
🎯 Key Takeaways
- Use the
devicesregistry to emulate phones and tablets accurately. - Emulation changes viewport, user agent, touch, and scale factor.
- Use
context.grantPermissionsandsetGeolocationto test GPS features. - Simulate latency by delaying mocked API routes.
- Configure devices in
playwright.config.tsprojects to run them in parallel.
