testplane
Version:
Tests framework based on mocha and wdio
129 lines • 6.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JSSelectivity = void 0;
const lodash_1 = require("lodash");
const node_url_1 = require("node:url");
const constants_1 = require("../../../error-snippets/constants");
const utils_1 = require("./utils");
const SOURCE_CODE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"];
const isSourceCodeFile = (sourceFileName) => {
return SOURCE_CODE_EXTENSIONS.some(ext => sourceFileName.endsWith(ext));
};
class JSSelectivity {
constructor(cdp, sessionId, sourceRoot = "") {
this._debuggerOnPausedFn = null;
this._debuggerOnScriptParsedFn = null;
this._scriptsSource = {};
this._scriptsSourceMap = {};
this._cdp = cdp;
this._sessionId = sessionId;
this._sourceRoot = sourceRoot;
}
_processScript({ scriptId, url, sourceMapURL }) {
if (!this._sessionId) {
return;
}
if (!url || !sourceMapURL) {
this._scriptsSource[scriptId] ||= null;
this._scriptsSourceMap[scriptId] ||= null;
return;
}
this._scriptsSource[scriptId] ||= this._cdp.debugger
.getScriptSource(this._sessionId, scriptId)
.then(res => res.scriptSource)
.catch((err) => err);
this._scriptsSourceMap[scriptId] ||= (0, utils_1.fetchTextWithBrowserFallback)((0, node_url_1.resolve)(url, sourceMapURL), this._cdp.runtime, this._sessionId);
}
async start() {
const debuggerOnPaused = (this._debuggerOnPausedFn = async () => {
return this._cdp.debugger.resume(this._sessionId).catch(() => { });
});
const debuggerOnScriptParsedFn = (this._debuggerOnScriptParsedFn = this._processScript.bind(this));
const sessionId = this._sessionId;
this._cdp.debugger.on("paused", debuggerOnPaused);
this._cdp.debugger.on("scriptParsed", debuggerOnScriptParsedFn);
await Promise.all([
this._cdp.target.setAutoAttach(sessionId, { autoAttach: true, waitForDebuggerOnStart: false }),
this._cdp.debugger.enable(sessionId),
this._cdp.profiler.enable(sessionId).then(() => this._cdp.profiler.startPreciseCoverage(sessionId, {
callCount: false,
detailed: false,
allowTriggeredUpdates: false,
})),
]);
}
/** @param drop only performs cleanup without providing actual deps. Should be "true" if test is failed */
async stop(drop) {
if (drop) {
this._debuggerOnPausedFn && this._cdp.debugger.off("paused", this._debuggerOnPausedFn);
this._debuggerOnScriptParsedFn && this._cdp.debugger.off("scriptParsed", this._debuggerOnScriptParsedFn);
return null;
}
const coverage = await this._cdp.profiler.takePreciseCoverage(this._sessionId);
const scriptsWithUrl = coverage.result.filter(script => script.url);
// If we haven't got "scriptParsed" event for the script, pull up source code + source map manually
scriptsWithUrl.forEach(({ scriptId, url }) => {
if (Object.hasOwn(this._scriptsSource, scriptId) && Object.hasOwn(this._scriptsSourceMap, scriptId)) {
return;
}
const scriptSourcePromise = this._cdp.debugger
.getScriptSource(this._sessionId, scriptId)
.then(res => res.scriptSource)
.catch((err) => err);
this._scriptsSource[scriptId] ||= scriptSourcePromise;
this._scriptsSourceMap[scriptId] ||= scriptSourcePromise.then(sourceCode => {
if (sourceCode instanceof Error) {
return sourceCode;
}
const sourceMapsStartIndex = sourceCode.lastIndexOf(constants_1.JS_SOURCE_MAP_URL_COMMENT);
const sourceMapsEndIndex = sourceCode.indexOf("\n", sourceMapsStartIndex);
if (sourceMapsStartIndex === -1) {
return new Error("Source maping url comment is missing");
}
const sourceMapURL = sourceMapsEndIndex === -1
? sourceCode.slice(sourceMapsStartIndex + constants_1.JS_SOURCE_MAP_URL_COMMENT.length)
: sourceCode.slice(sourceMapsStartIndex + constants_1.JS_SOURCE_MAP_URL_COMMENT.length, sourceMapsEndIndex);
return (0, utils_1.fetchTextWithBrowserFallback)((0, node_url_1.resolve)(url, sourceMapURL), this._cdp.runtime, this._sessionId);
});
});
const totalDependingSourceFiles = new Set();
const grouppedByScriptCoverage = (0, lodash_1.groupBy)(coverage.result, "scriptId");
const scriptIds = Object.keys(grouppedByScriptCoverage);
await Promise.all(scriptIds.map(async (scriptId) => {
// Every "scriptId" has only one uniq "url"
const url = grouppedByScriptCoverage[scriptId][0].url;
const [source, sourceMaps] = await Promise.all([
this._scriptsSource[scriptId],
this._scriptsSourceMap[scriptId],
]);
if (!source || !sourceMaps) {
return;
}
if (source instanceof Error) {
throw new Error(`JS Selectivity: Couldn't load source code at ${url}: ${source}`);
}
if (sourceMaps instanceof Error) {
throw new Error(`JS Selectivity: Couldn't load source maps of ${url}: ${sourceMaps}`);
}
const startOffsetsSet = new Set();
grouppedByScriptCoverage[scriptId].forEach(entry => {
entry.functions.forEach(fn => {
fn.ranges.forEach(range => {
startOffsetsSet.add(range.startOffset);
});
});
});
const dependingSourceFiles = await (0, utils_1.extractSourceFilesDeps)(source, sourceMaps, Array.from(startOffsetsSet), this._sourceRoot);
for (const sourceFile of dependingSourceFiles.values()) {
if (isSourceCodeFile(sourceFile)) {
totalDependingSourceFiles.add(sourceFile);
}
}
}));
this._debuggerOnPausedFn && this._cdp.debugger.off("paused", this._debuggerOnPausedFn);
this._debuggerOnScriptParsedFn && this._cdp.debugger.off("scriptParsed", this._debuggerOnScriptParsedFn);
return totalDependingSourceFiles;
}
}
exports.JSSelectivity = JSSelectivity;
//# sourceMappingURL=js-selectivity.js.map