Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we have the same asset for mouse? Can we unify the two, simply recording all the events for the button?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a similar one for keyboard, but it has a textarea.

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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is is actually ok to send touches to the non-touch viewport?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems fine.

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']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, how's getResult() different from result? You use one in the first test and other in the second

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it clears the result

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