diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index b4f1823e7209b..2d252fbeaa61c 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -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'; diff --git a/packages/trace-viewer/bundle.ts b/packages/trace-viewer/bundle.ts index 38375f8d7606a..71441e172e991 100644 --- a/packages/trace-viewer/bundle.ts +++ b/packages/trace-viewer/bundle.ts @@ -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][] = []; + 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(/]*?\bsrc\s*=\s*["'][^"']+["'][^>]*)><\/script>/gi, (match, attrs) => { + assets.push(['script', parseAttrs(attrs)]); + return ''; + }); + + html = html.replace(/]*?\bhref\s*=\s*["'][^"']+["'][^>]*)>/gi, (match, attrs) => { + assets.push(['link', parseAttrs(attrs)]); + return ''; + }); + + const dynamicAssets = [ + ...ctx.chunk?.dynamicImports ?? [], + ...ctx.chunk?.imports ?? [], + ...Object.keys(ctx.bundle) + ].map(f => `./${f}`); + + function assetScript(assets: [tag: string, attrs: Record][], dynamicAssets: string[]) { + const search = new URLSearchParams(window.location.search); + search.delete('trace'); + if (search.size === 0) + return; + + const importMap: Record = {}; + 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('', `\n `); + + return html; +} export function bundle(): Plugin { return { @@ -23,6 +83,10 @@ export function bundle(): Plugin { 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(/(?=/, '').replace('', ''); }, diff --git a/packages/trace-viewer/index.html b/packages/trace-viewer/index.html index d665c73e91d89..b94878147f688 100644 --- a/packages/trace-viewer/index.html +++ b/packages/trace-viewer/index.html @@ -20,6 +20,7 @@ + Playwright Trace Viewer diff --git a/packages/trace-viewer/snapshot.html b/packages/trace-viewer/snapshot.html index 60a5d82cf5194..cccbbe575a190 100644 --- a/packages/trace-viewer/snapshot.html +++ b/packages/trace-viewer/snapshot.html @@ -15,13 +15,16 @@ --> + + +