Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/src/trace-viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ Trace Viewer loads the trace entirely in your browser and does not transmit any

<img height="1684" width="2238" alt="Drop Playwright Trace to load" src="https://user-images.githubusercontent.com/13063165/194577918-b4d45726-2692-4093-8a28-9e73552617ef.png" />

### 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
<iframe id="viewer" src="https://trace.playwright.dev/?postMessageToken=<token>"></iframe>
```

```js
viewer.contentWindow.postMessage({
method: 'load',
params: {
trace: traceBlob,
postMessageToken: '<token>',
},
}, '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.
Expand Down
7 changes: 5 additions & 2 deletions packages/trace-viewer/src/ui/workbenchLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
49 changes: 49 additions & 0 deletions tests/library/trace-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<iframe id="viewer" src="${viewerURL}"></iframe>`);

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<HTMLIFrameElement>('#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' });

Expand Down