QA How-To
How to Use Playwright device emulation (2026)
Master Playwright device emulation for mobile profiles, viewports, touch, locale, geolocation, media, permissions, offline tests, and CI coverage patterns.
22 min read | 2,672 words
TL;DR
Playwright device emulation configures a browser context with a device descriptor or explicit options such as viewport, user agent, touch, locale, timezone, geolocation, permissions, color scheme, and offline state. Put stable profiles in Playwright projects, override descriptor fields after the spread, and remember that emulation is not a physical-device substitute.
Key Takeaways
- Start with a Playwright device descriptor, then override properties after the spread so intentional changes win.
- Treat viewport, user agent, touch, mobile layout behavior, locale, timezone, permissions, and media as separate risk dimensions.
- Use projects for repeatable coverage profiles and test.use for a small scenario-specific override.
- Grant geolocation permission as well as setting coordinates, and keep permission scope explicit.
- Assert product behavior under emulation, not the configured values alone.
- Keep real-device coverage for hardware, operating-system UI, performance, and installed-browser risks.
Playwright device emulation lets a test run the web application in a controlled browser context that behaves like a selected mobile, tablet, or desktop profile. It can configure viewport and screen size, device scale factor, mobile layout behavior, touch input, user agent, locale, timezone, geolocation, permissions, color scheme, offline state, and other context properties.
The important word is emulation. Playwright can reproduce many browser-visible conditions consistently, but it does not transform a desktop CI worker into physical phone hardware or reproduce every operating-system surface. This guide shows how to build a focused emulation matrix, write correct configuration, and interpret results without overstating coverage.
TL;DR
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'mobile-chromium',
use: {
...devices['Pixel 7'],
},
},
{
name: 'mobile-webkit',
use: {
...devices['iPhone 13'],
},
},
],
});
- Device descriptors are context option presets, not physical devices.
- Spread the descriptor first, then add overrides.
- Use projects for repeatable profiles.
- Assert responsive navigation, input behavior, formatting, permissions, and recovery states.
- Preserve a smaller real-device layer for risks the browser context cannot emulate.
1. What Playwright Device Emulation Controls
Playwright ships a device registry exposed as devices. Each named descriptor contains a reviewed group of browser-context options, commonly including viewport, screen size, device scale factor, user agent, touch support, and mobile behavior. Spreading a descriptor into a project's use object gives every test in that project the same profile. The exact registry belongs to the installed Playwright version, so inspect names in your dependency rather than inventing a device label.
Individual dimensions can also be configured without a named device. A desktop responsive test may need only viewport. A tax display may need locale and timezoneId. A store finder may need geolocation plus permission. A theme test may need colorScheme. Treat these as test inputs attached to a product risk, not as a checklist that every test must combine.
The browser context is the isolation boundary for most emulation. Options such as user agent and mobile behavior must be established when the context is created, which is why project configuration and test.use are preferred. Some properties can change later: page.setViewportSize() changes viewport, context.setGeolocation() changes coordinates for pages in the context, and page.emulateMedia() changes supported media features.
A passing profile proves browser behavior under those configured signals. It does not prove radio conditions, thermal throttling, GPU differences, camera hardware, biometric prompts, native share sheets, real virtual keyboards, installed-browser chrome, or vendor firmware. State that boundary in the test strategy so emulation results are trusted for what they actually cover.
2. Configure Playwright Device Emulation with Projects
Projects are the clearest way to create reusable emulation profiles. They give results descriptive names, support project-specific retries and dependencies, and let CI select a subset with --project. Start with business-relevant profiles rather than every descriptor in the registry.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'https://app.example.test',
trace: 'on-first-retry',
},
projects: [
{
name: 'desktop-chrome',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'android-chromium',
use: { ...devices['Pixel 7'] },
},
{
name: 'iphone-webkit',
use: { ...devices['iPhone 13'] },
},
],
});
Descriptors can carry a browser choice through defaultBrowserType. Playwright Test uses the complete project options when creating the browser and context. A named phone does not imply a cloud connection to that model. An iPhone descriptor with WebKit is still Playwright's WebKit build under emulated browser signals, not Mobile Safari running on an actual iPhone.
Use stable project names based on the coverage purpose. mobile-chromium is often more durable than repeating a marketing model in dashboards, while including the descriptor in configuration preserves the concrete profile. If the product team has a supported-device contract, names such as supported-small-ios-profile can connect results to it.
Run a profile locally with:
npx playwright test --project=android-chromium
npx playwright test tests/checkout.spec.ts --project=iphone-webkit --headed
Keep one fast default for ordinary pull requests, then run the broader matrix for responsive or release-critical suites according to risk.
3. Override Viewport, Screen, Touch, and Mobile Settings Correctly
Object spread order matters. A descriptor already defines viewport and other properties. Put it first, then add an intentional override so the later value wins. Reversing the order silently restores the descriptor value and can make a test claim the wrong coverage.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'small-mobile-webkit',
use: {
...devices['iPhone 13'],
viewport: { width: 360, height: 740 },
},
},
],
});
Viewport is the web page's CSS-pixel area. Screen size is the emulated screen value exposed to the page. deviceScaleFactor relates CSS pixels to device pixels. hasTouch enables touch support for APIs such as locator.tap(). isMobile affects mobile browser behavior, including whether a viewport meta tag is honored and whether touch events are enabled in supported engines. These settings overlap but are not interchangeable.
For a responsive desktop page, a viewport-only test is often enough:
import { test, expect } from '@playwright/test';
test.use({ viewport: { width: 800, height: 900 } });
test('collapses secondary navigation at tablet width', async ({ page }) => {
await page.goto('/account');
await expect(page.getByRole('button', { name: 'More navigation' }))
.toBeVisible();
});
Changing viewport after navigation with page.setViewportSize() is supported, but it resets screen size to match the new viewport and may trigger a different sequence of resize behavior. Prefer a context-level starting size unless resizing itself is the feature. Never loop through widths in one test and call that isolation. Separate tests or projects produce clearer artifacts and prevent state from one breakpoint influencing another.
4. Test Touch and Mobile Interaction Without False Assumptions
A device descriptor with touch enabled allows Playwright to dispatch touch-oriented input. locator.tap() is useful when the product registers touch-specific handlers or when pointer modality changes behavior. For controls that should work identically with all primary pointers, click() remains a good user-level action and receives Playwright actionability checks.
import { test, expect, devices } from '@playwright/test';
test.use({ ...devices['Pixel 7'] });
test('opens the product actions with touch', async ({ page }) => {
await page.goto('/products/coffee-grinder');
await page.getByRole('button', { name: 'Product actions' }).tap();
await expect(page.getByRole('menu')).toBeVisible();
await expect(page.getByRole('menuitem', { name: 'Share' })).toBeVisible();
});
Do not rewrite accessible flows around coordinates just because the project is mobile. Role and label locators remain preferable. Coordinate taps are appropriate only when coordinates are the contract, such as a canvas map, and should be backed by a focused assertion.
Emulation does not display a real on-screen keyboard. It cannot fully reproduce how a hardware keyboard, native autofill overlay, input accessory view, or safe-area insets behave on every phone. It also does not measure how a low-power device renders under memory pressure. Use real devices for those risks.
Responsive design defects are often input-independent: clipped controls, overlapping sticky elements, inaccessible menus, or content hidden behind fixed footers. Assert these through visibility and behavior, and add focused screenshots only when geometry matters. The Playwright visual regression guide provides a disciplined baseline strategy.
5. Emulate Locale, Timezone, and User Agent
Locale affects browser APIs, language negotiation, and formatting behavior. timezoneId changes the timezone observed inside the browser context. They do not change the Node test runner's timezone, so build expected values carefully. Prefer asserting the product output against explicit test data instead of formatting a second expected value with the runner's environment.
import { test, expect } from '@playwright/test';
test.use({
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
});
test('formats an invoice for the configured locale and zone', async ({ page }) => {
await page.goto('/invoices/INV-1048');
await expect(page.getByTestId('invoice-total')).toContainText('1.250,00');
await expect(page.getByTestId('invoice-total')).toContainText('€');
await expect(page.getByTestId('issued-date')).toHaveText('13.07.2026');
});
A user agent can be set explicitly, but descriptors already include one and most responsive behavior should use capabilities and CSS rather than user-agent sniffing. Override it only to test a deliberate server or client branch. The official desktop descriptors can include a platform-specific user agent. If a test should use the worker platform's normal user agent, set userAgent: undefined after spreading that descriptor.
use: {
...devices['Desktop Chrome'],
userAgent: undefined,
}
Localization testing should include expansion, truncation, input methods, plural rules, and right-to-left layout where supported, not just translated strings. Emulation provides the environment, while deterministic fixtures and assertions prove the product response. Do not infer the test-runner process locale from the browser setting.
For robust dynamic waits during formatting and rerenders, follow the Playwright assertions and auto-waiting guide.
6. Emulate Geolocation and Permissions Safely
Setting coordinates alone is not enough when the application calls the browser Geolocation API. The context must also have geolocation permission for the relevant origin. Configure both in a project or grant permission explicitly in a hook. Use fictional or public landmark coordinates, never personal locations from production users.
import { test, expect } from '@playwright/test';
test.use({
geolocation: { latitude: 40.7580, longitude: -73.9855 },
permissions: ['geolocation'],
});
test('finds stores near the emulated location', async ({ page }) => {
await page.goto('/stores');
await page.getByRole('button', { name: 'Use my location' }).click();
await expect(page.getByRole('heading', { name: 'Stores near you' }))
.toBeVisible();
await expect(page.getByTestId('store-results').getByRole('listitem'))
.not.toHaveCount(0);
});
To change location during a test, call context.setGeolocation. It affects all pages in that context, so avoid running independent page scenarios inside the same test.
await context.setGeolocation({ latitude: 34.0522, longitude: -118.2437 });
await page.getByRole('button', { name: 'Refresh nearby stores' }).click();
await expect(page.getByTestId('selected-city')).toHaveText('Los Angeles');
Test permission denial too. Start without the permission or clear granted permissions with context.clearPermissions(), then assert the application's fallback and instructions. Browser permission support can vary by browser and version, so validate the exact matrix and avoid claiming unsupported permissions are portable. Scope grants to an origin with context.grantPermissions([...], { origin }) when the suite crosses trusted and untrusted sites.
Permissions are security-relevant test state. Do not leave a broad grant hidden in global configuration if most tests do not need it. A named project or fixture makes the elevated condition visible.
7. Emulate Color Scheme, Reduced Motion, Contrast, and Print
Browser media features control meaningful branches in modern interfaces. colorScheme can be configured in use, while page.emulateMedia() can update color scheme, media type, reduced motion, and supported contrast or forced-color values according to the current API. Assert the component behavior or visual result driven by the media query.
import { test, expect } from '@playwright/test';
test.use({ colorScheme: 'dark' });
test('uses the dark theme token set', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
await expect(page.getByRole('img', { name: 'QAJobFit' }))
.toHaveAttribute('src', /logo-light/);
});
For reduced motion, verify a behavioral accommodation rather than trying to time a transition:
test('does not autoplay decorative animation with reduced motion', async ({ page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('/welcome');
await expect(page.getByTestId('hero-animation')).toHaveAttribute('data-state', 'paused');
});
Print styling can be exercised with page.emulateMedia({ media: 'print' }) and a screenshot or element assertions. This emulates CSS media, not the operating system's print dialog. Testing whether window.print() was called uses a separate technique because the native print UI is not a normal DOM element.
Keep color assertions semantic when possible. Exact CSS colors can be brittle across antialiasing and implementation changes. Visual snapshots are appropriate for a stable theme surface, while role and state assertions remain better for functionality.
8. Test Offline and JavaScript-Disabled Experiences
Set offline: true in a project or test-use block to create a context without network access. This is a deterministic connectivity condition, not a full model of slow or lossy mobile networks. Use it to validate cached shells, offline notices, disabled actions, queued operations, and recovery logic that responds to browser connectivity.
import { test, expect } from '@playwright/test';
test.use({ offline: true });
test('explains why a report cannot load offline', async ({ page }) => {
await page.goto('/reports/weekly');
await expect(page.getByRole('alert'))
.toHaveText('You are offline. Reconnect to load this report.');
await expect(page.getByRole('button', { name: 'Retry' })).toBeDisabled();
});
Navigation itself may fail if the page is not locally served or cached. For offline behavior after initial load, create a dedicated context in library mode or use context-level APIs supported by the current Playwright version to change connectivity after navigation. Keep the test flow aligned with how users actually enter the offline state.
javaScriptEnabled: false is a context creation option for progressive enhancement and content availability. It cannot validate JavaScript-disabled behavior in a single page created with JavaScript enabled. Put the setting in test.use or a project:
test.use({ javaScriptEnabled: false });
test('keeps article content readable without JavaScript', async ({ page }) => {
await page.goto('/resources/playwright-guide');
await expect(page.getByRole('heading', { name: 'Playwright Guide' }))
.toBeVisible();
});
Service workers can complicate network and offline tests. Decide whether the scenario owns service-worker behavior or should block it in test configuration. Document that choice because request routing and caching can otherwise produce confusing results.
9. Build a Risk-Based Emulation Matrix for CI
A device matrix should represent supported experiences and high-risk differences, not a collection of fashionable model names. Group risk across engine, viewport class, touch, pixel density, locale, timezone, theme, and permission state. Most tests need only a default desktop project. Apply broader profiles to tagged responsive, localization, or capability suites.
| Coverage profile | Main risk | Suggested frequency |
|---|---|---|
| Desktop Chromium | General functional regression | Every pull request |
| Small mobile Chromium | Responsive layout and touch | Relevant pull requests |
| Small WebKit profile | Engine and mobile layout differences | Daily or release gate |
| Alternate locale and timezone | Formatting and date boundaries | Relevant pull requests |
| Dark and reduced motion | Theme and motion accessibility | Component or visual changes |
| Offline or denied permission | Recovery and fallback behavior | Targeted suite |
| Physical supported devices | Hardware and OS integration | Release or device-lab cadence |
Use tags or test-file organization so a checkout scenario can run across mobile projects without forcing an API-only test through the same matrix. Report project names with artifacts. A screenshot from iphone-webkit should not be mistaken for physical iPhone evidence, so dashboards and release notes should use precise language.
Parallelism multiplies environment traffic. Mobile profiles do not consume physical devices locally, but each project still creates browser workers and application load. Cap workers based on CI capacity and backend test-data isolation. Track first-attempt failures separately from recovered retries.
For suite distribution at scale, see Playwright sharding in CI. The goal is fast, attributable coverage, not the largest project count.
10. Validate and Debug Playwright Device Emulation
When behavior differs from expectation, confirm the actual browser signals before blaming the responsive code. In a focused diagnostic test, inspect viewport dimensions, device pixel ratio, touch capability, locale, timezone, user agent, and media queries. Keep those checks out of ordinary business tests unless the signal itself is the contract.
const profile = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
dpr: window.devicePixelRatio,
touchPoints: navigator.maxTouchPoints,
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
dark: matchMedia('(prefers-color-scheme: dark)').matches,
userAgent: navigator.userAgent,
}));
console.log(profile);
Common causes include misspelled descriptor names, wrong object spread order, a viewport changed after navigation, an application that branches only on user agent, permission missing for the current origin, or server-rendered content cached without varying on the expected header. Use UI mode and traces to compare project behavior. Attach the evaluated profile to a failing test only if it improves diagnostics.
Code generation can start under emulation with options such as --device, --viewport-size, locale, timezone, color scheme, or geolocation. Generated code is a starting point. Replace fragile selectors, add assertions, remove accidental actions, and move reusable settings into projects.
Finally, reproduce suspicious results on a physical supported device when the defect touches installed browser UI, input methods, safe areas, scrolling physics, media capture, hardware APIs, or performance. Emulation narrows the search quickly, while physical validation closes the device-specific risk.
Interview Questions and Answers
Q: What does a Playwright device descriptor contain?
It is a preset of browser-context options such as viewport, screen, user agent, device scale factor, touch, mobile behavior, and a default browser type. The exact values come from the installed Playwright device registry. It represents browser signals, not a remote physical device.
Q: Why does spread order matter in device configuration?
JavaScript object properties written later overwrite earlier values. I spread the descriptor first and then set intentional overrides such as viewport or locale. Reversing that order can silently discard the override.
Q: Is viewport emulation the same as mobile emulation?
No. Viewport changes the page's CSS-pixel area, while a mobile descriptor can also set touch, user agent, screen, device scale factor, and mobile layout behavior. I choose the smallest set of signals that proves the product risk.
Q: How do you test browser geolocation?
I set coordinates and grant geolocation permission, preferably for an explicit origin. Then I trigger the user workflow and assert location-dependent output. I also cover denial or unavailable-location fallback where it matters.
Q: Can Playwright emulate a real iPhone completely?
No. An iPhone descriptor configures browser-visible characteristics and is often paired with Playwright WebKit. It does not run Mobile Safari on actual Apple hardware or reproduce all operating-system and hardware behavior.
Q: How do you choose a CI device matrix?
I map supported user segments and defect risk across browser engine, viewport class, touch, locale, media, and permissions. Broad functional coverage stays on one fast default, while targeted suites run on additional profiles. Physical devices cover the remaining hardware and OS risks.
Q: How do you debug an emulation profile that appears wrong?
I evaluate the browser signals such as inner width, pixel ratio, touch points, language, timezone, user agent, and media queries. Then I check descriptor spelling, spread order, origin-scoped permission, and server caching. A trace helps compare the failed project with the default profile.
Common Mistakes
- Calling a named descriptor a physical-device test.
- Overriding viewport before spreading the descriptor, which replaces the override.
- Treating a narrow viewport as proof of touch and mobile-browser behavior.
- Granting geolocation permission without setting coordinates, or setting coordinates without permission.
- Asserting only
window.innerWidthinstead of the responsive product behavior. - Running every test across every device profile and overwhelming CI without new risk coverage.
- Using user-agent sniffing as the main responsive design strategy.
- Expecting browser locale or timezone to change the Node test worker.
- Using offline mode as a substitute for realistic slow-network or packet-loss testing.
- Skipping physical devices for virtual keyboard, hardware, performance, or OS integration risks.
Conclusion
Playwright device emulation is a precise way to create repeatable browser contexts for responsive layout, touch, localization, geolocation, permissions, media preferences, and connectivity states. Put durable profiles in projects, override descriptor fields in the correct order, and assert what the application does with each signal.
Begin with one default desktop profile and one supported small-screen profile. Add locale, permission, media, and real-device coverage only where product risk calls for it, then keep project names and reports honest about the difference between emulated and physical evidence.
Interview Questions and Answers
What is Playwright device emulation?
It is browser-context configuration that reproduces selected device and environment signals. A descriptor can include viewport, screen, device scale factor, user agent, touch, mobile behavior, and browser type. Additional options cover locale, timezone, geolocation, permissions, media, and connectivity.
Why should device configuration use Playwright projects?
Projects make profiles repeatable, selectable, and visible in reports. They also create the context with settings that cannot be safely changed after creation. I use `test.use` only for focused file or describe-level variants.
Explain viewport, screen, and device scale factor.
Viewport is the page's CSS-pixel rendering area. Screen is the emulated screen size exposed to browser APIs. Device scale factor describes the relationship between CSS pixels and device pixels, so these values model different aspects of the environment.
What is required for a geolocation test?
The context needs coordinates and geolocation permission for the relevant origin. The test should trigger the user-facing location workflow and assert output, not merely inspect the configured coordinates. A denial case often provides equally important coverage.
What can device emulation not validate?
It cannot fully validate physical performance, radio behavior, GPU and memory limits, native browser chrome, real virtual keyboards, hardware sensors, biometrics, or vendor OS differences. Those risks need physical devices or specialized environments.
How do you prevent an emulation matrix from slowing CI?
I map tests to risk tags and run general coverage on one default profile. Only responsive, localization, permission, or engine-sensitive suites expand to additional projects. I monitor backend load, worker capacity, and first-attempt failure rates.
How would you prove that an emulation configuration is active?
In a diagnostic test I inspect inner dimensions, pixel ratio, touch points, locale, timezone, user agent, and media query results in the page. In business tests, I assert the resulting application behavior. This keeps configuration checks separate from user outcomes.
Frequently Asked Questions
How do I emulate a mobile device in Playwright?
Import `devices` from `@playwright/test` and spread a named descriptor into a project's `use` options or a `test.use` block. Verify that the descriptor name exists in the registry shipped with the installed Playwright version.
Does Playwright device emulation use a real phone?
No. It configures a browser context with device-like signals such as viewport, user agent, scale factor, touch, and mobile behavior. Hardware, operating-system UI, and installed mobile browsers require real-device testing.
Can I override the viewport in a Playwright device descriptor?
Yes. Spread the descriptor first, then set `viewport` so the later override wins. Reversing the order will replace your custom viewport with the descriptor's value.
How do I emulate geolocation in Playwright?
Set `geolocation` with latitude and longitude and grant the `geolocation` permission. Use `context.setGeolocation` if the scenario needs to change coordinates after context creation.
Can Playwright emulate dark mode and reduced motion?
Yes. Configure `colorScheme` in context use options, or call `page.emulateMedia` for supported media features such as color scheme and reduced motion. Assert the product behavior driven by the preference.
Does setting locale change the test runner timezone?
No. Browser `locale` and `timezoneId` affect the browser context, not the Node.js worker. Keep expected dates explicit so runner environment settings do not leak into assertions.
Should all Playwright tests run on every emulated device?
Usually not. Run broad tests on a fast default profile and apply mobile, localization, media, or permission profiles to scenarios that exercise those risks. This keeps CI fast and failures attributable.