Skip to content

Commit 4fa8422

Browse files
committed
Open Playwright traces directly from any report mode
1 parent 56624b8 commit 4fa8422

34 files changed

Lines changed: 263 additions & 1051 deletions

allure-commandline/src/main/java/io/qameta/allure/LocalReportServer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ final class LocalReportServer {
7474
private static final String CSP_BASE_URI_NONE = "base-uri 'none'; ";
7575
private static final String CSP_FORM_ACTION_NONE = "form-action 'none'; ";
7676
private static final String LOCALHOST = "localhost";
77+
private static final String PLAYWRIGHT_TRACE_VIEWER_ORIGIN = "https://trace.playwright.dev";
7778
private static final String ATTACHMENTS_REQUEST_PATH = "/data/attachments/";
7879
private static final Set<String> LOCAL_SERVER_HOSTS = Collections.unmodifiableSet(
7980
new HashSet<>(Arrays.asList(LOCALHOST, "127.0.0.1", "::1"))
@@ -87,9 +88,11 @@ final class LocalReportServer {
8788
+ "media-src 'self' data: blob: https:; "
8889
+ "font-src 'self' data: https:; "
8990
+ "connect-src 'self'; "
90-
+ "frame-src 'self' blob:; "
91+
+ "frame-src 'self' blob: " + PLAYWRIGHT_TRACE_VIEWER_ORIGIN + "; "
9192
+ "worker-src 'self' blob:; "
92-
+ "script-src 'self' 'unsafe-inline' https:; "
93+
// data: scripts are required by single-file reports, which inline their bundle through
94+
// <script src="data:...">.
95+
+ "script-src 'self' 'unsafe-inline' https: data:; "
9396
+ "style-src 'self' 'unsafe-inline' https:";
9497
private static final String ATTACHMENT_CONTENT_SECURITY_POLICY = "sandbox; default-src 'none'";
9598
private static final String HTML_ATTACHMENT_CONTENT_SECURITY_POLICY = "sandbox; default-src 'none'; "

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ void shouldSetSecurityHeadersWhenServingReport(@TempDir final Path temp) throws
127127
.contains("img-src 'self' data: blob: https:")
128128
.contains("media-src 'self' data: blob: https:")
129129
.contains("font-src 'self' data: https:")
130-
.contains("script-src 'self' 'unsafe-inline' https:")
130+
.contains("frame-src 'self' blob: https://trace.playwright.dev")
131+
.contains("script-src 'self' 'unsafe-inline' https: data:")
131132
.contains("style-src 'self' 'unsafe-inline' https:");
132133
assertThat(reportRequest.getHeaderField("X-Content-Type-Options")).isEqualTo("nosniff");
133134
assertThat(reportRequest.getHeaderField("Referrer-Policy")).isEqualTo("no-referrer");

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
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;
3130
import io.qameta.allure.core.Plugin;
3231
import io.qameta.allure.core.TestsResultsPlugin;
3332
import io.qameta.allure.duration.DurationPlugin;
@@ -97,7 +96,6 @@ public class ConfigurationBuilder {
9796
new SuitesPlugin(),
9897
new TestsResultsPlugin(),
9998
new AttachmentsPlugin(),
100-
new PlaywrightTraceViewerPlugin(),
10199
new MailPlugin(),
102100
new InfluxDbExportPlugin(),
103101
new PrometheusExportPlugin(),

allure-generator/src/main/java/io/qameta/allure/core/PlaywrightTraceViewerPlugin.java

Lines changed: 0 additions & 113 deletions
This file was deleted.

allure-generator/src/main/java/io/qameta/allure/core/StaticAssetManifest.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

allure-generator/src/main/javascript/core/services/reportData.mts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,37 @@ const assertOk = (response: Response, url = response.url): Response => {
183183
return response;
184184
};
185185

186+
const decodeBase64ToBytes = (base64: string) => {
187+
const binary = atob(base64);
188+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
189+
for (let index = 0; index < binary.length; index += 1) {
190+
bytes[index] = binary.charCodeAt(index);
191+
}
192+
return bytes;
193+
};
194+
195+
const dataUrlToBlob = (dataUrl: string): Blob => {
196+
const commaIndex = dataUrl.indexOf(",");
197+
if (commaIndex < 0) {
198+
throw new ReportDataError({
199+
message: defaultReportErrorMessage(),
200+
status: DEFAULT_REPORT_ERROR_STATUS,
201+
url: dataUrl,
202+
});
203+
}
204+
205+
const meta = dataUrl.slice("data:".length, commaIndex);
206+
const data = dataUrl.slice(commaIndex + 1);
207+
const isBase64 = /;base64$/iu.test(meta);
208+
const mediaType = meta.replace(/;base64$/iu, "") || "text/plain;charset=US-ASCII";
209+
210+
// A base64 payload decodes to raw bytes; a plain payload is percent-encoded
211+
// text that Blob will re-encode as UTF-8 on its own.
212+
return isBase64
213+
? new Blob([decodeBase64ToBytes(data)], { type: mediaType })
214+
: new Blob([decodeURIComponent(data)], { type: mediaType });
215+
};
216+
186217
const fetchReportData = async (
187218
url: string,
188219
{ contentType = "application/octet-stream", ...options }: ReportFetchOptions = {},
@@ -192,6 +223,21 @@ const fetchReportData = async (
192223
? url
193224
: await reportDataUrl(url, contentType);
194225

226+
// Embedded (single-file) report data resolves to data: URLs. Decode them
227+
// directly instead of fetch()-ing: under a strict connect-src CSP (such as the
228+
// one `allure open`/`allure serve` sends) the browser rejects fetch(data:...),
229+
// which would otherwise break every embedded attachment, including traces.
230+
if (fetchUrl.startsWith("data:")) {
231+
try {
232+
return new Response(dataUrlToBlob(fetchUrl), { status: 200 });
233+
} catch (error: unknown) {
234+
throw normalizeReportDataError(error, {
235+
status: DEFAULT_REPORT_ERROR_STATUS,
236+
url,
237+
});
238+
}
239+
}
240+
195241
try {
196242
return await fetch(fetchUrl, options).then((response) => assertOk(response, url));
197243
} catch (error: unknown) {
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
11
export const PLAYWRIGHT_TRACE_MIME = "application/vnd.allure.playwright-trace";
2-
export const PLAYWRIGHT_TRACE_VIEWER_INFO_URL = "data/playwright-trace-viewer.json";
3-
4-
export type PlaywrightTraceViewerInfo = {
5-
url: string;
6-
};

0 commit comments

Comments
 (0)