fix(vitest): report browser retries and failure artifacts · allure-framework/allure-js@ba55a35 · GitHub
Skip to content

Commit ba55a35

Browse files
committed
fix(vitest): report browser retries and failure artifacts
1 parent 41b0659 commit ba55a35

4 files changed

Lines changed: 321 additions & 8 deletions

File tree

‎packages/allure-vitest/src/browser/setup.ts‎

Lines changed: 32 additions & 1 deletion

‎packages/allure-vitest/src/reporter.ts‎

Lines changed: 146 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { existsSync } from "node:fs";
12
import { createRequire } from "node:module";
3+
import { basename, dirname, join } from "node:path";
24
import { fileURLToPath } from "node:url";
35

4-
import { Stage, Status } from "allure-js-commons";
6+
import { getTestName } from "@vitest/runner/utils";
7+
import { ContentType, Stage, Status } from "allure-js-commons";
58
import type { RuntimeMessage } from "allure-js-commons/sdk";
69
import { getMessageAndTraceFromError, getStatusFromError, toGlobalErrorMessage } from "allure-js-commons/sdk";
710
import type { ReporterConfig } from "allure-js-commons/sdk/reporter";
@@ -44,6 +47,29 @@ const browserSetupModulePath = fileURLToPath(new URL("./browser/setup.js", impor
4447
const normalizeSetupFilePath = (setupFilePath: string) =>
4548
setupFilePath.startsWith("file://") ? fileURLToPath(setupFilePath) : setupFilePath;
4649

50+
type TaskResultLike = NonNullable<Task["result"]>;
51+
52+
type VitestArtifactAttachment = {
53+
name?: string;
54+
path?: string;
55+
body?: string | Uint8Array;
56+
contentType?: string;
57+
};
58+
59+
type VitestArtifact = {
60+
type?: string;
61+
message?: string;
62+
attachments?: VitestArtifactAttachment[];
63+
};
64+
65+
type AllureVitestAttempt = {
66+
result?: TaskResultLike;
67+
runtimeMessages?: RuntimeMessage[];
68+
artifacts?: VitestArtifact[];
69+
};
70+
71+
const sanitizeTracePart = (value: string): string => value.replaceAll(/[^a-z0-9]/gi, "-");
72+
4773
export default class AllureVitestReporter implements Reporter {
4874
private allureReporterRuntime?: ReporterRuntime;
4975
private config: AllureVitestReporterConfig;
@@ -144,8 +170,7 @@ export default class AllureVitestReporter implements Reporter {
144170
const {
145171
allureRuntimeMessages = [],
146172
allureGlobalRuntimeMessages = [],
147-
vitestWorker,
148-
browser,
173+
allureFailedAttempts = [],
149174
allureSkip = false,
150175
} = task.meta;
151176

@@ -158,6 +183,33 @@ export default class AllureVitestReporter implements Reporter {
158183
this.globalRuntimeMessages.push(...allureGlobalRuntimeMessages);
159184
}
160185

186+
const finalAttemptResult = task.result
187+
? {
188+
...task.result,
189+
startTime: task.meta.allureAttemptStartTime ?? task.result.startTime,
190+
duration: task.meta.allureAttemptDuration ?? task.result.duration,
191+
}
192+
: undefined;
193+
194+
const attempts: AllureVitestAttempt[] = [
195+
...(allureFailedAttempts as AllureVitestAttempt[]),
196+
...(task.result?.state === "fail" && allureFailedAttempts.length ? [] : [{}]),
197+
];
198+
199+
for (const attempt of attempts) {
200+
this.writeTaskAttempt(task, {
201+
result: attempt.result ?? finalAttemptResult,
202+
runtimeMessages: attempt.runtimeMessages ?? allureRuntimeMessages,
203+
artifacts: attempt.artifacts ?? (task.artifacts as VitestArtifact[] | undefined),
204+
});
205+
}
206+
}
207+
208+
private writeTaskAttempt(
209+
task: Task,
210+
{ result: taskResult, runtimeMessages: allureRuntimeMessages = [], artifacts = [] }: AllureVitestAttempt,
211+
) {
212+
const { vitestWorker, browser } = task.meta;
161213
const {
162214
projectName,
163215
specPath,
@@ -170,7 +222,7 @@ export default class AllureVitestReporter implements Reporter {
170222
} = getTestMetadata(task);
171223
const testUuid = this.allureReporterRuntime!.startTest({
172224
name,
173-
start: task.result?.startTime,
225+
start: taskResult?.startTime,
174226
});
175227

176228
this.allureReporterRuntime!.updateTest(testUuid, (result) => {
@@ -211,9 +263,9 @@ export default class AllureVitestReporter implements Reporter {
211263
this.allureReporterRuntime!.applyRuntimeMessages(testUuid, runtimeMessages);
212264
}
213265

214-
switch (task.result?.state) {
266+
switch (taskResult?.state) {
215267
case "fail": {
216-
const [error] = task.result.errors || [];
268+
const [error] = taskResult.errors || [];
217269
const status = getStatusFromError(error);
218270

219271
result.statusDetails = {
@@ -234,11 +286,71 @@ export default class AllureVitestReporter implements Reporter {
234286
break;
235287
}
236288
}
289+
290+
if ((taskResult?.retryCount ?? 0) > 0) {
291+
result.parameters.push({ name: "Retry", value: String(taskResult?.retryCount), excluded: true });
292+
}
237293
});
238294

239-
this.allureReporterRuntime!.stopTest(testUuid, { duration: task.result?.duration ?? 0 });
295+
this.writeTaskArtifacts(testUuid, taskResult, artifacts);
296+
this.writeTaskTrace(testUuid, task, taskResult);
297+
298+
this.allureReporterRuntime!.stopTest(testUuid, { duration: taskResult?.duration ?? 0 });
240299
this.allureReporterRuntime!.writeTest(testUuid);
241300
}
301+
302+
private writeTaskArtifacts(testUuid: string, taskResult: TaskResultLike | undefined, artifacts: VitestArtifact[]) {
303+
if (taskResult?.state !== "fail") {
304+
return;
305+
}
306+
307+
for (const artifact of artifacts) {
308+
if (artifact.type === "internal:failureScreenshot") {
309+
this.writeArtifactAttachments(testUuid, artifact, "Failure screenshot");
310+
}
311+
312+
if (artifact.type === "internal:toMatchScreenshot") {
313+
this.writeArtifactAttachments(testUuid, artifact, artifact.message ?? "Screenshot diff");
314+
}
315+
}
316+
}
317+
318+
private writeArtifactAttachments(testUuid: string, artifact: VitestArtifact, fallbackName: string) {
319+
for (const attachment of artifact.attachments ?? []) {
320+
const contentType = attachment.contentType ?? ContentType.PNG;
321+
const name = attachment.name ?? (attachment.path ? basename(attachment.path) : fallbackName);
322+
323+
if (attachment.path) {
324+
this.allureReporterRuntime!.writeAttachment(testUuid, undefined, name, attachment.path, {
325+
contentType,
326+
});
327+
continue;
328+
}
329+
330+
if (attachment.body) {
331+
this.allureReporterRuntime!.writeAttachment(testUuid, undefined, name, Buffer.from(attachment.body), {
332+
contentType,
333+
});
334+
}
335+
}
336+
}
337+
338+
private writeTaskTrace(testUuid: string, task: Task, taskResult: TaskResultLike | undefined) {
339+
if (taskResult?.state !== "fail") {
340+
return;
341+
}
342+
343+
const tracePath = getBrowserTracePath(task, taskResult);
344+
345+
if (!tracePath || !existsSync(tracePath)) {
346+
return;
347+
}
348+
349+
this.allureReporterRuntime!.writeAttachment(testUuid, undefined, "Trace", tracePath, {
350+
contentType: ContentType.PLAYWRIGHT_TRACE,
351+
fileExtension: ".zip",
352+
});
353+
}
242354
}
243355

244356
const getHookGlobalErrorMessages = (task: Task): RuntimeMessage[] => {
@@ -276,3 +388,30 @@ const getHookGlobalErrorMessages = (task: Task): RuntimeMessage[] => {
276388
];
277389
});
278390
};
391+
392+
const getBrowserTracePath = (task: Task, result: TaskResultLike): string | undefined => {
393+
if (!task.meta.browser || !task.file?.filepath) {
394+
return undefined;
395+
}
396+
397+
const testFilePath = task.file.filepath;
398+
const projectName = task.file.projectName ?? task.meta.browser;
399+
const repeatCount = result.repeatCount ?? 0;
400+
const retryCount = result.retryCount ?? 0;
401+
const traceDirectory = join(dirname(testFilePath), "__traces__", basename(testFilePath));
402+
// Vitest browser trace names are deterministic but internal; prefer its own full test name formatting.
403+
const traceFileNames = [
404+
[sanitizeTracePart(projectName), sanitizeTracePart(getTestName(task, "-")), repeatCount, retryCount].join("-"),
405+
[sanitizeTracePart(projectName), sanitizeTracePart(getTestMetadata(task).name), repeatCount, retryCount].join("-"),
406+
];
407+
408+
for (const traceFileName of traceFileNames) {
409+
const tracePath = join(traceDirectory, `${traceFileName}.trace.zip`);
410+
411+
if (existsSync(tracePath)) {
412+
return tracePath;
413+
}
414+
}
415+
416+
return undefined;
417+
};

‎packages/allure-vitest/src/vitest-internal.d.ts‎

Lines changed: 9 additions & 0 deletions

0 commit comments

Comments
 (0)