[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:
JoelEinbinder 2017-09-01 19:03:51 -07:00 committed by Andrey Lushnikov
parent e95fb96279
commit 64124df62f
8 changed files with 147 additions and 10 deletions

View File

@ -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"

View File

@ -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;

View File

@ -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));
}

View File

@ -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);

View File

@ -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}
*/

View 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>

View File

@ -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 {

View File

@ -44,6 +44,7 @@ const EXCLUDE_METHODS = new Set([
'Headers.fromPayload',
'Keyboard.constructor',
'Mouse.constructor',
'Touchscreen.constructor',
'Tracing.constructor',
'Page.constructor',
'Page.create',