mirror of
https://github.com/microsoft/playwright.git
synced 2024-08-17 16:10:29 +03:00
feat: introduce touchscreen.touch() for dispatching raw touch events (#31457)
This commit is contained in:
parent
33ac75b7ab
commit
a3e31fd2c4
@ -20,3 +20,23 @@ Dispatches a `touchstart` and `touchend` event with a single touch at the positi
|
||||
### param: Touchscreen.tap.y
|
||||
* since: v1.8
|
||||
- `y` <[float]>
|
||||
|
||||
## async method: Touchscreen.touch
|
||||
* since: v1.46
|
||||
|
||||
Synthesizes a touch event.
|
||||
|
||||
### param: Touchscreen.touch.type
|
||||
* since: v1.46
|
||||
- `type` <[TouchType]<"touchstart"|"touchend"|"touchmove"|"touchcancel">>
|
||||
|
||||
Type of the touch event.
|
||||
|
||||
### param: Touchscreen.touch.touches
|
||||
* since: v1.46
|
||||
- `touchPoints` <[Array]<[Object]>>
|
||||
- `x` <[float]> x coordinate of the event in CSS pixels.
|
||||
- `y` <[float]> y coordinate of the event in CSS pixels.
|
||||
- `id` ?<[int]> Identifier used to track the touch point between events, must be unique within an event. Optional.
|
||||
|
||||
List of touch points for this event. `id` is a unique identifier of a touch point that helps identify it between touch events for the duration of its movement around the surface.
|
@ -89,4 +89,8 @@ export class Touchscreen implements api.Touchscreen {
|
||||
async tap(x: number, y: number) {
|
||||
await this._page._channel.touchscreenTap({ x, y });
|
||||
}
|
||||
|
||||
async touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[]) {
|
||||
await this._page._channel.touchscreenTouch({ type, touchPoints });
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ export const slowMoActions = new Set([
|
||||
'Page.mouseClick',
|
||||
'Page.mouseWheel',
|
||||
'Page.touchscreenTap',
|
||||
'Page.touchscreenTouch',
|
||||
'Frame.blur',
|
||||
'Frame.check',
|
||||
'Frame.click',
|
||||
@ -89,6 +90,7 @@ export const commandsWithTracingSnapshots = new Set([
|
||||
'Page.mouseClick',
|
||||
'Page.mouseWheel',
|
||||
'Page.touchscreenTap',
|
||||
'Page.touchscreenTouch',
|
||||
'Frame.evalOnSelector',
|
||||
'Frame.evalOnSelectorAll',
|
||||
'Frame.addScriptTag',
|
||||
|
@ -1237,6 +1237,15 @@ scheme.PageTouchscreenTapParams = tObject({
|
||||
y: tNumber,
|
||||
});
|
||||
scheme.PageTouchscreenTapResult = tOptional(tObject({}));
|
||||
scheme.PageTouchscreenTouchParams = tObject({
|
||||
type: tEnum(['touchstart', 'touchend', 'touchmove', 'touchcancel']),
|
||||
touchPoints: tArray(tObject({
|
||||
x: tNumber,
|
||||
y: tNumber,
|
||||
id: tOptional(tNumber),
|
||||
})),
|
||||
});
|
||||
scheme.PageTouchscreenTouchResult = tOptional(tObject({}));
|
||||
scheme.PageAccessibilitySnapshotParams = tObject({
|
||||
interestingOnly: tOptional(tBoolean),
|
||||
root: tOptional(tChannel(['ElementHandle'])),
|
||||
|
@ -179,4 +179,19 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
||||
}),
|
||||
]);
|
||||
}
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
let type: 'touchStart' | 'touchMove' | 'touchEnd' | 'touchCancel';
|
||||
switch (eventType) {
|
||||
case 'touchstart': type = 'touchStart'; break;
|
||||
case 'touchmove': type = 'touchMove'; break;
|
||||
case 'touchend': type = 'touchEnd'; break;
|
||||
case 'touchcancel': type = 'touchCancel'; break;
|
||||
default: throw new Error('Invalid eventType: ' + eventType);
|
||||
}
|
||||
await this._client.send('Input.dispatchTouchEvent', {
|
||||
type,
|
||||
touchPoints,
|
||||
modifiers: toModifiersMask(modifiers)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -265,6 +265,10 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
||||
await this._page.touchscreen.tap(params.x, params.y, metadata);
|
||||
}
|
||||
|
||||
async touchscreenTouch(params: channels.PageTouchscreenTouchParams, metadata: CallMetadata) {
|
||||
await this._page.touchscreen.touch(params.type, params.touchPoints, metadata);
|
||||
}
|
||||
|
||||
async accessibilitySnapshot(params: channels.PageAccessibilitySnapshotParams, metadata: CallMetadata): Promise<channels.PageAccessibilitySnapshotResult> {
|
||||
const rootAXNode = await this._page.accessibility.snapshot({
|
||||
interestingOnly: params.interestingOnly,
|
||||
|
@ -166,4 +166,8 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
||||
modifiers: toModifiersMask(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
throw new Error('Not implemented yet.');
|
||||
}
|
||||
}
|
||||
|
@ -308,6 +308,7 @@ function buildLayoutClosure(layout: keyboardLayout.KeyboardLayout): Map<string,
|
||||
|
||||
export interface RawTouchscreen {
|
||||
tap(x: number, y: number, modifiers: Set<types.KeyboardModifier>): Promise<void>;
|
||||
touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>): Promise<void>;
|
||||
}
|
||||
|
||||
export class Touchscreen {
|
||||
@ -326,4 +327,19 @@ export class Touchscreen {
|
||||
throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
|
||||
await this._raw.tap(x, y, this._page.keyboard._modifiers());
|
||||
}
|
||||
async touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], metadata?: CallMetadata) {
|
||||
if (metadata && touchPoints.length === 1)
|
||||
metadata.point = { x: touchPoints[0].x, y: touchPoints[0].y };
|
||||
if (!this._page._browserContext._options.hasTouch)
|
||||
throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
|
||||
const ids = new Set<number>();
|
||||
for (const point of touchPoints) {
|
||||
if (point.id !== undefined) {
|
||||
if (ids.has(point.id))
|
||||
throw new Error(`Duplicate touch point id: ${point.id}`);
|
||||
ids.add(point.id);
|
||||
}
|
||||
}
|
||||
await this._raw.touch(type, touchPoints, this._page.keyboard._modifiers());
|
||||
}
|
||||
}
|
||||
|
@ -182,4 +182,20 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
||||
modifiers: toModifiersMask(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
let type: 'touchStart' | 'touchMove' | 'touchEnd' | 'touchCancel';
|
||||
switch (eventType) {
|
||||
case 'touchstart': type = 'touchStart'; break;
|
||||
case 'touchmove': type = 'touchMove'; break;
|
||||
case 'touchend': type = 'touchEnd'; break;
|
||||
case 'touchcancel': type = 'touchCancel'; break;
|
||||
default: throw new Error('Invalid eventType: ' + eventType);
|
||||
}
|
||||
await this._pageProxySession.send('Input.dispatchTouchEvent', {
|
||||
type,
|
||||
touchPoints: touchPoints.map(p => ({ ...p, id: p.id || 0 })),
|
||||
modifiers: toModifiersMask(modifiers)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
23
packages/playwright-core/types/types.d.ts
vendored
23
packages/playwright-core/types/types.d.ts
vendored
@ -19682,6 +19682,29 @@ export interface Touchscreen {
|
||||
* @param y
|
||||
*/
|
||||
tap(x: number, y: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* Synthesizes a touch event.
|
||||
* @param type Type of the touch event.
|
||||
* @param touchPoints List of touch points for this event. `id` is a unique identifier of a touch point that helps identify it between
|
||||
* touch events for the duration of its movement around the surface.
|
||||
*/
|
||||
touch(type: "touchstart"|"touchend"|"touchmove"|"touchcancel", touchPoints: ReadonlyArray<{
|
||||
/**
|
||||
* x coordinate of the event in CSS pixels.
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* y coordinate of the event in CSS pixels.
|
||||
*/
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Identifier used to track the touch point between events, must be unique within an event. Optional.
|
||||
*/
|
||||
id?: number;
|
||||
}>): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1890,6 +1890,7 @@ export interface PageChannel extends PageEventTarget, EventTargetChannel {
|
||||
mouseClick(params: PageMouseClickParams, metadata?: CallMetadata): Promise<PageMouseClickResult>;
|
||||
mouseWheel(params: PageMouseWheelParams, metadata?: CallMetadata): Promise<PageMouseWheelResult>;
|
||||
touchscreenTap(params: PageTouchscreenTapParams, metadata?: CallMetadata): Promise<PageTouchscreenTapResult>;
|
||||
touchscreenTouch(params: PageTouchscreenTouchParams, metadata?: CallMetadata): Promise<PageTouchscreenTouchResult>;
|
||||
accessibilitySnapshot(params: PageAccessibilitySnapshotParams, metadata?: CallMetadata): Promise<PageAccessibilitySnapshotResult>;
|
||||
pdf(params: PagePdfParams, metadata?: CallMetadata): Promise<PagePdfResult>;
|
||||
startJSCoverage(params: PageStartJSCoverageParams, metadata?: CallMetadata): Promise<PageStartJSCoverageResult>;
|
||||
@ -2257,6 +2258,18 @@ export type PageTouchscreenTapOptions = {
|
||||
|
||||
};
|
||||
export type PageTouchscreenTapResult = void;
|
||||
export type PageTouchscreenTouchParams = {
|
||||
type: 'touchstart' | 'touchend' | 'touchmove' | 'touchcancel',
|
||||
touchPoints: {
|
||||
x: number,
|
||||
y: number,
|
||||
id?: number,
|
||||
}[],
|
||||
};
|
||||
export type PageTouchscreenTouchOptions = {
|
||||
|
||||
};
|
||||
export type PageTouchscreenTouchResult = void;
|
||||
export type PageAccessibilitySnapshotParams = {
|
||||
interestingOnly?: boolean,
|
||||
root?: ElementHandleChannel,
|
||||
|
@ -1603,6 +1603,27 @@ Page:
|
||||
slowMo: true
|
||||
snapshot: true
|
||||
|
||||
touchscreenTouch:
|
||||
parameters:
|
||||
type:
|
||||
type: enum
|
||||
literals:
|
||||
- touchstart
|
||||
- touchend
|
||||
- touchmove
|
||||
- touchcancel
|
||||
touchPoints:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
x: number
|
||||
y: number
|
||||
id: number?
|
||||
flags:
|
||||
slowMo: true
|
||||
snapshot: true
|
||||
|
||||
accessibilitySnapshot:
|
||||
parameters:
|
||||
interestingOnly: boolean?
|
||||
|
76
tests/library/touch.spec.ts
Normal file
76
tests/library/touch.spec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { contextTest as it, expect } from '../config/browserTest';
|
||||
import type { Locator } from 'playwright-core';
|
||||
|
||||
it.use({ hasTouch: true });
|
||||
|
||||
it.fixme(({ browserName }) => browserName === 'firefox');
|
||||
|
||||
it('slow swipe events @smoke', async ({ page }) => {
|
||||
it.fixme();
|
||||
await page.setContent(`<div id="a" style="background: lightblue; width: 200px; height: 200px">a</div>`);
|
||||
const eventsHandle = await trackEvents(await page.locator('#a'));
|
||||
const center = await centerPoint(page.locator('#a'));
|
||||
await page.touchscreen.touch('touchstart', [{ ...center, id: 1 }]);
|
||||
expect.soft(await eventsHandle.jsonValue()).toEqual([
|
||||
'pointerover',
|
||||
'pointerenter',
|
||||
'pointerdown',
|
||||
'touchstart',
|
||||
]);
|
||||
|
||||
await eventsHandle.evaluate(events => events.length = 0);
|
||||
await page.touchscreen.touch('touchmove', [{ x: center.x + 10, y: center.y + 10, id: 1 }]);
|
||||
await page.touchscreen.touch('touchmove', [{ x: center.x + 20, y: center.y + 20, id: 1 }]);
|
||||
expect.soft(await eventsHandle.jsonValue()).toEqual([
|
||||
'pointermove',
|
||||
'touchmove',
|
||||
'pointermove',
|
||||
'touchmove',
|
||||
]);
|
||||
|
||||
await eventsHandle.evaluate(events => events.length = 0);
|
||||
await page.touchscreen.touch('touchend', [{ x: center.x + 20, y: center.y + 20, id: 1 }]);
|
||||
expect.soft(await eventsHandle.jsonValue()).toEqual([
|
||||
'pointerup',
|
||||
'pointerout',
|
||||
'pointerleave',
|
||||
'touchend',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
async function trackEvents(target: Locator) {
|
||||
const eventsHandle = await target.evaluateHandle(target => {
|
||||
const events: string[] = [];
|
||||
for (const event of [
|
||||
'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'click',
|
||||
'pointercancel', 'pointerdown', 'pointerenter', 'pointerleave', 'pointermove', 'pointerout', 'pointerover', 'pointerup',
|
||||
'touchstart', 'touchend', 'touchmove', 'touchcancel',])
|
||||
target.addEventListener(event, () => events.push(event), { passive: false });
|
||||
return events;
|
||||
});
|
||||
return eventsHandle;
|
||||
}
|
||||
|
||||
async function centerPoint(e: Locator) {
|
||||
const box = await e.boundingBox();
|
||||
if (!box)
|
||||
throw new Error('Element is not visible');
|
||||
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
}
|
Loading…
Reference in New Issue
Block a user