Skip to content
Closed
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
7 changes: 6 additions & 1 deletion packages/html-reporter/src/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,12 @@ function downloadFileNameForAttachment(attachment: TestAttachment): string {
}

export function generateTraceUrl(traces: TestAttachment[]) {
return `trace/index.html?${traces.map((a, i) => `trace=${new URL(a.path!, window.location.href)}`).join('&')}`;
const searchParams = new URLSearchParams();
for (const trace of traces) {
const url = new URL(trace.path!, window.location.href);
searchParams.append('trace', formatUrl(url.toString()));
}
return `trace/index.html?${searchParams}`;
}

const kMissingContentType = 'x-playwright/missing';
Expand Down
66 changes: 65 additions & 1 deletion packages/trace-viewer/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,67 @@
* limitations under the License.
*/

import type { Plugin } from 'vite';
import type { IndexHtmlTransformContext, Plugin } from 'vite';

function transformAssetLinks(html: string, ctx: IndexHtmlTransformContext): string {
const assets: [tag: string, attrs: Record<string, string | boolean>][] = [];
const parseAttrs = (attrString: string) => {
const matches = attrString.matchAll(/(\w+)(?:\s*=\s*["']([^"']+)["'])?/g);
return Object.fromEntries(Array.from(matches, ([, key, value]) => [key, value !== undefined ? value : true]));
};

html = html.replace(/<script\s+([^>]*?\bsrc\s*=\s*["'][^"']+["'][^>]*)><\/script>/gi, (match, attrs) => {
assets.push(['script', parseAttrs(attrs)]);
return '';
});

html = html.replace(/<link\s+([^>]*?\bhref\s*=\s*["'][^"']+["'][^>]*)>/gi, (match, attrs) => {
assets.push(['link', parseAttrs(attrs)]);
return '';
});

const dynamicAssets = [
...ctx.chunk?.dynamicImports ?? [],
...ctx.chunk?.imports ?? [],
...Object.keys(ctx.bundle)

Check failure on line 39 in packages/trace-viewer/bundle.ts

View workflow job for this annotation

GitHub Actions / docs & lint

No overload matches this call.
].map(f => `./${f}`);

function assetScript(assets: [tag: string, attrs: Record<string, string | boolean>][], dynamicAssets: string[]) {
const search = new URLSearchParams(window.location.search);
search.delete('trace');
if (search.size === 0)
return;

const importMap: Record<string, string> = {};
for (const asset of dynamicAssets)
importMap[asset] = asset + '?' + search.toString();

for (const [tag, attrs] of assets) {
const el = document.createElement(tag);
for (const key in attrs) {
let value = attrs[key];
if ((key === 'src' || key === 'href')) {
value += '?' + search.toString();
importMap[key] = '' + value;
}
if (value === true)
el.setAttribute(key, '');
else
el.setAttribute(key, '' + value);

}
document.head.appendChild(el);
}

const script = document.createElement('script');
script.type = 'importmap';
script.textContent = JSON.stringify({ imports: importMap });
document.head.appendChild(script);
}
html = html.replace('</head>', `<script>(${assetScript})(${JSON.stringify(assets)}, ${JSON.stringify(dynamicAssets)})</script>\n </head>`);

return html;
}

export function bundle(): Plugin {
return {
Expand All @@ -23,6 +83,10 @@
handler(html, ctx) {
if (!ctx || !ctx.bundle)
return html;

if (ctx.filename.endsWith('index.html') || ctx.filename.endsWith('snapshot.html'))
html = transformAssetLinks(html, ctx);

// Workaround vite issue that we cannot exclude some scripts from preprocessing.
return html.replace(/(?=<!--)([\s\S]*?)-->/, '').replace('<!-- <script src="stall.js"></script> -->', '<script src="stall.js"></script>');
},
Expand Down
1 change: 1 addition & 0 deletions packages/trace-viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/playwright-logo.svg" type="image/svg+xml">
<link rel="manifest" href="/manifest.webmanifest">
<meta name="referrer" content="no-referrer">
<title>Playwright Trace Viewer</title>
</head>
<body>
Expand Down
5 changes: 4 additions & 1 deletion packages/trace-viewer/snapshot.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="referrer" content="no-referrer">
</head>
<body>
<iframe src="about:blank" style="position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;border:none;"></iframe>
<script>
(async () => {
if (!navigator.serviceWorker)
throw new Error(`Service workers are not supported.\nMake sure to serve the Trace Viewer (${window.location}) via HTTPS or localhost.`);
navigator.serviceWorker.register('sw.bundle.js');
navigator.serviceWorker.register('sw.bundle.js' + location.search);
if (!navigator.serviceWorker.controller)
await new Promise(f => navigator.serviceWorker.oncontrollerchange = f);
const traceUrl = new URL(location.href).searchParams.get('trace');
Expand Down
2 changes: 1 addition & 1 deletion packages/trace-viewer/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { LiveWorkbenchLoader } from './ui/liveWorkbenchLoader';
await new Promise(f => setTimeout(f, 1000));
if (!navigator.serviceWorker)
throw new Error(`Service workers are not supported.\nMake sure to serve the Trace Viewer (${window.location}) via HTTPS or localhost.`);
navigator.serviceWorker.register('sw.bundle.js');
navigator.serviceWorker.register(`sw.bundle.js?${queryParams}`);
if (!navigator.serviceWorker.controller) {
await new Promise<void>(f => {
navigator.serviceWorker.oncontrollerchange = () => f();
Expand Down
1 change: 1 addition & 0 deletions packages/trace-viewer/uiMode.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/playwright-logo.svg" type="image/svg+xml">
<meta name="referrer" content="no-referrer">
<title>Playwright Test</title>
</head>
<body>
Expand Down
43 changes: 43 additions & 0 deletions tests/playwright-test/reporter-html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import fs from 'fs';
import path from 'path';
import url from 'url';
import http from 'http';
import type { AddressInfo } from 'net';
import { test as baseTest, expect as baseExpect, createImage } from './playwright-test-fixtures';
import type { HttpServer } from '../../packages/playwright-core/lib/server/utils/httpServer';
import { startHtmlReportServer } from '../../packages/playwright/lib/reporters/html';
Expand Down Expand Up @@ -3306,6 +3308,47 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(page.getByRole('link', { name: 'previous' })).not.toBeVisible();
});
});

test('auth tokens are inherited', async ({ runInlineTest, page, server }) => {
const httpServer = http.createServer((req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`);
if (url.searchParams.get('token') !== 'foo') {
res.writeHead(403);
res.end('Forbidden');
return;
}
server.serveFile(req, res, test.info().outputPath('playwright-report', url.pathname));
});
const port = await new Promise<number>(resolve => {
httpServer.listen(0, () => {
resolve((httpServer.address() as AddressInfo).port);
});
});
const result = await runInlineTest({
'playwright.config.js': `
module.exports = { use: { trace: 'on' } };
`,
'a.test.js': `
import { test, expect, request } from '@playwright/test';
test('pass', async ({ page }) => {
await page.setContent('<div>hello</div>');
});
`,
}, { reporter: 'html,dot' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
await page.goto(`http://localhost:${port}/index.html?token=foo`);

await page.getByRole('link', { name: 'View Trace' }).click();
await expect(page.getByTestId('actions-tree')).toContainText('Set content');

await page.getByText('Source').click();
await expect(page.getByText('await page.setContent(\'<div>hello</div>\');')).toBeVisible();

await page.pause();

httpServer.close();
});
});
}

Expand Down
Loading