[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.setRequestInterceptionEnabled(value)](#pagesetrequestinterceptionenabledvalue)
+ [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) + [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
+ [page.setViewport(viewport)](#pagesetviewportviewport) + [page.setViewport(viewport)](#pagesetviewportviewport)
+ [page.tap(selector)](#pagetapselector)
+ [page.title()](#pagetitle) + [page.title()](#pagetitle)
+ [page.touchscreen](#pagetouchscreen)
+ [page.tracing](#pagetracing) + [page.tracing](#pagetracing)
+ [page.type(text, options)](#pagetypetext-options) + [page.type(text, options)](#pagetypetext-options)
+ [page.url()](#pageurl) + [page.url()](#pageurl)
@ -83,6 +85,8 @@
+ [mouse.down([options])](#mousedownoptions) + [mouse.down([options])](#mousedownoptions)
+ [mouse.move(x, y, [options])](#mousemovex-y-options) + [mouse.move(x, y, [options])](#mousemovex-y-options)
+ [mouse.up([options])](#mouseupoptions) + [mouse.up([options])](#mouseupoptions)
* [class: Touchscreen](#class-touchscreen)
+ [touchscreen.tap(x, y)](#touchscreentapx-y)
* [class: Tracing](#class-tracing) * [class: Tracing](#class-tracing)
+ [tracing.start(options)](#tracingstartoptions) + [tracing.start(options)](#tracingstartoptions)
+ [tracing.stop()](#tracingstop) + [tracing.stop()](#tracingstop)
@ -113,6 +117,7 @@
+ [elementHandle.dispose()](#elementhandledispose) + [elementHandle.dispose()](#elementhandledispose)
+ [elementHandle.evaluate(pageFunction, ...args)](#elementhandleevaluatepagefunction-args) + [elementHandle.evaluate(pageFunction, ...args)](#elementhandleevaluatepagefunction-args)
+ [elementHandle.hover()](#elementhandlehover) + [elementHandle.hover()](#elementhandlehover)
+ [elementHandle.tap()](#elementhandletap)
+ [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths) + [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
* [class: Request](#class-request) * [class: Request](#class-request)
+ [request.abort()](#requestabort) + [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. 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() #### page.title()
- returns: <[Promise]<[string]>> Returns page's title. - returns: <[Promise]<[string]>> Returns page's title.
Shortcut for [page.mainFrame().title()](#frametitle). Shortcut for [page.mainFrame().title()](#frametitle).
#### page.touchscreen
- returns: <[Touchscreen]>
#### page.tracing #### page.tracing
- returns: <[Tracing]> - returns: <[Tracing]>
@ -978,6 +993,15 @@ Dispatches a `mousemove` event.
Dispatches a `mouseup` 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 ### 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/). 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. 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. 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) #### 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). - `...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]> - returns: <[Promise]>
@ -1388,3 +1418,4 @@ Contains the URL of the response.
[ElementHandle]: #class-elementhandle "ElementHandle" [ElementHandle]: #class-elementhandle "ElementHandle"
[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail" [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" [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 {!Connection} client
* @param {!Object} remoteObject * @param {!Object} remoteObject
* @param {!Mouse} mouse * @param {!Mouse} mouse
* @param {!Touchscreen} touchscreen;
*/ */
constructor(client, remoteObject, mouse) { constructor(client, remoteObject, mouse, touchscreen) {
this._client = client; this._client = client;
this._remoteObject = remoteObject; this._remoteObject = remoteObject;
this._mouse = mouse; this._mouse = mouse;
this._touchscreen = touchscreen;
this._disposed = false; this._disposed = false;
} }
@ -96,6 +98,11 @@ class ElementHandle {
const objectId = this._remoteObject.objectId; const objectId = this._remoteObject.objectId;
return this._client.send('DOM.setFileInputFiles', { objectId, files }); 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; module.exports = ElementHandle;

View File

@ -24,11 +24,13 @@ class FrameManager extends EventEmitter {
* @param {!Session} client * @param {!Session} client
* @param {!Object} frameTree * @param {!Object} frameTree
* @param {!Mouse} mouse * @param {!Mouse} mouse
* @param {!Touchscreen} touchscreen
*/ */
constructor(client, mouse) { constructor(client, mouse, touchscreen) {
super(); super();
this._client = client; this._client = client;
this._mouse = mouse; this._mouse = mouse;
this._touchscreen = touchscreen;
/** @type {!Map<string, !Frame>} */ /** @type {!Map<string, !Frame>} */
this._frames = new Map(); this._frames = new Map();
@ -62,7 +64,7 @@ class FrameManager extends EventEmitter {
return; return;
console.assert(parentFrameId); console.assert(parentFrameId);
const parentFrame = this._frames.get(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._frames.set(frame._id, frame);
this.emit(FrameManager.Events.FrameAttached, frame); this.emit(FrameManager.Events.FrameAttached, frame);
} }
@ -89,7 +91,7 @@ class FrameManager extends EventEmitter {
frame._id = framePayload.id; frame._id = framePayload.id;
} else { } else {
// Initial main frame navigation. // 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._frames.set(framePayload.id, frame);
this._mainFrame = frame; this._mainFrame = frame;
@ -154,12 +156,14 @@ class Frame {
/** /**
* @param {!Session} client * @param {!Session} client
* @param {!Mouse} mouse * @param {!Mouse} mouse
* @param {!Touchscreen} touchscreen
* @param {?Frame} parentFrame * @param {?Frame} parentFrame
* @param {string} frameId * @param {string} frameId
*/ */
constructor(client, mouse, parentFrame, frameId) { constructor(client, mouse, touchscreen, parentFrame, frameId) {
this._client = client; this._client = client;
this._mouse = mouse; this._mouse = mouse;
this._touchscreen = touchscreen;
this._parentFrame = parentFrame; this._parentFrame = parentFrame;
this._url = ''; this._url = '';
this._id = frameId; this._id = frameId;
@ -190,7 +194,7 @@ class Frame {
async $(selector) { async $(selector) {
const remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector); const remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
if (remoteObject.subtype === 'node') 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); await helper.releaseObject(this._client, remoteObject);
return null; return null;
} }
@ -225,7 +229,7 @@ class Frame {
const releasePromises = [helper.releaseObject(this._client, remoteObject)]; const releasePromises = [helper.releaseObject(this._client, remoteObject)];
for (const property of properties) { for (const property of properties) {
if (property.enumerable && property.value.subtype === 'node') 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 else
releasePromises.push(helper.releaseObject(this._client, property.value)); 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 = { const keys = {
'Cancel': 3, 'Cancel': 3,
'Help': 6, 'Help': 6,
@ -288,6 +317,7 @@ function codeForKey(key) {
return 0; return 0;
} }
module.exports = { Keyboard, Mouse }; module.exports = { Keyboard, Mouse, Touchscreen};
helper.tracePublicAPI(Keyboard); helper.tracePublicAPI(Keyboard);
helper.tracePublicAPI(Mouse); helper.tracePublicAPI(Mouse);
helper.tracePublicAPI(Touchscreen);

View File

@ -22,7 +22,7 @@ const NavigatorWatcher = require('./NavigatorWatcher');
const Dialog = require('./Dialog'); const Dialog = require('./Dialog');
const EmulationManager = require('./EmulationManager'); const EmulationManager = require('./EmulationManager');
const FrameManager = require('./FrameManager'); const FrameManager = require('./FrameManager');
const {Keyboard, Mouse} = require('./Input'); const {Keyboard, Mouse, Touchscreen} = require('./Input');
const Tracing = require('./Tracing'); const Tracing = require('./Tracing');
const helper = require('./helper'); const helper = require('./helper');
@ -60,7 +60,8 @@ class Page extends EventEmitter {
this._client = client; this._client = client;
this._keyboard = new Keyboard(client); this._keyboard = new Keyboard(client);
this._mouse = new Mouse(client, this._keyboard); 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._networkManager = new NetworkManager(client);
this._emulationManager = new EmulationManager(client); this._emulationManager = new EmulationManager(client);
this._tracing = new Tracing(client); this._tracing = new Tracing(client);
@ -105,6 +106,23 @@ class Page extends EventEmitter {
return this._keyboard; 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} * @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] [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() { function dimensions() {
const rect = document.querySelector('textarea').getBoundingClientRect(); const rect = document.querySelector('textarea').getBoundingClientRect();
return { return {

View File

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