Skip to content

Commit fcd3b5f

Browse files
authored
fix playwright trace attachments (via #3357)
1 parent 9550d77 commit fcd3b5f

37 files changed

Lines changed: 1177 additions & 114 deletions

allure-generator/package-lock.json

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

allure-generator/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"author": "Dmitry Baev",
1111
"license": "Apache-2.0",
1212
"devDependencies": {
13+
"@playwright/test": "^1.60.0",
1314
"@types/d3-array": "^3.2.2",
1415
"@types/d3-axis": "^3.0.6",
1516
"@types/d3-brush": "^3.0.6",
@@ -29,7 +30,7 @@
2930
"eslint-plugin-prefer-arrow-functions": "^3.9.1",
3031
"oxfmt": "^0.45.0",
3132
"oxlint": "^1.60.0",
32-
"playwright": "^1.59.1",
33+
"playwright": "^1.60.0",
3334
"rimraf": "^5.0.5",
3435
"sass": "^1.99.0",
3536
"tsx": "^4.21.0",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ const contentTypes: Record<string, string> = {
2323
".png": "image/png",
2424
".svg": "image/svg+xml",
2525
".tsv": "text/tab-separated-values; charset=utf-8",
26+
".ttf": "font/ttf",
2627
".txt": "text/plain; charset=utf-8",
2728
".uri": "text/uri-list; charset=utf-8",
2829
".webm": "video/webm",
30+
".webmanifest": "application/manifest+json; charset=utf-8",
2931
".xml": "application/xml; charset=utf-8",
3032
".zip": "application/zip",
3133
};

allure-generator/src/main/java/io/qameta/allure/ConfigurationBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.qameta.allure.core.AttachmentsPlugin;
2828
import io.qameta.allure.core.Configuration;
2929
import io.qameta.allure.core.MarkdownDescriptionsPlugin;
30+
import io.qameta.allure.core.PlaywrightTraceViewerPlugin;
3031
import io.qameta.allure.core.Plugin;
3132
import io.qameta.allure.core.TestsResultsPlugin;
3233
import io.qameta.allure.duration.DurationPlugin;
@@ -96,6 +97,7 @@ public class ConfigurationBuilder {
9697
new SuitesPlugin(),
9798
new TestsResultsPlugin(),
9899
new AttachmentsPlugin(),
100+
new PlaywrightTraceViewerPlugin(),
99101
new MailPlugin(),
100102
new InfluxDbExportPlugin(),
101103
new PrometheusExportPlugin(),
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.core;
17+
18+
import io.qameta.allure.Aggregator2;
19+
import io.qameta.allure.ReportGenerationException;
20+
import io.qameta.allure.ReportStorage;
21+
import io.qameta.allure.entity.Attachment;
22+
import io.qameta.allure.entity.StageResult;
23+
import io.qameta.allure.entity.Step;
24+
import io.qameta.allure.entity.TestResult;
25+
import org.apache.commons.io.IOUtils;
26+
27+
import java.io.IOException;
28+
import java.io.InputStream;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Objects;
32+
33+
/**
34+
* Plugin that stores Playwright Trace Viewer assets when the report contains
35+
* Playwright trace attachments.
36+
*/
37+
public class PlaywrightTraceViewerPlugin implements Aggregator2 {
38+
39+
static final String PLAYWRIGHT_TRACE_MIME_TYPE = "application/vnd.allure.playwright-trace";
40+
static final String PLAYWRIGHT_TRACE_VIEWER_URL = "playwright-trace-viewer/index.html";
41+
static final String PLAYWRIGHT_TRACE_VIEWER_INFO_JSON = "data/playwright-trace-viewer.json";
42+
43+
private static final String PLAYWRIGHT_TRACE_VIEWER_ASSETS_JSON = "playwright-trace-viewer-assets.json";
44+
45+
@Override
46+
public void aggregate(final Configuration configuration,
47+
final List<LaunchResults> launchesResults,
48+
final ReportStorage storage) {
49+
if (storage instanceof InMemoryReportStorage || !hasAnyPlaywrightTraces(launchesResults)) {
50+
return;
51+
}
52+
53+
StaticAssetManifest.load(PLAYWRIGHT_TRACE_VIEWER_ASSETS_JSON)
54+
.forEach(resourceName -> storage.addDataBinary(resourceName, readResource(resourceName)));
55+
storage.addDataJson(
56+
PLAYWRIGHT_TRACE_VIEWER_INFO_JSON,
57+
Collections.singletonMap("url", PLAYWRIGHT_TRACE_VIEWER_URL)
58+
);
59+
}
60+
61+
static boolean hasAnyPlaywrightTraces(final List<LaunchResults> launchesResults) {
62+
return launchesResults.stream()
63+
.flatMap(launch -> launch.getAllResults().stream())
64+
.anyMatch(PlaywrightTraceViewerPlugin::hasTraceInResult);
65+
}
66+
67+
private static boolean hasTraceInResult(final TestResult result) {
68+
return hasTraceInStages(result.getBeforeStages())
69+
|| hasTraceInStage(result.getTestStage())
70+
|| hasTraceInStages(result.getAfterStages());
71+
}
72+
73+
private static boolean hasTraceInStages(final List<StageResult> stages) {
74+
return stages != null && stages.stream().anyMatch(PlaywrightTraceViewerPlugin::hasTraceInStage);
75+
}
76+
77+
private static boolean hasTraceInStage(final StageResult stage) {
78+
return stage != null
79+
&& (hasTraceInAttachments(stage.getAttachments())
80+
|| hasTraceInSteps(stage.getSteps()));
81+
}
82+
83+
private static boolean hasTraceInSteps(final List<Step> steps) {
84+
return steps != null && steps.stream().anyMatch(PlaywrightTraceViewerPlugin::hasTraceInStep);
85+
}
86+
87+
private static boolean hasTraceInStep(final Step step) {
88+
return step != null
89+
&& (hasTraceInAttachments(step.getAttachments())
90+
|| hasTraceInSteps(step.getSteps()));
91+
}
92+
93+
private static boolean hasTraceInAttachments(final List<Attachment> attachments) {
94+
return attachments != null && attachments.stream().anyMatch(PlaywrightTraceViewerPlugin::isTraceAttachment);
95+
}
96+
97+
private static boolean isTraceAttachment(final Attachment attachment) {
98+
return attachment != null && PLAYWRIGHT_TRACE_MIME_TYPE.equals(attachment.getType());
99+
}
100+
101+
@SuppressWarnings("PMD.ExceptionAsFlowControl")
102+
private static byte[] readResource(final String resourceName) {
103+
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
104+
if (Objects.isNull(is)) {
105+
throw new ReportGenerationException(String.format("Resource %s not found", resourceName));
106+
}
107+
return IOUtils.toByteArray(is);
108+
} catch (IOException e) {
109+
throw new ReportGenerationException("Can't read resource " + resourceName, e);
110+
}
111+
}
112+
113+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2016-2026 Qameta Software Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.core;
17+
18+
import com.fasterxml.jackson.core.type.TypeReference;
19+
import com.fasterxml.jackson.databind.json.JsonMapper;
20+
import io.qameta.allure.ReportGenerationException;
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.net.URL;
25+
import java.util.List;
26+
27+
final class StaticAssetManifest {
28+
29+
private static final JsonMapper JSON_MAPPER = JsonMapper.builder().build();
30+
private static final TypeReference<List<String>> STATIC_ASSET_MANIFEST_TYPE = new TypeReference<List<String>>() {
31+
};
32+
33+
private StaticAssetManifest() {
34+
}
35+
36+
static List<String> load(final String resourceName) {
37+
final URL resource = Thread.currentThread()
38+
.getContextClassLoader()
39+
.getResource(resourceName);
40+
if (resource == null) {
41+
throw new ReportGenerationException("Static asset manifest " + resourceName + " not found");
42+
}
43+
44+
try (InputStream input = resource.openStream()) {
45+
return JSON_MAPPER.readValue(input, STATIC_ASSET_MANIFEST_TYPE);
46+
} catch (IOException e) {
47+
throw new ReportGenerationException("Can't read static asset manifest " + resourceName, e);
48+
}
49+
}
50+
51+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export const PLAYWRIGHT_TRACE_MIME = "application/vnd.allure.playwright-trace";
2-
export const PLAYWRIGHT_TRACE_VIEWER_URL = "https://trace.playwright.dev/";
3-
export const PLAYWRIGHT_TRACE_VIEWER_ORIGIN = new URL(PLAYWRIGHT_TRACE_VIEWER_URL).origin;
2+
export const PLAYWRIGHT_TRACE_VIEWER_INFO_URL = "data/playwright-trace-viewer.json";
3+
4+
export type PlaywrightTraceViewerInfo = {
5+
url: string;
6+
};

allure-generator/src/main/javascript/features/attachments/views/AttachmentView.scss

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ $attachment-trace-fullscreen-offset: gap(12);
103103
justify-content: flex-end;
104104
padding: 0 0 gap(1);
105105
}
106+
&__trace-actions:empty {
107+
display: none;
108+
}
109+
&__trace-download {
110+
min-width: 0;
111+
overflow: hidden;
112+
text-overflow: ellipsis;
113+
white-space: nowrap;
114+
}
106115
&__trace-body {
107116
position: relative;
108117
min-height: gap(12);
@@ -133,4 +142,45 @@ $attachment-trace-fullscreen-offset: gap(12);
133142
justify-content: center;
134143
position: absolute;
135144
}
145+
&__trace-instructions {
146+
align-items: center;
147+
display: flex;
148+
justify-content: center;
149+
min-height: gap(16);
150+
padding: gap(6) gap(4);
151+
}
152+
&__trace-instructions-content {
153+
color: var(--color-text-primary);
154+
max-width: 720px;
155+
}
156+
&__trace-instructions-title {
157+
font-size: var(--font-size-xl);
158+
font-weight: var(--font-weight-bold);
159+
line-height: var(--line-height-l);
160+
margin: 0 0 gap(1);
161+
}
162+
&__trace-instructions-reason,
163+
&__trace-instructions-note {
164+
color: var(--color-text-secondary);
165+
margin: 0 0 gap(1);
166+
}
167+
&__trace-instructions-subtitle {
168+
font-size: var(--font-size-l);
169+
font-weight: var(--font-weight-bold);
170+
line-height: var(--line-height-l);
171+
margin: gap(3) 0 gap(1);
172+
}
173+
&__trace-instructions-list {
174+
color: var(--color-text-secondary);
175+
list-style-position: outside;
176+
list-style-type: decimal;
177+
margin: 0;
178+
padding-left: gap(3);
179+
}
180+
&__trace-instructions-list > li {
181+
padding-left: gap(0.5);
182+
}
183+
&__trace-instructions-list > li + li {
184+
margin-top: gap(1);
185+
}
136186
}

0 commit comments

Comments
 (0)