feat(trace-viewer): render snapshot urls (#9993)

This commit is contained in:
Pavel Feldman 2021-11-02 16:35:23 -08:00 committed by GitHub
parent 009478b8d5
commit cd47bf26e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 43 additions and 17 deletions

View File

@ -39,9 +39,12 @@ export class SnapshotServer {
return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html' } }); return new Response(renderedSnapshot.html, { status: 200, headers: { 'Content-Type': 'text/html' } });
} }
serveSnapshotSize(pathname: string, searchParams: URLSearchParams): Response { serveSnapshotInfo(pathname: string, searchParams: URLSearchParams): Response {
const snapshot = this._snapshot(pathname.substring('/snapshotSize'.length), searchParams); const snapshot = this._snapshot(pathname.substring('/snapshotInfo'.length), searchParams);
return this._respondWithJson(snapshot ? snapshot.viewport() : {}); return this._respondWithJson(snapshot ? {
viewport: snapshot.viewport(),
url: snapshot.snapshot().frameUrl
} : {});
} }
private _snapshot(pathname: string, params: URLSearchParams) { private _snapshot(pathname: string, params: URLSearchParams) {

View File

@ -74,10 +74,10 @@ async function doFetch(event: FetchEvent): Promise<Response> {
}); });
} }
if (relativePath.startsWith('/snapshotSize/')) { if (relativePath.startsWith('/snapshotInfo/')) {
if (!snapshotServer) if (!snapshotServer)
return new Response(null, { status: 404 }); return new Response(null, { status: 404 });
return snapshotServer.serveSnapshotSize(relativePath, url.searchParams); return snapshotServer.serveSnapshotInfo(relativePath, url.searchParams);
} }
if (relativePath.startsWith('/snapshot/')) { if (relativePath.startsWith('/snapshot/')) {

View File

@ -64,6 +64,17 @@
padding: 10px; padding: 10px;
} }
.snapshot-url {
background-color: var(--background);
margin: 10px;
padding: 4px;
border-radius: 3px;
height: 28px;
display: flex;
align-items: center;
overflow: hidden;
}
.snapshot-container { .snapshot-container {
display: block; display: block;
background: white; background: white;

View File

@ -23,8 +23,8 @@ import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
export const SnapshotTab: React.FunctionComponent<{ export const SnapshotTab: React.FunctionComponent<{
action: ActionTraceEvent | undefined, action: ActionTraceEvent | undefined,
defaultSnapshotSize: Size, defaultSnapshotInfo: { viewport: Size, url: string },
}> = ({ action, defaultSnapshotSize }) => { }> = ({ action, defaultSnapshotInfo }) => {
const [measure, ref] = useMeasure<HTMLDivElement>(); const [measure, ref] = useMeasure<HTMLDivElement>();
const [snapshotIndex, setSnapshotIndex] = React.useState(0); const [snapshotIndex, setSnapshotIndex] = React.useState(0);
@ -35,7 +35,7 @@ export const SnapshotTab: React.FunctionComponent<{
const snapshots = [actionSnapshot ? { ...actionSnapshot, title: 'action' } : undefined, snapshotMap.get('before'), snapshotMap.get('after')].filter(Boolean) as { title: string, snapshotName: string }[]; const snapshots = [actionSnapshot ? { ...actionSnapshot, title: 'action' } : undefined, snapshotMap.get('before'), snapshotMap.get('after')].filter(Boolean) as { title: string, snapshotName: string }[];
let snapshotUrl = 'data:text/html,<body style="background: #ddd"></body>'; let snapshotUrl = 'data:text/html,<body style="background: #ddd"></body>';
let snapshotSizeUrl: string | undefined; let snapshotInfoUrl: string | undefined;
let pointX: number | undefined; let pointX: number | undefined;
let pointY: number | undefined; let pointY: number | undefined;
if (action) { if (action) {
@ -43,7 +43,7 @@ export const SnapshotTab: React.FunctionComponent<{
if (snapshot && snapshot.snapshotName) { if (snapshot && snapshot.snapshotName) {
const traceUrl = new URL(window.location.href).searchParams.get('trace'); 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(); 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(); snapshotInfoUrl = new URL(`snapshotInfo/${action.metadata.pageId}?trace=${traceUrl}&name=${snapshot.snapshotName}`, window.location.href).toString();
if (snapshot.snapshotName.includes('action')) { if (snapshot.snapshotName.includes('action')) {
pointX = action.metadata.point?.x; pointX = action.metadata.point?.x;
pointY = action.metadata.point?.y; pointY = action.metadata.point?.y;
@ -57,12 +57,13 @@ export const SnapshotTab: React.FunctionComponent<{
}, [snapshotIndex, snapshots]); }, [snapshotIndex, snapshots]);
const iframeRef = React.useRef<HTMLIFrameElement>(null); const iframeRef = React.useRef<HTMLIFrameElement>(null);
const [snapshotSize, setSnapshotSize] = React.useState(defaultSnapshotSize); const [snapshotInfo, setSnapshotInfo] = React.useState(defaultSnapshotInfo);
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
if (snapshotSizeUrl) { if (snapshotInfoUrl) {
const response = await fetch(snapshotSizeUrl); const response = await fetch(snapshotInfoUrl);
setSnapshotSize(await response.json()); const info = await response.json();
setSnapshotInfo(info);
} }
if (!iframeRef.current) if (!iframeRef.current)
return; return;
@ -71,8 +72,9 @@ export const SnapshotTab: React.FunctionComponent<{
} catch (e) { } catch (e) {
} }
})(); })();
}, [iframeRef, snapshotUrl, snapshotSizeUrl, pointX, pointY]); }, [iframeRef, snapshotUrl, snapshotInfoUrl, pointX, pointY]);
const snapshotSize = snapshotInfo.viewport;
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height, 1); const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height, 1);
const scaledSize = { const scaledSize = {
width: snapshotSize.width * scale, width: snapshotSize.width * scale,
@ -96,6 +98,7 @@ export const SnapshotTab: React.FunctionComponent<{
</div>; </div>;
})} })}
</div> </div>
<div className='snapshot-url' title={snapshotInfo.url}>{snapshotInfo.url}</div>
<div ref={ref} className='snapshot-wrapper'> <div ref={ref} className='snapshot-wrapper'>
<div className='snapshot-container' style={{ <div className='snapshot-container' style={{
width: snapshotSize.width + 'px', width: snapshotSize.width + 'px',

View File

@ -83,7 +83,7 @@ export const Workbench: React.FunctionComponent<{
})(); })();
}, [traceURL]); }, [traceURL]);
const defaultSnapshotSize = contextEntry.options.viewport || { width: 1280, height: 720 }; const defaultSnapshotInfo = { viewport: contextEntry.options.viewport || { width: 1280, height: 720 }, url: '' };
const boundaries = { minimum: contextEntry.startTime, maximum: contextEntry.endTime }; const boundaries = { minimum: contextEntry.startTime, maximum: contextEntry.endTime };
@ -121,7 +121,7 @@ export const Workbench: React.FunctionComponent<{
</div> </div>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}> <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={300} orientation='horizontal'> <SplitView sidebarSize={300} orientation='horizontal'>
<SnapshotTab action={selectedAction} defaultSnapshotSize={defaultSnapshotSize} /> <SnapshotTab action={selectedAction} defaultSnapshotInfo={defaultSnapshotInfo} />
<TabbedPane tabs={tabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab}/> <TabbedPane tabs={tabs} selectedTab={selectedTab} setSelectedTab={setSelectedTab}/>
</SplitView> </SplitView>
<ActionList <ActionList

View File

@ -278,7 +278,16 @@ test('should have network requests', async ({ showTraceViewer }) => {
]); ]);
}); });
test('should capture iframe', async ({ page, server, browserName, runAndTrace }) => { test('should show snapshot URL', async ({ page, runAndTrace, server }) => {
const traceViewer = await runAndTrace(async () => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate('2+2');
});
await traceViewer.snapshotFrame('page.evaluate');
await expect(traceViewer.page.locator('.snapshot-url')).toHaveText(server.EMPTY_PAGE);
});
test('should capture iframe', async ({ page, server, runAndTrace }) => {
await page.route('**/empty.html', route => { await page.route('**/empty.html', route => {
route.fulfill({ route.fulfill({
body: '<iframe src="iframe.html"></iframe>', body: '<iframe src="iframe.html"></iframe>',