From 055655037d0e12d00b60ec7f18d44a320f8a2c74 Mon Sep 17 00:00:00 2001 From: Rui Figueira Date: Sat, 18 May 2024 00:59:17 +0100 Subject: [PATCH] feature(trace-viewer): embedded mode support PoC --- packages/playwright-core/src/cli/program.ts | 10 ++++++++-- .../src/server/trace/viewer/traceViewer.ts | 20 +++++++++++++++---- packages/trace-viewer/src/index.tsx | 9 ++++++--- .../trace-viewer/src/ui/workbenchLoader.tsx | 7 ++++--- packages/web/src/theme.ts | 12 +++++------ 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index ad943c049e0d8..b931692fd165e 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -23,7 +23,7 @@ import type { Command } from '../utilsBundle'; import { program } from '../utilsBundle'; export { program } from '../utilsBundle'; import { runDriver, runServer, printApiJson, launchBrowserServer } from './driver'; -import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer'; +import { runTraceInBrowser, runTraceViewerApp, runTraceViewerServer } from '../server/trace/viewer/traceViewer'; import type { TraceViewerServerOptions } from '../server/trace/viewer/traceViewer'; import * as playwright from '../..'; import type { BrowserContext } from '../client/browserContext'; @@ -296,6 +296,8 @@ program .option('-h, --host ', 'Host to serve trace on; specifying this option opens trace in a browser tab') .option('-p, --port ', 'Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab') .option('--stdin', 'Accept trace URLs over stdin to update the viewer') + .option('--server-only', 'Run server only') + .option('--theme ', 'light or dark theme') .description('show trace viewer') .action(function(traces, options) { if (options.browser === 'cr') @@ -309,9 +311,13 @@ program host: options.host, port: +options.port, isServer: !!options.stdin, + embedded: !!options.stdin && options.serverOnly, + theme: options.theme, }; - if (options.port !== undefined || options.host !== undefined) + if (openOptions.embedded) + runTraceViewerServer(traces, openOptions).catch(logErrorAndExit); + else if (options.port !== undefined || options.host !== undefined) runTraceInBrowser(traces, openOptions).catch(logErrorAndExit); else runTraceViewerApp(traces, options.browser, openOptions, true).catch(logErrorAndExit); diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index 3de6bec1b828f..d72e9b2dcc80a 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -33,6 +33,8 @@ export type TraceViewerServerOptions = { port?: number; isServer?: boolean; transport?: Transport; + embedded?: boolean; + theme?: 'light' | 'dark'; }; export type TraceViewerRedirectOptions = { @@ -46,6 +48,8 @@ export type TraceViewerRedirectOptions = { reporter?: string[]; webApp?: string; isServer?: boolean; + embedded?: boolean; + theme?: 'light' | 'dark'; }; export type TraceViewerAppOptions = { @@ -131,6 +135,10 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ params.append('headed', ''); for (const reporter of options.reporter || []) params.append('reporter', reporter); + if (options.theme) + params.append('theme', options.theme); + if (options.embedded) + params.append('embedded', ''); const urlPath = `/trace/${options.webApp || 'index.html'}?${params.toString()}`; server.routePath('/', (_, response) => { @@ -141,10 +149,16 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ }); } -export async function runTraceViewerApp(traceUrls: string[], browserName: string, options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) { +export async function runTraceViewerServer(traceUrls: string[], options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) { validateTraceUrls(traceUrls); const server = await startTraceViewerServer(options); await installRootRedirect(server, traceUrls, options); + process.stdout.write(server.urlPrefix() + '\n'); + return server; +} + +export async function runTraceViewerApp(traceUrls: string[], browserName: string, options: TraceViewerServerOptions & { headless?: boolean }, exitOnClose?: boolean) { + const server = await runTraceViewerServer(traceUrls, options); const page = await openTraceViewerApp(server.urlPrefix(), browserName, options); if (exitOnClose) page.on('close', () => gracefullyProcessExitDoNotHang(0)); @@ -152,9 +166,7 @@ export async function runTraceViewerApp(traceUrls: string[], browserName: string } export async function runTraceInBrowser(traceUrls: string[], options: TraceViewerServerOptions) { - validateTraceUrls(traceUrls); - const server = await startTraceViewerServer(options); - await installRootRedirect(server, traceUrls, options); + const server = await runTraceViewerServer(traceUrls, options); await openTraceInBrowser(server.urlPrefix()); } diff --git a/packages/trace-viewer/src/index.tsx b/packages/trace-viewer/src/index.tsx index 3f17856254818..4cd9a9c44d9b7 100644 --- a/packages/trace-viewer/src/index.tsx +++ b/packages/trace-viewer/src/index.tsx @@ -15,16 +15,19 @@ */ import '@web/common.css'; -import { applyTheme } from '@web/theme'; +import { type Theme, applyTheme } from '@web/theme'; import '@web/third_party/vscode/codicon.css'; import React from 'react'; import * as ReactDOM from 'react-dom'; import { WorkbenchLoader } from './ui/workbenchLoader'; (async () => { - applyTheme(); + const params = new URL(window.location.href).searchParams; + const theme = params.has('theme') ? `${params.get('theme')}-mode` as Theme : undefined; + + applyTheme(theme); if (window.location.protocol !== 'file:') { - if (window.location.href.includes('isUnderTest=true')) + if (params.get('isUnderTest') === 'true') 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.`); diff --git a/packages/trace-viewer/src/ui/workbenchLoader.tsx b/packages/trace-viewer/src/ui/workbenchLoader.tsx index 1038d8ac99f52..f23c3ed7cad20 100644 --- a/packages/trace-viewer/src/ui/workbenchLoader.tsx +++ b/packages/trace-viewer/src/ui/workbenchLoader.tsx @@ -33,6 +33,7 @@ export const WorkbenchLoader: React.FunctionComponent<{ const [dragOver, setDragOver] = React.useState(false); const [processingErrorMessage, setProcessingErrorMessage] = React.useState(null); const [fileForLocalModeError, setFileForLocalModeError] = React.useState(null); + const [embedded, setEmbedded] = React.useState(false); const processTraceFiles = React.useCallback((files: FileList) => { const blobUrls = []; @@ -74,7 +75,7 @@ export const WorkbenchLoader: React.FunctionComponent<{ const params = new URL(window.location.href).searchParams; const newTraceURLs = params.getAll('trace'); setIsServer(params.has('isServer')); - + setEmbedded(params.has('embedded')); // Don't accept file:// URLs - this means we re opened locally. for (const url of newTraceURLs) { if (url.startsWith('file:')) { @@ -138,7 +139,7 @@ export const WorkbenchLoader: React.FunctionComponent<{ const showFileUploadDropArea = !!(!isServer && !dragOver && !fileForLocalModeError && (!traceURLs.length || processingErrorMessage)); return
{ event.preventDefault(); setDragOver(true); }}> -
+ {!embedded &&
Playwright logo
@@ -146,7 +147,7 @@ export const WorkbenchLoader: React.FunctionComponent<{ {model.title &&
{model.title}
}
toggleTheme()}> -
+
}
diff --git a/packages/web/src/theme.ts b/packages/web/src/theme.ts index 44f663f9e6f49..c7ec7f533406b 100644 --- a/packages/web/src/theme.ts +++ b/packages/web/src/theme.ts @@ -16,7 +16,9 @@ import { settings } from './uiUtils'; -export function applyTheme() { +export type Theme = 'dark-mode' | 'light-mode'; + +export function applyTheme(theme?: Theme) { if ((document as any).playwrightThemeInitialized) return; (document as any).playwrightThemeInitialized = true; @@ -28,14 +30,12 @@ export function applyTheme() { document.body.classList.add('inactive'); }, false); - const currentTheme = settings.getString('theme', 'light-mode'); - const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); - if (currentTheme === 'dark-mode' || prefersDarkScheme.matches) + const defaultTheme = window.matchMedia('(prefers-color-scheme: dark)') ? 'dark-mode' : 'light-mode'; + const currentTheme = theme ?? settings.getString('theme', defaultTheme); + if (currentTheme === 'dark-mode') document.body.classList.add('dark-mode'); } -type Theme = 'dark-mode' | 'light-mode'; - const listeners = new Set<(theme: Theme) => void>(); export function toggleTheme() { const oldTheme = settings.getString('theme', 'light-mode');