feat(traceviewer): show load progress (#9726)
This commit is contained in:
parent
9eadbf9b81
commit
c890510d86
|
|
@ -32,13 +32,16 @@ const scopePath = new URL(self.registration.scope).pathname;
|
||||||
|
|
||||||
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer, clientId: string }>();
|
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer, clientId: string }>();
|
||||||
|
|
||||||
async function loadTrace(trace: string, clientId: string): Promise<TraceModel> {
|
async function loadTrace(trace: string, clientId: string, progress: (done: number, total: number) => void): Promise<TraceModel> {
|
||||||
const entry = loadedTraces.get(trace);
|
const entry = loadedTraces.get(trace);
|
||||||
if (entry)
|
if (entry)
|
||||||
return entry.traceModel;
|
return entry.traceModel;
|
||||||
const traceModel = new TraceModel();
|
const traceModel = new TraceModel();
|
||||||
const url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `/file?path=${trace}`;
|
let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `/file?path=${trace}`;
|
||||||
await traceModel.load(url);
|
// Dropbox does not support cors.
|
||||||
|
if (url.startsWith('https://www.dropbox.com/'))
|
||||||
|
url = 'https://dl.dropboxusercontent.com/' + url.substring('https://www.dropbox.com/'.length);
|
||||||
|
await traceModel.load(url, progress);
|
||||||
const snapshotServer = new SnapshotServer(traceModel.storage());
|
const snapshotServer = new SnapshotServer(traceModel.storage());
|
||||||
loadedTraces.set(trace, { traceModel, snapshotServer, clientId });
|
loadedTraces.set(trace, { traceModel, snapshotServer, clientId });
|
||||||
return traceModel;
|
return traceModel;
|
||||||
|
|
@ -47,8 +50,8 @@ async function loadTrace(trace: string, clientId: string): Promise<TraceModel> {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
async function doFetch(event: FetchEvent): Promise<Response> {
|
async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const request = event.request;
|
const request = event.request;
|
||||||
const snapshotUrl = request.mode === 'navigate' ?
|
const client = await self.clients.get(event.clientId);
|
||||||
request.url : (await self.clients.get(event.clientId))!.url;
|
const snapshotUrl = request.mode === 'navigate' ? request.url : client!.url;
|
||||||
const traceUrl = new URL(snapshotUrl).searchParams.get('trace')!;
|
const traceUrl = new URL(snapshotUrl).searchParams.get('trace')!;
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl) || {};
|
||||||
|
|
||||||
|
|
@ -62,7 +65,9 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath === '/context') {
|
if (relativePath === '/context') {
|
||||||
const traceModel = await loadTrace(traceUrl, event.clientId);
|
const traceModel = await loadTrace(traceUrl, event.clientId, (done: number, total: number) => {
|
||||||
|
client.postMessage({ method: 'progress', params: { done, total } });
|
||||||
|
});
|
||||||
return new Response(JSON.stringify(traceModel!.contextEntry), {
|
return new Response(JSON.stringify(traceModel!.contextEntry), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ export class TraceModel {
|
||||||
this.contextEntry = createEmptyContext();
|
this.contextEntry = createEmptyContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(traceURL: string) {
|
async load(traceURL: string, progress: (done: number, total: number) => void) {
|
||||||
const response = await fetch(traceURL, { mode: 'cors' });
|
const zipReader = new zipjs.ZipReader(
|
||||||
const blob = await response.blob();
|
new zipjs.HttpReader(traceURL, { mode: 'cors' }),
|
||||||
const zipReader = new zipjs.ZipReader(new zipjs.BlobReader(blob), { useWebWorkers: false }) as zip.ZipReader;
|
{ useWebWorkers: false }) as zip.ZipReader;
|
||||||
let traceEntry: zip.Entry | undefined;
|
let traceEntry: zip.Entry | undefined;
|
||||||
let networkEntry: zip.Entry | undefined;
|
let networkEntry: zip.Entry | undefined;
|
||||||
for (const entry of await zipReader.getEntries()) {
|
for (const entry of await zipReader.getEntries({ onprogress: progress })) {
|
||||||
if (entry.filename.endsWith('.trace'))
|
if (entry.filename.endsWith('.trace'))
|
||||||
traceEntry = entry;
|
traceEntry = entry;
|
||||||
if (entry.filename.endsWith('.network'))
|
if (entry.filename.endsWith('.network'))
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,19 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
width: 400px;
|
||||||
|
height: 10px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-progress {
|
||||||
|
background-color: #aaa;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.workbench {
|
.workbench {
|
||||||
contain: size;
|
contain: size;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||||
const [selectedTab, setSelectedTab] = React.useState<string>('logs');
|
const [selectedTab, setSelectedTab] = React.useState<string>('logs');
|
||||||
|
const [progress, setProgress] = React.useState<{ done: number, total: number }>({ done: 0, total: 0 });
|
||||||
|
|
||||||
const handleDropEvent = (event: any) => {
|
const handleDropEvent = (event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -52,7 +53,15 @@ export const Workbench: React.FunctionComponent<{
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (traceURL) {
|
if (traceURL) {
|
||||||
|
const swListener = (event: any) => {
|
||||||
|
if (event.data.method === 'progress')
|
||||||
|
setProgress(event.data.params);
|
||||||
|
};
|
||||||
|
navigator.serviceWorker.addEventListener('message', swListener);
|
||||||
|
setProgress({ done: 0, total: 1 });
|
||||||
const contextEntry = (await fetch(`context?trace=${traceURL}`).then(response => response.json())) as ContextEntry;
|
const contextEntry = (await fetch(`context?trace=${traceURL}`).then(response => response.json())) as ContextEntry;
|
||||||
|
navigator.serviceWorker.removeEventListener('message', swListener);
|
||||||
|
setProgress({ done: 0, total: 0 });
|
||||||
modelUtil.indexModel(contextEntry);
|
modelUtil.indexModel(contextEntry);
|
||||||
setContextEntry(contextEntry);
|
setContextEntry(contextEntry);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -64,18 +73,21 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const defaultSnapshotSize = contextEntry.options.viewport || { width: 1280, height: 720 };
|
const defaultSnapshotSize = contextEntry.options.viewport || { width: 1280, height: 720 };
|
||||||
const boundaries = { minimum: contextEntry.startTime, maximum: contextEntry.endTime };
|
const boundaries = { minimum: contextEntry.startTime, maximum: contextEntry.endTime };
|
||||||
|
|
||||||
if (!traceURL) {
|
if (!traceURL || progress.total) {
|
||||||
return <div className='vbox workbench'>
|
return <div className='vbox workbench'>
|
||||||
<div className='hbox header'>
|
<div className='hbox header'>
|
||||||
<div className='logo'>🎭</div>
|
<div className='logo'>🎭</div>
|
||||||
<div className='product'>Playwright</div>
|
<div className='product'>Playwright</div>
|
||||||
<div className='spacer'></div>
|
<div className='spacer'></div>
|
||||||
</div>
|
</div>
|
||||||
<div className='drop-target'
|
{!!progress.total && <div className='progress'>
|
||||||
|
<div className='inner-progress' style={{ width: (100 * progress.done / progress.total) + '%' }}></div>
|
||||||
|
</div>}
|
||||||
|
{!progress.total && <div className='drop-target'
|
||||||
onDragOver={event => { event.preventDefault(); }}
|
onDragOver={event => { event.preventDefault(); }}
|
||||||
onDrop={event => handleDropEvent(event)}>
|
onDrop={event => handleDropEvent(event)}>
|
||||||
Drop Playwright Trace here
|
Drop Playwright Trace here
|
||||||
</div>
|
</div>}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue