chore(tracing): look up snapshot resources only in the same context (#34645)
This commit is contained in:
parent
427d7a22ea
commit
3d3154de86
|
|
@ -103,7 +103,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
wallTime: 0,
|
wallTime: 0,
|
||||||
monotonicTime: 0,
|
monotonicTime: 0,
|
||||||
sdkLanguage: context.attribution.playwright.options.sdkLanguage,
|
sdkLanguage: context.attribution.playwright.options.sdkLanguage,
|
||||||
testIdAttributeName
|
testIdAttributeName,
|
||||||
|
contextId: context.guid,
|
||||||
};
|
};
|
||||||
if (context instanceof BrowserContext) {
|
if (context instanceof BrowserContext) {
|
||||||
this._snapshotter = new Snapshotter(context, this);
|
this._snapshotter = new Snapshotter(context, this);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export class InMemorySnapshotter implements SnapshotterDelegate, HarTracerDelega
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntryFinished(entry: har.Entry) {
|
onEntryFinished(entry: har.Entry) {
|
||||||
this._storage.addResource(entry);
|
this._storage.addResource('', entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentBlob(sha1: string, buffer: Buffer) {
|
onContentBlob(sha1: string, buffer: Buffer) {
|
||||||
|
|
@ -85,7 +85,7 @@ export class InMemorySnapshotter implements SnapshotterDelegate, HarTracerDelega
|
||||||
|
|
||||||
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
onFrameSnapshot(snapshot: FrameSnapshot): void {
|
||||||
++this._snapshotCount;
|
++this._snapshotCount;
|
||||||
const renderer = this._storage.addFrameSnapshot(snapshot, []);
|
const renderer = this._storage.addFrameSnapshot('', snapshot, []);
|
||||||
this._snapshotReadyPromises.get(snapshot.snapshotName || '')?.resolve(renderer);
|
this._snapshotReadyPromises.get(snapshot.snapshotName || '')?.resolve(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,14 +120,16 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
return snapshotServer.serveSnapshotInfo(relativePath, url.searchParams);
|
const pageOrFrameId = relativePath.substring('/snapshotInfo/'.length);
|
||||||
|
return snapshotServer.serveSnapshotInfo(pageOrFrameId, url.searchParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/snapshot/')) {
|
if (relativePath.startsWith('/snapshot/')) {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
const response = snapshotServer.serveSnapshot(relativePath, url.searchParams, url.href);
|
const pageOrFrameId = relativePath.substring('/snapshot/'.length);
|
||||||
|
const response = snapshotServer.serveSnapshot(pageOrFrameId, url.searchParams, url.href);
|
||||||
if (isDeployedAsHttps)
|
if (isDeployedAsHttps)
|
||||||
response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests');
|
response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests');
|
||||||
return response;
|
return response;
|
||||||
|
|
@ -137,7 +139,8 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
return snapshotServer.serveClosestScreenshot(relativePath, url.searchParams);
|
const pageOrFrameId = relativePath.substring('/closest-screenshot/'.length);
|
||||||
|
return snapshotServer.serveClosestScreenshot(pageOrFrameId, url.searchParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/sha1/')) {
|
if (relativePath.startsWith('/sha1/')) {
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ export class SnapshotServer {
|
||||||
this._resourceLoader = resourceLoader;
|
this._resourceLoader = resourceLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
serveSnapshot(pathname: string, searchParams: URLSearchParams, snapshotUrl: string): Response {
|
serveSnapshot(pageOrFrameId: string, searchParams: URLSearchParams, snapshotUrl: string): Response {
|
||||||
const snapshot = this._snapshot(pathname.substring('/snapshot'.length), searchParams);
|
const snapshot = this._snapshot(pageOrFrameId, searchParams);
|
||||||
if (!snapshot)
|
if (!snapshot)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
|
|
||||||
|
|
@ -41,16 +41,16 @@ export class SnapshotServer {
|
||||||
return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async serveClosestScreenshot(pathname: string, searchParams: URLSearchParams): Promise<Response> {
|
async serveClosestScreenshot(pageOrFrameId: string, searchParams: URLSearchParams): Promise<Response> {
|
||||||
const snapshot = this._snapshot(pathname.substring('/closest-screenshot'.length), searchParams);
|
const snapshot = this._snapshot(pageOrFrameId, searchParams);
|
||||||
const sha1 = snapshot?.closestScreenshot();
|
const sha1 = snapshot?.closestScreenshot();
|
||||||
if (!sha1)
|
if (!sha1)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
return new Response(await this._resourceLoader(sha1));
|
return new Response(await this._resourceLoader(sha1));
|
||||||
}
|
}
|
||||||
|
|
||||||
serveSnapshotInfo(pathname: string, searchParams: URLSearchParams): Response {
|
serveSnapshotInfo(pageOrFrameId: string, searchParams: URLSearchParams): Response {
|
||||||
const snapshot = this._snapshot(pathname.substring('/snapshotInfo'.length), searchParams);
|
const snapshot = this._snapshot(pageOrFrameId, searchParams);
|
||||||
return this._respondWithJson(snapshot ? {
|
return this._respondWithJson(snapshot ? {
|
||||||
viewport: snapshot.viewport(),
|
viewport: snapshot.viewport(),
|
||||||
url: snapshot.snapshot().frameUrl,
|
url: snapshot.snapshot().frameUrl,
|
||||||
|
|
@ -61,9 +61,9 @@ export class SnapshotServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _snapshot(pathname: string, params: URLSearchParams) {
|
private _snapshot(pageOrFrameId: string, params: URLSearchParams) {
|
||||||
const name = params.get('name')!;
|
const name = params.get('name')!;
|
||||||
return this._snapshotStorage.snapshotByName(pathname.slice(1), name);
|
return this._snapshotStorage.snapshotByName(pageOrFrameId, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _respondWithJson(object: any): Response {
|
private _respondWithJson(object: any): Response {
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,19 @@ import type { PageEntry } from '../types/entries';
|
||||||
import { LRUCache } from './lruCache';
|
import { LRUCache } from './lruCache';
|
||||||
|
|
||||||
export class SnapshotStorage {
|
export class SnapshotStorage {
|
||||||
private _resources: ResourceSnapshot[] = [];
|
|
||||||
private _frameSnapshots = new Map<string, {
|
private _frameSnapshots = new Map<string, {
|
||||||
raw: FrameSnapshot[],
|
raw: FrameSnapshot[],
|
||||||
renderers: SnapshotRenderer[]
|
renderers: SnapshotRenderer[],
|
||||||
}>();
|
}>();
|
||||||
private _cache = new LRUCache<SnapshotRenderer, string>(100_000_000); // 100MB per each trace
|
private _cache = new LRUCache<SnapshotRenderer, string>(100_000_000); // 100MB per each trace
|
||||||
|
private _contextToResources = new Map<string, ResourceSnapshot[]>();
|
||||||
|
|
||||||
addResource(resource: ResourceSnapshot): void {
|
addResource(contextId: string, resource: ResourceSnapshot): void {
|
||||||
resource.request.url = rewriteURLForCustomProtocol(resource.request.url);
|
resource.request.url = rewriteURLForCustomProtocol(resource.request.url);
|
||||||
this._resources.push(resource);
|
this._ensureResourcesForContext(contextId).push(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
addFrameSnapshot(snapshot: FrameSnapshot, screencastFrames: PageEntry['screencastFrames']) {
|
addFrameSnapshot(contextId: string, snapshot: FrameSnapshot, screencastFrames: PageEntry['screencastFrames']) {
|
||||||
for (const override of snapshot.resourceOverrides)
|
for (const override of snapshot.resourceOverrides)
|
||||||
override.url = rewriteURLForCustomProtocol(override.url);
|
override.url = rewriteURLForCustomProtocol(override.url);
|
||||||
let frameSnapshots = this._frameSnapshots.get(snapshot.frameId);
|
let frameSnapshots = this._frameSnapshots.get(snapshot.frameId);
|
||||||
|
|
@ -46,7 +46,8 @@ export class SnapshotStorage {
|
||||||
this._frameSnapshots.set(snapshot.pageId, frameSnapshots);
|
this._frameSnapshots.set(snapshot.pageId, frameSnapshots);
|
||||||
}
|
}
|
||||||
frameSnapshots.raw.push(snapshot);
|
frameSnapshots.raw.push(snapshot);
|
||||||
const renderer = new SnapshotRenderer(this._cache, this._resources, frameSnapshots.raw, screencastFrames, frameSnapshots.raw.length - 1);
|
const resources = this._ensureResourcesForContext(contextId);
|
||||||
|
const renderer = new SnapshotRenderer(this._cache, resources, frameSnapshots.raw, screencastFrames, frameSnapshots.raw.length - 1);
|
||||||
frameSnapshots.renderers.push(renderer);
|
frameSnapshots.renderers.push(renderer);
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +63,16 @@ export class SnapshotStorage {
|
||||||
|
|
||||||
finalize() {
|
finalize() {
|
||||||
// Resources are not necessarily sorted in the trace file, so sort them now.
|
// Resources are not necessarily sorted in the trace file, so sort them now.
|
||||||
this._resources.sort((a, b) => (a._monotonicTime || 0) - (b._monotonicTime || 0));
|
for (const resources of this._contextToResources.values())
|
||||||
|
resources.sort((a, b) => (a._monotonicTime || 0) - (b._monotonicTime || 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ensureResourcesForContext(contextId: string): ResourceSnapshot[] {
|
||||||
|
let resources = this._contextToResources.get(contextId);
|
||||||
|
if (!resources) {
|
||||||
|
resources = [];
|
||||||
|
this._contextToResources.set(contextId, resources);
|
||||||
|
}
|
||||||
|
return resources;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export class TraceModel {
|
||||||
this.contextEntries.push(contextEntry);
|
this.contextEntries.push(contextEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._snapshotStorage!.finalize();
|
this._snapshotStorage.finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasEntry(filename: string): Promise<boolean> {
|
async hasEntry(filename: string): Promise<boolean> {
|
||||||
|
|
@ -153,5 +153,6 @@ function createEmptyContext(): ContextEntry {
|
||||||
errors: [],
|
errors: [],
|
||||||
stdio: [],
|
stdio: [],
|
||||||
hasSource: false,
|
hasSource: false,
|
||||||
|
contextId: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ export class TraceModernizer {
|
||||||
contextEntry.sdkLanguage = event.sdkLanguage;
|
contextEntry.sdkLanguage = event.sdkLanguage;
|
||||||
contextEntry.options = event.options;
|
contextEntry.options = event.options;
|
||||||
contextEntry.testIdAttributeName = event.testIdAttributeName;
|
contextEntry.testIdAttributeName = event.testIdAttributeName;
|
||||||
|
contextEntry.contextId = event.contextId ?? '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'screencast-frame': {
|
case 'screencast-frame': {
|
||||||
|
|
@ -156,11 +157,11 @@ export class TraceModernizer {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'resource-snapshot':
|
case 'resource-snapshot':
|
||||||
this._snapshotStorage.addResource(event.snapshot);
|
this._snapshotStorage.addResource(this._contextEntry.contextId, event.snapshot);
|
||||||
contextEntry.resources.push(event.snapshot);
|
contextEntry.resources.push(event.snapshot);
|
||||||
break;
|
break;
|
||||||
case 'frame-snapshot':
|
case 'frame-snapshot':
|
||||||
this._snapshotStorage.addFrameSnapshot(event.snapshot, this._pageEntry(event.snapshot.pageId).screencastFrames);
|
this._snapshotStorage.addFrameSnapshot(this._contextEntry.contextId, event.snapshot, this._pageEntry(event.snapshot.pageId).screencastFrames);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Make sure there is a page entry for each page, even without screencast frames,
|
// Make sure there is a page entry for each page, even without screencast frames,
|
||||||
|
|
@ -388,12 +389,13 @@ export class TraceModernizer {
|
||||||
wallTime: 0,
|
wallTime: 0,
|
||||||
monotonicTime: 0,
|
monotonicTime: 0,
|
||||||
sdkLanguage: 'javascript',
|
sdkLanguage: 'javascript',
|
||||||
|
contextId: '',
|
||||||
};
|
};
|
||||||
result.push(event);
|
result.push(event);
|
||||||
}
|
}
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.type === 'context-options') {
|
if (event.type === 'context-options') {
|
||||||
result.push({ ...event, monotonicTime: 0, origin: 'library' });
|
result.push({ ...event, monotonicTime: 0, origin: 'library', contextId: '' });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Take wall and monotonic time from the first event.
|
// Take wall and monotonic time from the first event.
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export type ContextEntry = {
|
||||||
stdio: trace.StdioTraceEvent[];
|
stdio: trace.StdioTraceEvent[];
|
||||||
errors: trace.ErrorTraceEvent[];
|
errors: trace.ErrorTraceEvent[];
|
||||||
hasSource: boolean;
|
hasSource: boolean;
|
||||||
|
contextId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageEntry = {
|
export type PageEntry = {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export type ContextCreatedTraceEvent = {
|
||||||
options: BrowserContextEventOptions,
|
options: BrowserContextEventOptions,
|
||||||
sdkLanguage?: Language,
|
sdkLanguage?: Language,
|
||||||
testIdAttributeName?: string,
|
testIdAttributeName?: string,
|
||||||
|
contextId?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScreencastFrameTraceEvent = {
|
export type ScreencastFrameTraceEvent = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue