Testland
Browse all skills & agents

mobile-web-emulation-runner

Builds a workflow to run web E2E tests under mobile viewports + DPRs (device pixel ratios): Playwright's `devices` catalog (iPhone 15, Pixel 7), suite run per-device as matrix shards, per-device screenshots, mobile assertions (`.tap()`, viewport-conditional layout). Use when a responsive web app needs mobile-breakpoint regression without a real-device farm. Mobile WEB only - for native apps use appium-testing, detox-testing, or flutter-testing; for cross-shard aggregation use mobile-device-matrix-toolkit; for gesture sequences use touch-gesture-tester.

Install with skills.sh (any agent)

npx skills add testland/qa --skill mobile-web-emulation-runner
View source

mobile-web-emulation-runner

Overview

Many web apps support mobile via responsive design - but the desktop test suite never exercises mobile breakpoints. Real-device testing (appium-testing, xcuitest-suite) is heavy; viewport emulation in browser-based testing is light.

Playwright + Cypress + Selenium all support mobile emulation: viewport size, device pixel ratio, user agent, and touch event synthesis can be configured per test.

This skill builds the workflow.

When to use

  • The web app has a mobile responsive layout and the team wants regression coverage on mobile breakpoints.
  • A bug surfaced on mobile-only and needs a regression test that doesn't require a real device.
  • Pre-release sweep wants a "does the site look right at iPhone size" gate without setting up a mobile test farm.

If the app is a native mobile app (RN, Flutter, native iOS/Android), this isn't the right skill - see the per-platform alternatives.

How to use

  1. Choose the device profiles to cover from Playwright's devices catalog (Step 1, references/device-profiles-and-cypress.md).
  2. Declare one Playwright project per profile in playwright.config.ts (Step 2).
  3. Write mobile-aware assertions with .tap() and viewport-conditional layout checks (Step 3).
  4. Add per-device visual snapshots so mobile-size layout regressions are caught (Step 4).
  5. Run each project as its own CI matrix job with fail-fast: false (Step 5).
  6. Aggregate the per-device results into one pass/fail summary (Step 6).

Step 1 - Pick the device profiles

Playwright ships a devices catalog with realistic viewport / DPR / user-agent combinations - import it and spread a profile into a project's use block:

import { devices } from '@playwright/test';

// each entry carries viewport + DPR + user-agent + touch flags, e.g.
// devices['iPhone 15'] -> viewport 393x852, DPR 3, isMobile, hasTouch

isMobile: true triggers Playwright's mobile-mode quirks (meta viewport handling); hasTouch: true enables touch-event synthesis. The full profile catalog, the shape of each entry, and the Cypress runner equivalent are in references/device-profiles-and-cypress.md.

Step 2 - Per-device project config

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'desktop-chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile-iphone-15',
      use: { ...devices['iPhone 15'] },
    },
    {
      name: 'mobile-pixel-7',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'tablet-ipad-pro',
      use: { ...devices['iPad Pro 11'] },
    },
  ],
});

Run all projects (which is the desktop + 3 mobile shards):

npx playwright test

Run only mobile:

npx playwright test --project=mobile-iphone-15 --project=mobile-pixel-7

Step 3 - Mobile-specific assertions

Tests should distinguish desktop-only from mobile-aware behavior:

import { test, expect, devices } from '@playwright/test';

test.describe('Cart page - mobile layout', () => {
  test.use(devices['iPhone 15']);

  test('shows mobile drawer, not sidebar', async ({ page }) => {
    await page.goto('/cart');
    // Mobile-specific: drawer behind hamburger
    await expect(page.getByRole('button', { name: /menu/i })).toBeVisible();
    await expect(page.getByRole('navigation')).not.toBeVisible();   // hidden until open
  });

  test('tap (not click) on add-to-cart', async ({ page }) => {
    await page.goto('/products/BOOK-001');
    await page.getByRole('button', { name: /add to cart/i }).tap();   // .tap not .click
    await expect(page.getByRole('alert', { name: /added/i })).toBeVisible();
  });
});

.tap() synthesizes a touch event (isMobile: true enables); .click() synthesizes mouse events. Prefer .tap() on mobile profiles.

Step 4 - Visual regression per device

test('home page mobile layout snapshot', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('home-iphone-15.png');
});

Per-device screenshots produce per-device baselines; layout regressions on iPhone size catch issues that desktop-only tests miss. Pair with playwright-snapshots (in the qa-visual-regression plugin).

Step 5 - CI matrix

jobs:
  e2e:
    strategy:
      fail-fast: false
      matrix:
        project:
          - desktop-chromium
          - mobile-iphone-15
          - mobile-pixel-7
          - tablet-ipad-pro
    runs-on: ubuntu-latest
    name: ${{ matrix.project }}
    steps:
      - uses: actions/checkout@v5
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --project=${{ matrix.project }}

Each project runs as a separate matrix job; fail-fast: false ensures a failure on iPhone doesn't cancel Pixel.

Verify: assert every matrix shard exits 0. Because fail-fast: false lets shards fail independently, gate the merge on all shards green - one red shard (e.g. mobile-iphone-15) must block, never be averaged away.

Step 6 - Aggregating per-device results

Use the mobile-device-matrix-toolkit aggregator (Step 4) to produce a per-device summary:

| Project                | Tests | Pass | Fail | Time   |
|------------------------|------:|-----:|-----:|-------:|
| desktop-chromium        |   42  |   42 |    0 | 120s   |
| mobile-iphone-15        |   42  |   40 |    2 | 145s   |   ← drawer regression
| mobile-pixel-7          |   42  |   42 |    0 | 138s   |
| tablet-ipad-pro         |   42  |   41 |    1 | 132s   |   ← landscape layout

Verify: before merging, assert every row shows Fail = 0. If a shard is red
(e.g. mobile-iphone-15 above), open its report, reproduce with
`npx playwright test --project=<shard>`, fix the mobile-breakpoint
regression, and re-run that shard until green.

Worked example

A responsive storefront renders its nav as a sidebar on desktop and behind a hamburger on mobile. A regression once hid the hamburger at iPhone width.

  1. Add a mobile-iphone-15 project to playwright.config.ts alongside desktop-chromium (Step 2), each spreading its devices[...] profile.
  2. Author the 'shows mobile drawer, not sidebar' assertion from Step 3 under test.use(devices['iPhone 15']).
  3. Run npx playwright test --project=mobile-iphone-15.
  4. The hamburger assertion fails on the regressed build (the button is not visible) while the same suite passes on desktop-chromium. The matrix summary flags mobile-iphone-15 red and desktop green, pinpointing a mobile-breakpoint-only regression the desktop suite never exercised.

Anti-patterns

Anti-patternWhy it failsFix
Running the same desktop tests with viewport: { width: 375 } onlyMisses DPR / touch / user-agent differences.Use devices[...] catalog (Step 1).
.click() on mobile projectSynthesizes mouse events; misses touch-handler bugs..tap() for isMobile: true projects (Step 3).
One desktop+mobile mega-testBranching if (viewport.width < ...) clutters; per-project tests cleaner.Per-project tests (Step 2).
Mobile-only baselines without desktop comparisonMisses cases where the desktop layout regressed at the mobile breakpoint.Both desktop + mobile in CI (Step 5).
Treating emulation as substitute for real devicesEmulation doesn't catch real-device perf, touch sensitivity, browser quirks.Pair with farm runs for release tier.
Skipping --with-deps in Playwright installCI runner missing browser dependencies; mobile profiles fail.Always npx playwright install --with-deps in CI (Step 5).

Limitations

  • Emulation ≠ real device. Mobile Safari has quirks Chromium emulation doesn't reproduce (e.g. 100vh viewport behavior, iOS-specific gestures). Pair with real-device testing for the release tier.
  • No native APIs. Emulation can't test camera / push notifications / geolocation accuracy / biometrics.
  • Performance under emulation is the runner's CPU. For mobile perf testing, see mobile-web-perf-budget
    • Lighthouse mobile profile.
  • Cypress feature gap. Playwright's devices catalog is richer; Cypress requires more manual setup.

References

  • references/device-profiles-and-cypress.md - full Playwright devices catalog (viewport / DPR / UA / touch synthesis) and the Cypress runner equivalent.
  • mobile-device-matrix-toolkit - sibling: orchestrates per-target dispatch and aggregation.
  • mobile-web-perf-budget - performance testing under mobile profile.
  • touch-gesture-tester - detailed touch-gesture verification.
  • playwright-snapshots - per-device visual regression.

Mobile-web emulation - device profiles and the Cypress runner

View source (opens in new window)

Mobile-web emulation - device profiles and the Cypress runner

Lookup reference for the full Playwright devices catalog and for the Cypress equivalent of viewport emulation. Referenced from Step 1 and the References of the mobile-web-emulation-runner skill.

Playwright device profiles

Playwright ships a devices catalog with realistic viewport / DPR / user-agent combinations:

import { devices } from '@playwright/test';

// Common modern profiles:
devices['iPhone 15']
devices['iPhone 15 Pro Max']
devices['iPhone 14']
devices['Pixel 7']
devices['Pixel 5']
devices['Galaxy S9+']
devices['iPad Pro 11']
devices['iPad Mini']

Each entry includes:

{
  viewport: { width: 393, height: 852 },
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true,
  userAgent: 'Mozilla/5.0 (iPhone; ...) AppleWebKit/...',
}

isMobile: true triggers Playwright's mobile-mode quirks (meta viewport handling); hasTouch: true enables touch-event synthesis.

Cypress equivalent

// cypress.config.ts
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // Per-test or per-spec viewport
    },
    viewportWidth: 1280,
    viewportHeight: 720,
  },
});

// In a test:
beforeEach(() => {
  cy.viewport('iphone-15');   // built-in preset
  // OR
  cy.viewport(393, 852, 'portrait');
});

Cypress doesn't ship a devices catalog as rich as Playwright's; viewport sizing is the primary control. For touch-event synthesis, use cy.realTouch() (via cypress-real-events plugin).

Related skills

appium-testing

Wires Appium for cross-platform mobile UI automation - uses the WebDriver protocol, picks a driver per platform (XCUITest for iOS, UiAutomator2 / Espresso for Android, Mac2 for macOS, Windows for desktop), authors tests in JS / Python / Java / Ruby / .NET, configures `desiredCapabilities`, runs against simulators / emulators / device farms. Use when a single test suite must cover both iOS and Android, or when the team's stack is multi-platform (iOS + Android + Mac + Windows).

detox-testing

Authors React Native E2E tests with Detox (Wix) - gray-box architecture (runs in-process with the app), `element(by.id|by.text|by.label)` matchers, `waitFor()` for explicit sync beyond Detox's automatic async tracking, Jest runner. Use when the app is React Native and speed matters. For Flutter use flutter-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows; for non-RN native use xcuitest-suite or espresso-suite.

espresso-suite

Authors Espresso UI tests for Android - uses `onView(withId(...)).perform(...).check(matches(...))`, leans on Espresso's automatic synchronization (no `Thread.sleep`), wires `IdlingResource` for app-specific async, runs via `./gradlew connectedAndroidTest` and parses the JUnit XML output. Use when an Android app needs UI tests in Google's first-party framework.

flutter-testing

Authors Flutter tests across the three-layer pyramid - unit (`flutter test`), widget (`testWidgets` + `WidgetTester`), integration (`integration_test` on simulator/emulator/device). Picks the right layer per change, mocks via `mockito` + `build_runner`, LCOV coverage, CI with the Flutter Action. Use when the app is Flutter and the team wants its first-party stack. For React Native use detox-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows.

maestro-flows

Authors Maestro YAML flow files (`.maestro/*.yaml`) for mobile + web UI automation: declarative `tapOn`, `inputText`, `assertVisible`, `swipe`, supported targets (iOS, Android, Flutter, React Native, web), nested flow imports, JavaScript hooks for complex conditions. Use when the team has already chosen Maestro, is coming from an existing `.maestro/` directory, or explicitly wants YAML-declarative tests readable by non-engineers without a compile step. For framework selection or authoring tests in XCUITest / Espresso / Detox / Appium / Flutter, use a mobile driver-selection or per-flow mobile test-authoring step instead.

mobile-a11y-test-author

Authors native mobile accessibility tests covering iOS (Accessibility Inspector, XCUITest `performAccessibilityAudit()` introduced in iOS 17, VoiceOver label/trait/hint verification) and Android (Espresso `AccessibilityChecks.enable()`, Accessibility Scanner, TalkBack traversal, `contentDescription` labelling) with WCAG-aligned checks for element labels, 44pt/48dp touch targets, contrast ratios, and focus order. Use when an iOS or Android app needs automated and manual accessibility test coverage beyond what `xcuitest-suite` or `espresso-suite` provide.

mobile-device-matrix-toolkit

Dispatches mobile UI test runs across a 3-tier device matrix (smoke per-PR, regression per-merge, full farm at release) to control CI cost: generates per-target Appium capability configs from a central YAML, parallelises via GitHub Actions matrix strategy, and aggregates JUnit XML into a cross-device pass/fail table. Use when deciding which iOS / Android devices and OS versions to run tests on and at which stage (smoke / regression / full farm), not how to configure a specific test framework (for that, use xcuitest-suite, espresso-suite, etc.).

mobile-web-perf-budget

Pure-reference skill for mobile-web performance budgets - Core Web Vitals at the 75th percentile mobile (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1; FID retired March 2024 in favor of INP), Lighthouse mobile profile config, per-route resource budgets (JS bundle, image weight, font load). Use as the team's reference for "what should the mobile perf gate enforce" - paired with `lighthouse-perf` (the runner) and `lighthouse-budget-author` (the per-route author).

touch-gesture-tester

Verifies touch-gesture handlers (tap, double-tap, long-press, swipe, pinch, rotate, pan) work as expected under both mobile-emulation (Playwright) and native (XCUITest / Espresso / Detox) - distinguishes "mouse click handler also fires on tap" from "real touch event fired with correct properties." Use when the app has bespoke gesture handlers (custom carousels, sliders, drag-drop, pull-to-refresh) and the team needs targeted gesture verification beyond generic UI assertions.

xcuitest-suite

Authors XCUIest UI tests for iOS / iPadOS / tvOS - uses the three-class XCUIApplication / XCUIElement / XCUIElementQuery pattern, sets accessibility identifiers on production code, runs via `xcodebuild test` with destination, parses the `xcresult` bundle. Use when an iOS app needs UI tests in Apple's first-party framework (no external runtime; native to Xcode).