From f08c22b46791ae64a672e01d89d71907850f2755 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Sat, 23 Oct 2021 10:23:39 -0800 Subject: [PATCH] fix(trace-viewer): show source files in local version (#9732) --- .../src/server/trace/viewer/traceViewer.ts | 19 ++++---- .../playwright-core/src/web/traceViewer/sw.ts | 2 +- .../src/web/traceViewer/ui/sourceTab.tsx | 2 +- .../src/web/traceViewer/ui/workbench.tsx | 16 ++++--- .../playwright-test/src/reporters/html.ts | 29 ++++++++---- tests/playwright-test/reporter-html.spec.ts | 46 +++++++++++++++---- tests/trace-viewer/trace-viewer.spec.ts | 24 ++++++---- 7 files changed, 93 insertions(+), 45 deletions(-) diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index 4f48b0dc9c..aaf7827a3a 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -27,17 +27,16 @@ import { ProgressController } from '../../progress'; export async function showTraceViewer(traceUrl: string, browserName: string, headless = false, port?: number): Promise { const server = new HttpServer(); - server.routePath('/file', (request, response) => { - try { - const path = new URL('http://localhost' + request.url!).searchParams.get('path')!; - return server.serveFile(response, path); - } catch (e) { - return false; + server.routePrefix('/trace', (request, response) => { + const url = new URL('http://localhost' + request.url!); + const relativePath = url.pathname.slice('/trace'.length); + if (relativePath.startsWith('/file')) { + try { + return server.serveFile(response, url.searchParams.get('path')!); + } catch (e) { + return false; + } } - }); - - server.routePrefix('/', (request, response) => { - const relativePath = new URL('http://localhost' + request.url!).pathname.slice('/trace'.length); const absolutePath = path.join(__dirname, '..', '..', '..', 'webpack', 'traceViewer', ...relativePath.split('/')); return server.serveFile(response, absolutePath); }); diff --git a/packages/playwright-core/src/web/traceViewer/sw.ts b/packages/playwright-core/src/web/traceViewer/sw.ts index cf7a514bd2..d9d4c72b87 100644 --- a/packages/playwright-core/src/web/traceViewer/sw.ts +++ b/packages/playwright-core/src/web/traceViewer/sw.ts @@ -37,7 +37,7 @@ async function loadTrace(trace: string, clientId: string, progress: (done: numbe if (entry) return entry.traceModel; const traceModel = new TraceModel(); - let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `/file?path=${trace}`; + let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `file?path=${trace}`; // Dropbox does not support cors. if (url.startsWith('https://www.dropbox.com/')) url = 'https://dl.dropboxusercontent.com/' + url.substring('https://www.dropbox.com/'.length); diff --git a/packages/playwright-core/src/web/traceViewer/ui/sourceTab.tsx b/packages/playwright-core/src/web/traceViewer/ui/sourceTab.tsx index 1db59734d8..eaab1f2381 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/sourceTab.tsx +++ b/packages/playwright-core/src/web/traceViewer/ui/sourceTab.tsx @@ -62,7 +62,7 @@ export const SourceTab: React.FunctionComponent<{ } else { const filePath = stackInfo.frames[selectedFrame].file; if (!stackInfo.fileContent.has(filePath)) - stackInfo.fileContent.set(filePath, await fetch(`/file?${filePath}`).then(response => response.text()).catch(e => ``)); + stackInfo.fileContent.set(filePath, await fetch(`file?path=${filePath}`).then(response => response.text()).catch(e => ``)); value = stackInfo.fileContent.get(filePath)!; } return value; diff --git a/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx b/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx index 97bc55862d..e15c732bb1 100644 --- a/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx +++ b/packages/playwright-core/src/web/traceViewer/ui/workbench.tsx @@ -97,6 +97,15 @@ export const Workbench: React.FunctionComponent<{ const consoleCount = errors + warnings; const networkCount = selectedAction ? modelUtil.resourcesForAction(selectedAction).length : 0; + const tabs = [ + { id: 'logs', title: 'Call', count: 0, render: () => }, + { id: 'console', title: 'Console', count: consoleCount, render: () => }, + { id: 'network', title: 'Network', count: networkCount, render: () => }, + ]; + + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') + tabs.push({ id: 'source', title: 'Source', count: 0, render: () => }); + return
{ event.preventDefault(); }} onDrop={event => handleDropEvent(event)}> @@ -118,12 +127,7 @@ export const Workbench: React.FunctionComponent<{ - }, - { id: 'console', title: 'Console', count: consoleCount, render: () => }, - { id: 'network', title: 'Network', count: networkCount, render: () => }, - { id: 'source', title: 'Source', count: 0, render: () => }, - ]} selectedTab={selectedTab} setSelectedTab={setSelectedTab}/> + { - let relativePath = new URL('http://localhost' + request.url).pathname; - if (relativePath === '/') - relativePath = '/index.html'; - const absolutePath = path.join(folder, ...relativePath.split('/')); - return server.serveFile(response, absolutePath); - }); + const server = startHtmlReportServer(folder); const url = await server.start(9323); console.log(''); console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`)); @@ -175,6 +168,26 @@ export async function showHTMLReport(reportFolder: string | undefined) { await new Promise(() => {}); } +export function startHtmlReportServer(folder: string): HttpServer { + const server = new HttpServer(); + server.routePrefix('/', (request, response) => { + let relativePath = new URL('http://localhost' + request.url).pathname; + if (relativePath.startsWith('/trace/file')) { + const url = new URL('http://localhost' + request.url!); + try { + return server.serveFile(response, url.searchParams.get('path')!); + } catch (e) { + return false; + } + } + if (relativePath === '/') + relativePath = '/index.html'; + const absolutePath = path.join(folder, ...relativePath.split('/')); + return server.serveFile(response, absolutePath); + }); + return server; +} + class HtmlBuilder { private _reportFolder: string; private _tests = new Map(); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index ddfef54ffc..d0c30563c6 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -15,22 +15,16 @@ */ import fs from 'fs'; -import path from 'path'; import { test as baseTest, expect } from './playwright-test-fixtures'; import { HttpServer } from 'playwright-core/lib/utils/httpServer'; +import { startHtmlReportServer } from '../../packages/playwright-test/lib/reporters/html'; const test = baseTest.extend<{ showReport: () => Promise }>({ showReport: async ({ page }, use, testInfo) => { - const server = new HttpServer(); + let server: HttpServer; await use(async () => { const reportFolder = testInfo.outputPath('playwright-report'); - server.routePrefix('/', (request, response) => { - let relativePath = new URL('http://localhost' + request.url).pathname; - if (relativePath === '/') - relativePath = '/index.html'; - const absolutePath = path.join(reportFolder, ...relativePath.split('/')); - return server.serveFile(response, absolutePath); - }); + server = startHtmlReportServer(reportFolder); const location = await server.start(); await page.goto(location); }); @@ -263,3 +257,37 @@ test('should highlight error', async ({ runInlineTest, page, showReport }) => { await page.click('text=fails'); await expect(page.locator('.error-message span:has-text("received")').nth(1)).toHaveCSS('color', 'rgb(204, 0, 0)'); }); + +test('should show trace source', async ({ runInlineTest, page, showReport }) => { + const result = await runInlineTest({ + 'playwright.config.js': ` + module.exports = { use: { trace: 'on' } }; + `, + 'a.test.js': ` + const { test } = pwt; + test('passes', async ({ page }) => { + await page.evaluate('2 + 2'); + }); + `, + }, { reporter: 'dot,html' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + + await showReport(); + await page.click('text=passes'); + await page.click('img'); + await page.click('.action-title >> text=page.evaluate'); + await page.click('text=Source'); + + await expect(page.locator('.source-line')).toContainText([ + /const.*pwt;/, + /page\.evaluate/ + ]); + await expect(page.locator('.source-line-running')).toContainText('page.evaluate'); + + await expect(page.locator('.stack-trace-frame')).toContainText([ + /a.test.js:[\d]+/, + /fixtures.[tj]s:[\d]+/, + ]); + await expect(page.locator('.stack-trace-frame.selected')).toContainText('a.test.js'); +}); diff --git a/tests/trace-viewer/trace-viewer.spec.ts b/tests/trace-viewer/trace-viewer.spec.ts index 97c14a3d36..eada9ba5a7 100644 --- a/tests/trace-viewer/trace-viewer.spec.ts +++ b/tests/trace-viewer/trace-viewer.spec.ts @@ -277,8 +277,6 @@ test('should have network requests', async ({ showTraceViewer }) => { }); test('should capture iframe', async ({ page, server, browserName, runAndTrace }) => { - test.skip(browserName === 'firefox'); - await page.route('**/empty.html', route => { route.fulfill({ body: '', @@ -394,8 +392,6 @@ test('should work with adopted style sheets and replace/replaceSync', async ({ p }); test('should restore scroll positions', async ({ page, runAndTrace, browserName }) => { - test.skip(browserName === 'firefox'); - const traceViewer = await runAndTrace(async () => { await page.setContent(`