@allurereport/web-awesome
Version:
The static files for Allure Awesome Report
177 lines (143 loc) • 5.17 kB
text/typescript
import type { AttachmentTestStepResult, DefaultTestStepResult, TestError, TestStatus } from "@allurereport/core-api";
import type { AwesomeFixtureResult, AwesomeTestResult } from "types";
export type TestLevelErrorItem = {
type: "error";
id: string;
title: string;
status: TestStatus;
error: TestError;
};
export type TrStepItem = {
type: "step";
item: DefaultTestStepResult;
bodyItems: TrBodyItem[];
suppressInlineError: boolean;
};
export type TrBodyItem = TrStepItem | AttachmentTestStepResult | TestLevelErrorItem;
type TestErrorLike = Pick<TestError, "message" | "trace" | "actual" | "expected">;
type BuildResult = {
bodyItems: TrBodyItem[];
didPlaceSyntheticError: boolean;
};
// Same pattern as @allurereport/web-commons stripAnsi — kept inline to avoid
// module-resolution issues in the vitest/webpack dual environment.
// eslint-disable-next-line no-control-regex
const ansiRegex = /\x1B\[[0-9;?]*[ -/]*[@-~]/g;
export const getTestLevelErrorId = (testResultId: string) => `__test-error__:${testResultId}`;
const normalizeErrorText = (value?: string) => {
if (typeof value !== "string") {
return "";
}
return value.replace(ansiRegex, "").trim();
};
const getTestLevelErrorTitle = (message?: string) => {
return (
normalizeErrorText(message)
.split("\n")
.map((line) => line.trim())
.find((line) => line.length > 0) ?? ""
);
};
export const hasErrorDiff = (error?: TestErrorLike) =>
Boolean(error?.actual) && error.actual !== "undefined" && Boolean(error?.expected) && error.expected !== "undefined";
export const hasTestLevelErrorContent = (error?: TestErrorLike) =>
Boolean(normalizeErrorText(error?.trace).length) || hasErrorDiff(error);
const isDisplayableTestError = (error?: TestErrorLike) => {
return (
Boolean(normalizeErrorText(error?.message).length) ||
Boolean(normalizeErrorText(error?.trace).length) ||
hasErrorDiff(error)
);
};
const canHostSyntheticError = (step: DefaultTestStepResult, error: TestErrorLike) => {
if (step.status !== "failed" && step.status !== "broken") {
return false;
}
const stepMessage = normalizeErrorText(step.message);
const errorMessage = normalizeErrorText(error.message);
if (stepMessage && errorMessage) {
return stepMessage === errorMessage;
}
const stepTrace = normalizeErrorText(step.trace);
const errorTrace = normalizeErrorText(error.trace);
return Boolean(stepTrace) && Boolean(errorTrace) && stepTrace === errorTrace;
};
const createTestLevelErrorItem = (
testResultId: string,
status: TestStatus,
error: TestError,
fallbackTitle: string,
): TestLevelErrorItem => ({
type: "error",
id: getTestLevelErrorId(testResultId),
title: getTestLevelErrorTitle(error.message) || fallbackTitle,
status,
error,
});
const buildStepBodyItems = (
steps: AwesomeTestResult["steps"],
syntheticErrorItem: TestLevelErrorItem | undefined,
): BuildResult => {
const bodyItems: TrBodyItem[] = [];
let didPlaceSyntheticError = false;
for (const step of steps) {
if (step.type === "attachment") {
bodyItems.push(step);
continue;
}
const nestedResult = buildStepBodyItems(step.steps, syntheticErrorItem);
const shouldHostSyntheticError =
Boolean(syntheticErrorItem) &&
!nestedResult.didPlaceSyntheticError &&
canHostSyntheticError(step, syntheticErrorItem.error);
bodyItems.push({
type: "step",
item: step,
bodyItems: shouldHostSyntheticError ? [...nestedResult.bodyItems, syntheticErrorItem] : nestedResult.bodyItems,
suppressInlineError: shouldHostSyntheticError,
});
if (nestedResult.didPlaceSyntheticError || shouldHostSyntheticError) {
didPlaceSyntheticError = true;
}
}
return { bodyItems, didPlaceSyntheticError };
};
export const getStepBodyItems = (steps: AwesomeTestResult["steps"]): TrBodyItem[] =>
buildStepBodyItems(steps, undefined).bodyItems;
export const fixtureResultToTrStepItem = (fixture: AwesomeFixtureResult): TrStepItem => {
const err = fixture.error;
return {
type: "step",
item: {
type: "step",
name: fixture.name,
status: fixture.status,
parameters: [],
steps: fixture.steps,
stepId: fixture.id,
duration: fixture.duration,
message: err?.message,
trace: err?.trace,
error: err,
},
bodyItems: getStepBodyItems(fixture.steps),
suppressInlineError: false,
};
};
export const getBodyItems = (
testResult?: Pick<AwesomeTestResult, "id" | "status" | "steps" | "error">,
fallbackTitle = "Error",
): TrBodyItem[] => {
if (!testResult) {
return [];
}
const syntheticErrorItem =
(testResult.status === "failed" || testResult.status === "broken") && isDisplayableTestError(testResult.error)
? createTestLevelErrorItem(testResult.id, testResult.status, testResult.error, fallbackTitle)
: undefined;
const { bodyItems, didPlaceSyntheticError } = buildStepBodyItems(testResult.steps, syntheticErrorItem);
if (syntheticErrorItem && !didPlaceSyntheticError) {
return [...bodyItems, syntheticErrorItem];
}
return bodyItems;
};