Skip to content

Commit

Permalink
[api] add touchScreen.tap (puppeteer#639)
Browse files Browse the repository at this point in the history
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 puppeteer#568 and puppeteer#569.
  • Loading branch information
JoelEinbinder authored and aslushnikov committed Sep 2, 2017
1 parent e95fb96 commit 64124df
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 10 deletions.
31 changes: 31 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]>

Expand Down Expand Up @@ -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/).
Expand Down Expand Up @@ -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]>
Expand Down Expand Up @@ -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"
9 changes: 8 additions & 1 deletion lib/ElementHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down
16 changes: 10 additions & 6 deletions lib/FrameManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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));
}
Expand Down
32 changes: 31 additions & 1 deletion lib/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 changes: 20 additions & 2 deletions lib/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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}
*/
Expand Down
35 changes: 35 additions & 0 deletions test/assets/input/touches.html
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions utils/doclint/check_public_api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const EXCLUDE_METHODS = new Set([
'Headers.fromPayload',
'Keyboard.constructor',
'Mouse.constructor',
'Touchscreen.constructor',
'Tracing.constructor',
'Page.constructor',
'Page.create',
Expand Down

0 comments on commit 64124df

Please sign in to comment.