mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(trace): allow multiple traces in a single app, gc traces (#9478)
This commit is contained in:
		
							parent
							
								
									fc54f1937a
								
							
						
					
					
						commit
						cd99ad0da2
					
				@ -218,7 +218,7 @@ program
 | 
			
		||||
        options.browser = 'firefox';
 | 
			
		||||
      if (options.browser === 'wk')
 | 
			
		||||
        options.browser = 'webkit';
 | 
			
		||||
      showTraceViewer(trace, options.browser).catch(logErrorAndExit);
 | 
			
		||||
      showTraceViewer(trace, options.browser, false, 9322).catch(logErrorAndExit);
 | 
			
		||||
    }).addHelpText('afterAll', `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ import { internalCallMetadata } from '../../instrumentation';
 | 
			
		||||
import { createPlaywright } from '../../playwright';
 | 
			
		||||
import { ProgressController } from '../../progress';
 | 
			
		||||
 | 
			
		||||
export async function showTraceViewer(traceUrl: string, browserName: string, headless = false): Promise<BrowserContext | undefined> {
 | 
			
		||||
export async function showTraceViewer(traceUrl: string, browserName: string, headless = false, port?: number): Promise<BrowserContext | undefined> {
 | 
			
		||||
  const server = new HttpServer();
 | 
			
		||||
  server.routePath('/file', (request, response) => {
 | 
			
		||||
    try {
 | 
			
		||||
@ -42,7 +42,7 @@ export async function showTraceViewer(traceUrl: string, browserName: string, hea
 | 
			
		||||
    return server.serveFile(response, absolutePath);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const urlPrefix = await server.start();
 | 
			
		||||
  const urlPrefix = await server.start(port);
 | 
			
		||||
 | 
			
		||||
  const traceViewerPlaywright = createPlaywright('javascript', true);
 | 
			
		||||
  const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
 | 
			
		||||
 | 
			
		||||
@ -28,53 +28,86 @@ self.addEventListener('activate', function(event: any) {
 | 
			
		||||
  event.waitUntil(self.clients.claim());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let traceModel: TraceModel | undefined;
 | 
			
		||||
let snapshotServer: SnapshotServer | undefined;
 | 
			
		||||
const scopePath = new URL(self.registration.scope).pathname;
 | 
			
		||||
 | 
			
		||||
async function loadTrace(trace: string): Promise<TraceModel> {
 | 
			
		||||
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer, clientId: string }>();
 | 
			
		||||
 | 
			
		||||
async function loadTrace(trace: string, clientId: string): Promise<TraceModel> {
 | 
			
		||||
  const entry = loadedTraces.get(trace);
 | 
			
		||||
  if (entry)
 | 
			
		||||
    return entry.traceModel;
 | 
			
		||||
  const traceModel = new TraceModel();
 | 
			
		||||
  const url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `/file?path=${trace}`;
 | 
			
		||||
  await traceModel.load(url);
 | 
			
		||||
  const snapshotServer = new SnapshotServer(traceModel.storage());
 | 
			
		||||
  loadedTraces.set(trace, { traceModel, snapshotServer, clientId });
 | 
			
		||||
  return traceModel;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
async function doFetch(event: FetchEvent): Promise<Response> {
 | 
			
		||||
  const request = event.request;
 | 
			
		||||
  const url = new URL(request.url);
 | 
			
		||||
  const snapshotUrl = request.mode === 'navigate' ?
 | 
			
		||||
    request.url : (await self.clients.get(event.clientId))!.url;
 | 
			
		||||
  const traceUrl = new URL(snapshotUrl).searchParams.get('trace')!;
 | 
			
		||||
  const { snapshotServer } = loadedTraces.get(traceUrl) || {};
 | 
			
		||||
 | 
			
		||||
  if (request.url.startsWith(self.registration.scope)) {
 | 
			
		||||
    const url = new URL(request.url);
 | 
			
		||||
 | 
			
		||||
    const relativePath = url.pathname.substring(scopePath.length - 1);
 | 
			
		||||
    if (relativePath === '/context') {
 | 
			
		||||
      const trace = url.searchParams.get('trace')!;
 | 
			
		||||
      traceModel = await loadTrace(trace);
 | 
			
		||||
      snapshotServer = new SnapshotServer(traceModel.storage());
 | 
			
		||||
      await gc();
 | 
			
		||||
      const traceModel = await loadTrace(traceUrl, event.clientId);
 | 
			
		||||
      return new Response(JSON.stringify(traceModel!.contextEntry), {
 | 
			
		||||
        status: 200,
 | 
			
		||||
        headers: { 'Content-Type': 'application/json' }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (relativePath.startsWith('/snapshotSize/'))
 | 
			
		||||
      return snapshotServer!.serveSnapshotSize(relativePath, url.searchParams);
 | 
			
		||||
    if (relativePath.startsWith('/snapshot/'))
 | 
			
		||||
      return snapshotServer!.serveSnapshot(relativePath, url.searchParams, snapshotUrl);
 | 
			
		||||
    if (relativePath.startsWith('/sha1/')) {
 | 
			
		||||
      const blob = await traceModel!.resourceForSha1(relativePath.slice('/sha1/'.length));
 | 
			
		||||
      if (blob)
 | 
			
		||||
        return new Response(blob, { status: 200 });
 | 
			
		||||
      else
 | 
			
		||||
 | 
			
		||||
    if (relativePath.startsWith('/snapshotSize/')) {
 | 
			
		||||
      if (!snapshotServer)
 | 
			
		||||
        return new Response(null, { status: 404 });
 | 
			
		||||
      return snapshotServer.serveSnapshotSize(relativePath, url.searchParams);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (relativePath.startsWith('/snapshot/')) {
 | 
			
		||||
      if (!snapshotServer)
 | 
			
		||||
        return new Response(null, { status: 404 });
 | 
			
		||||
      return snapshotServer.serveSnapshot(relativePath, url.searchParams, snapshotUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (relativePath.startsWith('/sha1/')) {
 | 
			
		||||
      // Sha1 is unique, load it from either of the models for simplicity.
 | 
			
		||||
      for (const { traceModel } of loadedTraces.values()) {
 | 
			
		||||
        const blob = await traceModel!.resourceForSha1(relativePath.slice('/sha1/'.length));
 | 
			
		||||
        if (blob)
 | 
			
		||||
          return new Response(blob, { status: 200 });
 | 
			
		||||
      }
 | 
			
		||||
      return new Response(null, { status: 404 });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fallback to network.
 | 
			
		||||
    return fetch(event.request);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if (!snapshotServer)
 | 
			
		||||
    return new Response(null, { status: 404 });
 | 
			
		||||
  return snapshotServer!.serveResource(request.url, snapshotUrl);
 | 
			
		||||
  return snapshotServer.serveResource(request.url, snapshotUrl);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function gc() {
 | 
			
		||||
  const usedTraces = new Set<string>();
 | 
			
		||||
  for (const [traceUrl, entry] of loadedTraces) {
 | 
			
		||||
    const client = await self.clients.get(entry.clientId);
 | 
			
		||||
    if (client)
 | 
			
		||||
      usedTraces.add(traceUrl);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (const traceUrl of loadedTraces.keys()) {
 | 
			
		||||
    if (!usedTraces.has(traceUrl))
 | 
			
		||||
      loadedTraces.delete(traceUrl);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,9 @@ export const SnapshotTab: React.FunctionComponent<{
 | 
			
		||||
  if (action) {
 | 
			
		||||
    const snapshot = snapshots[snapshotIndex];
 | 
			
		||||
    if (snapshot && snapshot.snapshotName) {
 | 
			
		||||
      snapshotUrl = new URL(`snapshot/${action.metadata.pageId}?name=${snapshot.snapshotName}`, window.location.href).toString();
 | 
			
		||||
      snapshotSizeUrl = new URL(`snapshotSize/${action.metadata.pageId}?name=${snapshot.snapshotName}`, window.location.href).toString();
 | 
			
		||||
      const traceUrl = new URL(window.location.href).searchParams.get('trace');
 | 
			
		||||
      snapshotUrl = new URL(`snapshot/${action.metadata.pageId}?trace=${traceUrl}&name=${snapshot.snapshotName}`, window.location.href).toString();
 | 
			
		||||
      snapshotSizeUrl = new URL(`snapshotSize/${action.metadata.pageId}?trace=${traceUrl}&name=${snapshot.snapshotName}`, window.location.href).toString();
 | 
			
		||||
      if (snapshot.snapshotName.includes('action')) {
 | 
			
		||||
        pointX = action.metadata.point?.x;
 | 
			
		||||
        pointY = action.metadata.point?.y;
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,7 @@ class HtmlReporter {
 | 
			
		||||
        const absolutePath = path.join(reportFolder, ...relativePath.split('/'));
 | 
			
		||||
        return server.serveFile(response, absolutePath);
 | 
			
		||||
      });
 | 
			
		||||
      const url = await server.start();
 | 
			
		||||
      const url = await server.start(9323);
 | 
			
		||||
      console.log('');
 | 
			
		||||
      console.log(colors.cyan(`  Serving HTML report at ${url}. Press Ctrl+C to quit.`));
 | 
			
		||||
      console.log('');
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user