CtrlK
BlogDocsLog inGet started
Tessl Logo

mobile-emulation

Mobile device emulation and responsive testing with Playwright. Use when testing mobile layouts, touch interactions, device-specific features, or responsive breakpoints.

62

Quality

74%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./skills/mobile-emulation/SKILL.md
SKILL.md
Quality
Evals
Security

Mobile Emulation Testing with Playwright

Test responsive designs and mobile-specific features using Playwright's device emulation capabilities.

Quick Start

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

test.use(devices['iPhone 14']);

test('mobile navigation works', async ({ page }) => {
  await page.goto('/');

  // Mobile menu should be visible
  await page.getByRole('button', { name: 'Menu' }).click();
  await expect(page.getByRole('navigation')).toBeVisible();
});

Configuration

Project-Based Device Testing

playwright.config.ts:

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

export default defineConfig({
  projects: [
    // Desktop browsers
    {
      name: 'Desktop Chrome',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'Desktop Firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'Desktop Safari',
      use: { ...devices['Desktop Safari'] },
    },

    // Mobile devices
    {
      name: 'iPhone 14',
      use: { ...devices['iPhone 14'] },
    },
    {
      name: 'iPhone 14 Pro Max',
      use: { ...devices['iPhone 14 Pro Max'] },
    },
    {
      name: 'Pixel 7',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'Galaxy S23',
      use: { ...devices['Galaxy S III'] },  // Closest available
    },

    // Tablets
    {
      name: 'iPad Pro',
      use: { ...devices['iPad Pro 11'] },
    },
    {
      name: 'iPad Mini',
      use: { ...devices['iPad Mini'] },
    },
  ],
});

Custom Device Configuration

{
  name: 'Custom Mobile',
  use: {
    viewport: { width: 390, height: 844 },
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...',
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
    defaultBrowserType: 'webkit',
  },
},

Available Devices

Popular Devices

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

// iPhones
devices['iPhone 14']
devices['iPhone 14 Plus']
devices['iPhone 14 Pro']
devices['iPhone 14 Pro Max']
devices['iPhone 13']
devices['iPhone 12']
devices['iPhone SE']

// Android Phones
devices['Pixel 7']
devices['Pixel 5']
devices['Galaxy S III']
devices['Galaxy S5']
devices['Galaxy Note 3']
devices['Nexus 5']

// Tablets
devices['iPad Pro 11']
devices['iPad Pro 11 landscape']
devices['iPad Mini']
devices['iPad (gen 7)']
devices['Galaxy Tab S4']

// Desktop
devices['Desktop Chrome']
devices['Desktop Firefox']
devices['Desktop Safari']
devices['Desktop Edge']

List All Devices

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

console.log(Object.keys(devices));
// Outputs all available device names

Responsive Breakpoint Testing

Test Multiple Viewports

const breakpoints = [
  { name: 'mobile', width: 375, height: 667 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1280, height: 720 },
  { name: 'wide', width: 1920, height: 1080 },
];

for (const bp of breakpoints) {
  test(`layout at ${bp.name}`, async ({ page }) => {
    await page.setViewportSize({ width: bp.width, height: bp.height });
    await page.goto('/');
    await expect(page).toHaveScreenshot(`layout-${bp.name}.png`);
  });
}

Dynamic Viewport Changes

test('responsive navigation', async ({ page }) => {
  await page.goto('/');

  // Desktop - horizontal nav
  await page.setViewportSize({ width: 1280, height: 720 });
  await expect(page.locator('.desktop-nav')).toBeVisible();
  await expect(page.locator('.mobile-menu-button')).not.toBeVisible();

  // Tablet - may show hamburger
  await page.setViewportSize({ width: 768, height: 1024 });

  // Mobile - hamburger menu
  await page.setViewportSize({ width: 375, height: 667 });
  await expect(page.locator('.desktop-nav')).not.toBeVisible();
  await expect(page.locator('.mobile-menu-button')).toBeVisible();
});

Touch Interactions

Tap

test('tap interaction', async ({ page }) => {
  await page.goto('/');

  // Tap is equivalent to click on touch devices
  await page.getByRole('button').tap();
});

Swipe

test('swipe carousel', async ({ page }) => {
  await page.goto('/gallery');

  const carousel = page.locator('.carousel');
  const box = await carousel.boundingBox();

  if (box) {
    // Swipe left
    await page.mouse.move(box.x + box.width - 50, box.y + box.height / 2);
    await page.mouse.down();
    await page.mouse.move(box.x + 50, box.y + box.height / 2, { steps: 10 });
    await page.mouse.up();
  }

  await expect(page.locator('.slide-2')).toBeVisible();
});

Pinch to Zoom

test('pinch to zoom map', async ({ page }) => {
  await page.goto('/map');

  const map = page.locator('#map');
  const box = await map.boundingBox();

  if (box) {
    const centerX = box.x + box.width / 2;
    const centerY = box.y + box.height / 2;

    // Simulate pinch out (zoom in)
    await page.touchscreen.tap(centerX, centerY);
    // Note: Multi-touch pinch requires custom implementation
  }
});

Long Press

test('long press context menu', async ({ page }) => {
  await page.goto('/');

  const element = page.locator('.long-press-target');
  const box = await element.boundingBox();

  if (box) {
    await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
    await page.mouse.down();
    await page.waitForTimeout(1000);  // Hold for 1 second
    await page.mouse.up();
  }

  await expect(page.locator('.context-menu')).toBeVisible();
});

Orientation Testing

Portrait vs Landscape

test('orientation change', async ({ page }) => {
  // Portrait
  await page.setViewportSize({ width: 390, height: 844 });
  await page.goto('/video');
  await expect(page.locator('.video-container')).toHaveCSS('width', '390px');

  // Landscape
  await page.setViewportSize({ width: 844, height: 390 });
  await expect(page.locator('.video-container')).toHaveCSS('width', '844px');
});

Using Device Landscape Variants

test.use(devices['iPad Pro 11 landscape']);

test('tablet landscape layout', async ({ page }) => {
  await page.goto('/dashboard');

  // Sidebar should be visible in landscape
  await expect(page.locator('.sidebar')).toBeVisible();
});

Geolocation Testing

test.use({
  geolocation: { latitude: 40.7128, longitude: -74.0060 },  // NYC
  permissions: ['geolocation'],
});

test('shows nearby locations', async ({ page }) => {
  await page.goto('/locations');

  await page.getByRole('button', { name: 'Find Nearby' }).click();

  await expect(page.getByText('New York')).toBeVisible();
});

Change Location During Test

test('location change', async ({ page, context }) => {
  await context.setGeolocation({ latitude: 51.5074, longitude: -0.1278 });  // London
  await page.goto('/weather');
  await expect(page.getByText('London')).toBeVisible();

  await context.setGeolocation({ latitude: 35.6762, longitude: 139.6503 });  // Tokyo
  await page.reload();
  await expect(page.getByText('Tokyo')).toBeVisible();
});

Network Conditions

Slow 3G

test('works on slow network', async ({ page, context }) => {
  // Emulate slow 3G
  const client = await context.newCDPSession(page);
  await client.send('Network.emulateNetworkConditions', {
    offline: false,
    downloadThroughput: (500 * 1024) / 8,  // 500kb/s
    uploadThroughput: (500 * 1024) / 8,
    latency: 400,  // 400ms
  });

  await page.goto('/');

  // Should show skeleton loaders
  await expect(page.locator('.skeleton')).toBeVisible();

  // Eventually loads
  await expect(page.locator('.content')).toBeVisible({ timeout: 30000 });
});

Offline Mode

test('offline functionality', async ({ page, context }) => {
  await page.goto('/');

  // Cache page, then go offline
  await context.setOffline(true);

  await page.reload();

  // Should show offline message or cached content
  await expect(page.getByText(/offline/i)).toBeVisible();
});

Device-Specific Features

Notch/Safe Areas

test('respects safe areas', async ({ page }) => {
  // iPhone with notch
  test.use(devices['iPhone 14 Pro']);

  await page.goto('/');

  // Header should account for notch
  const header = page.locator('header');
  const paddingTop = await header.evaluate(el =>
    window.getComputedStyle(el).paddingTop
  );

  // Should have safe area inset
  expect(parseInt(paddingTop)).toBeGreaterThan(20);
});

Dark Mode

test.use({
  colorScheme: 'dark',
});

test('dark mode styling', async ({ page }) => {
  await page.goto('/');

  const body = page.locator('body');
  const bgColor = await body.evaluate(el =>
    window.getComputedStyle(el).backgroundColor
  );

  // Should have dark background
  expect(bgColor).toBe('rgb(0, 0, 0)');  // or dark color
});

Reduced Motion

test.use({
  reducedMotion: 'reduce',
});

test('respects reduced motion', async ({ page }) => {
  await page.goto('/');

  const animated = page.locator('.animated-element');
  const animationDuration = await animated.evaluate(el =>
    window.getComputedStyle(el).animationDuration
  );

  // Should have no animation
  expect(animationDuration).toBe('0s');
});

Visual Regression Across Devices

const testDevices = [
  'iPhone 14',
  'Pixel 7',
  'iPad Pro 11',
  'Desktop Chrome',
];

for (const deviceName of testDevices) {
  test.describe(`Visual: ${deviceName}`, () => {
    test.use(devices[deviceName]);

    test('homepage', async ({ page }) => {
      await page.goto('/');
      await expect(page).toHaveScreenshot(`homepage-${deviceName}.png`);
    });

    test('product page', async ({ page }) => {
      await page.goto('/products/1');
      await expect(page).toHaveScreenshot(`product-${deviceName}.png`);
    });
  });
}

Best Practices

  1. Test real devices too - Emulation is good but not perfect
  2. Cover major breakpoints - 375px, 768px, 1024px, 1280px minimum
  3. Test both orientations - Portrait and landscape
  4. Test touch vs click - Some interactions differ
  5. Test slow networks - Mobile users often have poor connectivity
  6. Test safe areas - Account for notches, home indicators

References

  • references/device-list.md - Complete device list with specs
  • references/touch-patterns.md - Touch gesture implementations
Repository
fernandezbaptiste/Skrillz
Last updated
First committed

Also appears in

fernandezbaptiste/Skrillz
In sync

since Sep 12, 2026

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.