mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(coverage): add an option to collect coverage of anonymous scripts (#2796)
This patch adds `reportAnonymousScripts` option to the `coverage.startJSCoverage` method. With this option, anonymous scripts are reported as well. Fixes #2777
This commit is contained in:
parent
96c558d544
commit
12bc1e1a62
@ -2939,8 +2939,11 @@ _To output coverage in a form consumable by [Istanbul](https://github.com/istanb
|
||||
#### coverage.startJSCoverage(options)
|
||||
- `options` <[Object]> Set of configurable options for coverage
|
||||
- `resetOnNavigation` <[boolean]> Whether to reset coverage on every navigation. Defaults to `true`.
|
||||
- `reportAnonymousScripts` <[boolean]> Whether anonymous scripts generated by the page should be reported. Defaults to `false`.
|
||||
- returns: <[Promise]> Promise that resolves when coverage is started
|
||||
|
||||
> **NOTE** Anonymous scripts are ones that don't have an associated url. These are scripts that are dynamically created on the page using `eval` or `new Function`. If `reportAnonymousScripts` is set to `true`, anonymous scripts will have `__puppeteer_evaluation_script__` as their URL.
|
||||
|
||||
#### coverage.stopCSSCoverage()
|
||||
- returns: <[Promise]<[Array]<[Object]>>> Promise that resolves to the array of coverage reports for all stylesheets
|
||||
- `url` <[string]> StyleSheet URL
|
||||
@ -2952,14 +2955,14 @@ _To output coverage in a form consumable by [Istanbul](https://github.com/istanb
|
||||
> **NOTE** CSS Coverage doesn't include dynamically injected style tags without sourceURLs.
|
||||
|
||||
#### coverage.stopJSCoverage()
|
||||
- returns: <[Promise]<[Array]<[Object]>>> Promise that resolves to the array of coverage reports for all non-anonymous scripts
|
||||
- returns: <[Promise]<[Array]<[Object]>>> Promise that resolves to the array of coverage reports for all scripts
|
||||
- `url` <[string]> Script URL
|
||||
- `text` <[string]> Script content
|
||||
- `ranges` <[Array]<[Object]>> Script ranges that were executed. Ranges are sorted and non-overlapping.
|
||||
- `start` <[number]> A start offset in text, inclusive
|
||||
- `end` <[number]> An end offset in text, exclusive
|
||||
|
||||
> **NOTE** JavaScript Coverage doesn't include anonymous scripts. However, scripts with sourceURLs are
|
||||
> **NOTE** JavaScript Coverage doesn't include anonymous scripts by default. However, scripts with sourceURLs are
|
||||
reported.
|
||||
|
||||
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
const {helper, debugError, assert} = require('./helper');
|
||||
|
||||
const {EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
|
||||
|
||||
/**
|
||||
* @typedef {Object} CoverageEntry
|
||||
* @property {string} url
|
||||
@ -83,6 +85,7 @@ class JSCoverage {
|
||||
async start(options = {}) {
|
||||
assert(!this._enabled, 'JSCoverage is already enabled');
|
||||
this._resetOnNavigation = options.resetOnNavigation === undefined ? true : !!options.resetOnNavigation;
|
||||
this._reportAnonymousScripts = !!options.reportAnonymousScripts;
|
||||
this._enabled = true;
|
||||
this._scriptURLs.clear();
|
||||
this._scriptSources.clear();
|
||||
@ -109,8 +112,11 @@ class JSCoverage {
|
||||
* @param {!Protocol.Debugger.scriptParsedPayload} event
|
||||
*/
|
||||
async _onScriptParsed(event) {
|
||||
// Ignore anonymous scripts
|
||||
if (!event.url)
|
||||
// Ignore puppeteer-injected scripts
|
||||
if (event.url === EVALUATION_SCRIPT_URL)
|
||||
return;
|
||||
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
|
||||
if (!event.url && !this._reportAnonymousScripts)
|
||||
return;
|
||||
try {
|
||||
const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId});
|
||||
|
@ -16,6 +16,9 @@
|
||||
|
||||
const {helper, assert} = require('./helper');
|
||||
|
||||
const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
|
||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||
|
||||
class ExecutionContext {
|
||||
/**
|
||||
* @param {!Puppeteer.CDPSession} client
|
||||
@ -61,11 +64,14 @@ class ExecutionContext {
|
||||
* @return {!Promise<!JSHandle>}
|
||||
*/
|
||||
async evaluateHandle(pageFunction, ...args) {
|
||||
const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
|
||||
|
||||
if (helper.isString(pageFunction)) {
|
||||
const contextId = this._contextId;
|
||||
const expression = /** @type {string} */ (pageFunction);
|
||||
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
|
||||
const {exceptionDetails, result: remoteObject} = await this._client.send('Runtime.evaluate', {
|
||||
expression,
|
||||
expression: expressionWithSourceUrl,
|
||||
contextId,
|
||||
returnByValue: false,
|
||||
awaitPromise: true,
|
||||
@ -76,8 +82,11 @@ class ExecutionContext {
|
||||
return this._objectHandleFactory(remoteObject);
|
||||
}
|
||||
|
||||
if (typeof pageFunction !== 'function')
|
||||
throw new Error('The following is not a function: ' + pageFunction);
|
||||
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: pageFunction.toString(),
|
||||
functionDeclaration: pageFunction.toString() + '\n' + suffix + '\n',
|
||||
executionContextId: this._contextId,
|
||||
arguments: args.map(convertArgument.bind(this)),
|
||||
returnByValue: false,
|
||||
@ -239,4 +248,4 @@ class JSHandle {
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(JSHandle);
|
||||
module.exports = {ExecutionContext, JSHandle};
|
||||
module.exports = {ExecutionContext, JSHandle, EVALUATION_SCRIPT_URL};
|
||||
|
1
test/assets/jscoverage/eval.html
Normal file
1
test/assets/jscoverage/eval.html
Normal file
@ -0,0 +1 @@
|
||||
<script>eval('console.log("foo")')</script>
|
@ -38,12 +38,27 @@ module.exports.addTests = function({testRunner, expect}) {
|
||||
expect(coverage.length).toBe(1);
|
||||
expect(coverage[0].url).toBe('nicename.js');
|
||||
});
|
||||
it('should ignore anonymous scripts', async function({page, server}) {
|
||||
await page.coverage.startJSCoverage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.evaluate(() => console.log(1));
|
||||
const coverage = await page.coverage.stopJSCoverage();
|
||||
expect(coverage.length).toBe(0);
|
||||
describe('anonymous scripts coverage', function() {
|
||||
it('should ignore eval() scripts by default', async function({page, server}) {
|
||||
await page.coverage.startJSCoverage();
|
||||
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
||||
const coverage = await page.coverage.stopJSCoverage();
|
||||
expect(coverage.length).toBe(1);
|
||||
});
|
||||
it('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({page, server}) {
|
||||
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
|
||||
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
||||
const coverage = await page.coverage.stopJSCoverage();
|
||||
expect(coverage.length).toBe(2);
|
||||
});
|
||||
it('should ignore injected scripts regardless of reportAnonymousScripts', async function({page, server}) {
|
||||
await page.coverage.startJSCoverage({reportAnonymousScripts: true});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.evaluate('console.log("foo")');
|
||||
await page.evaluate(() => console.log('bar'));
|
||||
const coverage = await page.coverage.stopJSCoverage();
|
||||
expect(coverage.length).toBe(0);
|
||||
});
|
||||
});
|
||||
it('should report multiple scripts', async function({page, server}) {
|
||||
await page.coverage.startJSCoverage();
|
||||
|
Loading…
Reference in New Issue
Block a user