This episode covers running tests in Chromium, Firefox, and WebKit, browser contexts with mobile emulation and geolocation, testing responsive layouts, as well as using real device clouds for real-device coverage.

Tests that only run in one browser give a false sense of security — layout or behavior bugs often only appear in a specific engine. This episode 10 covers cross-browser testing: running the same tests in Chromium, Firefox, and WebKit, plus mobile device simulation so coverage reflects the way real users actually access the application.
Playwright makes cross-browser nearly free. With a single projects list in the configuration, the same tests execute on every engine you register. Combined with powerful device emulation — viewport, user agent, touch events — you can test mobile behavior without holding a physical device.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});Each project defines one browser-and-device combination. devices['Desktop Chrome'] is a preset containing the user agent, viewport, and other options that mimic a device profile. With the three projects above, npx playwright test runs the whole suite on three engines.
To limit execution to a single project during development:
npx playwright test --project=chromiumThe --project=chromium flag filters execution to the project named chromium. A common practice: run all browsers in CI, and only one browser locally for fast feedback.
Device emulation works by modifying context attributes — not just the viewport. This includes user agent, touch, and device scale factor:
import { devices } from '@playwright/test';
const context = await browser.newContext({
...devices['iPhone 13'],
});The spread ...devices['iPhone 13'] brings the entire profile — a 390x844 viewport, an iOS user agent, and hasTouch: true. The page under test will think it's being opened on an iPhone, including the mobile header usually triggered by the user agent.
Device presets can also be used directly in the project list:
projects: [
{ name: 'desktop-chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],A single project list can combine desktop and mobile at once. Each combination appears as one row in the report, so you immediately know exactly which device a test failed on.
Location-based applications can be tested with geolocation injected via the context, combined with a granted permission:
const context = await browser.newContext({
geolocation: { latitude: -6.2, longitude: 106.8 },
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('/find-nearby-stores');geolocation: { latitude: -6.2, longitude: 106.8 } simulates a position in Jakarta, and permissions: ['geolocation'] grants location access automatically. The browser now returns the fabricated coordinates whenever the application asks for location.
To test behavior that depends on time zone and language:
const context = await browser.newContext({
locale: 'id-ID',
timezoneId: 'Asia/Jakarta',
});timezoneId: 'Asia/Jakarta' makes date, time, and Intl operations behave according to that time zone. This locale/timezone combination is important for applications with scheduling logic or regional number formatting.
Responsive design isn't sufficiently tested with a single viewport. Data-driven testing with screen widths gives broader coverage:
import { test, expect } from '@playwright/test';
for (const width of [375, 768, 1440]) {
test(`navigation shows at ${width}px width`, async ({ page }) => {
await page.setViewportSize({ width, height: 800 });
await page.goto('/home');
if (width < 768) {
await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();
} else {
await expect(page.getByRole('navigation')).toBeVisible();
}
});
}page.setViewportSize({ width, height: 800 }) changes the viewport size within a test. At small widths, the hamburger menu should appear; at large widths, the full navigation is visible — behavior you can verify explicitly.
One indicator of a broken layout: important elements being clipped or overlapping. The toBeInViewport assertion helps detect this:
await expect(page.getByRole('button', { name: 'Buy' })).toBeInViewport();toBeInViewport() ensures the element is visible within the current viewport area. Combine it with several screen widths to catch overflow issues that usually slip past the eye.
Emulation is already very good, but it's not a complete replacement for real devices — especially for catching hardware-specific issues like GPU rendering or old Safari behavior. Cloud browser providers like BrowserStack, Sauce Labs, or other platforms allow running tests on real cloud browsers.
Most providers offer a wrapper or connection options to their cloud. The common pattern adds a dedicated project:
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'cloud-ios', use: { ...devices['iPhone 15'] } },
],When run with credentials from the environment, the cloud project connects to the provider's infrastructure and runs tests on their devices. Keep this layer optional — local emulation remains the backbone of the daily suite.
npx playwright testEpisode 10 made your suite speak the language of different browsers: per-engine projects run tests in Chromium, Firefox, and WebKit, device emulation with presets like iPhone and Pixel brings tests to the mobile experience, and the combination of geolocation, timezone, and various viewport widths extends coverage far beyond a single profile.
Key takeaways:
projects in the configuration run the same tests on many browsers.devices presets bring viewport, user agent, and touch together.In the next episode we'll discuss test coverage and performance — measuring unit, integration, and functional test coverage, performance insight with tracing and browser metrics, measuring page load and responsiveness, and the basics of visual regression.