const { helper, debugError, assert } = require('../helper')
const { EVALUATION_SCRIPT_URL } = require('../executionContext')
const { convertToDisjointRanges } = require('../__shared')
class JSCoverage {
/**
* @param {Chrome|CRIConnection|CDPSession|Object} client
*/
constructor (client) {
/**
* @type {Chrome|CRIConnection|CDPSession|Object}
* @private
*/
this._client = client
/**
* @type {boolean}
* @private
*/
this._enabled = false
this._scriptURLs = new Map()
this._scriptSources = new Map()
this._eventListeners = []
this._resetOnNavigation = false
}
/**
* @param {!{resetOnNavigation?: boolean, reportAnonymousScripts?: boolean}} options
*/
async start (options = {}) {
assert(!this._enabled, 'JSCoverage is already enabled')
const { resetOnNavigation = true, reportAnonymousScripts = false } = options
this._resetOnNavigation = resetOnNavigation
this._reportAnonymousScripts = reportAnonymousScripts
this._enabled = true
this._scriptURLs.clear()
this._scriptSources.clear()
this._eventListeners = [
helper.addEventListener(
this._client,
'Debugger.scriptParsed',
this._onScriptParsed.bind(this)
),
helper.addEventListener(
this._client,
'Runtime.executionContextsCleared',
this._onExecutionContextsCleared.bind(this)
)
]
await Promise.all([
this._client.send('Profiler.enable'),
this._client.send('Profiler.startPreciseCoverage', {
callCount: false,
detailed: true
}),
this._client.send('Debugger.enable'),
this._client.send('Debugger.setSkipAllPauses', { skip: true })
])
}
_onExecutionContextsCleared () {
if (!this._resetOnNavigation) return
this._scriptURLs.clear()
this._scriptSources.clear()
}
/**
* @param {!Object} event
*/
async _onScriptParsed (event) {
// 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
})
this._scriptURLs.set(event.scriptId, event.url)
this._scriptSources.set(event.scriptId, response.scriptSource)
} catch (e) {
// This might happen if the page has already navigated away.
debugError(e)
console.error(e)
}
}
/**
* @return {Promise<Array<CoverageEntry>>}
*/
async stop () {
assert(this._enabled, 'JSCoverage is not enabled')
this._enabled = false
const [profileResponse] = await Promise.all([
this._client.send('Profiler.takePreciseCoverage'),
this._client.send('Profiler.stopPreciseCoverage'),
this._client.send('Profiler.disable'),
this._client.send('Debugger.disable')
])
helper.removeEventListeners(this._eventListeners)
const coverage = []
for (const entry of profileResponse.result) {
let url = this._scriptURLs.get(entry.scriptId)
if (!url && this._reportAnonymousScripts) {
url = 'debugger://VM' + entry.scriptId
}
const text = this._scriptSources.get(entry.scriptId)
if (text === undefined || url === undefined) continue
const flattenRanges = []
for (const func of entry.functions) flattenRanges.push(...func.ranges)
const ranges = convertToDisjointRanges(flattenRanges)
coverage.push({ url, ranges, text })
}
return coverage
}
}
module.exports = JSCoverage
function ScriptAgent(require, exports, module) {
'use strict';
var Inspector = require("LiveDevelopment/Inspector/Inspector");
var DOMAgent = require("LiveDevelopment/Agents/DOMAgent");
var _load; // the load promise
var _urlToScript; // url -> script info
var _idToScript; // id -> script info
var _insertTrace; // the last recorded trace of a DOM insertion
/** Add a call stack trace to a node
* @param {integer} node id
* @param [{Debugger.CallFrame}] call stack
*/
function _addTraceToNode(nodeId, trace) {
var node = DOMAgent.nodeWithId(nodeId);
node.trace = trace;
}
/** Get the script information for a given url
* @param {string} url
*/
function scriptWithId(url) {
return _idToScript[url];
}
/** Get the script information for a given url
* @param {string} url
*/
function scriptForURL(url) {
return _urlToScript[url];
}
// DOMAgent Event: Document root loaded
function _onGetDocument(res) {
Inspector.DOMDebugger.setDOMBreakpoint(res.root.nodeId, "subtree-modified");
_load.resolve();
}
// WebInspector Event: DOM.childNodeInserted
function _onChildNodeInserted(res) {
// res = {parentNodeId, previousNodeId, node}
if (_insertTrace) {
var node = DOMAgent.nodeWithId(res.node.nodeId);
node.trace = _insertTrace;
_insertTrace = undefined;
}
}
// WebInspector Event: Debugger.scriptParsed
function _onScriptParsed(res) {
// res = {scriptId, url, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL}
_idToScript[res.scriptId] = res;
_urlToScript[res.url] = res;
}
// WebInspector Event: Debugger.scriptFailedToParse
function _onScriptFailedToParse(res) {
// res = {url, scriptSource, startLine, errorLine, errorMessage}
}
// WebInspector Event: Debugger.paused
function _onPaused(res) {
// res = {callFrames, reason, data}
switch (res.reason) {
// Exception
case "exception":
Inspector.Debugger.resume();
// var callFrame = res.callFrames[0];
// var script = scriptWithId(callFrame.location.scriptId);
break;
// DOMBreakpoint
case "DOM":
Inspector.Debugger.resume();
if (res.data.type === "subtree-modified" && res.data.insertion === true) {
_insertTrace = res.callFrames;
}
break;
}
}
/** Initialize the agent */
function load() {
_urlToScript = {};
_idToScript = {};
_load = new $.Deferred();
Inspector.Debugger.enable();
Inspector.Debugger.setPauseOnExceptions("uncaught");
Inspector.on("DOMAgent.getDocument", _onGetDocument);
Inspector.on("Debugger.scriptParsed", _onScriptParsed);
Inspector.on("Debugger.scriptFailedToParse", _onScriptFailedToParse);
Inspector.on("Debugger.paused", _onPaused);
Inspector.on("DOM.childNodeInserted", _onChildNodeInserted);
return _load;
}
/** Clean up */
function unload() {
Inspector.off("DOMAgent.getDocument", _onGetDocument);
Inspector.off("Debugger.scriptParsed", _onScriptParsed);
Inspector.off("Debugger.scriptFailedToParse", _onScriptFailedToParse);
Inspector.off("Debugger.paused", _onPaused);
Inspector.off("DOM.childNodeInserted", _onChildNodeInserted);
}
// Export public functions
exports.scriptWithId = scriptWithId;
exports.scriptForURL = scriptForURL;
exports.load = load;
exports.unload = unload;
}
function ScriptAgent(require, exports, module) {
'use strict';
var Inspector = require("LiveDevelopment/Inspector/Inspector");
var DOMAgent = require("LiveDevelopment/Agents/DOMAgent");
var _load; // the load promise
var _urlToScript; // url -> script info
var _idToScript; // id -> script info
var _insertTrace; // the last recorded trace of a DOM insertion
/** Add a call stack trace to a node
* @param {integer} node id
* @param [{Debugger.CallFrame}] call stack
*/
function _addTraceToNode(nodeId, trace) {
var node = DOMAgent.nodeWithId(nodeId);
node.trace = trace;
}
/** Get the script information for a given url
* @param {string} url
*/
function scriptWithId(url) {
return _idToScript[url];
}
/** Get the script information for a given url
* @param {string} url
*/
function scriptForURL(url) {
return _urlToScript[url];
}
// DOMAgent Event: Document root loaded
function _onGetDocument(res) {
Inspector.DOMDebugger.setDOMBreakpoint(res.root.nodeId, "subtree-modified");
_load.resolve();
}
// WebInspector Event: DOM.childNodeInserted
function _onChildNodeInserted(res) {
// res = {parentNodeId, previousNodeId, node}
if (_insertTrace) {
var node = DOMAgent.nodeWithId(res.node.nodeId);
node.trace = _insertTrace;
_insertTrace = undefined;
}
}
// WebInspector Event: Debugger.scriptParsed
function _onScriptParsed(res) {
// res = {scriptId, url, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL}
_idToScript[res.scriptId] = res;
_urlToScript[res.url] = res;
}
// WebInspector Event: Debugger.scriptFailedToParse
function _onScriptFailedToParse(res) {
// res = {url, scriptSource, startLine, errorLine, errorMessage}
}
// WebInspector Event: Debugger.paused
function _onPaused(res) {
// res = {callFrames, reason, data}
switch (res.reason) {
// Exception
case "exception":
Inspector.Debugger.resume();
// var callFrame = res.callFrames[0];
// var script = scriptWithId(callFrame.location.scriptId);
break;
// DOMBreakpoint
case "DOM":
Inspector.Debugger.resume();
if (res.data.type === "subtree-modified" && res.data.insertion === true) {
_insertTrace = res.callFrames;
}
break;
}
}
/** Initialize the agent */
function load() {
_urlToScript = {};
_idToScript = {};
_load = new $.Deferred();
Inspector.Debugger.enable();
Inspector.Debugger.setPauseOnExceptions("uncaught");
Inspector.on("DOMAgent.getDocument", _onGetDocument);
Inspector.on("Debugger.scriptParsed", _onScriptParsed);
Inspector.on("Debugger.scriptFailedToParse", _onScriptFailedToParse);
Inspector.on("Debugger.paused", _onPaused);
Inspector.on("DOM.childNodeInserted", _onChildNodeInserted);
return _load;
}
/** Clean up */
function unload() {
Inspector.off("DOMAgent.getDocument", _onGetDocument);
Inspector.off("Debugger.scriptParsed", _onScriptParsed);
Inspector.off("Debugger.scriptFailedToParse", _onScriptFailedToParse);
Inspector.off("Debugger.paused", _onPaused);
Inspector.off("DOM.childNodeInserted", _onChildNodeInserted);
}
// Export public functions
exports.scriptWithId = scriptWithId;
exports.scriptForURL = scriptForURL;
exports.load = load;
exports.unload = unload;
}
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.