Playwright Python — справочникНа главный сайт

Legacy Touch Events API

Playwright позволяет эмулировать сенсорные события через dispatch_event. Это низкоуровневый механизм для отправки синтетических touch-событий в браузер. Он полезен для тестирования жестов на картах, галереях и других тач-интерфейсах, где стандартные методы click() или tap() недостаточны.

Важно: Touch Events API считается устаревшим (legacy). Современная альтернатива — эмуляция тач-устройства через параметр 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-точка описывается объектом со следующими полями:

ПолеТипОписание
identifierintУникальный идентификатор касания (0, 1, 2 ...)
clientXintКоордината X относительно viewport
clientYintКоордината Y относительно viewport
pageXintКоордината X относительно документа
pageYintКоордината Y относительно документа
radiusXintРадиус эллипса касания по оси X
radiusYintРадиус эллипса касания по оси Y
rotationAngleintУгол поворота эллипса касания (градусы)
forcefloatСила нажатия (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. Браузер помечает их как недоверенные. Некоторые приложения проверяют этот флаг и игнорируют программные события.

Важно: Если приложение игнорирует синтетические touch-события из-за проверки 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()