Devices and emulation
Run a scenario as a phone, in another locale, timezone, or color scheme.
Emulation lets one browser context behave like a different user environment. It is useful for responsive layouts, touch interactions, locale formatting, timezone logic, permissions, geolocation, and color-scheme behavior. It is not a substitute for a real device lab. Playwright changes browser context options; it does not reproduce hardware constraints, mobile browser chrome, camera quality, battery state, or every operating-system integration.
The recommended mental model is: a context is one isolated user profile, and emulation is part of that profile. Set environment assumptions before the page loads whenever possible. Responsive applications often compute layout, feature flags, locale, or permission behavior during startup.
Start from product risk
Do not emulate devices because the list exists. Choose a small matrix that represents real product risk:
- one desktop baseline for the default experience;
- one narrow mobile viewport for responsive layout;
- one locale or timezone that can expose formatting bugs;
- one permission or geolocation scenario only when the feature uses it;
- a targeted browser engine lane when product behavior depends on Chromium, Firefox, or WebKit.
This keeps the suite useful. Running every test against every device descriptor multiplies time without multiplying confidence.
Pin context options
Pass emulation options when creating the browser context:
<?php
require __DIR__.'/vendor/autoload.php';
use Playwright\PlaywrightFactory;
$playwright = PlaywrightFactory::create();
$browser = $playwright->chromium()->withHeadless()->launch();
$context = $browser->newContext([
'viewport' => ['width' => 390, 'height' => 844],
'deviceScaleFactor' => 3,
'isMobile' => true,
'hasTouch' => true,
'locale' => 'fr-FR',
'timezoneId' => 'Europe/Paris',
'colorScheme' => 'dark',
]);
$page = $context->newPage();
$page->goto('https://example.com');
$browser->close();
$playwright->close();
These options describe the environment seen by the page. viewport controls layout size. deviceScaleFactor affects pixel density. isMobile and hasTouch influence mobile and touch behavior where the browser supports it. locale and timezoneId affect APIs and formatting that depend on user environment. colorScheme lets you exercise light and dark modes.
For team defaults, pin viewport, locale, and timezone explicitly. CI machines rarely match developer laptops. A date assertion that uses the host timezone is a test waiting to fail.
Use the context builder when it improves readability
Raw arrays are compact, but a named builder can make shared setup clearer:
use Playwright\Browser\BrowserContextBuilder;
$contextOptions = BrowserContextBuilder::create()
->withViewport(390, 844)
->withDeviceScaleFactor(3)
->withIsMobile()
->withHasTouch()
->withLocale('fr-FR')
->withTimezoneId('Europe/Paris')
->withColorScheme('dark')
->toArray();
$context = $browser->newContext($contextOptions);
Use the builder when several tests share a named environment such as mobileFrenchCustomer() or desktopAdminInUtc(). Use arrays for a one-off local variation. The goal is not abstraction; the goal is making test intent obvious.
Use device descriptors as presets
The playwright-php/devices package provides named descriptors with viewport, user agent, scale factor, mobile flag, and touch support.
composer require playwright-php/devices
use Playwright\Device\DeviceRegistry;
$device = (new DeviceRegistry())->get('iPhone 15 Pro');
$context = $browser->newContext($device->toArray());
Device descriptors are a good starting point, especially for examples and product-critical mobile flows. Still keep your test objective explicit. If the bug is "the menu wraps below 400px", a named narrow viewport is often clearer than a specific phone model. If the bug is "our Safari mobile flow behaves differently", a WebKit lane with a mobile descriptor is more justified.
Change viewport only when resizing is the feature
Most scenarios should create a fresh context with the final viewport. Change viewport during the test only when the product is supposed to react to resizing.
$page = $context->newPage(['viewport' => ['width' => 1200, 'height' => 800]]);
$page->goto('https://app.example.com/dashboard');
$page->setViewportSize(390, 844);
expect($page->getByRole('button', ['name' => 'Open menu']))->toBeVisible();
If the app computes layout only on initial load, reload after resizing and make that behavior explicit in the test:
$page->setViewportSize(390, 844);
$page->reload(['waitUntil' => 'domcontentloaded']);
Avoid screenshot comparisons across different scale factors unless rendering differences are the point of the test.
Permissions and geolocation
Permissions belong to the context. Grant the smallest set needed by the scenario:
$context->grantPermissions(['geolocation']);
$context->setGeolocation(48.8566, 2.3522, 50);
$page = $context->newPage();
$page->goto('https://app.example.com/stores-near-me');
Clear permissions when reusing a context inside a manual script:
$context->clearPermissions();
In automated tests, prefer creating a new context instead of clearing and rebuilding state. A new context is easier to reason about.
Geolocation tests should assert product behavior, not the browser API itself. For example, check that the nearest store list changes, that a permission explanation appears, or that a map centers on the expected region. Do not assert exact GPS rounding unless your application owns that calculation.
Pair emulation with authentication and state
Authenticated mobile flows are common. Combine storage state with environment options in the same context:
$context = $browser->newContext([
'viewport' => ['width' => 390, 'height' => 844],
'hasTouch' => true,
'isMobile' => true,
'storageState' => __DIR__.'/.auth/customer.json',
]);
Keep the role and device concerns visible. A helper named mobileCustomerContext() is more readable than a generic contextFor($options) call that hides everything.
Understand the limits
Mobile-ish emulation does not guarantee the same result as a physical phone. Browser chrome, virtual keyboards, OS-level share sheets, file pickers, notification prompts, camera input, and hardware performance can differ. Use Playwright emulation to catch web-level behavior. Use manual testing or device farms for hardware-level confidence.
Locale and timezone emulation also have limits. They affect browser-side APIs and rendering, but server-rendered text depends on your backend configuration. If a page renders dates on the server, configure the server test environment too.
Common mistakes
Changing viewport after navigation and expecting startup code to rerun produces misleading failures. Set options before loading the page unless resizing is the feature.
Using a large device matrix in every pull request makes the suite slow and noisy. Put the broad matrix in scheduled jobs or release checks.
Testing screenshots without pinning timezone, locale, viewport, and fonts leads to unstable diffs.
Granting permissions globally hides permission prompts and denial states. Have at least one test for the denied or explanatory path when the feature depends on permission.
Go next
- Browsers and contexts: context isolation and browser lifecycle.
- Authentication and state: combine saved state with environment options.
- Actions and input: pointer, mouse, keyboard, and touch-oriented interactions.
- Assertions: assert behavior before relying on screenshots.
- Devices: device-oriented reference and subject pages.