testplane
Version:
Tests framework based on mocha and wdio
220 lines • 11.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startSelectivity = exports.clearUnusedSelectivityDumps = exports.updateSelectivityHashes = void 0;
const node_path_1 = __importDefault(require("node:path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const css_selectivity_1 = require("./css-selectivity");
const js_selectivity_1 = require("./js-selectivity");
const test_dependencies_writer_1 = require("./test-dependencies-writer");
const utils_1 = require("./utils");
const hash_writer_1 = require("./hash-writer");
const types_1 = require("./types");
const testplane_selectivity_1 = require("./testplane-selectivity");
const hash_reader_1 = require("./hash-reader");
const events_1 = require("../../../events");
const modes_1 = require("./modes");
const debug_1 = require("./debug");
const used_dumps_tracker_1 = require("./used-dumps-tracker");
/**
* Called at the end of testplane run
* Not using "Promise.all" here because all hashes are already calculated and cached at the start
*/
const updateSelectivityHashes = async (config, isRunFailed) => {
const browserIds = config.getBrowserIds();
const processedRoots = new Set();
for (const browserId of browserIds) {
const browserConfig = config.forBrowser(browserId);
const { enabled, testDependenciesPath, compression, disableSelectivityPatterns, saveIncompleteDumpOnFail } = browserConfig.selectivity;
const rootKey = `${testDependenciesPath}#${compression}`;
if ((isRunFailed || browserConfig.lastFailed.only) && !saveIncompleteDumpOnFail) {
continue;
}
if (!(0, modes_1.selectivityShouldWrite)(enabled) || processedRoots.has(rootKey)) {
continue;
}
const hashReader = (0, hash_reader_1.getHashReader)(testDependenciesPath, compression);
const hashWriter = (0, hash_writer_1.getHashWriter)(testDependenciesPath, compression);
for (const pattern of disableSelectivityPatterns) {
const isChanged = await hashReader.patternHasChanged(pattern);
if (isChanged) {
hashWriter.addPatternDependencyHash(pattern);
}
}
try {
await hashWriter.save();
}
catch (cause) {
throw new Error(["Selectivity: couldn't save test dependencies hash", String(cause)].join("\n"));
}
processedRoots.add(rootKey);
}
};
exports.updateSelectivityHashes = updateSelectivityHashes;
/**
* Called at the end of testplane run
*/
const clearUnusedSelectivityDumps = async (config, isRunFailed) => {
if (process.env.TESTPLANE_SELECTIVITY_CLEAR_UNUSED_DUMPS === "false") {
return;
}
const usedDumpsTracker = (0, used_dumps_tracker_1.getUsedDumpsTracker)();
const browserIds = config.getBrowserIds();
const selectivityRoots = [];
for (const browserId of browserIds) {
const browserConfig = config.forBrowser(browserId);
const { enabled, testDependenciesPath, saveIncompleteDumpOnFail } = browserConfig.selectivity;
if ((isRunFailed && !saveIncompleteDumpOnFail) || browserConfig.lastFailed.only) {
continue;
}
else if ((0, modes_1.selectivityShouldWrite)(enabled) && !selectivityRoots.includes(testDependenciesPath)) {
selectivityRoots.push(testDependenciesPath);
}
}
let filesTotal = 0;
let filesDeleted = 0;
// eslint-disable-next-line no-bitwise
const rwMode = fs_extra_1.default.constants.R_OK | fs_extra_1.default.constants.W_OK;
await Promise.all(selectivityRoots.map(async (selectivityRoot) => {
if (!usedDumpsTracker.usedDumpsFor(selectivityRoot)) {
return;
}
const testsPath = (0, utils_1.getSelectivityTestsPath)(selectivityRoot);
const accessError = await fs_extra_1.default.access(testsPath, rwMode).catch((err) => err);
if (accessError) {
if (!("code" in accessError && accessError.code === "ENOENT")) {
(0, debug_1.debugSelectivity)(`Couldn't access "${testsPath}" to clear stale files: %O`, accessError);
}
return;
}
const testsFileNames = await fs_extra_1.default.readdir(testsPath);
filesTotal += testsFileNames.length;
for (const testFileName of testsFileNames) {
const extensionPosition = testFileName.indexOf(".json");
// If the file does not look like selectivity test dependencies, skip it
if (extensionPosition === -1) {
continue;
}
const dumpId = testFileName.slice(0, extensionPosition);
if (!usedDumpsTracker.wasUsed(dumpId, selectivityRoot)) {
const filePath = node_path_1.default.join(testsPath, testFileName);
await fs_extra_1.default
.unlink(filePath)
.then(() => {
filesDeleted++;
})
.catch(err => {
(0, debug_1.debugSelectivity)(`Couldn't remove stale file "${filePath}": %O`, err);
});
}
}
}));
if (filesDeleted) {
(0, debug_1.debugSelectivity)(`Out of ${filesTotal} dump files, ${filesDeleted} were considered as outdated and deleted`);
}
};
exports.clearUnusedSelectivityDumps = clearUnusedSelectivityDumps;
const testplaneCoverageBreakScriptName = "__testplane_cdp_coverage_snapshot_pause";
const scriptToEvaluateOnNewDocument = `window.addEventListener("beforeunload", function ${testplaneCoverageBreakScriptName}() {debugger;});`;
const startSelectivity = async (browser) => {
const { enabled, compression, sourceRoot, testDependenciesPath, mapDependencyRelativePath, mapSourceMapUrl } = browser.config.selectivity;
if (!(0, modes_1.selectivityShouldWrite)(enabled) || !browser.publicAPI.isChromium) {
return () => Promise.resolve();
}
if (compression === types_1.Compression.ZSTD && !process.versions.zstd) {
throw new Error("Selectivity: Compression 'zstd' is not supported in your node version. Please, upgrade the node version to 22");
}
if (!browser.cdp) {
throw new Error("Selectivity: Devtools connection is not established, couldn't record selectivity without it");
}
const cdp = browser.cdp;
const handle = await browser.publicAPI.getWindowHandle();
const { targetInfos } = await cdp.target.getTargets();
const cdpTargetId = targetInfos.find(t => handle.includes(t.targetId))?.targetId;
if (!cdpTargetId) {
throw new Error([
"Selectivity: Couldn't find current page;",
`\n\t- webdriver handle: ${handle}`,
`\n\t- cdp targets: ${targetInfos.map(t => `"${t.targetId}"`).join(", ")}`,
].join(""));
}
const wdSessionId = browser.sessionId;
const cdpSessionId = await cdp.target.attachToTarget(cdpTargetId).then(r => r.sessionId);
const cssSelectivity = new css_selectivity_1.CSSSelectivity(cdp, cdpSessionId, wdSessionId, sourceRoot, mapSourceMapUrl);
const jsSelectivity = new js_selectivity_1.JSSelectivity(cdp, cdpSessionId, sourceRoot, mapSourceMapUrl);
await Promise.all([
cdp.dom.enable(cdpSessionId).then(() => cdp.css.enable(cdpSessionId)),
cdp.target.setAutoAttach(cdpSessionId, { autoAttach: true, waitForDebuggerOnStart: false }),
cdp.debugger.enable(cdpSessionId),
cdp.page.enable(cdpSessionId),
cdp.profiler.enable(cdpSessionId),
]);
await Promise.allSettled([cssSelectivity.start(), jsSelectivity.start()]).then(async ([css, js]) => {
if (css.status === "rejected" || js.status === "rejected") {
await Promise.all([cssSelectivity.stop(true), jsSelectivity.stop(true)]);
const originalError = css.status === "rejected" ? css.reason : js.status === "rejected" ? js.reason : "unknown reason";
throw new Error(["Selectivity: Couldn't start selectivity:", String(originalError)].join("\n"));
}
});
let pageSwitchPromise = Promise.resolve();
let isSelectivityStopped = false;
const debuggerPausedFn = ({ callFrames }, eventCdpSessionId) => {
if (eventCdpSessionId !== cdpSessionId) {
return;
}
if (callFrames[0]?.functionName !== testplaneCoverageBreakScriptName || isSelectivityStopped) {
cdp.debugger.resume(cdpSessionId).catch(() => { });
return;
}
pageSwitchPromise = pageSwitchPromise.finally(() => Promise.all([cssSelectivity.takeCoverageSnapshot(), jsSelectivity.takeCoverageSnapshot()])
.catch(err => {
console.error("Selectivity: couldn't take snapshot while navigating:", err);
})
.then(() => {
cdp.debugger.resume(cdpSessionId).catch(() => { });
}));
};
cdp.debugger.on("paused", debuggerPausedFn);
await cdp.page.addScriptToEvaluateOnNewDocument(cdpSessionId, { source: scriptToEvaluateOnNewDocument });
/** @param drop only performs cleanup without writing anything. Should be "true" if test is failed */
return async function stopSelectivity(test, drop) {
isSelectivityStopped = true;
await pageSwitchPromise;
const [cssDependenciesPromise, jsDependenciesPromise] = await Promise.allSettled([
cssSelectivity.stop(drop),
jsSelectivity.stop(drop),
]);
cdp.debugger.off("paused", debuggerPausedFn);
cdp.target.detachFromTarget(cdpSessionId).catch(() => { });
if (jsDependenciesPromise.status === "rejected") {
throw jsDependenciesPromise.reason;
}
if (cssDependenciesPromise.status === "rejected") {
throw cssDependenciesPromise.reason;
}
const cssDependencies = cssDependenciesPromise.value;
const jsDependencies = jsDependenciesPromise.value;
if (drop || (!cssDependencies?.size && !jsDependencies?.size)) {
return;
}
const testDependencyWriter = (0, test_dependencies_writer_1.getTestDependenciesWriter)(testDependenciesPath, compression);
const browserDeps = (0, utils_1.transformSourceDependencies)({ css: cssDependencies, js: jsDependencies, png: null }, mapDependencyRelativePath, "browser");
const testplaneDeps = (0, utils_1.transformSourceDependencies)({ css: null, js: (0, testplane_selectivity_1.getCollectedTestplaneJsDependencies)(), png: (0, testplane_selectivity_1.getCollectedTestplanePngDependencies)() }, mapDependencyRelativePath, "testplane");
process.send?.({
event: events_1.MasterEvents.TEST_DEPENDENCIES,
context: {
testDependenciesPath,
compression,
testId: test.id,
fullTitle: test.fullTitle(),
browserId: test.browserId,
},
data: (0, utils_1.mergeSourceDependencies)(browserDeps, testplaneDeps),
});
await testDependencyWriter.saveFor(test, browserDeps, testplaneDeps);
};
};
exports.startSelectivity = startSelectivity;
//# sourceMappingURL=index.js.map