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:
JoelEinbinder 2017-07-21 20:29:31 -07:00 committed by Andrey Lushnikov
parent eb2cb67b0e
commit 98ee35655f
17 changed files with 458 additions and 55 deletions

View File

@ -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"

View File

@ -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
View 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;

View File

@ -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
});
} }
/** /**

View File

@ -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

View File

@ -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';

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

View 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>

View File

@ -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');

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',