[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.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"
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
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 = {
|
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);
|
||||||
|
22
lib/Page.js
22
lib/Page.js
@ -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}
|
||||||
*/
|
*/
|
||||||
|
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]
|
[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 {
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user