/* Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import { ToolbarButton } from '@web/components/toolbarButton'; import * as React from 'react'; import type { ContextEntry } from '../entries'; import { MultiTraceModel } from './modelUtil'; import './workbenchLoader.css'; import { toggleTheme } from '@web/theme'; import { Workbench } from './workbench'; import { TestServerConnection } from '@testIsomorphic/testServerConnection'; export const WorkbenchLoader: React.FunctionComponent<{ }> = () => { const [isServer, setIsServer] = React.useState(false); const [traceURLs, setTraceURLs] = React.useState([]); const [uploadedTraceNames, setUploadedTraceNames] = React.useState([]); const [model, setModel] = React.useState(emptyModel); const [progress, setProgress] = React.useState<{ done: number, total: number }>({ done: 0, total: 0 }); const [dragOver, setDragOver] = React.useState(false); const [processingErrorMessage, setProcessingErrorMessage] = React.useState(null); const [fileForLocalModeError, setFileForLocalModeError] = React.useState(null); const processTraceFiles = React.useCallback((files: FileList) => { const blobUrls = []; const fileNames = []; const url = new URL(window.location.href); for (let i = 0; i < files.length; i++) { const file = files.item(i); if (!file) continue; const blobTraceURL = URL.createObjectURL(file); blobUrls.push(blobTraceURL); fileNames.push(file.name); url.searchParams.append('trace', blobTraceURL); url.searchParams.append('traceFileName', file.name); } const href = url.toString(); // Snapshot loaders will inherit the trace url from the query parameters, // so set it here. window.history.pushState({}, '', href); setTraceURLs(blobUrls); setUploadedTraceNames(fileNames); setDragOver(false); setProcessingErrorMessage(null); }, []); const handleDropEvent = React.useCallback((event: React.DragEvent) => { event.preventDefault(); processTraceFiles(event.dataTransfer.files); }, [processTraceFiles]); const handleFileInputChange = React.useCallback((event: any) => { event.preventDefault(); if (!event.target.files) return; processTraceFiles(event.target.files); }, [processTraceFiles]); React.useEffect(() => { const params = new URL(window.location.href).searchParams; const newTraceURLs = params.getAll('trace'); setIsServer(params.has('isServer')); // Don't accept file:// URLs - this means we re opened locally. for (const url of newTraceURLs) { if (url.startsWith('file:')) { setFileForLocalModeError(url || null); return; } } if (params.has('isServer')) { const guid = new URLSearchParams(window.location.search).get('ws'); const wsURL = new URL(`../${guid}`, window.location.toString()); wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:'); const testServerConnection = new TestServerConnection(wsURL.toString()); testServerConnection.onLoadTraceRequested(async params => { setTraceURLs(params.traceUrl ? [params.traceUrl] : []); setDragOver(false); setProcessingErrorMessage(null); }); testServerConnection.initialize({}).catch(() => {}); } else if (!newTraceURLs.some(url => url.startsWith('blob:'))) { // Don't re-use blob file URLs on page load (results in Fetch error) setTraceURLs(newTraceURLs); } }, []); React.useEffect(() => { (async () => { if (traceURLs.length) { const swListener = (event: any) => { if (event.data.method === 'progress') setProgress(event.data.params); }; navigator.serviceWorker.addEventListener('message', swListener); setProgress({ done: 0, total: 1 }); const contextEntries: ContextEntry[] = []; for (let i = 0; i < traceURLs.length; i++) { const url = traceURLs[i]; const params = new URLSearchParams(); params.set('trace', url); if (uploadedTraceNames.length) params.set('traceFileName', uploadedTraceNames[i]); const response = await fetch(`contexts?${params.toString()}`); if (!response.ok) { if (!isServer) setTraceURLs([]); setProcessingErrorMessage((await response.json()).error); return; } contextEntries.push(...(await response.json())); } navigator.serviceWorker.removeEventListener('message', swListener); const model = new MultiTraceModel(contextEntries); setProgress({ done: 0, total: 0 }); setModel(model); } else { setModel(emptyModel); } })(); }, [isServer, traceURLs, uploadedTraceNames]); const showFileUploadDropArea = !!(!isServer && !dragOver && !fileForLocalModeError && (!traceURLs.length || processingErrorMessage)); return
{ event.preventDefault(); setDragOver(true); }}>
Playwright logo
Playwright
{model.title &&
{model.title}
}
toggleTheme()}>
{fileForLocalModeError &&
Trace Viewer uses Service Workers to show traces. To view trace:
1. Click here to put your trace into the download shelf
3. Drop the trace from the download shelf into the page
} {showFileUploadDropArea &&
{processingErrorMessage}
Drop Playwright Trace to load
or
Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, it opens it locally.
} {isServer && !traceURLs.length &&
Select test to see the trace
} {dragOver &&
{ setDragOver(false); }} onDrop={event => handleDropEvent(event)}>
Release to analyse the Playwright Trace
}
; }; export const emptyModel = new MultiTraceModel([]);