testplane
Version:
Tests framework based on mocha and wdio
305 lines • 13.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readTestDependencies = exports.getTestDependenciesPath = exports.getSelectivityTestsPath = exports.readHashFileContents = exports.getSelectivityHashesPath = exports.shallowSortObject = exports.mergeSourceDependencies = exports.transformSourceDependencies = exports.hasProtocol = exports.extractSourceFilesDeps = exports.patchSourceMapSources = exports.fetchTextWithBrowserFallback = void 0;
const lodash_1 = require("lodash");
const source_map_js_1 = require("source-map-js");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const url_1 = require("url");
const logger = __importStar(require("../../../utils/logger"));
const fs_2 = require("../../../utils/fs");
const constants_1 = require("./constants");
const json_utils_1 = require("./json-utils");
/**
* Tries to fetch text by url from node.js, then falls back to "fetch" from browser, if node.js fetch fails
* @param url text url
* @param runtime CDP runtime domain
* @param sessionId CDP session id
* @returns text
*/
const fetchTextWithBrowserFallback = async (url, runtime, sessionId) => {
const isSourceMapEmbedded = new url_1.URL(url).protocol === "data:";
if (isSourceMapEmbedded) {
// With "data" protocol it just decodes embedded source maps without actual network requests
// So we can do it directly from node.js
return fetch(url)
.then(r => r.text())
.catch((err) => err);
}
// At first, trying to fetch sourceMaps directly from the node.js
// Then falling back to do it via browser (if Testplane has no direct network access to it, for example)
try {
return await fetch(url).then(r => r.text());
}
catch {
return runtime
.evaluate(sessionId, {
expression: `fetch("${url.replaceAll('"', '\\"')}").then(r => r.text())`,
awaitPromise: true,
returnByValue: true,
})
.then(r => r.result.value)
.catch((err) => err);
}
};
exports.fetchTextWithBrowserFallback = fetchTextWithBrowserFallback;
const isWebpackProtocol = (posixRelativeSourceFilePath) => {
return posixRelativeSourceFilePath.startsWith(constants_1.WEBPACK_PROTOCOL);
};
/**
* Replaces "webpack://" protocol with source path because "source-map" doesn't work well with protocol paths
* @param sourceMap Raw source maps in https://tc39.es/ecma426/ format
* @param sourceRoot Source root
*/
const patchSourceMapSources = (sourceMap, sourceRoot) => {
sourceMap.sourceRoot = sourceRoot || sourceMap.sourceRoot;
for (let i = 0; i < sourceMap.sources.length; i++) {
if (isWebpackProtocol(sourceMap.sources[i])) {
sourceMap.sources[i] = sourceMap.sources[i].slice(constants_1.WEBPACK_PROTOCOL.length);
}
}
return sourceMap;
};
exports.patchSourceMapSources = patchSourceMapSources;
/**
* Given compiled code, its source map, and the executed offsets
* It returns the original source files touched
* Useful for turning coverage ranges into real TS/JS module dependencies
* @param source Compiled source code
* @param sourceMaps Source maps JSON string
* @param startOffsets Executed start offsets (v8 format)
* @param sourceRoot Source root
*/
const extractSourceFilesDeps = async (source, sourceMaps, startOffsets, sourceRoot) => {
const dependantSourceFiles = new Set();
const sourceMapsParsed = (0, exports.patchSourceMapSources)(JSON.parse(sourceMaps), sourceRoot);
const consumer = new source_map_js_1.SourceMapConsumer(sourceMapsParsed);
let sourceOffset = source.indexOf("\n");
const offsetToLine = [0];
while (sourceOffset !== -1) {
offsetToLine.push(++sourceOffset);
sourceOffset = source.indexOf("\n", sourceOffset);
}
for (const startOffset of startOffsets) {
let line = (0, lodash_1.sortedIndex)(offsetToLine, startOffset);
if (startOffset < offsetToLine[line]) {
line--;
}
const column = startOffset - offsetToLine[line];
const position = consumer.originalPositionFor({ line: line + 1, column });
if (position.source) {
dependantSourceFiles.add(position.source);
}
}
return dependantSourceFiles;
};
exports.extractSourceFilesDeps = extractSourceFilesDeps;
/**
* @returns True, if fileUrlLikePath has some kind of protocol ("data://", "webpack://", "turbopack://", "file://")...
*/
const hasProtocol = (fileUrlLikePath) => {
if (!fileUrlLikePath.includes("://")) {
return false;
}
try {
return Boolean(new url_1.URL(fileUrlLikePath).protocol);
}
catch {
return false;
}
};
exports.hasProtocol = hasProtocol;
const getProtocol = (fileUrlLikePath) => {
if (!fileUrlLikePath.includes("://")) {
return null;
}
try {
return new url_1.URL(fileUrlLikePath).protocol;
}
catch {
return null;
}
};
const ensurePosixRelativeDependencyPathExists = (0, lodash_1.memoize)((posixRelativePath) => {
const relativePath = posixRelativePath.replaceAll(path_1.default.posix.sep, path_1.default.sep);
if (fs_1.default.existsSync(relativePath)) {
return;
}
throw new Error([
`Selectivity: Couldn't find "${relativePath}", which is test's dependency`,
"Please ensure 'sources' of generated source maps contain valid paths to existing files",
"Configuring 'sourceRoot' in Testplane selectivity config also might help",
].join("\n"));
});
const warnUnsupportedProtocol = (0, lodash_1.memoize)((protocol, dependency) => {
logger.warn(`Selectivity: Ignoring dependencies of unsupported protocol "${protocol}" (example: "${dependency}")`);
});
/**
* @param cssDependencies set of css dependenciy URI's
* @param jsDependencies set of js dependenciy URI's
* @returns sorted uniq arrays of relative paths
*/
const transformSourceDependencies = (cssDependencies, jsDependencies, mapDependencyPathFn) => {
const nodeModulesLabel = "node_modules/";
const cssSet = new Set();
const jsSet = new Set();
const modulesSet = new Set();
const classifyDependency = (dependency, typedResultSet) => {
dependency = decodeURIComponent((0, fs_2.softFileURLToPath)(dependency));
const protocol = getProtocol(dependency);
if (protocol) {
warnUnsupportedProtocol(protocol, dependency);
return;
}
const initialDependencyRelativePath = path_1.default.posix.relative(path_1.default.posix.resolve(), path_1.default.posix.resolve(dependency));
const dependencyRelativePath = mapDependencyPathFn
? mapDependencyPathFn(initialDependencyRelativePath)
: initialDependencyRelativePath;
if (!dependencyRelativePath) {
return;
}
const nodeModulesLabelPos = dependencyRelativePath.indexOf(nodeModulesLabel);
if (nodeModulesLabelPos === -1) {
ensurePosixRelativeDependencyPathExists(dependencyRelativePath);
typedResultSet.add(dependencyRelativePath);
return;
}
const modulePos = nodeModulesLabelPos + nodeModulesLabel.length;
const isScopedDependency = dependencyRelativePath[modulePos] === "@";
let moduleEndPos;
if (isScopedDependency) {
const scopeEndPos = dependencyRelativePath.indexOf("/", modulePos + 1);
moduleEndPos = scopeEndPos === -1 ? -1 : dependencyRelativePath.indexOf("/", scopeEndPos + 1);
}
else {
moduleEndPos = dependencyRelativePath.indexOf("/", modulePos + 1);
}
if (moduleEndPos === -1) {
ensurePosixRelativeDependencyPathExists(dependencyRelativePath);
typedResultSet.add(dependencyRelativePath);
}
else {
const modulePath = dependencyRelativePath.slice(0, moduleEndPos);
ensurePosixRelativeDependencyPathExists(modulePath);
modulesSet.add(modulePath);
}
};
if (cssDependencies) {
for (const cssDependency of cssDependencies.values()) {
classifyDependency(cssDependency, cssSet);
}
}
if (jsDependencies) {
for (const jsDependency of jsDependencies.values()) {
classifyDependency(jsDependency, jsSet);
}
}
const cmpStr = (a, b) => a.localeCompare(b);
return {
css: Array.from(cssSet).sort(cmpStr),
js: Array.from(jsSet).sort(cmpStr),
modules: Array.from(modulesSet).sort(cmpStr),
};
};
exports.transformSourceDependencies = transformSourceDependencies;
/** Merges two sorted deps array into one with uniq values */
const mergeSourceDependencies = (a, b) => {
const result = { css: [], js: [], modules: [] };
for (const depType of Object.keys(result)) {
let aInd = 0, bInd = 0;
while (aInd < a[depType].length || bInd < b[depType].length) {
let compareResult;
if (bInd >= b[depType].length) {
compareResult = -1;
}
else if (aInd >= a[depType].length) {
compareResult = 1;
}
else {
compareResult = a[depType][aInd].localeCompare(b[depType][bInd]);
}
if (compareResult < 0) {
result[depType].push(a[depType][aInd]);
do {
aInd++;
} while (a[depType][aInd] === a[depType][aInd - 1]);
}
else if (compareResult > 0) {
result[depType].push(b[depType][bInd]);
do {
bInd++;
} while (b[depType][bInd] === b[depType][bInd - 1]);
}
else {
result[depType].push(a[depType][aInd]);
do {
aInd++;
} while (a[depType][aInd] === a[depType][aInd - 1]);
do {
bInd++;
} while (b[depType][bInd] === b[depType][bInd - 1]);
}
}
}
return result;
};
exports.mergeSourceDependencies = mergeSourceDependencies;
// Ensures file consistency
const shallowSortObject = (obj) => {
const testBrowsers = Object.keys(obj).sort((a, b) => a.localeCompare(b));
for (const testBrowser of testBrowsers) {
const testBrowserDeps = obj[testBrowser];
delete obj[testBrowser];
obj[testBrowser] = testBrowserDeps;
}
};
exports.shallowSortObject = shallowSortObject;
const getSelectivityHashesPath = (testDependenciesPath) => path_1.default.join(testDependenciesPath, "hashes.json");
exports.getSelectivityHashesPath = getSelectivityHashesPath;
const readHashFileContents = (selectivityHashesPath, compression) => (0, json_utils_1.readJsonWithCompression)(selectivityHashesPath, compression, {
defaultValue: { files: {}, modules: {}, patterns: {} },
})
.catch(() => ({ files: {}, modules: {}, patterns: {} }))
.then(res => {
res.files ||= {};
res.modules ||= {};
res.patterns ||= {};
return res;
});
exports.readHashFileContents = readHashFileContents;
const getSelectivityTestsPath = (testDependenciesPath) => path_1.default.join(testDependenciesPath, "tests");
exports.getSelectivityTestsPath = getSelectivityTestsPath;
const getTestDependenciesPath = (selectivityTestsPath, test) => path_1.default.join(selectivityTestsPath, `${test.id}.json`);
exports.getTestDependenciesPath = getTestDependenciesPath;
/** @returns `Promise<Record<BrowserID, Record<DepType, NormalizedDependencies>>>` */
const readTestDependencies = (selectivityTestsPath, test, compression) => (0, json_utils_1.readJsonWithCompression)((0, exports.getTestDependenciesPath)(selectivityTestsPath, test), compression, {
defaultValue: {},
}).catch(() => ({}));
exports.readTestDependencies = readTestDependencies;
//# sourceMappingURL=utils.js.map