ScreenshotNeo

BlogHow-to

How to Use Puppeteer Touchscreen for Mobile Testing

Emulate a mobile device in Puppeteer, send taps and touch gestures, and write reliable tests for mobile interactions.

By the ScreenshotNeo team4 October 20268 min read

To test mobile touch interactions with Puppeteer, first configure mobile device emulation, then navigate to the page and send touch input. Use page.emulate(KnownDevices[...]) when you want a documented device profile, or page.setViewport() for custom metrics. Use a locator for a normal element interaction; use page.touchscreen when the test needs coordinate-based touch events.

1. Set up Puppeteer and choose a mobile configuration

Install Puppeteer in your project if it is not already a dependency:

npm install puppeteer

The examples below use ES modules. Save one as mobile-touch-test.mjs and run it with node mobile-touch-test.mjs. Replace the example URL and coordinates with the page and target you are testing.

Use a known device profile

page.emulate() applies both the device’s viewport settings and user agent. The device name below appears in Puppeteer’s documentation; confirm that it exists in your installed Puppeteer version because the available list can change.

import puppeteer, {KnownDevices} from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.emulate(KnownDevices['iPhone 17 Pro']);
  await page.goto('https://example.com');
  await page.touchscreen.tap(120, 240);
} finally {
  await browser.close();
}

Use a custom mobile viewport

When you need specific dimensions or touch settings, set them explicitly. Width and height are CSS pixels; the values below are an example configuration, not a required setting for any particular phone.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({
    width: 390,
    height: 844,
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
  });
  await page.goto('https://example.com');
  await page.touchscreen.tap(120, 240);
} finally {
  await browser.close();
}

The documented viewport fields include width, height, deviceScaleFactor, isMobile, isLandscape, and hasTouch. Defaults for device scale factor, mobile mode, and touch support are 1, false, and false. Set mobile or touch options deliberately when the test depends on them.

2. Configure emulation before navigating

Set the device profile or viewport before page.goto() whenever possible. Puppeteer notes that many sites do not expect a phone to change size; changing isMobile or hasTouch with setViewport() may reload the page. Applying settings first makes the initial page load use the intended configuration.

  1. Create a browser and page.
  2. Apply page.emulate() or page.setViewport().
  3. Navigate to the URL.
  4. Wait for the relevant page state, then perform the touch action.
  5. Assert the visible or application-level result of the interaction.

Known-device emulation is convenient when you want viewport metrics and a user agent together. A custom viewport is useful when you need to control those viewport fields directly. Neither configuration alone proves how a physical phone will behave; it configures browser emulation for the test.

3. Choose between a locator and touchscreen coordinates

For a test such as “the menu opens when the menu button is activated,” a locator expresses the target and action more clearly. Puppeteer recommends locators for element interactions; they check conditions such as viewport presence, visibility, enabled state, and bounding-box stability before acting.

import puppeteer, {KnownDevices} from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.emulate(KnownDevices['iPhone 17 Pro']);
  await page.goto('https://example.com');

  const menuButton = page.locator('[data-testid="menu-button"]');
  await menuButton.click();
  await page.locator('[data-testid="mobile-menu"]').wait();
} finally {
  await browser.close();
}

Use page.touchscreen when the test specifically concerns touch input, coordinates, or a sequence of low-level touch actions. A locator action and a coordinate touch answer different testing questions: the former targets an element; the latter sends input at a point.

4. Send taps and touch sequences

Tap a coordinate

page.touchscreen.tap(x, y) dispatches a touchstart followed by touchend at the supplied position. The coordinates correspond to the emulated page viewport, so confirm that the target falls within the current viewport and is not obscured.

await page.touchscreen.tap(120, 240);

Start, move, and end a touch

For a lower-level gesture, start a touch, move it, and end it. Keep the end call in a finally block so an assertion or other error does not leave the interaction sequence unfinished.

const touch = await page.touchscreen.touchStart(120, 240);
try {
  await page.touchscreen.touchMove(180, 240);
} finally {
  await page.touchscreen.touchEnd();
}

Puppeteer documents that Chrome may optimize touch-move dispatch: not every touchMove() call necessarily emits a DOM touchmove event. Avoid assertions that expect one browser event for every API call. Test the user-visible or application-level effect of the gesture when that is the behavior under test.

5. A complete reusable test pattern

This example combines mobile emulation, navigation, a coordinate tap, and a result check. The selectors and expected text are placeholders for the application under test.

import assert from 'node:assert/strict';
import puppeteer, {KnownDevices} from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.emulate(KnownDevices['iPhone 17 Pro']);
  await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});

  // Replace these coordinates with a point inside the intended control.
  await page.touchscreen.tap(120, 240);

  // Prefer an application-level result over asserting raw event counts.
  await page.locator('[data-testid="result"]', {
    text: 'Expected result',
  }).wait();
  const result = await page.locator('[data-testid="result"]').map(el => el.textContent);
  assert.match(result, /Expected result/);
} finally {
  await browser.close();
}

Adapt the readiness condition to your application. If the target is an element and the goal is simply to activate it, use a locator action instead of hard-coding a coordinate. If coordinates are part of the test, derive or verify them against the emulated viewport and the element’s position.

6. Configuration options and edge cases

Choice Use it when Things to check
page.emulate(device) You want a supplied device profile, including viewport and user agent. Confirm the device key exists in your installed Puppeteer version.
page.setViewport() You need explicit dimensions or viewport properties. Set isMobile and hasTouch when required; changing them may reload the page.
deviceScaleFactor The test needs a particular device scale factor. It defaults to 1; do not assume a custom value is a real-device specification.
isLandscape The test needs landscape viewport orientation. Make orientation part of setup and verify the page after it is applied.
Locator action The test checks an element-level outcome. Use a stable selector and wait for the meaningful result.
Touchscreen coordinates The test needs coordinate-based touch input or a low-level sequence. Coordinates depend on viewport layout; overlays and scrolling can shift the target.

Other edge cases to account for include responsive layout changes, sticky headers, consent overlays, content that loads after navigation, and controls that move after a scroll or animation. Wait for the relevant page state before acting, and avoid reusing coordinates after the layout changes.

7. Troubleshooting

Symptom Likely cause Fix
Tap has no effect The point misses the target, an overlay covers it, or the page has not reached the expected state. Wait for the target state, check the viewport and element position, and use a locator if the goal is simply to activate that element.
The page reloads after viewport setup isMobile or hasTouch was changed after initial setup. Set emulation before navigation where possible.
KnownDevices[deviceName] is missing The device key is not present in the installed Puppeteer version, or the name differs. Inspect the known-device entries available in that version or use an explicit viewport.
A touchmove listener sees fewer events than expected Chrome may throttle or optimize touch-move event delivery. Do not assert one DOM event per touchMove() call; assert the resulting behavior instead.
Mobile layout differs from expectations Viewport, user agent, mobile mode, or touch support may not match the intended test setup. Use a known-device profile or explicitly set the needed viewport fields before navigation.
Test behaves differently across Puppeteer versions Documentation and APIs can vary by version. Check the installed package version and use documentation for that version.
Browser stays open after a failure Cleanup did not run after an exception. Close the browser in a finally block, as in the examples.

8. Performance, reliability, and cost

Emulation and touch input are browser automation work, so the main practical costs are the browser process and page load. Keep the browser open for a group of related cases when appropriate, create and close pages deliberately, and wait for the state your test needs instead of adding arbitrary long delays. Use a locator for element interactions where its readiness checks fit the test.

For reliability, configure before navigation, use stable selectors for element-oriented checks, account for layout movement, and assert outcomes rather than assuming every low-level input call maps to one DOM event. Puppeteer documentation does not establish that emulation is equivalent to a physical-device test or provide a reliability guarantee; use physical-device coverage when the behavior specifically depends on real hardware.

Puppeteer itself is an open-source browser automation library; this workflow has no ScreenshotNeo per-shot charge because it runs in your own browser automation setup. Your execution environment and browser infrastructure may have their own costs.

9. Or skip the browser setup

If your goal is to capture a page image rather than test touch input, ScreenshotNeo is a website screenshot API and MCP server. A single request returns a PNG, JPEG, WebP, or PDF. It is not a replacement for a touchscreen interaction test, but it can remove the browser setup for screenshot capture.

See the ScreenshotNeo API documentation for request options. Here is the one-call cURL example:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

10. FAQ

Does a touchscreen tap require hasTouch: true?

For a test intended to represent a touch-enabled mobile configuration, set touch support through a device profile or hasTouch: true. The documented viewport default is false.

Should I use a locator or page.touchscreen.tap()?

Use a locator when the test is about an element’s outcome. Use touchscreen methods when coordinate-based or low-level touch input is itself important.

Does Puppeteer emulate every physical phone behavior?

The cited Puppeteer documentation describes browser emulation settings and input APIs; it does not establish equivalence to physical devices. Treat emulation as browser testing and use real devices for hardware-dependent behavior.

Can I assert the exact number of touch-move events?

Avoid that assumption. Browser optimizations mean a call to touchMove() does not necessarily produce a corresponding DOM event.