diff --git a/docs/src/trace-viewer.md b/docs/src/trace-viewer.md index ed5a855e5c22b..aacce2c9fbc2b 100644 --- a/docs/src/trace-viewer.md +++ b/docs/src/trace-viewer.md @@ -45,6 +45,24 @@ Trace Viewer loads the trace entirely in your browser and does not transmit any Drop Playwright Trace to load +### Embedding Trace Viewer + +Reports that embed Trace Viewer and already have the trace available as a `Blob` can pass it with `postMessage`. Use an unguessable `postMessageToken` value for every embedded viewer instance. + +```html + +``` + +```js +viewer.contentWindow.postMessage({ + method: 'load', + params: { + trace: traceBlob, + postMessageToken: '', + }, +}, 'https://trace.playwright.dev'); +``` + ### Viewing remote traces You can open remote traces directly using its URL. This makes it easy to view the remote trace without having to manually download the file from CI runs, for example. diff --git a/packages/trace-viewer/src/ui/workbenchLoader.tsx b/packages/trace-viewer/src/ui/workbenchLoader.tsx index 5a112d23775a3..ae58db466e2d3 100644 --- a/packages/trace-viewer/src/ui/workbenchLoader.tsx +++ b/packages/trace-viewer/src/ui/workbenchLoader.tsx @@ -69,12 +69,15 @@ export const WorkbenchLoader: React.FunctionComponent<{ }); React.useEffect(() => { const listener = (e: MessageEvent) => { - if (e.origin !== window.location.origin) - return; const { method, params } = e.data; if (method !== 'load' || !(params?.trace instanceof Blob)) return; + if (e.origin !== window.location.origin) { + const postMessageToken = new URL(window.location.href).searchParams.get('postMessageToken'); + if (!postMessageToken || params.postMessageToken !== postMessageToken) + return; + } const traceFile = new File([params.trace], 'trace.zip', { type: 'application/zip' }); const dataTransfer = new DataTransfer(); diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index eb2008452f5b0..81daa8d968f71 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -2043,6 +2043,55 @@ test('should render blob trace received from message', async ({ showTraceViewer ]); }); +test('should render cross-origin blob trace received from message with postMessageToken', async ({ showTraceViewer, server }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/40960' }); + + const postMessageToken = 'report-token'; + const traceViewer = await showTraceViewer(undefined, { host: 'localhost' }); + const viewerURL = new URL(traceViewer.page.url()); + viewerURL.searchParams.set('postMessageToken', postMessageToken); + const viewerOrigin = viewerURL.origin; + const trace = fs.readFileSync(traceFile, 'base64'); + + await traceViewer.page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await traceViewer.page.setContent(``); + + const frame = traceViewer.page.frameLocator('#viewer'); + await expect(frame.locator('.drop-target')).toBeVisible(); + await expect(frame.locator('.action-title')).not.toBeVisible(); + + const postTrace = async (token?: string) => { + await traceViewer.page.evaluate(({ trace, token, viewerOrigin }) => { + const uint8Array = Uint8Array.from(atob(trace), c => c.charCodeAt(0)); + const params: { trace: Blob, postMessageToken?: string } = { + trace: new Blob([uint8Array], { type: 'application/zip' }), + }; + if (token !== undefined) + params.postMessageToken = token; + document.querySelector('#viewer')!.contentWindow!.postMessage({ + method: 'load', + params, + }, viewerOrigin); + }, { trace, token, viewerOrigin }); + }; + + const expectNoTraceLoaded = async (token?: string) => { + const contextsRequest = traceViewer.page.waitForRequest(request => request.url().includes('/contexts?'), { timeout: 1000 }).then(() => true).catch(() => false); + await postTrace(token); + expect(await contextsRequest).toBe(false); + await expect(frame.locator('.drop-target')).toBeVisible(); + await expect(frame.locator('.action-title')).not.toBeVisible(); + }; + + await expectNoTraceLoaded(); + await expectNoTraceLoaded('wrong-token'); + + await postTrace(postMessageToken); + + await expect(frame.locator('.drop-target')).not.toBeVisible(); + await expect(frame.locator('.action-title')).toContainText([/Create page/]); +}); + test("shouldn't render not-blob trace received from message", async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer(undefined, { host: 'localhost' });