UNPKG

testplane

Version:

Tests framework based on mocha and wdio

305 lines 13.2 kB
"use strict"; 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