2025-01-02 23:36:28 +01:00
---
id: touch-events
title: "Emulating touch events"
---
## Introduction
2025-01-03 19:40:30 +01:00
Mobile web sites may listen to [touch events ](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events ) and react to user touch gestures such as swipe, pinch, tap etc. To test this functionality you can manually generate [TouchEvent]s in the page context using [`method: Locator.evaluate`].
2025-01-02 23:36:28 +01:00
2025-01-03 19:40:38 +01:00
If your web application relies on [pointer events ](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events ) instead of touch events, you can use [`method: Locator.click`] and raw [`Mouse`] events to simulate a single-finger touch, and this will trigger all the same pointer events.
2025-01-02 23:36:28 +01:00
### Dispatching TouchEvent
You can dispatch touch events to the page using [`method: Locator.dispatchEvent`]. [Touch ](https://developer.mozilla.org/en-US/docs/Web/API/Touch ) points can be passed as arguments, see examples below.
#### Emulating pan gesture
2025-01-03 19:41:02 +01:00
In the example below, we emulate pan gesture that is expected to move the map. The app under test only uses `clientX/clientY` coordinates of the touch point, so we initialize just that. In a more complex scenario you may need to also set `pageX/pageY/screenX/screenY` , if your app needs them.
2025-01-02 23:36:28 +01:00
```js
2025-01-03 20:21:53 +01:00
import { test, expect, devices, type Locator } from '@playwright/test';
2025-01-02 23:36:28 +01:00
test.use({ ...devices['Pixel 7'] });
async function pan(locator: Locator, deltaX?: number, deltaY?: number, steps?: number) {
const { centerX, centerY } = await locator.evaluate((target: HTMLElement) => {
const bounds = target.getBoundingClientRect();
const centerX = bounds.left + bounds.width / 2;
const centerY = bounds.top + bounds.height / 2;
return { centerX, centerY };
});
// Providing only clientX and clientY as the app only cares about those.
const touches = [{
identifier: 0,
clientX: centerX,
clientY: centerY,
}];
2025-01-04 01:06:26 +01:00
await locator.dispatchEvent('touchstart',
{ touches, changedTouches: touches, targetTouches: touches });
2025-01-02 23:36:28 +01:00
steps = steps ?? 5;
deltaX = deltaX ?? 0;
deltaY = deltaY ?? 0;
2025-01-03 20:21:53 +01:00
for (let i = 1; i < = steps; i++) {
2025-01-02 23:36:28 +01:00
const touches = [{
identifier: 0,
clientX: centerX + deltaX * i / steps,
clientY: centerY + deltaY * i / steps,
}];
2025-01-04 01:22:01 +01:00
await locator.dispatchEvent('touchmove',
2025-01-07 20:55:58 +01:00
{ touches, changedTouches: touches, targetTouches: touches });
2025-01-02 23:36:28 +01:00
}
await locator.dispatchEvent('touchend');
}
test(`pan gesture to move the map`, async ({ page }) => {
2025-01-04 01:22:01 +01:00
await page.goto('https://www.google.com/maps/place/@37.4117722,-122.0713234,15z',
2025-01-07 20:55:58 +01:00
{ waitUntil: 'commit' });
2025-01-02 23:36:28 +01:00
await page.getByRole('button', { name: 'Keep using web' }).click();
await expect(page.getByRole('button', { name: 'Keep using web' })).not.toBeVisible();
// Get the map element.
const met = page.locator('[data-test-id="met"]');
for (let i = 0; i < 5 ; i + + )
await pan(met, 200, 100);
// Ensure the map has been moved.
await expect(met).toHaveScreenshot();
});
```
#### Emulating pinch gesture
2025-01-03 19:41:28 +01:00
In the example below, we emulate pinch gesture, i.e. two touch points moving closer to each other. It is expected to zoom out the map. The app under test only uses `clientX/clientY` coordinates of touch points, so we initialize just that. In a more complex scenario you may need to also set `pageX/pageY/screenX/screenY` , if your app needs them.
2025-01-02 23:36:28 +01:00
```js
2025-01-03 20:21:53 +01:00
import { test, expect, devices, type Locator } from '@playwright/test';
2025-01-02 23:36:28 +01:00
test.use({ ...devices['Pixel 7'] });
2025-01-04 01:06:26 +01:00
async function pinch(locator: Locator,
2025-01-04 01:22:01 +01:00
arg: { deltaX?: number, deltaY?: number, steps?: number, direction?: 'in' | 'out' }) {
2025-01-02 23:36:28 +01:00
const { centerX, centerY } = await locator.evaluate((target: HTMLElement) => {
const bounds = target.getBoundingClientRect();
const centerX = bounds.left + bounds.width / 2;
const centerY = bounds.top + bounds.height / 2;
return { centerX, centerY };
});
2025-01-03 20:21:53 +01:00
const deltaX = arg.deltaX ?? 50;
const steps = arg.steps ?? 5;
const stepDeltaX = deltaX / (steps + 1);
2025-01-02 23:36:28 +01:00
// Two touch points equally distant from the center of the element.
const touches = [
{
identifier: 0,
2025-01-03 20:21:53 +01:00
clientX: centerX - (arg.direction === 'in' ? deltaX : stepDeltaX),
2025-01-02 23:36:28 +01:00
clientY: centerY,
},
{
identifier: 1,
2025-01-03 20:21:53 +01:00
clientX: centerX + (arg.direction === 'in' ? deltaX : stepDeltaX),
2025-01-02 23:36:28 +01:00
clientY: centerY,
},
];
2025-01-04 01:22:01 +01:00
await locator.dispatchEvent('touchstart',
2025-01-07 20:55:58 +01:00
{ touches, changedTouches: touches, targetTouches: touches });
2025-01-02 23:36:28 +01:00
// Move the touch points towards or away from each other.
2025-01-03 20:21:53 +01:00
for (let i = 1; i < = steps; i++) {
2025-01-04 01:22:01 +01:00
const offset = (arg.direction === 'in' ? (deltaX - i * stepDeltaX) : (stepDeltaX * (i + 1)));
2025-01-02 23:36:28 +01:00
const touches = [
{
identifier: 0,
2025-01-07 20:55:58 +01:00
clientX: centerX - offset,
2025-01-03 20:21:53 +01:00
clientY: centerY,
2025-01-02 23:36:28 +01:00
},
{
identifier: 0,
2025-01-04 01:22:01 +01:00
clientX: centerX + offset,
2025-01-03 20:21:53 +01:00
clientY: centerY,
2025-01-02 23:36:28 +01:00
},
];
2025-01-04 01:22:01 +01:00
await locator.dispatchEvent('touchmove',
2025-01-07 20:55:58 +01:00
{ touches, changedTouches: touches, targetTouches: touches });
2025-01-02 23:36:28 +01:00
}
await locator.dispatchEvent('touchend', { touches: [], changedTouches: [], targetTouches: [] });
}
test(`pinch in gesture to zoom out the map`, async ({ page }) => {
2025-01-07 20:55:58 +01:00
await page.goto('https://www.google.com/maps/place/@37.4117722,-122.0713234,15z',
{ waitUntil: 'commit' });
2025-01-02 23:36:28 +01:00
await page.getByRole('button', { name: 'Keep using web' }).click();
await expect(page.getByRole('button', { name: 'Keep using web' })).not.toBeVisible();
// Get the map element.
const met = page.locator('[data-test-id="met"]');
for (let i = 0; i < 5 ; i + + )
await pinch(met, { deltaX: 40, direction: 'in' });
// Ensure the map has been zoomed out.
await expect(met).toHaveScreenshot();
});
```