Introduce Page.$ and Page.$$ (#75)
This patch introduces Page.$ and Page.$$ methods which are aliases for `document.querySelector` and `document.querySelectorAll`. Fixes #78.
This commit is contained in:
parent
bf7698e8f8
commit
117a128b42
49
docs/api.md
49
docs/api.md
@ -24,6 +24,8 @@
|
||||
* [event: 'requestfailed'](#event-requestfailed)
|
||||
* [event: 'requestfinished'](#event-requestfinished)
|
||||
* [event: 'response'](#event-response)
|
||||
* [page.$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
||||
* [page.$$(selector, pageFunction, ...args)](#pageselector-pagefunction-args)
|
||||
* [page.addScriptTag(url)](#pageaddscripttagurl)
|
||||
* [page.click(selector)](#pageclickselector)
|
||||
* [page.close()](#pageclose)
|
||||
@ -68,6 +70,8 @@
|
||||
* [dialog.message()](#dialogmessage)
|
||||
* [dialog.type](#dialogtype)
|
||||
- [class: Frame](#class-frame)
|
||||
* [frame.$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||
* [frame.$$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||
* [frame.childFrames()](#framechildframes)
|
||||
* [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||
* [frame.isDetached()](#frameisdetached)
|
||||
@ -255,6 +259,35 @@ Emitted when a request is successfully finished.
|
||||
|
||||
Emitted when a [response] is received.
|
||||
|
||||
#### page.$(selector, pageFunction, ...args)
|
||||
|
||||
- `selector` <[string]> A selector to be matched in the page
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in-page with first element matching `selector`
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Object]>> Promise which resolves to function return value.
|
||||
|
||||
Example:
|
||||
```js
|
||||
const outerhtml = await page.$('#box', e => e.outerHTML);
|
||||
```
|
||||
|
||||
Shortcut for [page.mainFrame().$(selector, pageFunction, ...args)](#pageselector-fun-args).
|
||||
|
||||
#### page.$$(selector, pageFunction, ...args)
|
||||
|
||||
- `selector` <[string]> A selector to be matched in the page
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in-page for every matching element.
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Array]<[Object]>>> Promise which resolves to array of function return values.
|
||||
|
||||
Example:
|
||||
```js
|
||||
const headings = await page.$$('h1,h2,h3,h4', el => el.textContent);
|
||||
for (const heading of headings) console.log(heading);
|
||||
```
|
||||
|
||||
Shortcut for [page.mainFrame().$$(selector, pageFunction, ...args)](#pageselector-fun-args).
|
||||
|
||||
#### page.addScriptTag(url)
|
||||
- `url` <[string]> Url of a script to be added
|
||||
- returns: <[Promise]> Promise which resolves as the script gets added and loads.
|
||||
@ -632,13 +665,24 @@ browser.newPage().then(async page => {
|
||||
});
|
||||
```
|
||||
|
||||
#### frame.$(selector, pageFunction, ...args)
|
||||
- `selector` <[string]> A selector to be matched in the page
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluated with first element matching `selector`
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Object]>> Promise which resolves to function return value.
|
||||
|
||||
#### frame.$$(selector, pageFunction, ...args)
|
||||
- `selector` <[string]> A selector to be matched in the page
|
||||
- `pageFunction` <[function]\([Element]\)> Function to be evaluted for every element matching `selector`.
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Array]<[Object]>>> Promise which resolves to array of function return values.
|
||||
|
||||
#### frame.childFrames()
|
||||
- returns: <[Array]<[Frame]>>
|
||||
|
||||
|
||||
#### frame.evaluate(pageFunction, ...args)
|
||||
- `pageFunction` <[function]> Function to be evaluated in browser context
|
||||
- `...args` <[Array]<[string]>> Arguments to pass to `pageFunction`
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Object]>> Promise which resolves to function return value
|
||||
|
||||
If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value.
|
||||
@ -868,5 +912,6 @@ If there's already a header with name `name`, the header gets overwritten.
|
||||
[Request]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-request "Request"
|
||||
[Browser]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-browser "Browser"
|
||||
[Body]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-body "Body"
|
||||
[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"
|
||||
[Dialog]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-dialog "Dialog"
|
||||
|
@ -154,18 +154,16 @@ class FrameManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* @param {!Frame} frame
|
||||
* @param {function()} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @param {string} expression
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async _evaluateOnFrame(frame, pageFunction, ...args) {
|
||||
async _evaluateOnFrame(frame, expression) {
|
||||
let contextId = undefined;
|
||||
if (!frame.isMainFrame()) {
|
||||
contextId = this._frameIdToExecutionContextId.get(frame._id);
|
||||
console.assert(contextId, 'Frame does not have default context to evaluate in!');
|
||||
}
|
||||
let syncExpression = helper.evaluationString(pageFunction, ...args);
|
||||
let expression = `Promise.resolve(${syncExpression})`;
|
||||
expression = `Promise.resolve(${expression})`;
|
||||
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise: true });
|
||||
if (exceptionDetails) {
|
||||
let message = await helper.getExceptionMessage(this._client, exceptionDetails);
|
||||
@ -192,6 +190,7 @@ class FrameManager extends EventEmitter {
|
||||
functionDeclaration: 'function() { return this; }',
|
||||
returnByValue: true,
|
||||
});
|
||||
this._client.send('Runtime.releaseObject', {objectId: remoteObject.objectId});
|
||||
return response.result.value;
|
||||
}
|
||||
|
||||
@ -275,7 +274,7 @@ class Frame {
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
return this._frameManager._evaluateOnFrame(this, pageFunction, ...args);
|
||||
return this._frameManager._evaluateOnFrame(this, helper.evaluationString(pageFunction, ...args));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,6 +327,38 @@ class Frame {
|
||||
await this._frameManager._waitForInFrame(selector, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function(!Element):T} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<?T>}
|
||||
*/
|
||||
async $(selector, pageFunction, ...args) {
|
||||
let argsString = ['node'].concat(args.map(x => JSON.stringify(x))).join(',');
|
||||
let expression = `(()=>{
|
||||
let node = document.querySelector(${JSON.stringify(selector)});
|
||||
if (!node)
|
||||
return null;
|
||||
return (${pageFunction})(${argsString});
|
||||
})()`;
|
||||
return this._frameManager._evaluateOnFrame(this, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function(!Element):T} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<!Array<T>>}
|
||||
*/
|
||||
async $$(selector, pageFunction, ...args) {
|
||||
let argsString = ['node, index'].concat(args.map(x => JSON.stringify(x))).join(',');
|
||||
let expression = `(()=>{
|
||||
let nodes = document.querySelectorAll(${JSON.stringify(selector)});
|
||||
return Array.prototype.map.call(nodes, (node, index) => (${pageFunction})(${argsString}));
|
||||
})()`;
|
||||
return this._frameManager._evaluateOnFrame(this, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Object} framePayload
|
||||
*/
|
||||
|
20
lib/Page.js
20
lib/Page.js
@ -592,6 +592,26 @@ class Page extends EventEmitter {
|
||||
await this._client.send('DOM.disable');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function(!Element):T} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<?T>}
|
||||
*/
|
||||
async $(selector, pageFunction, ...args) {
|
||||
return this.mainFrame().$(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function(!Element):T} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<!Array<T>>}
|
||||
*/
|
||||
async $$(selector, pageFunction, ...args) {
|
||||
return this.mainFrame().$$(selector, pageFunction, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/** @enum {string} */
|
||||
|
15
test/assets/playground.html
Normal file
15
test/assets/playground.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Playground</title>
|
||||
</head>
|
||||
<body>
|
||||
<button>A button</button>
|
||||
<textarea>A text area</textarea>
|
||||
<div id="first">First div</div>
|
||||
<div id="second">
|
||||
Second div
|
||||
<span class="inner">Inner span</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
28
test/test.js
28
test/test.js
@ -960,6 +960,34 @@ describe('Puppeteer', function() {
|
||||
expect(await page.title()).toBe('Button test');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Query selector', function() {
|
||||
it('Page.$', SX(async function() {
|
||||
await page.navigate(PREFIX + '/playground.html');
|
||||
expect(await page.$('#first', element => element.textContent)).toBe('First div');
|
||||
expect(await page.$('#second span', element => element.textContent)).toBe('Inner span');
|
||||
expect(await page.$('#first', (element, arg1) => arg1, 'value1')).toBe('value1');
|
||||
expect(await page.$('#first', (element, arg1, arg2) => arg2, 'value1', 'value2')).toBe('value2');
|
||||
expect(await page.$('doesnot-exist', element => 5)).toBe(null);
|
||||
expect(await page.$('button', function(element, arg1) {
|
||||
element.textContent = arg1;
|
||||
return true;
|
||||
}, 'new button text')).toBe(true);
|
||||
expect(await page.$('button', function(element) {
|
||||
return element.textContent;
|
||||
})).toBe('new button text');
|
||||
}));
|
||||
|
||||
it('Page.$$', SX(async function() {
|
||||
await page.navigate(PREFIX + '/playground.html');
|
||||
expect((await page.$$('div', element => element.textContent)).length).toBe(2);
|
||||
expect((await page.$$('div', (element, index) => index))[0]).toBe(0);
|
||||
expect((await page.$$('div', (element, index) => index))[1]).toBe(1);
|
||||
expect((await page.$$('doesnotexist', function(){})).length).toBe(0);
|
||||
expect((await page.$$('div', element => element.textContent))[0]).toBe('First div');
|
||||
expect((await page.$$('span', (element, index, arg1) => arg1, 'value1'))[0]).toBe('value1');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// Since Jasmine doesn't like async functions, they should be wrapped
|
||||
|
@ -63,7 +63,7 @@ class MDOutline {
|
||||
this.errors = errors;
|
||||
const classHeading = /^class: (\w+)$/;
|
||||
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
||||
const methodRegex = /^(\w+)\.(\w+)\((.*)\)$/;
|
||||
const methodRegex = /^(\w+)\.([\w$]+)\((.*)\)$/;
|
||||
const propertyRegex = /^(\w+)\.(\w+)$/;
|
||||
const eventRegex = /^event: '(\w+)'$/;
|
||||
let currentClassName = null;
|
||||
|
@ -1,5 +1,9 @@
|
||||
### class: Foo
|
||||
|
||||
#### foo.$()
|
||||
|
||||
#### foo.money$$money()
|
||||
|
||||
#### foo.proceed()
|
||||
|
||||
#### foo.start()
|
||||
|
@ -7,4 +7,10 @@ class Foo {
|
||||
|
||||
get zzz() {
|
||||
}
|
||||
|
||||
$() {
|
||||
}
|
||||
|
||||
money$$money() {
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user