html-reporter
Version:
Html-reporter and GUI for viewing and managing results of a tests run. Currently supports Testplane and Hermione.
319 lines • 16 kB
JavaScript
"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.ToolRunner = void 0;
const node_path_1 = __importDefault(require("node:path"));
const node_os_1 = __importDefault(require("node:os"));
const chalk_1 = __importDefault(require("chalk"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const lodash_1 = __importDefault(require("lodash"));
const looks_same_1 = __importDefault(require("looks-same"));
const p_queue_1 = __importDefault(require("p-queue"));
const gui_1 = require("../../report-builder/gui");
const event_source_1 = require("../event-source");
const sqlite_client_1 = require("../../sqlite-client");
const cache_1 = require("../../cache");
const images_info_saver_1 = require("../../images-info-saver");
const image_store_1 = require("../../image-store");
const reporterHelper = __importStar(require("../../reporter-helpers"));
const common_utils_1 = require("../../common-utils");
const utils_1 = require("./utils");
const server_utils_1 = require("../../server-utils");
const server_1 = require("../../db-utils/server");
const constants_1 = require("../../constants");
class ToolRunner {
static create(args) {
return new this(args);
}
constructor({ paths, toolAdapter, cli }) {
this._testFiles = [].concat(paths);
this._toolAdapter = toolAdapter;
this._tree = null;
this._collection = null;
this._globalOpts = cli.tool;
this._guiOpts = cli.options;
this._reporterConfig = this._toolAdapter.reporterConfig;
this._reportPath = this._reporterConfig.path;
this._eventSource = new event_source_1.EventSource();
this._reportBuilder = null;
this._testAdapters = {};
this._expectedImagesCache = new cache_1.Cache(server_utils_1.getExpectedCacheKey);
}
get config() {
return this._toolAdapter.config;
}
get tree() {
if (!this._tree) {
return null;
}
const features = [];
if (this._toolAdapter.toolName === constants_1.ToolName.Testplane && (0, server_utils_1.getTimeTravelModeEnumSafe)()) {
features.push(constants_1.TimeTravelFeature);
}
return Object.assign({}, this._tree, {
browserFeatures: this._toolAdapter.browserFeatures,
features
});
}
async initialize() {
await (0, utils_1.mergeDatabasesForReuse)(this._reportPath);
await (0, utils_1.prepareLocalDatabase)(this._reportPath);
const dbClient = await sqlite_client_1.SqliteClient.create({ htmlReporter: this._toolAdapter.htmlReporter, reportPath: this._reportPath, reuse: true });
const imageStore = new image_store_1.SqliteImageStore(dbClient);
const imagesInfoSaver = new images_info_saver_1.ImagesInfoSaver({
imageFileSaver: this._toolAdapter.htmlReporter.imagesSaver,
expectedPathsCache: this._expectedImagesCache,
imageStore,
reportPath: this._toolAdapter.htmlReporter.config.path
});
this._reportBuilder = gui_1.GuiReportBuilder.create({
htmlReporter: this._toolAdapter.htmlReporter,
reporterConfig: this._reporterConfig,
dbClient,
imagesInfoSaver
});
this._toolAdapter.handleTestResults(this._reportBuilder, this._eventSource);
this._collection = await this._readTests();
this._toolAdapter.htmlReporter.emit(constants_1.PluginEvents.DATABASE_CREATED, dbClient.getRawConnection());
await this._reportBuilder.saveStaticFiles();
this._reportBuilder.setApiValues(this._toolAdapter.htmlReporter.values);
await this._handleRunnableCollection();
}
async _readTests() {
return this._toolAdapter.readTests(this._testFiles, this._globalOpts);
}
_ensureReportBuilder() {
if (!this._reportBuilder) {
throw new Error('ToolRunner has to be initialized before usage');
}
return this._reportBuilder;
}
_ensureTestCollection() {
if (!this._collection) {
throw new Error('ToolRunner has to be initialized before usage');
}
return this._collection;
}
async finalize() {
return this._ensureReportBuilder().finalize();
}
addClient(connection) {
this._eventSource.addConnection(connection);
}
sendClientEvent(event, data) {
this._eventSource.emit(event, data);
}
getTestsDataToUpdateRefs(imageIds) {
return this._ensureReportBuilder().getTestsDataToUpdateRefs(imageIds);
}
getImageDataToFindEqualDiffs(imageIds) {
const [selectedImage, ...comparedImages] = this._ensureReportBuilder().getImageDataToFindEqualDiffs(imageIds);
const imagesWithEqualBrowserName = comparedImages.filter((image) => image.browserName === selectedImage.browserName);
const imagesWithEqualDiffSizes = (0, utils_1.filterByEqualDiffSizes)(imagesWithEqualBrowserName, selectedImage.diffClusters);
return lodash_1.default.isEmpty(imagesWithEqualDiffSizes) ? [] : [selectedImage].concat(imagesWithEqualDiffSizes);
}
async updateReferenceImage(tests) {
const reportBuilder = this._ensureReportBuilder();
return Promise.all(tests.map(async (test) => {
const testAdapter = this._getTestAdapterById(test);
const assertViewResults = this._prepareAssertViewResults(test.imagesInfo, testAdapter);
const { sessionId, url } = test.metaInfo;
const latestAttempt = reportBuilder.getLatestAttempt({
fullName: testAdapter.fullName,
browserId: testAdapter.browserId
});
const latestResult = testAdapter.createTestResult({
assertViewResults,
status: constants_1.UPDATED,
attempt: latestAttempt,
error: test.error,
sessionId,
meta: { url },
duration: 0
});
const estimatedStatus = reportBuilder.getUpdatedReferenceTestStatus(latestResult);
const formattedResultWithoutAttempt = testAdapter.createTestResult({
assertViewResults,
status: constants_1.UPDATED,
attempt: constants_1.UNKNOWN_ATTEMPT,
error: test.error,
sessionId,
meta: { url },
duration: 0
});
const formattedResult = reportBuilder.provideAttempt(formattedResultWithoutAttempt);
const formattedResultUpdated = await reporterHelper.updateReferenceImages(formattedResult, this._reportPath, this._handleReferenceUpdate.bind(this));
await reportBuilder.addTestResult(formattedResultUpdated, { status: estimatedStatus });
return reportBuilder.getTestBranch(formattedResultUpdated.id);
}));
}
async undoAcceptImages(tests) {
const updatedImages = [], removedResultIds = [];
const reportBuilder = this._ensureReportBuilder();
await Promise.all(tests.map(async (test) => {
const testAdapter = this._getTestAdapterById(test);
const assertViewResults = this._prepareAssertViewResults(test.imagesInfo, testAdapter);
const { sessionId, url } = test.metaInfo;
const formattedResultWithoutAttempt = testAdapter.createTestResult({
assertViewResults,
status: constants_1.UPDATED,
attempt: constants_1.UNKNOWN_ATTEMPT,
error: test.error,
sessionId,
meta: { url },
duration: 0
});
await Promise.all(formattedResultWithoutAttempt.imagesInfo.map(async (imageInfo) => {
const { stateName } = imageInfo;
const undoResultData = reportBuilder.undoAcceptImage(formattedResultWithoutAttempt, stateName);
if (undoResultData === null) {
return;
}
const { updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newResult } = undoResultData;
updatedImage && updatedImages.push(updatedImage);
removedResult && removedResultIds.push(removedResult.id);
if (shouldRemoveReference) {
await reporterHelper.removeReferenceImage(newResult, stateName);
}
if (shouldRevertReference && removedResult) {
await reporterHelper.revertReferenceImage(removedResult, newResult, stateName);
}
if (previousExpectedPath) {
this._expectedImagesCache.set([{
testPath: [testAdapter.fullName],
browserId: testAdapter.browserId
}, stateName], previousExpectedPath);
}
}));
}));
return { updatedImages, removedResults: removedResultIds };
}
async findEqualDiffs(images) {
const [selectedImage, ...comparedImages] = images;
const { tolerance, antialiasingTolerance } = this.config;
const compareOpts = { tolerance, antialiasingTolerance, stopOnFirstFail: true, shouldCluster: false };
const comparisons = await Promise.all(comparedImages.map(async (image) => {
for (let i = 0; i < image.diffClusters.length; i++) {
const diffCluster = image.diffClusters[i];
try {
const refComparisonRes = await (0, looks_same_1.default)({ source: this._resolveImgPath(selectedImage.expectedImg.path), boundingBox: selectedImage.diffClusters[i] }, { source: this._resolveImgPath(image.expectedImg.path), boundingBox: diffCluster }, compareOpts);
if (!refComparisonRes.equal) {
return false;
}
const actComparisonRes = await (0, looks_same_1.default)({ source: this._resolveImgPath(selectedImage.actualImg.path), boundingBox: selectedImage.diffClusters[i] }, { source: this._resolveImgPath(image.actualImg.path), boundingBox: diffCluster }, compareOpts);
if (!actComparisonRes.equal) {
return false;
}
}
catch (err) {
if (err !== false) {
throw err;
}
return false;
}
}
return image;
}));
return comparisons.filter(Boolean).map(image => image.id);
}
async run(tests = []) {
const testCollection = this._ensureTestCollection();
const shouldRunAllTests = lodash_1.default.isEmpty(tests);
// if tests are not passed, then run all tests with all available retries
// if tests are specified, then retry only passed tests without retries
return shouldRunAllTests
? this._toolAdapter.run(testCollection, tests, this._globalOpts)
: this._toolAdapter.runWithoutRetries(testCollection, tests, this._globalOpts);
}
async _handleRunnableCollection() {
const reportBuilder = this._ensureReportBuilder();
const queue = new p_queue_1.default({ concurrency: node_os_1.default.cpus().length });
for (const test of this._ensureTestCollection().tests) {
if (test.disabled || test.silentlySkipped) {
continue;
}
// TODO: remove toString after publish major version
const testId = (0, utils_1.formatId)(test.id.toString(), test.browserId);
this._testAdapters[testId] = test;
if (test.pending) {
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({ status: constants_1.SKIPPED, duration: 0 })));
}
else {
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({ status: constants_1.IDLE, duration: 0 })));
}
}
await queue.onIdle();
await this._fillTestsTree();
}
_getTestAdapterById(updateData) {
const fullTitle = (0, utils_1.mkFullTitle)(updateData);
const testId = this._toolAdapter.toolName === constants_1.ToolName.Testplane
? (0, utils_1.formatId)((0, common_utils_1.getShortMD5)(fullTitle), updateData.browserId)
: (0, utils_1.formatId)(fullTitle, updateData.browserId);
return this._testAdapters[testId];
}
_prepareAssertViewResults(imagesInfo, testAdapter) {
const assertViewResults = [];
imagesInfo
.filter(({ stateName, actualImg }) => Boolean(stateName) && Boolean(actualImg))
.forEach((imageInfo) => {
const { stateName, actualImg } = imageInfo;
const absoluteRefImgPath = this._toolAdapter.config.getScreenshotPath(testAdapter, stateName);
const relativeRefImgPath = absoluteRefImgPath && node_path_1.default.relative(process.cwd(), absoluteRefImgPath);
const refImg = { path: absoluteRefImgPath, relativePath: relativeRefImgPath, size: actualImg.size };
assertViewResults.push({ stateName, refImg, currImg: actualImg, isUpdated: (0, common_utils_1.isUpdatedStatus)(imageInfo.status) });
});
return assertViewResults;
}
_handleReferenceUpdate(testResult, imageInfo, state) {
this._expectedImagesCache.set([testResult, imageInfo.stateName], imageInfo.expectedImg.path);
this._toolAdapter.updateReference({ refImg: imageInfo.refImg, state });
}
async _fillTestsTree() {
const reportBuilder = this._ensureReportBuilder();
const { autoRun } = this._guiOpts;
const testsTree = await this._loadDataFromDatabase();
if (testsTree && !lodash_1.default.isEmpty(testsTree)) {
reportBuilder.reuseTestsTree(testsTree);
}
this._tree = { ...reportBuilder.getResult(), autoRun, browserFeatures: {}, features: [] };
}
async _loadDataFromDatabase() {
const dbPath = node_path_1.default.resolve(this._reportPath, constants_1.LOCAL_DATABASE_NAME);
if (await fs_extra_1.default.pathExists(dbPath)) {
return (0, server_1.getTestsTreeFromDatabase)(dbPath, this._reporterConfig.baseHost);
}
common_utils_1.logger.warn(chalk_1.default.yellow(`Nothing to reuse in ${this._reportPath}: can not load data from ${constants_1.DATABASE_URLS_JSON_NAME}`));
return null;
}
_resolveImgPath(imgPath) {
return node_path_1.default.resolve(process.cwd(), this._reporterConfig.path, imgPath);
}
}
exports.ToolRunner = ToolRunner;
//# sourceMappingURL=index.js.map