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
JavaScript
;
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