Skip to content

Commit 3e7dde9

Browse files
authored
Add support for HTTP Exchange attachments (via #3331)
1 parent d875fe7 commit 3e7dde9

60 files changed

Lines changed: 4371 additions & 109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

allure-commandline/src/test/java/io/qameta/allure/CommandsTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,36 @@ void shouldServeImageDiffAttachment(@TempDir final Path temp) throws Exception {
206206
}
207207
}
208208

209+
/**
210+
* Verifies the report server preserves the Allure HTTP Exchange content type.
211+
* The test checks a .httpexchange file is served with the expected media type and body.
212+
*/
213+
@Description
214+
@Test
215+
void shouldServeHttpAttachment(@TempDir final Path temp) throws Exception {
216+
final Path home = Files.createDirectories(temp.resolve("home"));
217+
final Path reportDirectory = Files.createDirectories(temp.resolve("report"));
218+
final String payload = "{\"schemaVersion\":1,"
219+
+ "\"request\":{\"method\":\"GET\",\"url\":\"https://example.org\"}}";
220+
Files.writeString(reportDirectory.resolve("api-call.httpexchange"), payload);
221+
222+
final Commands commands = new Commands(home);
223+
final HttpServer server = commands.setUpServer("127.0.0.1", 0, reportDirectory);
224+
server.start();
225+
226+
try {
227+
final int port = server.getAddress().getPort();
228+
229+
final HttpURLConnection fileRequest = openConnection(port, "/api-call.httpexchange");
230+
assertThat(fileRequest.getResponseCode()).isEqualTo(200);
231+
assertThat(fileRequest.getHeaderField("Content-Type"))
232+
.isEqualTo("application/vnd.allure.http+json");
233+
assertThat(readResponse(fileRequest)).isEqualTo(payload);
234+
} finally {
235+
server.stop(0);
236+
}
237+
}
238+
209239
/**
210240
* Verifies unknown report files are served as generic binary attachments.
211241
* The test checks an unrecognized extension receives octet-stream content type and the original body.

allure-generator/scripts/e2e-static-server.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const contentTypes: Record<string, string> = {
1515
".html": "text/html; charset=utf-8",
1616
".ico": "image/x-icon",
1717
".imagediff": "application/vnd.allure.image.diff; charset=utf-8",
18+
".httpexchange": "application/vnd.allure.http+json; charset=utf-8",
1819
".jpeg": "image/jpeg",
1920
".jpg": "image/jpeg",
2021
".js": "application/javascript; charset=utf-8",

allure-generator/scripts/prepare-playwright-report.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface ReportRequest {
2424
export const DEFAULT_REPORTS: ReportRequest[] = [
2525
{ fixture: "ui-demo", mode: REPORT_MODES.SINGLE_FILE },
2626
{ fixture: "ui-demo", mode: REPORT_MODES.DIRECTORY },
27+
{ fixture: "attachments", mode: REPORT_MODES.SINGLE_FILE },
2728
{ fixture: "attachments", mode: REPORT_MODES.DIRECTORY },
2829
{ fixture: "screen-diff", mode: REPORT_MODES.DIRECTORY },
2930
{ fixture: "playwright-trace", mode: REPORT_MODES.SINGLE_FILE },

allure-generator/src/main/java/io/qameta/allure/detect/WellKnownFileExtensionsUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public final class WellKnownFileExtensionsUtils {
5353
m.put("tar", "application/x-tar");
5454
m.put("gtar", "application/x-gtar");
5555
m.put("imagediff", "application/vnd.allure.image.diff");
56+
m.put("httpexchange", "application/vnd.allure.http+json");
5657
m.put("urls", "text/uri-list");
5758
m.put("ogv", "video/ogg");
5859
m.put("tsv", "text/tab-separated-values");

allure-generator/src/main/javascript/features/attachments/index.mts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import { HTTP_EXCHANGE_ATTACHMENT_MIME } from "./model/httpAttachment.mts";
2+
import { HttpAttachmentView } from "./views/HttpAttachmentView.mts";
13
import { ScreenDiffAttachmentView, ScreenDiffTestResultView } from "./views/ScreenDiffView.mts";
24

35
type AttachmentViewers = import("../../core/registry/types.mts").AttachmentViewers;
46
type TestResultBlockFactory = import("../../core/registry/types.mts").TestResultBlockFactory;
57
type TestResultBlocks = import("../../core/registry/types.mts").TestResultBlocks;
68

79
export const attachmentViewers: AttachmentViewers = {
10+
[HTTP_EXCHANGE_ATTACHMENT_MIME]: {
11+
create: HttpAttachmentView,
12+
icon: "lineDevDataflow3",
13+
},
814
"application/vnd.allure.image.diff": {
915
create: ScreenDiffAttachmentView,
1016
icon: "lineLayoutsColumns2",
Lines changed: 8 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { csvParseRows, tsvParseRows } from "d3-dsv";
21
import { getAttachmentViewer } from "../../../core/registry/index.mts";
3-
import { PLAYWRIGHT_TRACE_MIME } from "./playwrightTrace.mts";
2+
import {
3+
getBuiltinAttachmentType,
4+
type AttachmentTypeInfo as BuiltinAttachmentTypeInfo,
5+
} from "./builtinAttachmentType.mts";
6+
47
type AttachmentViewerDescriptor =
58
import("../../../core/registry/types.mts").AttachmentViewerDescriptor;
6-
type IconName = import("../../../shared/icon/index.mts").IconName;
79

8-
export type AttachmentTypeInfo = Partial<AttachmentViewerDescriptor> & {
9-
type: string | null;
10-
icon: IconName;
11-
parser?: (content: string) => unknown;
12-
};
10+
export type AttachmentTypeInfo = BuiltinAttachmentTypeInfo & Partial<AttachmentViewerDescriptor>;
1311

1412
export default function attachmentType(type: string): AttachmentTypeInfo {
1513
const customAttachmentViewer = getAttachmentViewer(type);
@@ -19,101 +17,6 @@ export default function attachmentType(type: string): AttachmentTypeInfo {
1917
...customAttachmentViewer,
2018
};
2119
}
22-
switch (type) {
23-
case "image/bmp":
24-
case "image/gif":
25-
case "image/tiff":
26-
case "image/jpeg":
27-
case "image/jpg":
28-
case "image/png":
29-
case "image/webp":
30-
case "image/*":
31-
return {
32-
type: "image",
33-
icon: "lineImagesImage",
34-
};
35-
case "text/xml":
36-
case "application/xml":
37-
case "application/json":
38-
case "text/json":
39-
case "text/yaml":
40-
case "application/yaml":
41-
case "application/x-yaml":
42-
case "text/x-yaml":
43-
return {
44-
type: "code",
45-
icon: "lineDevCodeSquare",
46-
parser: (d) => d,
47-
};
48-
case "text/plain":
49-
case "text/*":
50-
return {
51-
type: "text",
52-
icon: "lineFilesFile2",
53-
parser: (d) => d,
54-
};
55-
case "application/xhtml+xml":
56-
case "text/html":
57-
return {
58-
type: "html",
59-
icon: "lineDevCodeSquare",
60-
};
61-
case "text/csv":
62-
return {
63-
type: "table",
64-
icon: "lineGeneralChecklist3",
65-
parser: (d) => csvParseRows(d),
66-
};
67-
case "text/tab-separated-values":
68-
return {
69-
type: "table",
70-
icon: "lineGeneralChecklist3",
71-
parser: (d) => tsvParseRows(d),
72-
};
73-
case "image/svg+xml":
74-
return {
75-
type: "svg",
76-
icon: "lineImagesImage",
77-
};
78-
case "video/mp4":
79-
case "video/ogg":
80-
case "video/webm":
81-
return {
82-
type: "video",
83-
icon: "lineHelpersPlayCircle",
84-
};
85-
case "text/uri-list":
86-
return {
87-
type: "uri",
88-
icon: "lineGeneralLink1",
89-
parser: (d) =>
90-
d
91-
.split("\n")
92-
.map((line) => line.trim())
93-
.filter((line) => line.length > 0)
94-
.map((line) => ({
95-
comment: line.indexOf("#") === 0,
96-
text: line,
97-
})),
98-
};
99-
case "application/x-tar":
100-
case "application/x-gtar":
101-
case "application/x-bzip2":
102-
case "application/gzip":
103-
case "application/zip":
104-
return {
105-
type: "archive",
106-
icon: "lineFilesFileAttachment2",
107-
};
108-
case PLAYWRIGHT_TRACE_MIME:
109-
return {
110-
type: "playwright-trace",
111-
icon: "lineDevCodeSquare",
112-
};
113-
default:
114-
return {
115-
type: null,
116-
icon: "lineFilesFile2",
117-
};
118-
}
20+
21+
return getBuiltinAttachmentType(type);
11922
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { csvParseRows, tsvParseRows } from "d3-dsv";
2+
import { PLAYWRIGHT_TRACE_MIME } from "./playwrightTrace.mts";
3+
4+
type IconName = import("../../../shared/icon/index.mts").IconName;
5+
6+
export type AttachmentTypeInfo = {
7+
type: string | null;
8+
icon: IconName;
9+
parser?: (content: string) => unknown;
10+
};
11+
12+
export const normalizeAttachmentContentType = (type: string) =>
13+
type.split(";")[0].trim().toLowerCase();
14+
15+
export const isJsonAttachmentContentType = (type: string) => {
16+
const normalized = normalizeAttachmentContentType(type);
17+
18+
return (
19+
normalized === "application/json" || normalized === "text/json" || normalized.endsWith("+json")
20+
);
21+
};
22+
23+
export const getBuiltinAttachmentType = (type: string): AttachmentTypeInfo => {
24+
const normalizedType = normalizeAttachmentContentType(type);
25+
26+
switch (normalizedType) {
27+
case "image/bmp":
28+
case "image/gif":
29+
case "image/tiff":
30+
case "image/jpeg":
31+
case "image/jpg":
32+
case "image/png":
33+
case "image/webp":
34+
case "image/*":
35+
return {
36+
type: "image",
37+
icon: "lineImagesImage",
38+
};
39+
case "text/xml":
40+
case "application/xml":
41+
case "text/yaml":
42+
case "application/yaml":
43+
case "application/x-yaml":
44+
case "text/x-yaml":
45+
return {
46+
type: "code",
47+
icon: "lineDevCodeSquare",
48+
parser: (d) => d,
49+
};
50+
case "text/plain":
51+
case "text/event-stream":
52+
case "text/*":
53+
return {
54+
type: "text",
55+
icon: "lineFilesFile2",
56+
parser: (d) => d,
57+
};
58+
case "application/xhtml+xml":
59+
case "text/html":
60+
return {
61+
type: "html",
62+
icon: "lineDevCodeSquare",
63+
};
64+
case "text/csv":
65+
return {
66+
type: "table",
67+
icon: "lineGeneralChecklist3",
68+
parser: (d) => csvParseRows(d),
69+
};
70+
case "text/tab-separated-values":
71+
return {
72+
type: "table",
73+
icon: "lineGeneralChecklist3",
74+
parser: (d) => tsvParseRows(d),
75+
};
76+
case "image/svg+xml":
77+
return {
78+
type: "svg",
79+
icon: "lineImagesImage",
80+
};
81+
case "video/mp4":
82+
case "video/ogg":
83+
case "video/webm":
84+
return {
85+
type: "video",
86+
icon: "lineHelpersPlayCircle",
87+
};
88+
case "text/uri-list":
89+
return {
90+
type: "uri",
91+
icon: "lineGeneralLink1",
92+
parser: (d) =>
93+
d
94+
.split("\n")
95+
.map((line) => line.trim())
96+
.filter((line) => line.length > 0)
97+
.map((line) => ({
98+
comment: line.indexOf("#") === 0,
99+
text: line,
100+
})),
101+
};
102+
case "application/x-tar":
103+
case "application/x-gtar":
104+
case "application/x-bzip2":
105+
case "application/gzip":
106+
case "application/zip":
107+
return {
108+
type: "archive",
109+
icon: "lineFilesFileAttachment2",
110+
};
111+
case PLAYWRIGHT_TRACE_MIME:
112+
return {
113+
type: "playwright-trace",
114+
icon: "lineDevCodeSquare",
115+
};
116+
default:
117+
if (isJsonAttachmentContentType(normalizedType)) {
118+
return {
119+
type: "code",
120+
icon: "lineDevCodeSquare",
121+
parser: (d) => d,
122+
};
123+
}
124+
125+
return {
126+
type: null,
127+
icon: "lineFilesFile2",
128+
};
129+
}
130+
};

0 commit comments

Comments
 (0)