UNPKG

testplane

Version:

Tests framework based on mocha and wdio

243 lines 13.3 kB
"use strict"; 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 fs_cache_1 = require("./fs-cache"); const debug_1 = require("./debug"); 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, mapSourceMapUrl) { this._debuggerOnScriptParsedFn = null; this._scriptsSource = {}; this._scriptsSourceMap = {}; this._scriptIdToSourceUrl = {}; this._scriptIdToSourceMapUrl = {}; this._coverageResult = []; this._cdp = cdp; this._sessionId = sessionId; this._sourceRoot = sourceRoot; this._mapSourceMapUrl = mapSourceMapUrl; } _processScript({ scriptId, url, sourceMapURL }, cdpSessionId) { if (!this._sessionId || cdpSessionId !== this._sessionId) { return; } this._scriptIdToSourceUrl[scriptId] ||= url; if (!url || !sourceMapURL || url.startsWith("chrome-error://")) { this._scriptsSource[scriptId] ||= null; this._scriptsSourceMap[scriptId] ||= null; return; } if (this._scriptsSource[scriptId] && this._scriptsSourceMap[scriptId]) { return; } let sourceMapResolvedUrl = (0, node_url_1.resolve)(url, sourceMapURL); const mapResult = this._mapSourceMapUrl ? this._mapSourceMapUrl({ type: "js", sourceUrl: url, sourceMapUrl: sourceMapResolvedUrl }) : true; if (!mapResult) { this._scriptsSource[scriptId] ||= null; this._scriptsSourceMap[scriptId] ||= null; return; } if (mapResult !== true) { this._scriptIdToSourceMapUrl[scriptId] = sourceMapResolvedUrl = mapResult; } this._scriptsSource[scriptId] ||= (0, fs_cache_1.hasCachedSelectivityFile)(fs_cache_1.CacheType.Asset, url).then(isCached => { return isCached ? true : this._cdp.debugger .getScriptSource(this._sessionId, scriptId) .then(res => res.scriptSource) .then(data => (0, fs_cache_1.setCachedSelectivityFile)(fs_cache_1.CacheType.Asset, url, data) .then(() => true) .catch(err => { (0, debug_1.debugSelectivity)(`Couldn't offload asset from "${url}" to fs-cache: %O`, err); return data; })) .catch((err) => err); }); // Embedded source maps are not cached on file system because of their large cache key if ((0, utils_1.isDataProtocol)(sourceMapResolvedUrl)) { this._scriptIdToSourceMapUrl[scriptId] = null; this._scriptsSourceMap[scriptId] ||= (0, utils_1.fetchTextWithBrowserFallback)(sourceMapResolvedUrl, this._cdp.runtime, this._sessionId).catch((err) => err); } else { this._scriptIdToSourceMapUrl[scriptId] = sourceMapResolvedUrl; this._scriptsSourceMap[scriptId] ||= (0, fs_cache_1.hasCachedSelectivityFile)(fs_cache_1.CacheType.Asset, sourceMapResolvedUrl).then(isCached => { return isCached ? true : (0, utils_1.fetchTextWithBrowserFallback)(sourceMapResolvedUrl, this._cdp.runtime, this._sessionId) .then(data => (0, fs_cache_1.setCachedSelectivityFile)(fs_cache_1.CacheType.Asset, sourceMapResolvedUrl, data) .then(() => true) .catch(err => { (0, debug_1.debugSelectivity)(`Couldn't offload asset from "${sourceMapResolvedUrl}" to fs-cache: %O`, err); return data; })) .catch((err) => err); }); } } // If we haven't got "scriptParsed" event for the script, pull up source code + source map manually _ensureScriptsAreLoading(coverage) { coverage.forEach(({ scriptId, url }) => { const fixedUrl = url || this._scriptIdToSourceUrl[scriptId]; const wasProcessed = Object.hasOwn(this._scriptsSource, scriptId) && Object.hasOwn(this._scriptsSourceMap, scriptId); const isAnonymous = !fixedUrl; const urlWasCorrected = url && url !== this._scriptIdToSourceUrl[scriptId]; const shouldRecalculateSource = Boolean(urlWasCorrected && this._mapSourceMapUrl); if ((wasProcessed && !shouldRecalculateSource) || isAnonymous) { return; } // Not dropping sources to fs the end of test (when "stop" is called) because we use it immediately const scriptSourcePromise = (async () => { const currentValue = await this._scriptsSource[scriptId]; const cacheResolvedValue = (0, utils_1.isCachedOnFs)(currentValue) ? await (0, fs_cache_1.getCachedSelectivityFile)(fs_cache_1.CacheType.Asset, fixedUrl) : currentValue; if (cacheResolvedValue && typeof cacheResolvedValue === "string") { return cacheResolvedValue; } return this._cdp.debugger .getScriptSource(this._sessionId, scriptId) .then(({ scriptSource }) => { (0, fs_cache_1.setCachedSelectivityFile)(fs_cache_1.CacheType.Asset, fixedUrl, scriptSource).catch(() => { }); return scriptSource; }) .catch((err) => err); })(); this._scriptIdToSourceUrl[scriptId] ||= url; this._scriptsSource[scriptId] ||= scriptSourcePromise; if (!this._scriptsSourceMap[scriptId] || shouldRecalculateSource) { this._scriptsSourceMap[scriptId] = scriptSourcePromise.then(async (sourceCode) => { if (sourceCode instanceof Error) { return sourceCode; } const sourceMapsStartIndex = sourceCode.lastIndexOf(constants_1.JS_SOURCE_MAP_URL_COMMENT); const sourceMapsEndIndex = sourceCode.indexOf("\n", sourceMapsStartIndex); // Source maps are not generated for this source file if (sourceMapsStartIndex === -1) { return null; } 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); let resolvedSourceMapUrl = (0, node_url_1.resolve)(fixedUrl, sourceMapURL); const mappedResult = this._mapSourceMapUrl ? this._mapSourceMapUrl({ type: "js", sourceUrl: fixedUrl, sourceMapUrl: resolvedSourceMapUrl }) : true; if (!mappedResult) { this._scriptsSource[scriptId] = null; return null; } if (mappedResult !== true) { this._scriptIdToSourceMapUrl[scriptId] = resolvedSourceMapUrl = mappedResult; } else { this._scriptIdToSourceMapUrl[scriptId] ||= resolvedSourceMapUrl; } if ((0, utils_1.isDataProtocol)(resolvedSourceMapUrl)) { return (0, utils_1.fetchTextWithBrowserFallback)(resolvedSourceMapUrl, this._cdp.runtime, this._sessionId).catch((err) => err); } try { const cachedSourceMaps = await (0, fs_cache_1.getCachedSelectivityFile)(fs_cache_1.CacheType.Asset, resolvedSourceMapUrl); if (cachedSourceMaps) { return cachedSourceMaps; } const sourceMap = await (0, utils_1.fetchTextWithBrowserFallback)(resolvedSourceMapUrl, this._cdp.runtime, this._sessionId); (0, fs_cache_1.setCachedSelectivityFile)(fs_cache_1.CacheType.Asset, resolvedSourceMapUrl, sourceMap).catch(() => { }); return sourceMap; } catch (err) { return err; } }); } }); } async _waitForLoadingScripts() { await Promise.all([ Promise.allSettled(Object.values(this._scriptsSource)), Promise.allSettled(Object.values(this._scriptsSourceMap)), ]); } async start() { const debuggerOnScriptParsedFn = (this._debuggerOnScriptParsedFn = this._processScript.bind(this)); const sessionId = this._sessionId; this._cdp.debugger.on("scriptParsed", debuggerOnScriptParsedFn); await this._cdp.profiler.startPreciseCoverage(sessionId, { callCount: false, detailed: false, allowTriggeredUpdates: false, }); } async takeCoverageSnapshot() { const coveragePart = await this._cdp.profiler.takePreciseCoverage(this._sessionId); this._ensureScriptsAreLoading(coveragePart.result); await this._waitForLoadingScripts(); this._coverageResult.push(...coveragePart.result); } /** @param drop only performs cleanup without providing actual deps. Should be "true" if test is failed */ async stop(drop) { try { if (drop) { return null; } const coverageLastPart = await this._cdp.profiler.takePreciseCoverage(this._sessionId); const coverageScripts = [...this._coverageResult, ...coverageLastPart.result]; this._ensureScriptsAreLoading(coverageLastPart.result); const totalDependingSourceFiles = new Set(); const grouppedByScriptCoverage = (0, lodash_1.groupBy)(coverageScripts, "scriptId"); const scriptIds = Object.keys(grouppedByScriptCoverage); await Promise.all(scriptIds.map(async (scriptId) => { const [source, sourceMaps] = await Promise.all([ this._scriptsSource[scriptId], this._scriptsSourceMap[scriptId], ]); const sourceMapUrl = this._scriptIdToSourceMapUrl[scriptId]; // Every "scriptId" has only one uniq "url" const sourceUrl = this._scriptIdToSourceUrl[scriptId] || grouppedByScriptCoverage[scriptId][0].url; // Function was called, but source maps were not generated for the file // Or its anonymous script, without source url if (!source || !sourceMaps || !sourceUrl) { return; } if (source instanceof Error) { throw new Error([`JS Selectivity: Couldn't load source code from ${sourceUrl}:`, String(source)].join("\n")); } if (sourceMaps instanceof Error) { throw new Error([`JS Selectivity: Couldn't load source maps from ${sourceMapUrl}`, String(sourceMaps)].join("\n")); } if ((0, utils_1.isCachedOnFs)(sourceMaps) && !sourceMapUrl) { throw new Error("Assertation failed: souce map url has to present if source maps are fs-cached"); } const [sourceString, sourceMapsString] = await Promise.all([ (0, utils_1.isCachedOnFs)(source) ? (0, fs_cache_1.getCachedSelectivityFile)(fs_cache_1.CacheType.Asset, sourceUrl) : source, (0, utils_1.isCachedOnFs)(sourceMaps) ? (0, fs_cache_1.getCachedSelectivityFile)(fs_cache_1.CacheType.Asset, sourceMapUrl) : sourceMaps, ]); if (!sourceString || !sourceMapsString) { throw new Error(`JS Selectivity: fs-cache is broken for ${sourceUrl}`); } const parsedSourceMapRanges = await (0, utils_1.parseSourceMapRanges)(sourceString, sourceMapsString, this._sourceRoot); const dependingSourceFiles = (0, utils_1.extractSourceFilesDeps)(parsedSourceMapRanges, grouppedByScriptCoverage[scriptId], isSourceCodeFile); for (const sourceFile of dependingSourceFiles.values()) { totalDependingSourceFiles.add(sourceFile); } })); return totalDependingSourceFiles; } finally { this._debuggerOnScriptParsedFn && this._cdp.debugger.off("scriptParsed", this._debuggerOnScriptParsedFn); } } } exports.JSSelectivity = JSSelectivity; //# sourceMappingURL=js-selectivity.js.map