$ and $$ for querySelector and querySelectorAll

This commit is contained in:
Joel Einbinder 2017-07-07 13:32:22 -07:00
parent 6c7ae41ae6
commit fa35554166
6 changed files with 149 additions and 22 deletions

View File

@ -9,6 +9,8 @@
- [browser.newPage()](#browsernewpage) - [browser.newPage()](#browsernewpage)
- [browser.version()](#browserversion) - [browser.version()](#browserversion)
+ [class: Page](#class-page) + [class: Page](#class-page)
- [page.$(querySelector, fun, args)](#pagequeryselector-fun-args)
- [page.$$(querySelector, fun, args)](#pagequeryselector-fun-args-1)
- [page.addScriptTag(url)](#pageaddscripttagurl) - [page.addScriptTag(url)](#pageaddscripttagurl)
- [page.close()](#pageclose) - [page.close()](#pageclose)
- [page.evaluate(fun, args)](#pageevaluatefun-args) - [page.evaluate(fun, args)](#pageevaluatefun-args)
@ -104,6 +106,20 @@ browser.newPage().then(page => {
``` ```
Pages could be closed by `page.close()` method. Pages could be closed by `page.close()` method.
#### page.$(querySelector, fun, args)
- `querySelector` <[string]> Query selector to be run on the page
- `fun` <[function<[Element]>]> Function to be evaluated with first element matching `querySelector`
- `args` <[Array]<[string]>> Arguments to pass to `fun`
- returns: <[Promise<[Object]]> Promise which resolves to function return value.
#### page.$$(querySelector, fun, args)
- `querySelector` <[string]> Query selector to be run on the page
- `fun` <[function<[Element]>]> Function to be evaluted for every element matching `querySelector`.
- `args` <[Array]<[string]>> Arguments to pass to `fun`
- returns: <[Promise<[Array<[Object]>]>]> Promise which resolves to array of function return values.
#### page.addScriptTag(url) #### page.addScriptTag(url)
- `url` <[string]> Url of a script to be added - `url` <[string]> Url of a script to be added
@ -295,3 +311,4 @@ Pages could be closed by `page.close()` method.
[Page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page" [Page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page"
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" [string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"

View File

@ -174,31 +174,11 @@ class FrameManager extends EventEmitter {
let message = await helper.getExceptionMessage(this._client, exceptionDetails); let message = await helper.getExceptionMessage(this._client, exceptionDetails);
throw new Error('Evaluation failed: ' + message); throw new Error('Evaluation failed: ' + message);
} }
if (remoteObject.unserializableValue) { return helper.serializeRemoteObjet(this._client, remoteObject);
switch (remoteObject.unserializableValue) {
case '-0':
return -0;
case 'NaN':
return NaN;
case 'Infinity':
return Infinity;
case '-Infinity':
return -Infinity;
default:
throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue);
}
}
if (!remoteObject.objectId)
return remoteObject.value;
let response = await this._client.send('Runtime.callFunctionOn', {
objectId: remoteObject.objectId,
functionDeclaration: 'function() { return this; }',
returnByValue: true,
});
return response.result.value;
} }
} }
/** @enum {string} */ /** @enum {string} */
FrameManager.Events = { FrameManager.Events = {
FrameAttached: 'frameattached', FrameAttached: 'frameattached',

View File

@ -499,6 +499,17 @@ class Page extends EventEmitter {
})).nodeId; })).nodeId;
} }
/**
* @param {string} selector
* @param {!Promise<!Array<number>>}
*/
async _querySelectorAll(selector) {
return (await this._client.send('DOM.querySelectorAll', {
nodeId: await this._rootNodeId(),
selector
})).nodeIds;
}
/** /**
* @param {string} selector * @param {string} selector
* @param {!Promise} * @param {!Promise}
@ -561,6 +572,53 @@ class Page extends EventEmitter {
}); });
} }
} }
/**
* @param {number} nodeId
* @param {function(!Element):T} fun
* @param {!Array<*>} args
* @return {!Promise<T>}
*/
async _callFunctionOnNode(nodeId, fun, ...args) {
let { objectId } = (await this._client.send('DOM.resolveNode', { nodeId })).object;
let argsString = ['this'].concat(args.map(x => JSON.stringify(x))).join(',');
let {result, exceptionDetails} = await this._client.send('Runtime.callFunctionOn', {
objectId,
functionDeclaration: `function(){return (${fun})(${argsString})}`,
returnByValue: false,
});
this._client.send('Runtime.releaseObject', {objectId});
if (exceptionDetails) {
let message = await helper.getExceptionMessage(this._client, exceptionDetails);
throw new Error('Evaluation failed: ' + message);
}
let value = helper.serializeRemoteObjet(this._client, result);
return value;
}
/**
* @param {string} querySelector
* @param {function(!Element):T} fun
* @param {!Array<*>} args
* @return {!Promise<?T>}
*/
async $(querySelector, fun, ...args) {
let nodeId = await this._querySelector(querySelector);
if (!nodeId)
return null;
return this._callFunctionOnNode(await this._querySelector(querySelector), fun, ...args);
}
/**
* @param {string} querySelector
* @param {function(!Element):T} fun
* @param {!Array<*>} args
* @return {!Promise<!Array<T>>}
*/
async $$(querySelector, fun, ...args) {
let nodeIds = await this._querySelectorAll(querySelector);
return Promise.all(nodeIds.map((nodeId, index) => this._callFunctionOnNode(nodeId, fun, index, ...args)));
}
} }
/** @enum {string} */ /** @enum {string} */

View File

@ -52,6 +52,36 @@ class Helper {
} }
return message; return message;
} }
/**
* @param {!Connection} client
* @param {!Object} remoteObject
*/
static async serializeRemoteObjet(client, remoteObject) {
if (remoteObject.unserializableValue) {
switch (remoteObject.unserializableValue) {
case '-0':
return -0;
case 'NaN':
return NaN;
case 'Infinity':
return Infinity;
case '-Infinity':
return -Infinity;
default:
throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue);
}
}
if (!remoteObject.objectId)
return remoteObject.value;
let response = client.send('Runtime.callFunctionOn', {
objectId: remoteObject.objectId,
functionDeclaration: 'function() { return this; }',
returnByValue: true,
});
client.send('Runtime.releaseObject', {objectId: remoteObject.objectId});
return (await response).result.value;
}
} }
module.exports = Helper; module.exports = Helper;

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

View File

@ -630,6 +630,33 @@ describe('Puppeteer', function() {
]); ]);
})); }));
}); });
describe('Query selector', function() {
it('Page.$', SX(async function() {
await page.navigate(STATIC_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(STATIC_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