Skip to content

Commit

Permalink
Implement Basic input API
Browse files Browse the repository at this point in the history
This patch implements Basic Input api:
- Page.focus(selector) - focuses element with selector
- Page.click(selector) - clicks element with selector
- Page.type(text) - types text into a focused element

Fixed #43.
  • Loading branch information
JoelEinbinder authored and aslushnikov committed Jun 28, 2017
1 parent 3d90ea3 commit d5a9165
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
89 changes: 89 additions & 0 deletions lib/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class Page extends EventEmitter {
this._inPageCallbacks = new Map();
/** @type {?function(!Request)} */
this._requestInterceptor = null;
/** @type {?Promise<number>} */
this._rootNodeIdPromise = null;

this._screenshotTaskChain = Promise.resolve();

Expand All @@ -73,6 +75,7 @@ class Page extends EventEmitter {
client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails));
client.on('DOM.documentUpdated', event => this._rootNodeIdPromise = null);
}

/**
Expand Down Expand Up @@ -479,6 +482,92 @@ class Page extends EventEmitter {
async close() {
await this._client.dispose();
}

/**
* @return {!Promise<number>}
*/
_rootNodeId() {
if (!this._rootNodeIdPromise) {
this._rootNodeIdPromise = this._client.send('DOM.getDocument', {
depth: 0
}).then(obj => obj.root.nodeId);
}
return this._rootNodeIdPromise;
}

/**
* @param {string} selector
* @param {!Promise<number>}
*/
async _querySelector(selector) {
return (await this._client.send('DOM.querySelector', {
nodeId: await this._rootNodeId(),
selector
})).nodeId;
}

/**
* @param {string} selector
* @param {!Promise}
*/
async click(selector) {
let boxModel = (await this._client.send('DOM.getBoxModel', {
nodeId: await this._querySelector(selector)
})).model.content;
let x = Math.round((boxModel[0] + boxModel[4]) / (2 * this._screenDPI));
let y = Math.round((boxModel[1] + boxModel[5]) / (2 * this._screenDPI));

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

/**
* @param {string} selector
* @param {!Promise}
*/
async focus(selector) {
await this._client.send('DOM.focus', {
nodeId: await this._querySelector(selector)
});
}

/**
* @param {string} text
* @param {!Promise}
*/
async type(text) {
for (let i = 0; i < text.length; i++) {
let char = text.charAt(i);
this._client.send('Input.dispatchKeyEvent', {
type: 'keyDown',
key: char
});
this._client.send('Input.dispatchKeyEvent', {
type: 'char',
text: char,
key: char,
unmodifiedText: char
});
await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp',
key: char
});
}
}
}

/** @enum {string} */
Expand Down
15 changes: 15 additions & 0 deletions test/assets/input/button.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>Button test</title>
</head>
<body>
<button onclick="clicked();">Click target</button>
<script>
window.result = 'Was not clicked';
function clicked() {
result = 'Clicked';
}
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions test/assets/input/textarea.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Textarea test</title>
</head>
<body>
<textarea></textarea>
<script>
window.result = '';
let textarea = document.querySelector('textarea');
textarea.addEventListener('input', () => result = textarea.value, false);
</script>
</body>
</html>
21 changes: 21 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,27 @@ describe('Puppeteer', function() {
expect(navigatedFrames.length).toBe(1);
}));
});

describe('input', function() {
it('should click the button', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/button.html');
await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked');
}));
it('should type into the textarea', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/textarea.html');
await page.focus('textarea');
await page.type('Type in this text!');
expect(await page.evaluate(() => result)).toBe('Type in this text!');
}));
it('should click the button after navigation ', SX(async function() {
await page.navigate(STATIC_PREFIX + '/input/button.html');
await page.click('button');
await page.navigate(STATIC_PREFIX + '/input/button.html');
await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked');
}));
});
});

// Since Jasmine doesn't like async functions, they should be wrapped
Expand Down

0 comments on commit d5a9165

Please sign in to comment.