[api] add touchScreen.tap (#639)
This patch: - adds `page.touchscreen` namespace, similar to `page.mouse` and `page.keyboard`. - adds tapping to multiple layers: - `page.touchscreen.tap` - `page.tap` - convenience method which accepts selector - `elementHandle.tap` Fixes #568 and #569.
This commit is contained in:
parent
e95fb96279
commit
64124df62f
31
docs/api.md
31
docs/api.md
@ -65,7 +65,9 @@
|
||||
+ [page.setRequestInterceptionEnabled(value)](#pagesetrequestinterceptionenabledvalue)
|
||||
+ [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
|
||||
+ [page.setViewport(viewport)](#pagesetviewportviewport)
|
||||
+ [page.tap(selector)](#pagetapselector)
|
||||
+ [page.title()](#pagetitle)
|
||||
+ [page.touchscreen](#pagetouchscreen)
|
||||
+ [page.tracing](#pagetracing)
|
||||
+ [page.type(text, options)](#pagetypetext-options)
|
||||
+ [page.url()](#pageurl)
|
||||
@ -83,6 +85,8 @@
|
||||
+ [mouse.down([options])](#mousedownoptions)
|
||||
+ [mouse.move(x, y, [options])](#mousemovex-y-options)
|
||||
+ [mouse.up([options])](#mouseupoptions)
|
||||
* [class: Touchscreen](#class-touchscreen)
|
||||
+ [touchscreen.tap(x, y)](#touchscreentapx-y)
|
||||
* [class: Tracing](#class-tracing)
|
||||
+ [tracing.start(options)](#tracingstartoptions)
|
||||
+ [tracing.stop()](#tracingstop)
|
||||
@ -113,6 +117,7 @@
|
||||
+ [elementHandle.dispose()](#elementhandledispose)
|
||||
+ [elementHandle.evaluate(pageFunction, ...args)](#elementhandleevaluatepagefunction-args)
|
||||
+ [elementHandle.hover()](#elementhandlehover)
|
||||
+ [elementHandle.tap()](#elementhandletap)
|
||||
+ [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
|
||||
* [class: Request](#class-request)
|
||||
+ [request.abort()](#requestabort)
|
||||
@ -778,11 +783,21 @@ puppeteer.launch().then(async browser => {
|
||||
|
||||
In the case of multiple pages in a single browser, each page can have its own viewport size.
|
||||
|
||||
#### page.tap(selector)
|
||||
- `selector` <[string]> A [selector] to search for element to tap. If there are multiple elements satisfying the selector, the first will be tapped.
|
||||
- returns: <[Promise]>
|
||||
|
||||
This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element.
|
||||
If there's no element matching `selector`, the method throws an error.
|
||||
|
||||
#### page.title()
|
||||
- returns: <[Promise]<[string]>> Returns page's title.
|
||||
|
||||
Shortcut for [page.mainFrame().title()](#frametitle).
|
||||
|
||||
#### page.touchscreen
|
||||
- returns: <[Touchscreen]>
|
||||
|
||||
#### page.tracing
|
||||
- returns: <[Tracing]>
|
||||
|
||||
@ -978,6 +993,15 @@ Dispatches a `mousemove` event.
|
||||
|
||||
Dispatches a `mouseup` event.
|
||||
|
||||
### class: Touchscreen
|
||||
|
||||
#### touchscreen.tap(x, y)
|
||||
- `x` <[number]>
|
||||
- `y` <[number]>
|
||||
- returns: <[Promise]>
|
||||
|
||||
Dispatches a `touchstart` and `touchend` event.
|
||||
|
||||
### class: Tracing
|
||||
|
||||
You can use [`tracing.start`](#tracingstartoptions) and [`tracing.stop`](#tracingstop) to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
|
||||
@ -1266,6 +1290,12 @@ The element will be passed as the first argument to `pageFunction`, followed by
|
||||
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.tap()
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM.
|
||||
|
||||
This method scrolls element into view if needed, and then uses [touchscreen.tap](#touchscreentapx-y) to tap in the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.uploadFile(...filePaths)
|
||||
- `...filePaths` <...[string]> Sets the value of the file input these paths. If some of the `filePaths` are relative paths, then they are resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||
- returns: <[Promise]>
|
||||
@ -1388,3 +1418,4 @@ Contains the URL of the response.
|
||||
[ElementHandle]: #class-elementhandle "ElementHandle"
|
||||
[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail"
|
||||
[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
|
||||
[Touchscreen]: #class-touchscreen "Touchscreen"
|
||||
|
@ -21,11 +21,13 @@ class ElementHandle {
|
||||
* @param {!Connection} client
|
||||
* @param {!Object} remoteObject
|
||||
* @param {!Mouse} mouse
|
||||
* @param {!Touchscreen} touchscreen;
|
||||
*/
|
||||
constructor(client, remoteObject, mouse) {
|
||||
constructor(client, remoteObject, mouse, touchscreen) {
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
this._mouse = mouse;
|
||||
this._touchscreen = touchscreen;
|
||||
this._disposed = false;
|
||||
}
|
||||
|
||||
@ -96,6 +98,11 @@ class ElementHandle {
|
||||
const objectId = this._remoteObject.objectId;
|
||||
return this._client.send('DOM.setFileInputFiles', { objectId, files });
|
||||
}
|
||||
|
||||
async tap() {
|
||||
const {x, y} = await this._visibleCenter();
|
||||
await this._touchscreen.tap(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ElementHandle;
|
||||
|
@ -24,11 +24,13 @@ class FrameManager extends EventEmitter {
|
||||
* @param {!Session} client
|
||||
* @param {!Object} frameTree
|
||||
* @param {!Mouse} mouse
|
||||
* @param {!Touchscreen} touchscreen
|
||||
*/
|
||||
constructor(client, mouse) {
|
||||
constructor(client, mouse, touchscreen) {
|
||||
super();
|
||||
this._client = client;
|
||||
this._mouse = mouse;
|
||||
this._touchscreen = touchscreen;
|
||||
/** @type {!Map<string, !Frame>} */
|
||||
this._frames = new Map();
|
||||
|
||||
@ -62,7 +64,7 @@ class FrameManager extends EventEmitter {
|
||||
return;
|
||||
console.assert(parentFrameId);
|
||||
const parentFrame = this._frames.get(parentFrameId);
|
||||
const frame = new Frame(this._client, this._mouse, parentFrame, frameId);
|
||||
const frame = new Frame(this._client, this._mouse, this._touchscreen, parentFrame, frameId);
|
||||
this._frames.set(frame._id, frame);
|
||||
this.emit(FrameManager.Events.FrameAttached, frame);
|
||||
}
|
||||
@ -89,7 +91,7 @@ class FrameManager extends EventEmitter {
|
||||
frame._id = framePayload.id;
|
||||
} else {
|
||||
// Initial main frame navigation.
|
||||
frame = new Frame(this._client, this._mouse, null, framePayload.id);
|
||||
frame = new Frame(this._client, this._mouse, this._touchscreen, null, framePayload.id);
|
||||
}
|
||||
this._frames.set(framePayload.id, frame);
|
||||
this._mainFrame = frame;
|
||||
@ -154,12 +156,14 @@ class Frame {
|
||||
/**
|
||||
* @param {!Session} client
|
||||
* @param {!Mouse} mouse
|
||||
* @param {!Touchscreen} touchscreen
|
||||
* @param {?Frame} parentFrame
|
||||
* @param {string} frameId
|
||||
*/
|
||||
constructor(client, mouse, parentFrame, frameId) {
|
||||
constructor(client, mouse, touchscreen, parentFrame, frameId) {
|
||||
this._client = client;
|
||||
this._mouse = mouse;
|
||||
this._touchscreen = touchscreen;
|
||||
this._parentFrame = parentFrame;
|
||||
this._url = '';
|
||||
this._id = frameId;
|
||||
@ -190,7 +194,7 @@ class Frame {
|
||||
async $(selector) {
|
||||
const remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
|
||||
if (remoteObject.subtype === 'node')
|
||||
return new ElementHandle(this._client, remoteObject, this._mouse);
|
||||
return new ElementHandle(this._client, remoteObject, this._mouse, this._touchscreen);
|
||||
await helper.releaseObject(this._client, remoteObject);
|
||||
return null;
|
||||
}
|
||||
@ -225,7 +229,7 @@ class Frame {
|
||||
const releasePromises = [helper.releaseObject(this._client, remoteObject)];
|
||||
for (const property of properties) {
|
||||
if (property.enumerable && property.value.subtype === 'node')
|
||||
result.push(new ElementHandle(this._client, property.value, this._mouse));
|
||||
result.push(new ElementHandle(this._client, property.value, this._mouse, this._touchscreen));
|
||||
else
|
||||
releasePromises.push(helper.releaseObject(this._client, property.value));
|
||||
}
|
||||
|
32
lib/Input.js
32
lib/Input.js
@ -169,6 +169,35 @@ class Mouse {
|
||||
}
|
||||
}
|
||||
|
||||
class Touchscreen {
|
||||
/**
|
||||
* @param {Session} client
|
||||
* @param {Keyboard} keyboard
|
||||
*/
|
||||
constructor(client, keyboard) {
|
||||
this._client = client;
|
||||
this._keyboard = keyboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
async tap(x, y) {
|
||||
const touchPoints = [{x: Math.round(x), y: Math.round(y)}];
|
||||
await this._client.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchStart',
|
||||
touchPoints,
|
||||
modifiers: this._keyboard._modifiers
|
||||
});
|
||||
await this._client.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchEnd',
|
||||
touchPoints: [],
|
||||
modifiers: this._keyboard._modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const keys = {
|
||||
'Cancel': 3,
|
||||
'Help': 6,
|
||||
@ -288,6 +317,7 @@ function codeForKey(key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
module.exports = { Keyboard, Mouse };
|
||||
module.exports = { Keyboard, Mouse, Touchscreen};
|
||||
helper.tracePublicAPI(Keyboard);
|
||||
helper.tracePublicAPI(Mouse);
|
||||
helper.tracePublicAPI(Touchscreen);
|
||||
|
22
lib/Page.js
22
lib/Page.js
@ -22,7 +22,7 @@ const NavigatorWatcher = require('./NavigatorWatcher');
|
||||
const Dialog = require('./Dialog');
|
||||
const EmulationManager = require('./EmulationManager');
|
||||
const FrameManager = require('./FrameManager');
|
||||
const {Keyboard, Mouse} = require('./Input');
|
||||
const {Keyboard, Mouse, Touchscreen} = require('./Input');
|
||||
const Tracing = require('./Tracing');
|
||||
const helper = require('./helper');
|
||||
|
||||
@ -60,7 +60,8 @@ class Page extends EventEmitter {
|
||||
this._client = client;
|
||||
this._keyboard = new Keyboard(client);
|
||||
this._mouse = new Mouse(client, this._keyboard);
|
||||
this._frameManager = new FrameManager(client, this._mouse);
|
||||
this._touchscreen = new Touchscreen(client, this._keyboard);
|
||||
this._frameManager = new FrameManager(client, this._mouse, this._touchscreen);
|
||||
this._networkManager = new NetworkManager(client);
|
||||
this._emulationManager = new EmulationManager(client);
|
||||
this._tracing = new Tracing(client);
|
||||
@ -105,6 +106,23 @@ class Page extends EventEmitter {
|
||||
return this._keyboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Touchscreen}
|
||||
*/
|
||||
get touchscreen() {
|
||||
return this._touchscreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
*/
|
||||
async tap(selector) {
|
||||
const handle = await this.$(selector);
|
||||
console.assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.tap();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Tracing}
|
||||
*/
|
||||
|
35
test/assets/input/touches.html
Normal file
35
test/assets/input/touches.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Touch test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="mouse-helper.js"></script>
|
||||
<button onclick="clicked();">Click target</button>
|
||||
<script>
|
||||
window.result = [];
|
||||
const button = document.querySelector('button');
|
||||
button.style.height = '200px';
|
||||
button.style.width = '200px';
|
||||
button.focus();
|
||||
button.addEventListener('touchstart', event => {
|
||||
log('Touchstart:', ...Array.from(event.changedTouches).map(touch => touch.identifier));
|
||||
});
|
||||
button.addEventListener('touchend', event => {
|
||||
log('Touchend:', ...Array.from(event.changedTouches).map(touch => touch.identifier));
|
||||
});
|
||||
button.addEventListener('touchmove', event => {
|
||||
log('Touchmove:', ...Array.from(event.changedTouches).map(touch => touch.identifier));
|
||||
});
|
||||
function log(...args) {
|
||||
console.log.apply(console, args);
|
||||
result.push(args.join(' '));
|
||||
}
|
||||
function getResult() {
|
||||
let temp = result;
|
||||
result = [];
|
||||
return temp;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
11
test/test.js
11
test/test.js
@ -1509,6 +1509,17 @@ describe('Page', function() {
|
||||
[200, 300]
|
||||
]);
|
||||
}));
|
||||
it('should tap the button', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
await page.tap('button');
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
}));
|
||||
it('should report touches', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/touches.html');
|
||||
const button = await page.$('button');
|
||||
await button.tap();
|
||||
expect(await page.evaluate(() => getResult())).toEqual(['Touchstart: 0', 'Touchend: 0']);
|
||||
}));
|
||||
function dimensions() {
|
||||
const rect = document.querySelector('textarea').getBoundingClientRect();
|
||||
return {
|
||||
|
@ -44,6 +44,7 @@ const EXCLUDE_METHODS = new Set([
|
||||
'Headers.fromPayload',
|
||||
'Keyboard.constructor',
|
||||
'Mouse.constructor',
|
||||
'Touchscreen.constructor',
|
||||
'Tracing.constructor',
|
||||
'Page.constructor',
|
||||
'Page.create',
|
||||
|
Loading…
Reference in New Issue
Block a user