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)
|
#### coverage.startJSCoverage(options)
|
||||||
- `options` <[Object]> Set of configurable options for coverage
|
- `options` <[Object]> Set of configurable options for coverage
|
||||||
- `resetOnNavigation` <[boolean]> Whether to reset coverage on every navigation. Defaults to `true`.
|
- `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
|
- 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()
|
#### coverage.stopCSSCoverage()
|
||||||
- returns: <[Promise]<[Array]<[Object]>>> Promise that resolves to the array of coverage reports for all stylesheets
|
- returns: <[Promise]<[Array]<[Object]>>> Promise that resolves to the array of coverage reports for all stylesheets
|
||||||
- `url` <[string]> StyleSheet URL
|
- `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.
|
> **NOTE** CSS Coverage doesn't include dynamically injected style tags without sourceURLs.
|
||||||
|
|
||||||
#### coverage.stopJSCoverage()
|
#### 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
|
- `url` <[string]> Script URL
|
||||||
- `text` <[string]> Script content
|
- `text` <[string]> Script content
|
||||||
- `ranges` <[Array]<[Object]>> Script ranges that were executed. Ranges are sorted and non-overlapping.
|
- `ranges` <[Array]<[Object]>> Script ranges that were executed. Ranges are sorted and non-overlapping.
|
||||||
- `start` <[number]> A start offset in text, inclusive
|
- `start` <[number]> A start offset in text, inclusive
|
||||||
- `end` <[number]> An end offset in text, exclusive
|
- `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.
|
reported.
|
||||||
|
|
||||||
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
|
[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 {helper, debugError, assert} = require('./helper');
|
||||||
|
|
||||||
|
const {EVALUATION_SCRIPT_URL} = require('./ExecutionContext');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CoverageEntry
|
* @typedef {Object} CoverageEntry
|
||||||
* @property {string} url
|
* @property {string} url
|
||||||
@ -83,6 +85,7 @@ class JSCoverage {
|
|||||||
async start(options = {}) {
|
async start(options = {}) {
|
||||||
assert(!this._enabled, 'JSCoverage is already enabled');
|
assert(!this._enabled, 'JSCoverage is already enabled');
|
||||||
this._resetOnNavigation = options.resetOnNavigation === undefined ? true : !!options.resetOnNavigation;
|
this._resetOnNavigation = options.resetOnNavigation === undefined ? true : !!options.resetOnNavigation;
|
||||||
|
this._reportAnonymousScripts = !!options.reportAnonymousScripts;
|
||||||
this._enabled = true;
|
this._enabled = true;
|
||||||
this._scriptURLs.clear();
|
this._scriptURLs.clear();
|
||||||
this._scriptSources.clear();
|
this._scriptSources.clear();
|
||||||
@ -109,8 +112,11 @@ class JSCoverage {
|
|||||||
* @param {!Protocol.Debugger.scriptParsedPayload} event
|
* @param {!Protocol.Debugger.scriptParsedPayload} event
|
||||||
*/
|
*/
|
||||||
async _onScriptParsed(event) {
|
async _onScriptParsed(event) {
|
||||||
// Ignore anonymous scripts
|
// Ignore puppeteer-injected scripts
|
||||||
if (!event.url)
|
if (event.url === EVALUATION_SCRIPT_URL)
|
||||||
|
return;
|
||||||
|
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
|
||||||
|
if (!event.url && !this._reportAnonymousScripts)
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId});
|
const response = await this._client.send('Debugger.getScriptSource', {scriptId: event.scriptId});
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
const {helper, assert} = require('./helper');
|
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 {
|
class ExecutionContext {
|
||||||
/**
|
/**
|
||||||
* @param {!Puppeteer.CDPSession} client
|
* @param {!Puppeteer.CDPSession} client
|
||||||
@ -61,11 +64,14 @@ class ExecutionContext {
|
|||||||
* @return {!Promise<!JSHandle>}
|
* @return {!Promise<!JSHandle>}
|
||||||
*/
|
*/
|
||||||
async evaluateHandle(pageFunction, ...args) {
|
async evaluateHandle(pageFunction, ...args) {
|
||||||
|
const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
|
||||||
|
|
||||||
if (helper.isString(pageFunction)) {
|
if (helper.isString(pageFunction)) {
|
||||||
const contextId = this._contextId;
|
const contextId = this._contextId;
|
||||||
const expression = /** @type {string} */ (pageFunction);
|
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', {
|
const {exceptionDetails, result: remoteObject} = await this._client.send('Runtime.evaluate', {
|
||||||
expression,
|
expression: expressionWithSourceUrl,
|
||||||
contextId,
|
contextId,
|
||||||
returnByValue: false,
|
returnByValue: false,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
@ -76,8 +82,11 @@ class ExecutionContext {
|
|||||||
return this._objectHandleFactory(remoteObject);
|
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', {
|
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||||
functionDeclaration: pageFunction.toString(),
|
functionDeclaration: pageFunction.toString() + '\n' + suffix + '\n',
|
||||||
executionContextId: this._contextId,
|
executionContextId: this._contextId,
|
||||||
arguments: args.map(convertArgument.bind(this)),
|
arguments: args.map(convertArgument.bind(this)),
|
||||||
returnByValue: false,
|
returnByValue: false,
|
||||||
@ -239,4 +248,4 @@ class JSHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
helper.tracePublicAPI(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.length).toBe(1);
|
||||||
expect(coverage[0].url).toBe('nicename.js');
|
expect(coverage[0].url).toBe('nicename.js');
|
||||||
});
|
});
|
||||||
it('should ignore anonymous scripts', async function({page, server}) {
|
describe('anonymous scripts coverage', function() {
|
||||||
await page.coverage.startJSCoverage();
|
it('should ignore eval() scripts by default', async function({page, server}) {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.coverage.startJSCoverage();
|
||||||
await page.evaluate(() => console.log(1));
|
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
||||||
const coverage = await page.coverage.stopJSCoverage();
|
const coverage = await page.coverage.stopJSCoverage();
|
||||||
expect(coverage.length).toBe(0);
|
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}) {
|
it('should report multiple scripts', async function({page, server}) {
|
||||||
await page.coverage.startJSCoverage();
|
await page.coverage.startJSCoverage();
|
||||||
|
Loading…
Reference in New Issue
Block a user