mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
Mouse (#101)
This patch: - adds Mouse class which holds mouse state and implements mouse primitives, such as moving, button down and button up. - implements high-level mouse api, such as `page.click` and `page.hover`. References #40, References #89
This commit is contained in:
parent
eb2cb67b0e
commit
98ee35655f
69
docs/api.md
69
docs/api.md
@ -29,7 +29,7 @@
|
|||||||
+ [page.$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
+ [page.$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
||||||
+ [page.$$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
+ [page.$$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
||||||
+ [page.addScriptTag(url)](#pageaddscripttagurl)
|
+ [page.addScriptTag(url)](#pageaddscripttagurl)
|
||||||
+ [page.click(selector)](#pageclickselector)
|
+ [page.click(selector[, options])](#pageclickselector-options)
|
||||||
+ [page.close()](#pageclose)
|
+ [page.close()](#pageclose)
|
||||||
+ [page.evaluate(pageFunction, ...args)](#pageevaluatepagefunction-args)
|
+ [page.evaluate(pageFunction, ...args)](#pageevaluatepagefunction-args)
|
||||||
+ [page.evaluateOnInitialized(pageFunction, ...args)](#pageevaluateoninitializedpagefunction-args)
|
+ [page.evaluateOnInitialized(pageFunction, ...args)](#pageevaluateoninitializedpagefunction-args)
|
||||||
@ -37,10 +37,12 @@
|
|||||||
+ [page.frames()](#pageframes)
|
+ [page.frames()](#pageframes)
|
||||||
+ [page.goBack(options)](#pagegobackoptions)
|
+ [page.goBack(options)](#pagegobackoptions)
|
||||||
+ [page.goForward(options)](#pagegoforwardoptions)
|
+ [page.goForward(options)](#pagegoforwardoptions)
|
||||||
|
+ [page.hover(selector)](#pagehoverselector)
|
||||||
+ [page.httpHeaders()](#pagehttpheaders)
|
+ [page.httpHeaders()](#pagehttpheaders)
|
||||||
+ [page.injectFile(filePath)](#pageinjectfilefilepath)
|
+ [page.injectFile(filePath)](#pageinjectfilefilepath)
|
||||||
+ [page.keyboard](#pagekeyboard)
|
+ [page.keyboard](#pagekeyboard)
|
||||||
+ [page.mainFrame()](#pagemainframe)
|
+ [page.mainFrame()](#pagemainframe)
|
||||||
|
+ [page.mouse](#pagemouse)
|
||||||
+ [page.navigate(url, options)](#pagenavigateurl-options)
|
+ [page.navigate(url, options)](#pagenavigateurl-options)
|
||||||
+ [page.pdf(options)](#pagepdfoptions)
|
+ [page.pdf(options)](#pagepdfoptions)
|
||||||
+ [page.plainText()](#pageplaintext)
|
+ [page.plainText()](#pageplaintext)
|
||||||
@ -67,6 +69,11 @@
|
|||||||
+ [keyboard.modifiers()](#keyboardmodifiers)
|
+ [keyboard.modifiers()](#keyboardmodifiers)
|
||||||
+ [keyboard.sendCharacter(char)](#keyboardsendcharacterchar)
|
+ [keyboard.sendCharacter(char)](#keyboardsendcharacterchar)
|
||||||
+ [keyboard.up(key)](#keyboardupkey)
|
+ [keyboard.up(key)](#keyboardupkey)
|
||||||
|
* [class: Mouse](#class-mouse)
|
||||||
|
+ [mouse.down([options])](#mousedownoptions)
|
||||||
|
+ [mouse.move(x, y)](#mousemovex-y)
|
||||||
|
+ [mouse.press([options])](#mousepressoptions)
|
||||||
|
+ [mouse.up([options])](#mouseupoptions)
|
||||||
* [class: Dialog](#class-dialog)
|
* [class: Dialog](#class-dialog)
|
||||||
+ [dialog.accept([promptText])](#dialogacceptprompttext)
|
+ [dialog.accept([promptText])](#dialogacceptprompttext)
|
||||||
+ [dialog.dismiss()](#dialogdismiss)
|
+ [dialog.dismiss()](#dialogdismiss)
|
||||||
@ -76,7 +83,9 @@
|
|||||||
+ [frame.$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
+ [frame.$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||||
+ [frame.$$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
+ [frame.$$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||||
+ [frame.childFrames()](#framechildframes)
|
+ [frame.childFrames()](#framechildframes)
|
||||||
|
+ [frame.click(selector[, options])](#frameclickselector-options)
|
||||||
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||||
|
+ [frame.hover(selector)](#framehoverselector)
|
||||||
+ [frame.isDetached()](#frameisdetached)
|
+ [frame.isDetached()](#frameisdetached)
|
||||||
+ [frame.isMainFrame()](#frameismainframe)
|
+ [frame.isMainFrame()](#frameismainframe)
|
||||||
+ [frame.name()](#framename)
|
+ [frame.name()](#framename)
|
||||||
@ -351,8 +360,11 @@ Shortcut for [page.mainFrame().$$(selector, pageFunction, ...args)](#pageselecto
|
|||||||
|
|
||||||
Adds a `<script></script>` tag to the page with the desired url. Alternatively, javascript could be injected to the page via `page.injectFile` method.
|
Adds a `<script></script>` tag to the page with the desired url. Alternatively, javascript could be injected to the page via `page.injectFile` method.
|
||||||
|
|
||||||
#### page.click(selector)
|
#### page.click(selector[, options])
|
||||||
- `selector` <[string]> A query selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
- `selector` <[string]> A query selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. Promise gets rejected if there's no element matching `selector`.
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. Promise gets rejected if there's no element matching `selector`.
|
||||||
|
|
||||||
#### page.close()
|
#### page.close()
|
||||||
@ -393,6 +405,10 @@ can not go back, resolves to null.
|
|||||||
|
|
||||||
Navigate to the next page in history.
|
Navigate to the next page in history.
|
||||||
|
|
||||||
|
#### page.hover(selector)
|
||||||
|
- `selector` <[string]> A query selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully hovered. Promise gets rejected if there's no element matching `selector`.
|
||||||
|
|
||||||
#### page.httpHeaders()
|
#### page.httpHeaders()
|
||||||
- returns: <[Object]> Key-value set of additional http headers which will be sent with every request.
|
- returns: <[Object]> Key-value set of additional http headers which will be sent with every request.
|
||||||
|
|
||||||
@ -409,6 +425,10 @@ Navigate to the next page in history.
|
|||||||
|
|
||||||
Page is guaranteed to have a main frame which persists during navigations.
|
Page is guaranteed to have a main frame which persists during navigations.
|
||||||
|
|
||||||
|
#### page.mouse
|
||||||
|
|
||||||
|
- returns: <[Mouse]>
|
||||||
|
|
||||||
#### page.navigate(url, options)
|
#### page.navigate(url, options)
|
||||||
- `url` <[string]> URL to navigate page to
|
- `url` <[string]> URL to navigate page to
|
||||||
- `options` <[Object]> Navigation parameters which might have the following properties:
|
- `options` <[Object]> Navigation parameters which might have the following properties:
|
||||||
@ -671,6 +691,39 @@ page.keyboard.sendCharacter('嗨');
|
|||||||
|
|
||||||
Dispatches a `keyup` event.
|
Dispatches a `keyup` event.
|
||||||
|
|
||||||
|
### class: Mouse
|
||||||
|
|
||||||
|
#### mouse.down([options])
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
Dispatches a `mousedown` event.
|
||||||
|
|
||||||
|
#### mouse.move(x, y)
|
||||||
|
- `x` <[number]>
|
||||||
|
- `y` <[number]>
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
Dispatches a `mousemove` event.
|
||||||
|
|
||||||
|
#### mouse.press([options])
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
Shortcut for [`mouse.down`](#mousedownkey) and [`mouse.up`](#mouseupkey).
|
||||||
|
|
||||||
|
#### mouse.up([options])
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
|
- returns: <[Promise]>
|
||||||
|
|
||||||
|
Dispatches a `mouseup` event.
|
||||||
|
|
||||||
### class: Dialog
|
### class: Dialog
|
||||||
|
|
||||||
[Dialog] objects are dispatched by page via the ['dialog'](#event-dialog) event.
|
[Dialog] objects are dispatched by page via the ['dialog'](#event-dialog) event.
|
||||||
@ -749,6 +802,13 @@ browser.newPage().then(async page => {
|
|||||||
#### frame.childFrames()
|
#### frame.childFrames()
|
||||||
- returns: <[Array]<[Frame]>>
|
- returns: <[Array]<[Frame]>>
|
||||||
|
|
||||||
|
#### frame.click(selector[, options])
|
||||||
|
- `selector` <[string]> A query selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. Promise gets rejected if there's no element matching `selector`.
|
||||||
|
|
||||||
#### frame.evaluate(pageFunction, ...args)
|
#### frame.evaluate(pageFunction, ...args)
|
||||||
- `pageFunction` <[function]> Function to be evaluated in browser context
|
- `pageFunction` <[function]> Function to be evaluated in browser context
|
||||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||||
@ -768,6 +828,10 @@ browser.newPage().then(async page =>
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### frame.hover(selector)
|
||||||
|
- `selector` <[string]> A query selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully hovered. Promise gets rejected if there's no element matching `selector`.
|
||||||
|
|
||||||
#### frame.isDetached()
|
#### frame.isDetached()
|
||||||
- returns: <[boolean]>
|
- returns: <[boolean]>
|
||||||
|
|
||||||
@ -1011,3 +1075,4 @@ If there's already a header with name `name`, the header gets overwritten.
|
|||||||
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
||||||
[Keyboard]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-keyboard "Keyboard"
|
[Keyboard]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-keyboard "Keyboard"
|
||||||
[Dialog]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-dialog "Dialog"
|
[Dialog]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-dialog "Dialog"
|
||||||
|
[Mouse]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-mouse "Mouse"
|
||||||
|
@ -20,20 +20,23 @@ let helper = require('./helper');
|
|||||||
class FrameManager extends EventEmitter {
|
class FrameManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* @param {!Connection} client
|
* @param {!Connection} client
|
||||||
|
* @param {!Mouse} mouse
|
||||||
* @return {!Promise<!FrameManager>}
|
* @return {!Promise<!FrameManager>}
|
||||||
*/
|
*/
|
||||||
static async create(client) {
|
static async create(client, mouse) {
|
||||||
let mainFramePayload = await client.send('Page.getResourceTree');
|
let mainFramePayload = await client.send('Page.getResourceTree');
|
||||||
return new FrameManager(client, mainFramePayload.frameTree);
|
return new FrameManager(client, mainFramePayload.frameTree, mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Connection} client
|
* @param {!Connection} client
|
||||||
* @param {!Object} frameTree
|
* @param {!Object} frameTree
|
||||||
|
* @param {!Mouse} mouse
|
||||||
*/
|
*/
|
||||||
constructor(client, frameTree) {
|
constructor(client, frameTree, mouse) {
|
||||||
super();
|
super();
|
||||||
this._client = client;
|
this._client = client;
|
||||||
|
this._mouse = mouse;
|
||||||
/** @type {!Map<string, !Frame>} */
|
/** @type {!Map<string, !Frame>} */
|
||||||
this._frames = new Map();
|
this._frames = new Map();
|
||||||
this._mainFrame = this._addFramesRecursively(null, frameTree);
|
this._mainFrame = this._addFramesRecursively(null, frameTree);
|
||||||
@ -405,6 +408,39 @@ class Frame {
|
|||||||
return this._frameManager._evaluateOnFrame(this, expression);
|
return this._frameManager._evaluateOnFrame(this, expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} selector
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async hover(selector) {
|
||||||
|
let center = await this.evaluate(selector => {
|
||||||
|
let element = document.querySelector(selector);
|
||||||
|
if (!element)
|
||||||
|
return null;
|
||||||
|
element.scrollIntoViewIfNeeded();
|
||||||
|
let rect = element.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: (rect.left + rect.right) / 2,
|
||||||
|
y: (rect.top + rect.bottom) / 2
|
||||||
|
};
|
||||||
|
}, selector);
|
||||||
|
if (!center)
|
||||||
|
throw new Error('No node found for selector: ' + selector);
|
||||||
|
await this._frameManager._mouse.move(center.x, center.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} selector
|
||||||
|
* @param {!Object=} options
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async click(selector, options) {
|
||||||
|
await this.hover(selector);
|
||||||
|
await this._frameManager._mouse.press(options);
|
||||||
|
// This is a hack for now, to make clicking less race-prone
|
||||||
|
await this.evaluate(() => new Promise(f => requestAnimationFrame(f)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?Object} framePayload
|
* @param {?Object} framePayload
|
||||||
*/
|
*/
|
||||||
|
106
lib/Mouse.js
Normal file
106
lib/Mouse.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Mouse {
|
||||||
|
/**
|
||||||
|
* @param {!Connection} client
|
||||||
|
* @param {!Keyboard} keyboard
|
||||||
|
*/
|
||||||
|
constructor(client, keyboard) {
|
||||||
|
this._client = client;
|
||||||
|
this._keyboard = keyboard;
|
||||||
|
this._x = 0;
|
||||||
|
this._y = 0;
|
||||||
|
this._button = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async move(x, y) {
|
||||||
|
this._x = x;
|
||||||
|
this._y = y;
|
||||||
|
await this._client.send('Input.dispatchMouseEvent', {
|
||||||
|
type: 'mouseMoved',
|
||||||
|
button: this._button,
|
||||||
|
x, y,
|
||||||
|
modifiers: this._modifiersMask()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Object=} options
|
||||||
|
*/
|
||||||
|
async press(options) {
|
||||||
|
await this.down(options);
|
||||||
|
await this.up(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Object=} options
|
||||||
|
*/
|
||||||
|
async down(options) {
|
||||||
|
if (!options)
|
||||||
|
options = {};
|
||||||
|
this._button = (options.button || 'left');
|
||||||
|
await this._client.send('Input.dispatchMouseEvent', {
|
||||||
|
type: 'mousePressed',
|
||||||
|
button: this._button,
|
||||||
|
x: this._x,
|
||||||
|
y: this._y,
|
||||||
|
modifiers: this._modifiersMask(),
|
||||||
|
clickCount: (options.clickCount || 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Object=} options
|
||||||
|
*/
|
||||||
|
async up(options) {
|
||||||
|
if (!options)
|
||||||
|
options = {};
|
||||||
|
this._button = 'none';
|
||||||
|
await this._client.send('Input.dispatchMouseEvent', {
|
||||||
|
type: 'mouseReleased',
|
||||||
|
button: (options.button || 'left'),
|
||||||
|
x: this._x,
|
||||||
|
y: this._y,
|
||||||
|
modifiers: this._modifiersMask(),
|
||||||
|
clickCount: (options.clickCount || 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
_modifiersMask() {
|
||||||
|
let modifiers = this._keyboard.modifiers();
|
||||||
|
let mask = 0;
|
||||||
|
if (modifiers.Alt)
|
||||||
|
mask += 1;
|
||||||
|
if (modifiers.Control)
|
||||||
|
mask += 2;
|
||||||
|
if (modifiers.Meta)
|
||||||
|
mask += 4;
|
||||||
|
if (modifiers.Shift)
|
||||||
|
mask += 8;
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Mouse;
|
63
lib/Page.js
63
lib/Page.js
@ -23,6 +23,7 @@ let Dialog = require('./Dialog');
|
|||||||
let EmulationManager = require('./EmulationManager');
|
let EmulationManager = require('./EmulationManager');
|
||||||
let FrameManager = require('./FrameManager');
|
let FrameManager = require('./FrameManager');
|
||||||
let Keyboard = require('./Keyboard');
|
let Keyboard = require('./Keyboard');
|
||||||
|
let Mouse = require('./Mouse');
|
||||||
let helper = require('./helper');
|
let helper = require('./helper');
|
||||||
|
|
||||||
class Page extends EventEmitter {
|
class Page extends EventEmitter {
|
||||||
@ -40,9 +41,11 @@ class Page extends EventEmitter {
|
|||||||
]);
|
]);
|
||||||
let userAgentExpression = helper.evaluationString(() => window.navigator.userAgent);
|
let userAgentExpression = helper.evaluationString(() => window.navigator.userAgent);
|
||||||
let {result:{value: userAgent}} = await client.send('Runtime.evaluate', { expression: userAgentExpression, returnByValue: true });
|
let {result:{value: userAgent}} = await client.send('Runtime.evaluate', { expression: userAgentExpression, returnByValue: true });
|
||||||
let frameManager = await FrameManager.create(client);
|
let keyboard = new Keyboard(client);
|
||||||
|
let mouse = new Mouse(client, keyboard);
|
||||||
|
let frameManager = await FrameManager.create(client, mouse);
|
||||||
let networkManager = new NetworkManager(client, userAgent);
|
let networkManager = new NetworkManager(client, userAgent);
|
||||||
let page = new Page(client, frameManager, networkManager, screenshotTaskQueue);
|
let page = new Page(client, frameManager, networkManager, screenshotTaskQueue, mouse, keyboard);
|
||||||
// Initialize default page size.
|
// Initialize default page size.
|
||||||
await page.setViewport({width: 400, height: 300});
|
await page.setViewport({width: 400, height: 300});
|
||||||
return page;
|
return page;
|
||||||
@ -53,8 +56,10 @@ class Page extends EventEmitter {
|
|||||||
* @param {!FrameManager} frameManager
|
* @param {!FrameManager} frameManager
|
||||||
* @param {!NetworkManager} networkManager
|
* @param {!NetworkManager} networkManager
|
||||||
* @param {!TaskQueue} screenshotTaskQueue
|
* @param {!TaskQueue} screenshotTaskQueue
|
||||||
|
* @param {!Mouse} mouse
|
||||||
|
* @param {!Keyboard} keyboard
|
||||||
*/
|
*/
|
||||||
constructor(client, frameManager, networkManager, screenshotTaskQueue) {
|
constructor(client, frameManager, networkManager, screenshotTaskQueue, mouse, keyboard) {
|
||||||
super();
|
super();
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
@ -62,7 +67,8 @@ class Page extends EventEmitter {
|
|||||||
/** @type {!Map<string, function>} */
|
/** @type {!Map<string, function>} */
|
||||||
this._inPageCallbacks = new Map();
|
this._inPageCallbacks = new Map();
|
||||||
|
|
||||||
this._keyboard = new Keyboard(this._client);
|
this._keyboard = keyboard;
|
||||||
|
this._mouse = mouse;
|
||||||
|
|
||||||
this._screenshotTaskQueue = screenshotTaskQueue;
|
this._screenshotTaskQueue = screenshotTaskQueue;
|
||||||
|
|
||||||
@ -527,41 +533,28 @@ class Page extends EventEmitter {
|
|||||||
return nodeId;
|
return nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!Mouse}
|
||||||
|
*/
|
||||||
|
get mouse() {
|
||||||
|
return this._mouse;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} selector
|
* @param {string} selector
|
||||||
|
* @param {!Object} options
|
||||||
* @return {!Promise}
|
* @return {!Promise}
|
||||||
*/
|
*/
|
||||||
async click(selector) {
|
async click(selector, options) {
|
||||||
let center = await this.evaluate(selector => {
|
await this.mainFrame().click(selector, options);
|
||||||
let node = document.querySelector(selector);
|
}
|
||||||
if (!node)
|
|
||||||
return null;
|
/**
|
||||||
let rect = node.getBoundingClientRect();
|
* @param {string} selector
|
||||||
return {
|
* @param {!Promise}
|
||||||
x: (rect.left + rect.right) / 2,
|
*/
|
||||||
y: (rect.top + rect.bottom) / 2
|
async hover(selector) {
|
||||||
};
|
await this.mainFrame().hover(selector);
|
||||||
}, selector);
|
|
||||||
if (!center)
|
|
||||||
throw new Error('No node found for selector: ' + selector);
|
|
||||||
let x = Math.round(center.x);
|
|
||||||
let y = Math.round(center.y);
|
|
||||||
this._client.send('Input.dispatchMouseEvent', {
|
|
||||||
type: 'mouseMoved',
|
|
||||||
x, y
|
|
||||||
});
|
|
||||||
this._client.send('Input.dispatchMouseEvent', {
|
|
||||||
type: 'mousePressed',
|
|
||||||
button: 'left',
|
|
||||||
x, y,
|
|
||||||
clickCount: 1
|
|
||||||
});
|
|
||||||
await this._client.send('Input.dispatchMouseEvent', {
|
|
||||||
type: 'mouseReleased',
|
|
||||||
button: 'left',
|
|
||||||
x, y,
|
|
||||||
clickCount: 1
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,6 +77,9 @@ class WebPage {
|
|||||||
Backspace: ['Backspace'],
|
Backspace: ['Backspace'],
|
||||||
Cut: ['Cut'],
|
Cut: ['Cut'],
|
||||||
Paste: ['Paste']
|
Paste: ['Paste']
|
||||||
|
},
|
||||||
|
modifier: {
|
||||||
|
shift: 'Shift'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -401,6 +404,8 @@ class WebPage {
|
|||||||
sendEvent(eventType, ...args) {
|
sendEvent(eventType, ...args) {
|
||||||
if (eventType.startsWith('key'))
|
if (eventType.startsWith('key'))
|
||||||
this._sendKeyboardEvent.apply(this, arguments);
|
this._sendKeyboardEvent.apply(this, arguments);
|
||||||
|
else
|
||||||
|
this._sendMouseEvent.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -449,6 +454,40 @@ class WebPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} eventType
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @param {string|undefined} button
|
||||||
|
* @param {number|undefined} modifier
|
||||||
|
*/
|
||||||
|
_sendMouseEvent(eventType, x, y, button, modifier) {
|
||||||
|
if (modifier)
|
||||||
|
await(this._page.keyboard.down(modifier));
|
||||||
|
await(this._page.mouse.move(x, y));
|
||||||
|
switch (eventType) {
|
||||||
|
case 'mousemove':
|
||||||
|
break;
|
||||||
|
case 'mousedown':
|
||||||
|
await(this._page.mouse.down({button}));
|
||||||
|
break;
|
||||||
|
case 'mouseup':
|
||||||
|
await(this._page.mouse.up({button}));
|
||||||
|
break;
|
||||||
|
case 'doubleclick':
|
||||||
|
await(this._page.mouse.press({button}));
|
||||||
|
await(this._page.mouse.press({button, clickCount: 2}));
|
||||||
|
break;
|
||||||
|
case 'click':
|
||||||
|
await(this._page.mouse.press({button}));
|
||||||
|
break;
|
||||||
|
case 'contextmenu':
|
||||||
|
await(this._page.mouse.press({button: 'right'}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (modifier)
|
||||||
|
await(this._page.keyboard.up(modifier));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param {string} html
|
* @param {string} html
|
||||||
* @param {function()=} callback
|
* @param {function()=} callback
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<title>Button test</title>
|
<title>Button test</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script src="mouse-helper.js"></script>
|
||||||
<button onclick="clicked();">Click target</button>
|
<button onclick="clicked();">Click target</button>
|
||||||
<script>
|
<script>
|
||||||
window.result = 'Was not clicked';
|
window.result = 'Was not clicked';
|
||||||
|
62
test/assets/input/mouse-helper.js
Normal file
62
test/assets/input/mouse-helper.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// This injects a box into the page that moves with the mouse;
|
||||||
|
// Useful for debugging
|
||||||
|
(function(){
|
||||||
|
let box = document.createElement('div');
|
||||||
|
box.classList.add('mouse-helper');
|
||||||
|
let styleElement = document.createElement('style');
|
||||||
|
styleElement.innerHTML = `
|
||||||
|
.mouse-helper {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(0,0,0,.4);
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-top: -10px;
|
||||||
|
transition: background .2s, border-radius .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
.mouse-helper.button-1 {
|
||||||
|
transition: none;
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
}
|
||||||
|
.mouse-helper.button-2 {
|
||||||
|
transition: none;
|
||||||
|
border-color: rgba(0,0,255,0.9);
|
||||||
|
}
|
||||||
|
.mouse-helper.button-3 {
|
||||||
|
transition: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.mouse-helper.button-4 {
|
||||||
|
transition: none;
|
||||||
|
border-color: rgba(255,0,0,0.9);
|
||||||
|
}
|
||||||
|
.mouse-helper.button-5 {
|
||||||
|
transition: none;
|
||||||
|
border-color: rgba(0,255,0,0.9);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(styleElement);
|
||||||
|
document.body.appendChild(box);
|
||||||
|
document.addEventListener('mousemove', event => {
|
||||||
|
box.style.left = event.pageX + 'px';
|
||||||
|
box.style.top = event.pageY + 'px';
|
||||||
|
updateButtons(event.buttons);
|
||||||
|
}, true);
|
||||||
|
document.addEventListener('mousedown', event => {
|
||||||
|
updateButtons(event.buttons);
|
||||||
|
box.classList.add('button-' + event.which);
|
||||||
|
}, true);
|
||||||
|
document.addEventListener('mouseup', event => {
|
||||||
|
updateButtons(event.buttons);
|
||||||
|
box.classList.remove('button-' + event.which);
|
||||||
|
}, true);
|
||||||
|
function updateButtons(buttons) {
|
||||||
|
for (let i = 0; i < 5; i++)
|
||||||
|
box.classList.toggle('button-' + i, buttons & (1 << i));
|
||||||
|
}
|
||||||
|
})();
|
23
test/assets/input/scrollable.html
Normal file
23
test/assets/input/scrollable.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Scrollable test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src='mouse-helper.js'></script>
|
||||||
|
<script>
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
let button = document.createElement('button');
|
||||||
|
button.textContent = i + ': not clicked';
|
||||||
|
button.id = 'button-' + i;
|
||||||
|
button.onclick = () => button.textContent = 'clicked';
|
||||||
|
button.oncontextmenu = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
button.textContent = 'context menu';
|
||||||
|
}
|
||||||
|
document.body.appendChild(button);
|
||||||
|
document.body.appendChild(document.createElement('br'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -5,6 +5,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<textarea></textarea>
|
<textarea></textarea>
|
||||||
|
<script src='mouse-helper.js'></script>
|
||||||
<script>
|
<script>
|
||||||
window.result = '';
|
window.result = '';
|
||||||
let textarea = document.querySelector('textarea');
|
let textarea = document.querySelector('textarea');
|
||||||
|
82
test/test.js
82
test/test.js
@ -877,6 +877,88 @@ describe('Puppeteer', function() {
|
|||||||
expect(keyboard.modifiers().Shift).toBe(false);
|
expect(keyboard.modifiers().Shift).toBe(false);
|
||||||
expect(keyboard.modifiers().Alt).toBe(false);
|
expect(keyboard.modifiers().Alt).toBe(false);
|
||||||
}));
|
}));
|
||||||
|
it('should resize the textarea', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/textarea.html');
|
||||||
|
let {x, y, width, height} = await page.evaluate(dimensions);
|
||||||
|
let mouse = page.mouse;
|
||||||
|
await mouse.move(x + width - 4, y + height - 4);
|
||||||
|
await mouse.down();
|
||||||
|
await mouse.move(x + width + 100, y + height + 100);
|
||||||
|
await mouse.up();
|
||||||
|
let newDimensions = await page.evaluate(dimensions);
|
||||||
|
expect(newDimensions.width).toBe(width + 104);
|
||||||
|
expect(newDimensions.height).toBe(height + 104);
|
||||||
|
}));
|
||||||
|
it('should scroll and click the button', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/scrollable.html');
|
||||||
|
await page.click('#button-5');
|
||||||
|
expect(await page.$('#button-5', button => button.textContent)).toBe('clicked');
|
||||||
|
await page.click('#button-80');
|
||||||
|
expect(await page.$('#button-80', button => button.textContent)).toBe('clicked');
|
||||||
|
}));
|
||||||
|
it('should select the text with mouse', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/textarea.html');
|
||||||
|
await page.focus('textarea');
|
||||||
|
let text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
|
||||||
|
await page.type(text);
|
||||||
|
await page.$('textarea', textarea => textarea.scrollTop = 0);
|
||||||
|
let {x, y} = await page.evaluate(dimensions);
|
||||||
|
await page.mouse.move(x + 2,y + 2);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(100,100);
|
||||||
|
await page.mouse.up();
|
||||||
|
expect(await page.evaluate(() => window.getSelection().toString())).toBe(text);
|
||||||
|
}));
|
||||||
|
it('should select the text by triple clicking', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/textarea.html');
|
||||||
|
await page.focus('textarea');
|
||||||
|
let text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
|
||||||
|
await page.type(text);
|
||||||
|
await page.click('textarea');
|
||||||
|
await page.click('textarea', {clickCount: 2});
|
||||||
|
await page.click('textarea', {clickCount: 3});
|
||||||
|
expect(await page.evaluate(() => window.getSelection().toString())).toBe(text);
|
||||||
|
}));
|
||||||
|
it('should trigger hover state', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/scrollable.html');
|
||||||
|
await page.hover('#button-6');
|
||||||
|
expect(await page.$('button:hover', button => button.id)).toBe('button-6');
|
||||||
|
await page.hover('#button-2');
|
||||||
|
expect(await page.$('button:hover', button => button.id)).toBe('button-2');
|
||||||
|
await page.hover('#button-91');
|
||||||
|
expect(await page.$('button:hover', button => button.id)).toBe('button-91');
|
||||||
|
}));
|
||||||
|
it('should fire contextmenu event on right click', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/scrollable.html');
|
||||||
|
await page.click('#button-8', {button: 'right'});
|
||||||
|
expect(await page.$('#button-8', button => button.textContent)).toBe('context menu');
|
||||||
|
}));
|
||||||
|
it('should set modifier keys on click', SX(async function(){
|
||||||
|
await page.navigate(PREFIX + '/input/scrollable.html');
|
||||||
|
await page.$('#button-3', button => button.addEventListener('mousedown', e => window.lastEvent = e, true));
|
||||||
|
let modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'};
|
||||||
|
for (let modifier in modifiers) {
|
||||||
|
await page.keyboard.down(modifier);
|
||||||
|
await page.click('#button-3');
|
||||||
|
if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
|
||||||
|
fail(modifiers[modifier] + ' should be true');
|
||||||
|
await page.keyboard.up(modifier);
|
||||||
|
}
|
||||||
|
await page.click('#button-3');
|
||||||
|
for (let modifier in modifiers) {
|
||||||
|
if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier])))
|
||||||
|
fail(modifiers[modifier] + ' should be false');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
function dimensions() {
|
||||||
|
let rect = document.querySelector('textarea').getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME: remove this when crbug.com/741689 is fixed.
|
// FIXME: remove this when crbug.com/741689 is fixed.
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var page = require('webpage').create();
|
var page = require('webpage').create();
|
||||||
|
|
||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('contextmenu', function(event) {
|
window.addEventListener('contextmenu', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.contextmenu = event;
|
window.loggedEvent.contextmenu = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
page.sendEvent('contextmenu', 42, 217);
|
page.sendEvent('contextmenu', 42, 217);
|
||||||
@ -20,7 +19,7 @@ test(function () {
|
|||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('contextmenu', function(event) {
|
window.addEventListener('contextmenu', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.contextmenu = event;
|
window.loggedEvent.contextmenu = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
page.sendEvent('contextmenu', 100, 100, 'left', page.event.modifier.shift);
|
page.sendEvent('contextmenu', 100, 100, 'left', page.event.modifier.shift);
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var page = require('webpage').create();
|
var page = require('webpage').create();
|
||||||
|
|
||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('mousedown', function(event) {
|
window.addEventListener('mousedown', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.mousedown = event;
|
window.loggedEvent.mousedown = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
window.addEventListener('mouseup', function(event) {
|
window.addEventListener('mouseup', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.mouseup = event;
|
window.loggedEvent.mouseup = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
page.sendEvent('click', 42, 217);
|
page.sendEvent('click', 42, 217);
|
||||||
@ -26,7 +25,7 @@ test(function () {
|
|||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('click', function(event) {
|
window.addEventListener('click', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.click = event;
|
window.loggedEvent.click = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
page.sendEvent('click', 100, 100, 'left', page.event.modifier.shift);
|
page.sendEvent('click', 100, 100, 'left', page.event.modifier.shift);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var page = require('webpage').create();
|
var page = require('webpage').create();
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ test(function () {
|
|||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('dblclick', function(event) {
|
window.addEventListener('dblclick', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || {};
|
window.loggedEvent = window.loggedEvent || {};
|
||||||
window.loggedEvent.dblclick = event;
|
window.loggedEvent.dblclick = {clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey};
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
page.sendEvent('doubleclick', 100, 100, 'left', page.event.modifier.shift);
|
page.sendEvent('doubleclick', 100, 100, 'left', page.event.modifier.shift);
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var page = require('webpage').create();
|
var page = require('webpage').create();
|
||||||
|
|
||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('mousedown', function(event) {
|
window.addEventListener('mousedown', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || [];
|
window.loggedEvent = window.loggedEvent || [];
|
||||||
window.loggedEvent.push(event);
|
window.loggedEvent.push({clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey});
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var page = require('webpage').create();
|
var page = require('webpage').create();
|
||||||
|
|
||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('mousemove', function(event) {
|
window.addEventListener('mousemove', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || [];
|
window.loggedEvent = window.loggedEvent || [];
|
||||||
window.loggedEvent.push(event);
|
window.loggedEvent.push({clientX: event.clientX, clientY: event.clientY});
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
//! unsupported
|
|
||||||
test(function () {
|
test(function () {
|
||||||
var webpage = require('webpage');
|
var webpage = require('webpage');
|
||||||
var page = webpage.create();
|
var page = webpage.create();
|
||||||
@ -6,7 +5,7 @@ test(function () {
|
|||||||
page.evaluate(function() {
|
page.evaluate(function() {
|
||||||
window.addEventListener('mouseup', function(event) {
|
window.addEventListener('mouseup', function(event) {
|
||||||
window.loggedEvent = window.loggedEvent || [];
|
window.loggedEvent = window.loggedEvent || [];
|
||||||
window.loggedEvent.push(event);
|
window.loggedEvent.push({clientX: event.clientX, clientY: event.clientY, shiftKey: event.shiftKey});
|
||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ const EXCLUDE_METHODS = new Set([
|
|||||||
'Headers.fromPayload',
|
'Headers.fromPayload',
|
||||||
'InterceptedRequest.constructor',
|
'InterceptedRequest.constructor',
|
||||||
'Keyboard.constructor',
|
'Keyboard.constructor',
|
||||||
|
'Mouse.constructor',
|
||||||
'Page.constructor',
|
'Page.constructor',
|
||||||
'Page.create',
|
'Page.create',
|
||||||
'Request.constructor',
|
'Request.constructor',
|
||||||
|
Loading…
Reference in New Issue
Block a user