UNPKG

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
"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