Playwright Mobile Testing: How to Test on Real Devices vs Emulators (2026 Guide)
Struggling with mobile test coverage? This guide walks you through mobile testing, device emulation, cloud platform integration, Android setup, touch events, and CI pipelines with working code examples against a real e-commerce site.
Playwright doesn't run tests on real mobile browsers.
It emulates devices by setting viewport size, user agent, and touch capabilities inside Chromium or WebKit desktop engines.
-
For responsive web testing, that covers most of what you need.
-
For native gestures, hardware sensors, or actual Safari on iOS, it doesn't.
This guide covers what Playwright mobile testing can and can't do, how to configure device emulation, how to connect real Android devices via CDP, and when you need a cloud platform instead.
Playwright mobile testing refers to using the Playwright framework to validate web applications on mobile viewports, either through built-in device emulation (spoofing viewport, user agent, touch support) or by connecting to real physical devices via cloud device providers like LambdaTest, BrowserStack, or PCloudy.
What is Playwright mobile testing and how does it work?
Playwright is a browser automation framework built by Microsoft. It controls Chromium, Firefox, and WebKit (Safari's engine) through a single API. For mobile testing, it operates in two distinct modes.
Mode 1: Device emulation.
Playwright ships with a built-in registry of device descriptors covering over 100 devices. Each descriptor defines viewport width, height, device scale factor, user agent string, and touch support. When you apply a profile like iPhone 13 or Pixel 5, Playwright configures the browser context to mimic that device.
Mode 2: Real device testing.
Playwright can connect to real physical devices through cloud providers. These platforms expose real Android and iOS hardware via WebSocket or CDP (Chrome DevTools Protocol) connections. Playwright targets these remote browsers the same way it targets a local one.
Note: Playwright is designed for mobile web testing. It automates browsers and WebViews, not native mobile app UIs. For native app testing, tools like Appium or Maestro are better suited.
Here is what happens under the hood when you pick a device profile:
- Playwright reads the device descriptor from its internal JSON registry.
- A new browser context is created with matching viewport, userAgent, deviceScaleFactor, isMobile, and hasTouch properties.
- The browser renders the page as if it were running on that device.
- All pointer events are dispatched as touch events instead of mouse events.
This approach is fast, free, and completely local.
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
projects: [
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"] },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 13"] },
},
],
});
The config above creates two test projects. Every test in your suite runs once on a Pixel 5 emulation and once on an iPhone 13 emulation. Playwright handles viewport resizing, user agent spoofing, and touch event routing automatically.
Setting up Playwright device emulation (step-by-step)
Let's walk through the full setup from scratch. Every test below targets the TestDino Demo Store at storedemo.testdino.com, so you can run them yourself.
Step 1: Install Playwright
npm init -y
npm install -D @playwright/test
npx playwright install
The last command downloads browser binaries for Chromium, Firefox, and WebKit.
Step 2: Configure mobile device profiles
Open your playwright.config.ts and add mobile projects using the built-in devices object.
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
timeout: 30000,
use: {
baseURL: "https://storedemo.testdino.com",
trace: "on-first-retry",
},
projects: [
{
name: "Desktop Chrome",
use: { ...devices["Desktop Chrome"] },
},
{
name: "Pixel 5",
use: { ...devices["Pixel 5"] },
},
{
name: "iPhone 13",
use: { ...devices["iPhone 13"] },
},
{
name: "Galaxy S9+",
use: { ...devices["Galaxy S9+"] },
},
],
});
Step 3: Write a test that validates mobile navigation
On desktop, the TestDino Demo Store shows a full navigation bar. On mobile viewports, it collapses into a hamburger menu. This test verifies that behavior.
import { test, expect, devices } from '@playwright/test';
test.use({
...devices['Pixel 5'],
});
test('test', async ({ page }) => {
await page.goto('/');
await page.getByTestId('header-menu-icon').click();
await page.getByTestId('header-menu-all-products').nth(1).click();
});
Step 4: Write a test that adds a product to cart on mobile
import { test, expect } from "@playwright/test";
test("add product to cart on mobile viewport", async ({ page }) => {
await page.goto("/");
// Tap "Shop Now" on the hero section
await page.getByRole("link", { name: "Shop Now" }).tap();
// Click on the first product card to view details
await page.locator(".product-card").first().click();
// Wait for product detail page to load
await expect(page.getByRole("button", { name: "ADD TO CART" })).toBeVisible();
// Tap the Add to Cart button
await page.getByRole("button", { name: "ADD TO CART" }).tap();
// Verify the cart counter updates (badge shows item count)
const cartBadge = page.locator('[class*="badge"]').first();
await expect(cartBadge).toBeVisible();
});
Step 5: Run the test
npx playwright test --project="Pixel 5"

Tip: Use npx playwright test --project="iPhone 13" --headed to visually watch the test run in a resized browser window. This helps debug layout issues that only appear on specific viewports.
Step 6: Custom device profiles.
If the built-in profiles do not match your target device, define a custom one.
{
name: 'Custom Android Tablet',
use: {
viewport: { width: 800, height: 1280 },
userAgent: 'Mozilla/5.0 (Linux; Android 13; SM-X200) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
},
}
The Playwright architecture uses a client-server model where these device parameters are sent to the browser server process, which applies them before rendering any page.
Emulation vs real device testing: what actually changes?
This is the core question every team faces. Emulation is fast and free. Real device testing is accurate but costs money. The right answer depends on what you are testing.
| Factor | Emulation | Real device testing |
|---|---|---|
| Speed | Fast. Tests run locally, no network latency. | Slower. Network round-trip to cloud device. |
| Cost | Free. Included with Playwright. | Paid. Requires cloud platform subscription. |
| Rendering accuracy | Uses desktop browser engine. WebKit on desktop differs from Safari on iOS. | Uses actual device browser with real rendering engine. |
| Touch/gesture fidelity | Simulated. Cannot replicate multi-touch hardware behavior. | Real hardware touch screen and gesture processing. |
| Performance metrics | Misleading. Uses host machine CPU/GPU resources. | Accurate. Real CPU, RAM, and GPU constraints. |
| Hardware features | Cannot test GPS, camera, biometrics, or sensors. | Full access to device hardware capabilities. |
| CI/CD integration | Simple. No external dependencies. | Requires API keys and cloud platform configuration. |
| Device coverage | 100+ built-in profiles. Custom profiles possible. | Thousands of real devices across OS versions. |

Tip: A practical strategy: use emulation for 80% of mobile test runs (layout validation, responsive behavior, functional flows) and reserve real device testing for the remaining 20% (performance benchmarks, Safari-specific rendering, hardware-dependent features).
The biggest gap shows up with iOS Safari. Playwright uses WebKit on desktop to emulate Safari, but desktop WebKit is not the same engine as mobile Safari on an iPhone. Apple's mobile Safari has unique scrolling behavior, fixed positioning quirks, and viewport handling that desktop WebKit cannot replicate. Teams that skip real device validation for iOS routinely discover Safari-only bugs in production.

A common failure pattern: your emulated test on storedemo.testdino.com shows the product grid rendering in 2 columns on an iPhone 13 viewport. The test passes. But on a real iPhone 13, the grid renders in 1 column because mobile Safari interprets the CSS gap property differently when combined with flex-wrap. Emulation cannot catch this.
Here is a test that validates the product grid column count on mobile:
import { test, expect } from "@playwright/test";
test("product grid shows correct column layout on mobile", async ({ page }) => {
await page.goto("/");
// Scroll to the product section
const productSection = page.locator(".product-card").first();
await productSection.scrollIntoViewIfNeeded();
// Get the viewport width
const viewport = page.viewportSize();
if (viewport && viewport.width < 768) {
// On mobile, products should stack or show 2 columns max
const firstCard = await page.locator(".product-card").first().boundingBox();
const secondCard = await page.locator(".product-card").nth(1).boundingBox();
if (firstCard && secondCard) {
// If cards are stacked, second card's Y should be greater than first card's Y + height
// If side by side, they share approximately the same Y
const isStacked = secondCard.y > firstCard.y + firstCard.height / 2;
const isTwoColumn = Math.abs(secondCard.y - firstCard.y) < 10;
expect(isStacked || isTwoColumn).toBeTruthy();
}
}
});
Source: Perfecto's 2024 "Mobile Testing Coverage Report" comparing emulation vs physical device defect discovery across 12,000 test suites

How do you run Playwright tests on real mobile devices?
Playwright does not ship with built-in real device connectivity. To run tests on physical Android or iOS devices, you connect Playwright to a remote browser session hosted by a cloud device provider.
Here are the three main approaches.
Approach 1: Cloud SDK integration (LambdaTest example)
Most cloud providers offer an SDK or WebSocket-based connection. Here is a setup using LambdaTest as an example. The pattern is similar across providers.
Step 1: Install the provider SDK
npm install -D lambdatest-node-sdk
Step 2: Configure device capabilities
import { test, expect } from "@playwright/test";
const capabilities = {
browserName: "Chrome",
browserVersion: "latest",
"LT:Options": {
platform: "Android",
deviceName: "Pixel 7",
platformVersion: "13.0",
user: process.env.LT_USERNAME,
accessKey: process.env.LT_ACCESS_KEY,
network: true,
console: true,
},
};
test("verify product page loads on real Pixel 7", async ({ browser }) => {
const context = await browser.newContext(capabilities);
const page = await context.newPage();
await page.goto("https://storedemo.testdino.com");
await expect(page.getByText("Demo E-commerce Testing Store")).toBeVisible();
// Navigate to products and verify grid renders
await page.getByText("All Products").click();
await expect(page.getByPlaceholder("Search products...")).toBeVisible();
await context.close();
});
Approach 2: BrowserStack SDK integration
BrowserStack uses a YAML config file for device targets.
userName: YOUR_USERNAME
accessKey: YOUR_ACCESS_KEY
platforms:
- deviceName: Samsung Galaxy S23
osVersion: 13.0
browserName: chrome
browserVersion: latest
- deviceName: iPhone 15
osVersion: 17
browserName: safari
browserVersion: latest
parallelsPerPlatform: 2
npm install -D browserstack-node-sdk
npx browserstack-node-sdk playwright test
Approach 3: Experimental Android support (local USB)
Playwright has experimental support for connecting to real Android devices via ADB. No cloud subscription needed.
import { _android as android } from "playwright";
(async () => {
const [device] = await android.devices();
console.log(`Connected to: ${device.model()}`);
await device.shell("am force-stop com.android.chrome");
const context = await device.launchBrowser();
const page = await context.newPage();
await page.goto("https://storedemo.testdino.com");
// Verify the store loads on a real Android device
const heading = page.getByText("Demo E-commerce Testing Store");
console.log(`Heading visible: ${await heading.isVisible()}`);
await context.close();
await device.close();
})();
Note: The _android API is experimental (Playwright v1.20+) and requires: (1) a physical Android device connected via USB, (2) ADB daemon running, (3) USB debugging enabled, and (4) Chrome 87+ installed. Screenshots only work when the device screen is awake.
For iOS, Playwright cannot connect directly to Safari on physical iPhones from a local setup. Apple restricts third-party browser automation on iOS devices. Cloud providers are the only option for real iPhone testing.
Understanding how Playwright locators work across device contexts is critical. Locators like getByRole and getByText stay consistent whether you test on emulation or a real device.
Cloud platform comparison: choosing a real device provider
Picking a provider depends on device coverage needs, budget, and CI integration.
| Feature | LambdaTest | BrowserStack | PCloudy |
|---|---|---|---|
| Real device count | 3,000+ (Android & iOS) | 3,500+ (Android & iOS) | 500+ (Android & iOS) |
| Playwright support | WebSocket-based connection | Native SDK integration | CDP-based connection |
| iOS Safari on real iPhone | Yes (supported) | Yes (full support) | Limited |
| Parallel execution | Yes (plan-based limits) | Yes (plan-based limits) | Yes (limited) |
| Video recording | Automatic | Automatic | Manual trigger |
| CI/CD integration | GitHub Actions, GitLab CI, Jenkins, Azure DevOps | GitHub Actions, GitLab CI, Jenkins, CircleCI | Jenkins, CircleCI |
| Pricing | Starts at $15/month | Starts at $29/month | Starts at $100/month |
| Free trial | 100 minutes | 100 minutes | Free trial available |
All three support Playwright. LambdaTest is the most budget-friendly option. BrowserStack has the widest iOS device catalog. PCloudy is a niche choice for teams already using it for Appium-based native testing.
When running Playwright parallel execution on cloud platforms, align your worker count with your subscription tier to avoid session queue bottlenecks.
Handling touch events and mobile gestures in Playwright
When a device profile has hasTouch: true, Playwright automatically routes pointer events as touch events. This means page.click() internally dispatches a touch event instead of a mouse event on mobile contexts.
Here are working examples against the TestDino Demo Store.
Tap to all products:
import { test, expect, devices } from '@playwright/test';
test.use({
...devices['Pixel 5'],
});
test('test', async ({ page }) => {
await page.goto('/');
await page.getByTestId('header-menu-icon').click();
await page.getByTestId('header-menu-all-products').nth(1).click();
});

Tip: Always set isMobile: true in your device config. This property controls whether the browser respects the <meta name="viewport"> tag. Without it, mobile-optimized pages render at desktop width even if the viewport size is set correctly.
A common debugging pitfall: page.tap() fails with "element not visible" on a mobile viewport because a sticky header covers the target element. Fix this by scrolling the element into view first:
await page.locator(".target-element").scrollIntoViewIfNeeded();
await page.locator(".target-element").tap();

Running mobile tests in CI/CD pipelines
Mobile emulation tests run identically in CI as they do locally. No special configuration needed because emulation uses the same browser binaries.
GitHub Actions example:
name: Mobile Tests
on: [push, pull_request]
jobs:
mobile-emulation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test --project="Pixel 5" --project="iPhone 13"
- uses: actions/upload-artifact@v4
if: always()
with:
name: mobile-test-report
path: playwright-report/
real-device-tests:
runs-on: ubuntu-latest
needs: mobile-emulation
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run test:cloud-devices
env:
CLOUD_USERNAME: ${{ secrets.CLOUD_USERNAME }}
CLOUD_ACCESS_KEY: ${{ secrets.CLOUD_ACCESS_KEY }}
This workflow creates a two-stage pipeline.
- Stage 1 runs tests on mobile emulation profiles. Fast, free, catches layout and functional regressions.
- Stage 2 runs the same tests on real devices via your cloud provider. Validates rendering accuracy on actual hardware.
The needs: mobile-emulation directive ensures real device tests only run after emulation passes. This saves cloud minutes by skipping real device runs when tests are already failing.
Note: Store cloud provider credentials as encrypted secrets in your CI system. Never hardcode API keys in config files or commit them to version control.
GitLab CI example:
mobile-tests:
image: mcr.microsoft.com/playwright:v1.49.0-noble
stage: test
script:
- npm ci
- npx playwright test --project="Pixel 5" --project="iPhone 13"
artifacts:
when: always
paths:
- playwright-report/
expire_in: 7 days
When investigating flaky tests in mobile CI runs, pay special attention to viewport-dependent selectors and timing issues caused by mobile-specific animations.
Teams using TestDino alongside their Playwright CI setup can track mobile test pass/fail trends across devices from a single observability dashboard. This makes it easier to catch device-specific regressions before they reach production.
Playwright vs Appium vs Maestro vs Detox: picking the right mobile testing tool
A common question: should you use Playwright for mobile testing at all, or pick a dedicated mobile automation framework? Four tools dominate the landscape, and each targets a different slice of mobile testing.

Playwright vs Appium vs Maestro vs Detox
| Capability | Playwright | Appium | Maestro | Detox |
|---|---|---|---|---|
| Primary use case | Mobile web, PWA, hybrid WebView | Native, hybrid, and mobile web apps | Native mobile UI (iOS & Android) | React Native E2E testing |
| Testing approach | Black-box (browser automation) | Black-box (WebDriver protocol) | Black-box (UI-level) | Gray-box (in-process, app-aware) |
| Language support | JS/TS, Python, Java, C# | Java, Python, Ruby, C#, JS | YAML (declarative) | JS/TS only (Jest integration) |
| Setup complexity | Low (npm install) | High (server, drivers, SDKs) | Low (CLI install) | Medium (native build config required) |
| Execution speed | Fast (direct browser protocol) | Moderate (WebDriver overhead) | Fast (interpreted, no compilation) | Very fast (in-process, same thread) |
| Flaky test handling | Auto-wait + retry on assertion | Explicit waits needed, flake-prone | Intelligent UI wait, auto-retry | Auto-sync with animations, network, and RN bridge |
| iOS real device | Via cloud platforms only | Yes (XCUITest driver) | Yes (native support) | Yes (simulators + real devices) |
| Android real device | Experimental (_android API) + cloud | Yes (UIAutomator2/Espresso) | Yes (native support) | Yes (emulators + real devices) |
| Cross-browser testing | Chromium, Firefox, WebKit | Limited to device browser | No (app-focused) | No (app-focused) |
| CI/CD friendliness | Excellent (Docker images, GitHub Actions) | Good (requires Appium server) | Good (Maestro Cloud available) | Excellent (built for CI, headless mode) |
| Learning curve | Moderate | Steep | Low (YAML-based) | Moderate (JS/TS + native config) |
| Community (GitHub stars) | 68k+ | 18k+ | 7k+ | 11k+ |
When Playwright is the right choice:
-
You are testing a responsive web app, PWA, or hybrid app with WebViews.
-
You need cross-browser coverage (Chromium + Firefox + WebKit) on mobile viewports.
-
Your team already uses Playwright for desktop web testing and wants to reuse the same framework.
-
You want fast CI/CD feedback with zero external dependencies for mobile emulation.
-
Your test suite needs to cover both desktop and mobile web flows without switching tools.
When Appium is the right choice:
-
You are testing a native Android or iOS app with complex UI interactions.
-
You need access to hardware features like GPS, camera, biometrics, or push notifications.
-
Your team works across multiple platforms and languages (Java, Python, Ruby, C#).
-
You are testing hybrid apps that mix native components with WebViews.
-
Your organization has invested in the Selenium/WebDriver ecosystem and wants to stay on that standard.
When Maestro is the right choice:
-
You want fast, no-code mobile UI validation using YAML.
-
Your team includes QA engineers or product managers who are not comfortable writing JavaScript.
-
You are testing Flutter, React Native, or SwiftUI apps where visual test creation speeds up coverage.
-
You need to spin up quick smoke tests without configuring build systems.
-
You want cloud-hosted test orchestration with Maestro Cloud for parallel runs.
When Detox is the right choice:
-
Your product is built on React Native and you need E2E tests tightly coupled to the app lifecycle.
-
Test flakiness is a major issue because your app uses heavy animations, complex async operations, or network-dependent flows.
-
Your team writes JavaScript/TypeScript and wants a testing framework that integrates natively with Jest.
-
You need tests that automatically wait for the React Native bridge, animations, and network calls to settle before each action.
-
CI reliability matters more than language flexibility. Detox's gray-box approach produces some of the most deterministic test results in the mobile testing space.
A practical combination that many teams use: Playwright for mobile web testing, Detox for React Native E2E coverage, and a cloud provider for real device validation of critical flows. This covers the full spectrum without forcing any single tool to do everything.
According to the Appium market share data, Appium remains the most widely adopted mobile testing framework as of 2026. However, Detox adoption has grown steadily among React Native teams, and Playwright is increasingly chosen by teams that primarily test mobile web experiences.

Source: Based on JetBrains "State of Developer Ecosystem" surveys 2022-2024 and Stack Overflow Developer Surveys 2022-2025 adoption trend data.
Conclusion
Playwright mobile testing gives you two paths: emulation for fast, free validation of layouts and functional flows, and real device clouds for Safari accuracy and hardware-level performance. Use the built-in devices API for emulation and a cloud SDK for real devices. The same test code runs on both, and CI integration needs nothing more than a YAML config and API credentials.
For responsive web apps, PWAs, or hybrid apps with WebViews, Playwright covers your mobile testing needs without forcing you into a separate framework. For native mobile apps, pair it with Appium, Maestro, or Detox depending on your stack and complexity.
Start with emulation. Add real devices for your most critical user flows. Build a BDD-driven test structure so your suite stays maintainable as device targets grow, and use TestDino to track pass/fail rates across every config from one dashboard.
Your mobile users will not wait for a broken experience to be fixed. Test it before they see it.
FAQs
Table of content
Flaky tests killing your velocity?
TestDino auto-detects flakiness, categorizes root causes, tracks patterns over time.