mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
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: 'requestfailed'](#event-requestfailed)
|
||||||
* [event: 'requestfinished'](#event-requestfinished)
|
* [event: 'requestfinished'](#event-requestfinished)
|
||||||
* [event: 'response'](#event-response)
|
* [event: 'response'](#event-response)
|
||||||
|
* [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)](#pageclickselector)
|
||||||
* [page.close()](#pageclose)
|
* [page.close()](#pageclose)
|
||||||
@ -68,6 +70,8 @@
|
|||||||
* [dialog.message()](#dialogmessage)
|
* [dialog.message()](#dialogmessage)
|
||||||
* [dialog.type](#dialogtype)
|
* [dialog.type](#dialogtype)
|
||||||
- [class: Frame](#class-frame)
|
- [class: Frame](#class-frame)
|
||||||
|
* [frame.$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||||
|
* [frame.$$(selector, pageFunction, ...args)](#frameselector-pagefunction-args)
|
||||||
* [frame.childFrames()](#framechildframes)
|
* [frame.childFrames()](#framechildframes)
|
||||||
* [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
* [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||||
* [frame.isDetached()](#frameisdetached)
|
* [frame.isDetached()](#frameisdetached)
|
||||||
@ -255,6 +259,35 @@ Emitted when a request is successfully finished.
|
|||||||
|
|
||||||
Emitted when a [response] is received.
|
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)
|
#### page.addScriptTag(url)
|
||||||
- `url` <[string]> Url of a script to be added
|
- `url` <[string]> Url of a script to be added
|
||||||
- returns: <[Promise]> Promise which resolves as the script gets added and loads.
|
- 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()
|
#### frame.childFrames()
|
||||||
- returns: <[Array]<[Frame]>>
|
- returns: <[Array]<[Frame]>>
|
||||||
|
|
||||||
|
|
||||||
#### 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` <[Array]<[string]>> Arguments to pass to `pageFunction`
|
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||||
- returns: <[Promise]<[Object]>> Promise which resolves to function return value
|
- 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.
|
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"
|
[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"
|
[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"
|
[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"
|
[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"
|
||||||
|
@ -154,18 +154,16 @@ class FrameManager extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Frame} frame
|
* @param {!Frame} frame
|
||||||
* @param {function()} pageFunction
|
* @param {string} expression
|
||||||
* @param {!Array<*>} args
|
|
||||||
* @return {!Promise<(!Object|undefined)>}
|
* @return {!Promise<(!Object|undefined)>}
|
||||||
*/
|
*/
|
||||||
async _evaluateOnFrame(frame, pageFunction, ...args) {
|
async _evaluateOnFrame(frame, expression) {
|
||||||
let contextId = undefined;
|
let contextId = undefined;
|
||||||
if (!frame.isMainFrame()) {
|
if (!frame.isMainFrame()) {
|
||||||
contextId = this._frameIdToExecutionContextId.get(frame._id);
|
contextId = this._frameIdToExecutionContextId.get(frame._id);
|
||||||
console.assert(contextId, 'Frame does not have default context to evaluate in!');
|
console.assert(contextId, 'Frame does not have default context to evaluate in!');
|
||||||
}
|
}
|
||||||
let syncExpression = helper.evaluationString(pageFunction, ...args);
|
expression = `Promise.resolve(${expression})`;
|
||||||
let expression = `Promise.resolve(${syncExpression})`;
|
|
||||||
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise: true });
|
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise: true });
|
||||||
if (exceptionDetails) {
|
if (exceptionDetails) {
|
||||||
let message = await helper.getExceptionMessage(this._client, exceptionDetails);
|
let message = await helper.getExceptionMessage(this._client, exceptionDetails);
|
||||||
@ -192,6 +190,7 @@ class FrameManager extends EventEmitter {
|
|||||||
functionDeclaration: 'function() { return this; }',
|
functionDeclaration: 'function() { return this; }',
|
||||||
returnByValue: true,
|
returnByValue: true,
|
||||||
});
|
});
|
||||||
|
this._client.send('Runtime.releaseObject', {objectId: remoteObject.objectId});
|
||||||
return response.result.value;
|
return response.result.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +274,7 @@ class Frame {
|
|||||||
* @return {!Promise<(!Object|undefined)>}
|
* @return {!Promise<(!Object|undefined)>}
|
||||||
*/
|
*/
|
||||||
async evaluate(pageFunction, ...args) {
|
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);
|
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
|
* @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');
|
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} */
|
/** @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');
|
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
|
// Since Jasmine doesn't like async functions, they should be wrapped
|
||||||
|
@ -63,7 +63,7 @@ class MDOutline {
|
|||||||
this.errors = errors;
|
this.errors = errors;
|
||||||
const classHeading = /^class: (\w+)$/;
|
const classHeading = /^class: (\w+)$/;
|
||||||
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
||||||
const methodRegex = /^(\w+)\.(\w+)\((.*)\)$/;
|
const methodRegex = /^(\w+)\.([\w$]+)\((.*)\)$/;
|
||||||
const propertyRegex = /^(\w+)\.(\w+)$/;
|
const propertyRegex = /^(\w+)\.(\w+)$/;
|
||||||
const eventRegex = /^event: '(\w+)'$/;
|
const eventRegex = /^event: '(\w+)'$/;
|
||||||
let currentClassName = null;
|
let currentClassName = null;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
### class: Foo
|
### class: Foo
|
||||||
|
|
||||||
|
#### foo.$()
|
||||||
|
|
||||||
|
#### foo.money$$money()
|
||||||
|
|
||||||
#### foo.proceed()
|
#### foo.proceed()
|
||||||
|
|
||||||
#### foo.start()
|
#### foo.start()
|
||||||
|
@ -7,4 +7,10 @@ class Foo {
|
|||||||
|
|
||||||
get zzz() {
|
get zzz() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$() {
|
||||||
|
}
|
||||||
|
|
||||||
|
money$$money() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user