html-reporter
Version:
Html-reporter and GUI for viewing and managing results of a tests run. Currently supports Testplane and Hermione.
276 lines • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlaywrightTestResultAdapter = exports.getStatus = exports.DEFAULT_DIFF_OPTIONS = exports.ImageTitleEnding = exports.PwtTestStatus = void 0;
const path_1 = __importDefault(require("path"));
const image_size_1 = __importDefault(require("image-size"));
const lodash_1 = __importDefault(require("lodash"));
const strip_ansi_1 = __importDefault(require("strip-ansi"));
const common_utils_1 = require("../../common-utils");
const constants_1 = require("../../constants");
const errors_1 = require("../../errors");
const types_1 = require("../../types");
var PwtTestStatus;
(function (PwtTestStatus) {
PwtTestStatus["PASSED"] = "passed";
PwtTestStatus["FAILED"] = "failed";
PwtTestStatus["TIMED_OUT"] = "timedOut";
PwtTestStatus["INTERRUPTED"] = "interrupted";
PwtTestStatus["SKIPPED"] = "skipped";
})(PwtTestStatus || (exports.PwtTestStatus = PwtTestStatus = {}));
var ImageTitleEnding;
(function (ImageTitleEnding) {
ImageTitleEnding["Expected"] = "-expected.png";
ImageTitleEnding["Actual"] = "-actual.png";
ImageTitleEnding["Diff"] = "-diff.png";
ImageTitleEnding["Previous"] = "-previous.png";
})(ImageTitleEnding || (exports.ImageTitleEnding = ImageTitleEnding = {}));
const ANY_IMAGE_ENDING_REGEXP = new RegExp(Object.values(ImageTitleEnding).map(ending => `${ending}$`).join('|'));
exports.DEFAULT_DIFF_OPTIONS = {
diffColor: '#ff00ff'
};
const getStatus = (result) => {
if (result.status === constants_1.TestStatus.RUNNING) {
return constants_1.TestStatus.RUNNING;
}
if (result.status === constants_1.TestStatus.UPDATED) {
return constants_1.TestStatus.UPDATED;
}
if (result.status === PwtTestStatus.PASSED) {
return constants_1.TestStatus.SUCCESS;
}
if ([PwtTestStatus.FAILED, PwtTestStatus.TIMED_OUT, PwtTestStatus.INTERRUPTED].includes(result.status)) {
return constants_1.TestStatus.FAIL;
}
if (result.status === PwtTestStatus.SKIPPED) {
return constants_1.TestStatus.SKIPPED;
}
return constants_1.TestStatus.IDLE;
};
exports.getStatus = getStatus;
const extractErrorMessage = (result) => {
if (lodash_1.default.isEmpty(result.errors)) {
return '';
}
if (result.errors.length === 1) {
return (0, strip_ansi_1.default)(result.errors[0].message || '').split('\n')[0];
}
return JSON.stringify(result.errors.map(err => (0, strip_ansi_1.default)(err.message || '').split('\n')[0]));
};
const extractErrorStack = (result) => {
if (lodash_1.default.isEmpty(result.errors)) {
return '';
}
if (result.errors.length === 1) {
return (0, strip_ansi_1.default)(result.errors[0].stack || '');
}
return JSON.stringify(result.errors.map(err => (0, strip_ansi_1.default)(err.stack || '')));
};
const extractToMatchScreenshotError = (result, { state, expectedAttachment, diffAttachment, actualAttachment }) => {
const snapshotName = state + '.png';
if (expectedAttachment && diffAttachment && actualAttachment) {
const errors = (result.errors || []);
const imageDiffError = errors.find(err => {
return err.meta?.type === errors_1.ErrorName.IMAGE_DIFF && err.meta.snapshotName === snapshotName;
});
return { name: errors_1.ErrorName.IMAGE_DIFF, message: '', diffClusters: imageDiffError?.meta?.diffClusters };
}
const errors = (result.errors || []);
const noRefImageError = errors.find(err => {
return err.meta?.type === errors_1.ErrorName.NO_REF_IMAGE && err.meta.snapshotName === snapshotName;
});
return noRefImageError ? {
name: errors_1.ErrorName.NO_REF_IMAGE,
message: (0, strip_ansi_1.default)(noRefImageError?.message)
} : null;
};
const getImageData = (attachment) => {
if (!attachment) {
return null;
}
return {
path: attachment.path,
size: !attachment.size ? lodash_1.default.pick((0, image_size_1.default)(attachment.path), ['height', 'width']) : attachment.size,
relativePath: attachment.relativePath || path_1.default.relative(process.cwd(), attachment.path)
};
};
const getHistory = (steps) => {
return steps?.map(step => ({
[types_1.TestStepKey.Name]: step.title,
[types_1.TestStepKey.Args]: [],
[types_1.TestStepKey.IsFailed]: Boolean(step.error),
[types_1.TestStepKey.TimeStart]: step.startTime instanceof Date ? step.startTime.getTime() : new Date(step.startTime).getTime(),
[types_1.TestStepKey.Duration]: step.duration,
[types_1.TestStepKey.Children]: getHistory(step.steps),
[types_1.TestStepKey.IsGroup]: step.steps?.length > 0
})) ?? [];
};
class PlaywrightTestResultAdapter {
static create(testCase, testResult, attempt) {
return new this(testCase, testResult, attempt);
}
constructor(testCase, testResult, attempt) {
this._testCase = testCase;
this._testResult = testResult;
this._attempt = attempt;
}
get attempt() {
return this._attempt;
}
get browserId() {
return this._testCase.parent.project()?.name;
}
get description() {
return undefined;
}
get error() {
const message = extractErrorMessage(this._testResult);
if (message) {
const result = { name: errors_1.ErrorName.GENERAL_ERROR, message };
const stack = extractErrorStack(this._testResult);
if (!lodash_1.default.isNil(stack)) {
result.stack = stack;
}
if (/snapshot .*doesn't exist/.test(message) && message.includes('.png')) {
result.name = errors_1.ErrorName.NO_REF_IMAGE;
}
else if (message.includes('Screenshot comparison failed')) {
result.name = errors_1.ErrorName.IMAGE_DIFF;
}
return result;
}
return undefined;
}
get errorDetails() {
return null;
}
get file() {
return path_1.default.relative(process.cwd(), this._testCase.location.file);
}
get fullName() {
return this.testPath.join(constants_1.DEFAULT_TITLE_DELIMITER);
}
get history() {
return getHistory(this._testResult.steps);
}
get id() {
return this.testPath.concat(this.browserId, this.attempt.toString()).join(' ');
}
get imageDir() {
return (0, common_utils_1.getShortMD5)(this.fullName);
}
get imagesInfo() {
const imagesInfo = Object.entries(this._attachmentsByState).map(([state, attachments]) => {
const expectedAttachment = attachments.find(a => a.name?.endsWith(ImageTitleEnding.Expected));
const diffAttachment = attachments.find(a => a.name?.endsWith(ImageTitleEnding.Diff));
const actualAttachment = attachments.find(a => a.name?.endsWith(ImageTitleEnding.Actual));
const [expectedImg, diffImg, actualImg] = [expectedAttachment, diffAttachment, actualAttachment].map(getImageData);
const error = extractToMatchScreenshotError(this._testResult, { state, expectedAttachment, diffAttachment, actualAttachment }) || this.error;
// We now provide refImg here, though on some pwt versions it's impossible to provide correct path:
// older pwt versions had test-results directory in expected path instead of project directory.
if (error?.name === errors_1.ErrorName.IMAGE_DIFF && expectedImg && diffImg && actualImg) {
return {
status: constants_1.FAIL,
stateName: state,
diffImg,
actualImg,
expectedImg,
refImg: lodash_1.default.clone(expectedImg),
diffClusters: lodash_1.default.get(error, 'diffClusters', []),
// TODO: extract diffOptions from config
diffOptions: { current: actualImg.path, reference: expectedImg.path, ...exports.DEFAULT_DIFF_OPTIONS }
};
}
else if (error?.name === errors_1.ErrorName.NO_REF_IMAGE && actualImg) {
return {
status: constants_1.ERROR,
stateName: state,
error: lodash_1.default.pick(error, ['message', 'name', 'stack']),
actualImg,
...(expectedImg ? { refImg: lodash_1.default.clone(expectedImg) } : {})
};
}
else if (expectedAttachment?.isUpdated && expectedImg && actualImg) {
return {
status: constants_1.UPDATED,
stateName: state,
refImg: lodash_1.default.clone(expectedImg),
expectedImg,
actualImg
};
}
else if (!error && expectedImg) {
return {
status: constants_1.SUCCESS,
stateName: state,
expectedImg,
...(actualImg ? { actualImg } : {})
};
}
return null;
}).filter((value) => value !== null);
if (this.screenshot) {
imagesInfo.push({
status: lodash_1.default.isEmpty((0, common_utils_1.getError)(this.error)) ? constants_1.SUCCESS : constants_1.ERROR,
actualImg: this.screenshot
});
}
return imagesInfo;
}
get meta() {
return Object.fromEntries(this._testCase.annotations.map(a => [a.type, a.description ?? '']));
}
get multipleTabs() {
return true;
}
get screenshot() {
const pageScreenshot = this._testResult.attachments.find(a => a.contentType === 'image/png' && a.name === 'screenshot');
return getImageData(pageScreenshot);
}
get sessionId() {
// TODO: implement getting sessionId
return '';
}
get skipReason() {
return this._testCase.annotations.find(a => a.type === 'skip')?.description || '';
}
get state() {
return { name: this._testCase.title };
}
get status() {
const status = (0, exports.getStatus)(this._testResult);
if (status === constants_1.TestStatus.FAIL) {
if ((0, common_utils_1.isNoRefImageError)(this.error) || (0, common_utils_1.isImageDiffError)(this.error)) {
return constants_1.FAIL;
}
return constants_1.TestStatus.ERROR;
}
return status;
}
get testPath() {
// slicing because first entries are not actually test-name, but a file, etc.
return this._testCase.titlePath().slice(3);
}
get timestamp() {
return this._testResult.startTime.getTime();
}
get url() {
// TODO: HERMIONE-1191
return '';
}
get _attachmentsByState() {
// Filtering out only images. Page screenshots on reject are named "screenshot", we don't want them in state either.
const imageAttachments = this._testResult.attachments.filter(a => a.contentType === 'image/png' && ANY_IMAGE_ENDING_REGEXP.test(a.name));
return lodash_1.default.groupBy(imageAttachments, a => a.name.replace(ANY_IMAGE_ENDING_REGEXP, ''));
}
get duration() {
return this._testResult.duration;
}
get attachments() {
return [];
}
}
exports.PlaywrightTestResultAdapter = PlaywrightTestResultAdapter;
//# sourceMappingURL=playwright.js.map