UNPKG

html-reporter

Version:

Html-reporter and GUI for viewing and managing results of a tests run. Currently supports Testplane and Hermione.

338 lines 14.3 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.mergeSnippetIntoErrorStack = exports.trimArray = exports.isImageInfoWithState = exports.isImageBufferData = exports.getTestHash = exports.getDetailsFileName = exports.getToggledCheckboxState = exports.isCheckboxUnchecked = exports.isCheckboxIndeterminate = exports.isCheckboxChecked = exports.normalizeUrls = exports.fetchFile = exports.isUrl = exports.isBase64Image = exports.determineStatus = exports.hasDiff = exports.getError = exports.hasUnrelatedToScreenshotsErrors = exports.hasFailedImages = exports.hasNoRefImageErrors = exports.isImageError = exports.isInvalidRefImageError = exports.isNoRefImageError = exports.isImageDiffError = exports.isAssertViewError = exports.mkTestId = exports.getRelativeUrl = exports.getUrlWithBase = exports.determineFinalStatus = exports.isCommitedStatus = exports.isStagedStatus = exports.isUpdatedStatus = exports.isSkippedStatus = exports.isErrorStatus = exports.isRunningStatus = exports.isIdleStatus = exports.isFailStatus = exports.isSuccessStatus = exports.logger = exports.getShortMD5 = void 0; const crypto_1 = __importDefault(require("crypto")); const lodash_1 = require("lodash"); const url_1 = __importDefault(require("url")); const constants_1 = require("./constants"); const checked_statuses_1 = require("./constants/checked-statuses"); const errors_1 = require("./errors"); const getShortMD5 = (str) => { return crypto_1.default.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7); }; exports.getShortMD5 = getShortMD5; const statusPriority = [ // non-final constants_1.RUNNING, constants_1.QUEUED, // final constants_1.ERROR, constants_1.FAIL, constants_1.STAGED, constants_1.COMMITED, constants_1.UPDATED, constants_1.SUCCESS, constants_1.IDLE, constants_1.SKIPPED ]; exports.logger = (0, lodash_1.pick)(console, ['log', 'warn', 'error']); const isSuccessStatus = (status) => status === constants_1.SUCCESS; exports.isSuccessStatus = isSuccessStatus; const isFailStatus = (status) => status === constants_1.FAIL; exports.isFailStatus = isFailStatus; const isIdleStatus = (status) => status === constants_1.IDLE; exports.isIdleStatus = isIdleStatus; const isRunningStatus = (status) => status === constants_1.RUNNING; exports.isRunningStatus = isRunningStatus; const isErrorStatus = (status) => status === constants_1.ERROR; exports.isErrorStatus = isErrorStatus; const isSkippedStatus = (status) => status === constants_1.SKIPPED; exports.isSkippedStatus = isSkippedStatus; const isUpdatedStatus = (status) => status === constants_1.UPDATED; exports.isUpdatedStatus = isUpdatedStatus; const isStagedStatus = (status) => status === constants_1.STAGED; exports.isStagedStatus = isStagedStatus; const isCommitedStatus = (status) => status === constants_1.COMMITED; exports.isCommitedStatus = isCommitedStatus; const determineFinalStatus = (statuses) => { if (!statuses.length) { return constants_1.SUCCESS; } const set = new Set(statuses); for (const status of statusPriority) { if (set.has(status)) { return status; } } console.error('Unknown statuses: ' + JSON.stringify(statuses)); return null; }; exports.determineFinalStatus = determineFinalStatus; const getUrlWithBase = (url, base) => { if ((0, lodash_1.isEmpty)(base)) { return url ?? ''; } try { const userUrl = new URL(url ?? '', base); // Manually overriding properties, because if userUrl is absolute, the above won't work const baseUrl = new URL(base); userUrl.host = baseUrl.host; userUrl.protocol = baseUrl.protocol; userUrl.port = baseUrl.port; userUrl.username = baseUrl.username; userUrl.password = baseUrl.password; for (const [key, value] of baseUrl.searchParams) { userUrl.searchParams.append(key, value); } return userUrl.href; } catch { return url || base || ''; } }; exports.getUrlWithBase = getUrlWithBase; const getRelativeUrl = (absoluteUrl) => { try { const urlObj = new URL(absoluteUrl); return urlObj.pathname + urlObj.search; } catch { return absoluteUrl; } }; exports.getRelativeUrl = getRelativeUrl; const mkTestId = (fullTitle, browserId) => { return fullTitle + '.' + browserId; }; exports.mkTestId = mkTestId; const isAssertViewError = (error) => { return error?.name === errors_1.ErrorName.ASSERT_VIEW; }; exports.isAssertViewError = isAssertViewError; const isImageDiffError = (error) => { return error?.name === errors_1.ErrorName.IMAGE_DIFF; }; exports.isImageDiffError = isImageDiffError; const isNoRefImageError = (error) => { return error?.name === errors_1.ErrorName.NO_REF_IMAGE; }; exports.isNoRefImageError = isNoRefImageError; const isInvalidRefImageError = (error) => { return error?.name === errors_1.ErrorName.INVALID_REF_IMAGE; }; exports.isInvalidRefImageError = isInvalidRefImageError; const isImageError = (error) => { return (0, exports.isAssertViewError)(error) || (0, exports.isImageDiffError)(error) || (0, exports.isNoRefImageError)(error); }; exports.isImageError = isImageError; const hasNoRefImageErrors = ({ assertViewResults = [] }) => { return assertViewResults.some((assertViewResult) => (0, exports.isNoRefImageError)(assertViewResult)); }; exports.hasNoRefImageErrors = hasNoRefImageErrors; const hasFailedImages = (imagesInfo = []) => { return imagesInfo.some((imageInfo) => { return imageInfo.stateName && ((0, exports.isErrorStatus)(imageInfo.status) || (0, exports.isFailStatus)(imageInfo.status) || (0, exports.isNoRefImageError)(imageInfo) || (0, exports.isImageDiffError)(imageInfo)); }); }; exports.hasFailedImages = hasFailedImages; const hasUnrelatedToScreenshotsErrors = (error) => { return !(0, exports.isNoRefImageError)(error) && !(0, exports.isImageDiffError)(error) && !(0, exports.isAssertViewError)(error); }; exports.hasUnrelatedToScreenshotsErrors = hasUnrelatedToScreenshotsErrors; const getError = (error) => { if (!error) { return undefined; } return (0, lodash_1.pick)(error, ['name', 'message', 'stack', 'stateName', 'snippet']); }; exports.getError = getError; const hasDiff = (assertViewResults) => { return assertViewResults.some((result) => (0, exports.isImageDiffError)(result)); }; exports.hasDiff = hasDiff; /* This method tries to determine true status of testResult by using fields like error, imagesInfo */ const determineStatus = (testResult) => { if (!(0, exports.hasFailedImages)(testResult.imagesInfo) && !(0, exports.isSkippedStatus)(testResult.status) && (!testResult.error || !(0, exports.hasUnrelatedToScreenshotsErrors)(testResult.error))) { return constants_1.SUCCESS; } const imageErrors = (testResult.imagesInfo ?? []).map(imagesInfo => imagesInfo.error ?? {}); if ((0, exports.hasDiff)(imageErrors) || (0, exports.hasNoRefImageErrors)({ assertViewResults: imageErrors })) { return constants_1.FAIL; } if (!(0, lodash_1.isEmpty)(testResult.error)) { return constants_1.ERROR; } return testResult.status; }; exports.determineStatus = determineStatus; const isBase64Image = (image) => { return Boolean(image?.base64); }; exports.isBase64Image = isBase64Image; const isUrl = (str) => { if (typeof str !== 'string') { return false; } const parsedUrl = url_1.default.parse(str); return !!parsedUrl.host && !!parsedUrl.protocol; }; exports.isUrl = isUrl; const fetchFile = async (url, options) => { const { default: axios } = await Promise.resolve().then(() => __importStar(require('axios'))); try { const { data, status } = await axios.get(url, options); return { data, status }; } catch (e) { // eslint-disable-line @typescript-eslint/no-explicit-any exports.logger.warn(`Error while fetching ${url}`, e); // 'unknown' for request blocked by CORS policy const status = e.response ? e.response.status : 'unknown'; return { data: null, status }; } }; exports.fetchFile = fetchFile; const isRelativeUrl = (url) => { try { // eslint-disable-next-line no-new new URL(url); return false; } catch (e) { return true; } }; const normalizeUrls = (urls = [], baseUrl) => { const baseUrlsSearch = new URL(baseUrl).search; return urls.map(url => { try { const newUrl = new URL(url, baseUrl); // URL's parameters can specify directory at file server if (isRelativeUrl(url) && !newUrl.search) { newUrl.search = baseUrlsSearch; } return newUrl.href; } catch (e) { exports.logger.warn(`Can not normalize url '${url} for base url '${baseUrl}'`, e); return url; } }); }; exports.normalizeUrls = normalizeUrls; // TODO: use enum types instead of numbers below const isCheckboxChecked = (status) => Number(status) === checked_statuses_1.CHECKED; exports.isCheckboxChecked = isCheckboxChecked; const isCheckboxIndeterminate = (status) => Number(status) === checked_statuses_1.INDETERMINATE; exports.isCheckboxIndeterminate = isCheckboxIndeterminate; const isCheckboxUnchecked = (status) => Number(status) === checked_statuses_1.UNCHECKED; exports.isCheckboxUnchecked = isCheckboxUnchecked; const getToggledCheckboxState = (status) => (0, exports.isCheckboxChecked)(status) ? checked_statuses_1.UNCHECKED : checked_statuses_1.CHECKED; exports.getToggledCheckboxState = getToggledCheckboxState; function getDetailsFileName(testId, browserId, attempt) { return `${testId}-${browserId}_${Number(attempt) + 1}_${Date.now()}.json`; } exports.getDetailsFileName = getDetailsFileName; const getTestHash = (testResult) => { return testResult.testPath.concat(testResult.browserId, testResult.attempt.toString()).join(' '); }; exports.getTestHash = getTestHash; const isImageBufferData = (imageData) => { return Boolean(imageData && imageData.buffer); }; exports.isImageBufferData = isImageBufferData; const isImageInfoWithState = (imageInfo) => { return Boolean(imageInfo.stateName); }; exports.isImageInfoWithState = isImageInfoWithState; const trimArray = (array) => { let indexBegin = 0; let indexEnd = array.length; while (indexBegin < array.length && !array[indexBegin]) { indexBegin++; } while (indexEnd > 0 && !array[indexEnd - 1]) { indexEnd--; } return array.slice(indexBegin, indexEnd); }; exports.trimArray = trimArray; const getErrorTitle = (e) => { let errorName = e.name; if (!errorName && e.stack) { const columnIndex = e.stack.indexOf(':'); if (columnIndex !== -1) { errorName = e.stack.slice(0, columnIndex); } else { errorName = e.stack.slice(0, e.stack.indexOf('\n')); } } if (!errorName) { errorName = 'Error'; } return e.message ? `${errorName}: ${e.message}` : errorName; }; const getErrorRawStackFrames = (e) => { const errorTitle = getErrorTitle(e) + '\n'; const errorTitleStackIndex = e.stack.indexOf(errorTitle); if (errorTitleStackIndex !== -1) { return e.stack.slice(errorTitleStackIndex + errorTitle.length); } const errorString = e.toString ? e.toString() + '\n' : ''; const errorStringIndex = e.stack.indexOf(errorString); if (errorString && errorStringIndex !== -1) { return e.stack.slice(errorStringIndex + errorString.length); } const errorMessageStackIndex = e.stack.indexOf(e.message); const errorMessageEndsStackIndex = e.stack.indexOf('\n', errorMessageStackIndex + e.message.length); return e.stack.slice(errorMessageEndsStackIndex + 1); }; const cloneError = (error) => { const originalProperties = ['name', 'message', 'stack']; const clonedError = new Error(error.message); originalProperties.forEach(property => { delete clonedError[property]; }); const customProperties = Object.getOwnPropertyNames(error); originalProperties.concat(customProperties).forEach((property) => { clonedError[property] = error[property]; // eslint-disable-line @typescript-eslint/no-explicit-any }); return clonedError; }; const mergeSnippetIntoErrorStack = (error) => { if (!error || !error.snippet) { return error; } const clonedError = cloneError(error); delete clonedError.snippet; if (!error.stack) { clonedError.stack = [ getErrorTitle(error), error.snippet ].join('\n'); return clonedError; } const grayBegin = '\x1B[90m'; const grayEnd = '\x1B[39m'; clonedError.stack = [ getErrorTitle(error), error.snippet, grayBegin + getErrorRawStackFrames(error) + grayEnd ].join('\n'); return clonedError; }; exports.mergeSnippetIntoErrorStack = mergeSnippetIntoErrorStack; //# sourceMappingURL=common-utils.js.map