Сенсорные события
Эмуляция касаний, жесты pan и pinch, DispatchEvent для Touch-событий
Legacy Touch Events API
Playwright позволяет эмулировать сенсорные события через dispatch_event. Это низкоуровневый механизм для отправки синтетических touch-событий в браузер. Он полезен для тестирования жестов на картах, галереях и других тач-интерфейсах, где стандартные методы click() или tap() недостаточны.
has_touch=True в контексте браузера, что заставляет браузер генерировать нативные Pointer Events.
Метод dispatch_event
Метод locator.dispatch_event(type, event_init_dict) отправляет DOM-событие указанному элементу. Для touch-событий тип может быть "touchstart", "touchmove" или "touchend".
page.locator("#map").dispatch_event(
"touchstart",
{
"touches": [{
"identifier": 0,
"clientX": 100,
"clientY": 200,
"pageX": 100,
"pageY": 200,
"radiusX": 10,
"radiusY": 10,
"rotationAngle": 0,
"force": 1
}],
"changedTouches": [],
"targetTouches": []
}
)
Параметры Touch-точки
Каждая touch-точка описывается объектом со следующими полями:
| Поле | Тип | Описание |
|---|---|---|
identifier | int | Уникальный идентификатор касания (0, 1, 2 ...) |
clientX | int | Координата X относительно viewport |
clientY | int | Координата Y относительно viewport |
pageX | int | Координата X относительно документа |
pageY | int | Координата Y относительно документа |
radiusX | int | Радиус эллипса касания по оси X |
radiusY | int | Радиус эллипса касания по оси Y |
rotationAngle | int | Угол поворота эллипса касания (градусы) |
force | float | Сила нажатия (0.0 — 1.0) |
События dispatch_event
- dispatch_event("touchstart", event_init_dict) — начало касания
- dispatch_event("touchmove", event_init_dict) — перемещение пальца
- dispatch_event("touchend", event_init_dict) — завершение касания
Структура event_init_dict включает три массива: "touches" (все активные касания), "targetTouches" (касания на данном элементе), "changedTouches" (изменённые в последнем событии).
Пример жеста Pan (перемещение карты)
Жест pan имитирует перетаскивание одним пальцем. Последовательность: touchstart → touchmove (несколько раз) → touchend.
def pan(page, selector, start_x, start_y, end_x, end_y, steps=10):
locator = page.locator(selector)
dx = (end_x - start_x) / steps
dy = (end_y - start_y) / steps
# touchstart
locator.dispatch_event("touchstart", {
"touches": [{"identifier": 0,
"clientX": start_x,
"clientY": start_y,
"pageX": start_x,
"pageY": start_y,
"radiusX": 10,
"radiusY": 10,
"rotationAngle": 0,
"force": 1}],
"changedTouches": [{"identifier": 0,
"clientX": start_x,
"clientY": start_y,
"pageX": start_x,
"pageY": start_y,
"radiusX": 10,
"radiusY": 10,
"rotationAngle": 0,
"force": 1}],
"targetTouches": [{"identifier": 0,
"clientX": start_x,
"clientY": start_y,
"pageX": start_x,
"pageY": start_y,
"radiusX": 10,
"radiusY": 10,
"rotationAngle": 0,
"force": 1}]
})
# touchmove steps
for step in range(1, steps + 1):
x = int(start_x + dx * step)
y = int(start_y + dy * step)
locator.dispatch_event("touchmove", {
"touches": [{"identifier": 0,
"clientX": x, "clientY": y,
"pageX": x, "pageY": y,
"radiusX": 10, "radiusY": 10,
"rotationAngle": 0, "force": 1}],
"changedTouches": [{"identifier": 0,
"clientX": x, "clientY": y,
"pageX": x, "pageY": y,
"radiusX": 10, "radiusY": 10,
"rotationAngle": 0, "force": 1}],
"targetTouches": [{"identifier": 0,
"clientX": x, "clientY": y,
"pageX": x, "pageY": y,
"radiusX": 10, "radiusY": 10,
"rotationAngle": 0, "force": 1}]
})
# touchend
locator.dispatch_event("touchend", {
"touches": [],
"changedTouches": [{"identifier": 0,
"clientX": end_x,
"clientY": end_y,
"pageX": end_x,
"pageY": end_y,
"radiusX": 10,
"radiusY": 10,
"rotationAngle": 0,
"force": 1}],
"targetTouches": []
})
# Использование
pan(page, "#map", 300, 400, 100, 400)
Пример жеста Pinch (масштабирование двумя пальцами)
Жест pinch требует двух touch-точек. Пальцы двигаются в противоположных направлениях.
def pinch(page, selector, center_x, center_y, distance, steps=10):
locator = page.locator(selector)
half = distance // 2
# touchstart с двумя пальцами
locator.dispatch_event("touchstart", {
"touches": [
{"identifier": 0, "clientX": center_x - half,
"clientY": center_y, "pageX": center_x - half,
"pageY": center_y, "radiusX": 10,
"radiusY": 10, "rotationAngle": 0,
"force": 1},
{"identifier": 1, "clientX": center_x + half,
"clientY": center_y, "pageX": center_x + half,
"pageY": center_y, "radiusX": 10,
"radiusY": 10, "rotationAngle": 0,
"force": 1}
],
"changedTouches": [
{"identifier": 0, "clientX": center_x - half,
"clientY": center_y},
{"identifier": 1, "clientX": center_x + half,
"clientY": center_y}
],
"targetTouches": [
{"identifier": 0, "clientX": center_x - half,
"clientY": center_y},
{"identifier": 1, "clientX": center_x + half,
"clientY": center_y}
]
})
# Сведение пальцев (zoom out)
for step in range(1, steps + 1):
offset = half - int((half / steps) * step)
locator.dispatch_event("touchmove", {
"touches": [
{"identifier": 0,
"clientX": center_x - offset,
"clientY": center_y,
"pageX": center_x - offset,
"pageY": center_y,
"radiusX": 10, "radiusY": 10,
"rotationAngle": 0, "force": 1},
{"identifier": 1,
"clientX": center_x + offset,
"clientY": center_y,
"pageX": center_x + offset,
"pageY": center_y,
"radiusX": 10, "radiusY": 10,
"rotationAngle": 0, "force": 1}
],
"changedTouches": [
{"identifier": 0,
"clientX": center_x - offset,
"clientY": center_y},
{"identifier": 1,
"clientX": center_x + offset,
"clientY": center_y}
],
"targetTouches": [
{"identifier": 0,
"clientX": center_x - offset,
"clientY": center_y},
{"identifier": 1,
"clientX": center_x + offset,
"clientY": center_y}
]
})
# touchend
locator.dispatch_event("touchend", {
"touches": [],
"changedTouches": [
{"identifier": 0, "clientX": center_x,
"clientY": center_y},
{"identifier": 1, "clientX": center_x,
"clientY": center_y}
],
"targetTouches": []
})
# zoom out на карте
pinch(page, "#map", 300, 400, 100)
Event.isTrusted
Синтетические события, созданные через dispatch_event, имеют флаг event.isTrusted = false. Браузер помечает их как недоверенные. Некоторые приложения проверяют этот флаг и игнорируют программные события.
isTrusted, необходимо использовать современную эмуляцию тач-устройств (см. ниже) вместо DispatchEvent.
# Проверка из JavaScript:
element.addEventListener("touchstart", (e) => {
if (!e.isTrusted) {
return; // игнорируем синтетику
}
// обработка реального касания
});
Современная альтернатива: эмуляция тач-устройств
Вместо ручной отправки touch-событий рекомендуется использовать параметр has_touch=True при создании контекста браузера. В этом режиме Playwright эмулирует полноценное тач-устройство, и браузер сам генерирует нативные события Touch, Pointer и Mouse.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
has_touch=True,
is_mobile=True,
viewport={"width": 390, "height": 844}
)
page = context.new_page()
page.goto("https://example.com")
# Метод tap() работает в тач-режиме
page.get_by_text("Меню").tap()
browser.close()
has_touch=True вместе с профилем устройства, например **p.devices["iPhone 13"], который уже включает все необходимые параметры тач-эмуляции.
Полный пример: тестирование карты
from playwright.sync_api import sync_playwright
def test_map_gestures():
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(**p.devices["iPhone 13"])
page = context.new_page()
page.goto("https://openstreetmap.org")
page.wait_for_selector("#map")
# Pan gesture — сдвиг карты влево
pan(page, "#map", 300, 400, 50, 400)
# Pinch — уменьшение масштаба
pinch(page, "#map", 200, 300, 80)
page.screenshot(path="map-after-gestures.png")
browser.close()
test_map_gestures()