chore(traceviewer): drop intermediate iframe, add drop handler (#9455)
This commit is contained in:
parent
617380ffee
commit
cdfe075b2a
|
|
@ -113,6 +113,18 @@ export class HttpServer {
|
|||
}
|
||||
|
||||
private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||
response.setHeader('Access-Control-Allow-Origin', '*');
|
||||
response.setHeader('Access-Control-Request-Method', '*');
|
||||
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
|
||||
if (request.headers.origin)
|
||||
response.setHeader('Access-Control-Allow-Headers', request.headers.origin);
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
response.writeHead(200);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
request.on('error', () => response.end());
|
||||
try {
|
||||
if (!request.url) {
|
||||
|
|
|
|||
|
|
@ -220,6 +220,23 @@ function snapshotScript() {
|
|||
element.scrollLeft = +element.getAttribute(scrollLeftAttribute)!;
|
||||
element.removeAttribute(scrollLeftAttribute);
|
||||
}
|
||||
|
||||
const search = new URL(window.location.href).searchParams;
|
||||
const pointX = search.get('pointX');
|
||||
const pointY = search.get('pointY');
|
||||
if (pointX) {
|
||||
const pointElement = document.createElement('x-pw-pointer');
|
||||
pointElement.style.position = 'fixed';
|
||||
pointElement.style.backgroundColor = 'red';
|
||||
pointElement.style.width = '20px';
|
||||
pointElement.style.height = '20px';
|
||||
pointElement.style.borderRadius = '10px';
|
||||
pointElement.style.margin = '-10px 0 0 -10px';
|
||||
pointElement.style.zIndex = '2147483647';
|
||||
pointElement.style.left = pointX + 'px';
|
||||
pointElement.style.top = pointY + 'px';
|
||||
document.documentElement.appendChild(pointElement);
|
||||
}
|
||||
};
|
||||
window.addEventListener('load', onLoad);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,36 +30,6 @@ export class SnapshotServer {
|
|||
this._snapshotStorage = snapshotStorage;
|
||||
}
|
||||
|
||||
static serveSnapshotRoot(): Response {
|
||||
return new Response(`
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<script>
|
||||
(${rootScript})();
|
||||
</script>
|
||||
</body>
|
||||
`, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000',
|
||||
'Content-Type': 'text/html'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
serveSnapshot(pathname: string, searchParams: URLSearchParams, snapshotUrl: string): Response {
|
||||
const snapshot = this._snapshot(pathname.substring('/snapshot'.length), searchParams);
|
||||
if (!snapshot)
|
||||
|
|
@ -132,33 +102,6 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
function rootScript() {
|
||||
const pointElement = document.createElement('div');
|
||||
pointElement.style.position = 'fixed';
|
||||
pointElement.style.backgroundColor = 'red';
|
||||
pointElement.style.width = '20px';
|
||||
pointElement.style.height = '20px';
|
||||
pointElement.style.borderRadius = '10px';
|
||||
pointElement.style.margin = '-10px 0 0 -10px';
|
||||
pointElement.style.zIndex = '2147483647';
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
(window as any).showSnapshot = async (url: string, options: { point?: Point } = {}) => {
|
||||
iframe.src = url;
|
||||
if (options.point) {
|
||||
pointElement.style.left = options.point.x + 'px';
|
||||
pointElement.style.top = options.point.y + 'px';
|
||||
document.documentElement.appendChild(pointElement);
|
||||
} else {
|
||||
pointElement.remove();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', event => {
|
||||
window.showSnapshot(window.location.href + event.data.snapshotUrl);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function removeHash(url: string) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ let snapshotServer: SnapshotServer | undefined;
|
|||
|
||||
async function loadTrace(trace: string): Promise<TraceModel> {
|
||||
const traceModel = new TraceModel();
|
||||
const url = trace.startsWith('http') ? trace : `/file?path=${trace}`;
|
||||
const url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `/file?path=${trace}`;
|
||||
await traceModel.load(url);
|
||||
return traceModel;
|
||||
}
|
||||
|
|
@ -53,8 +53,6 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
|||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
if (pathname === '/snapshot/')
|
||||
return SnapshotServer.serveSnapshotRoot();
|
||||
if (pathname.startsWith('/snapshotSize/'))
|
||||
return snapshotServer!.serveSnapshotSize(pathname, searchParams);
|
||||
if (pathname.startsWith('/snapshot/'))
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class TraceModel {
|
|||
}
|
||||
|
||||
async load(traceURL: string) {
|
||||
const response = await fetch(traceURL);
|
||||
const response = await fetch(traceURL, { mode: 'cors' });
|
||||
const blob = await response.blob();
|
||||
const zipReader = new zipjs.ZipReader(new zipjs.BlobReader(blob), { useWebWorkers: false }) as zip.ZipReader;
|
||||
let traceEntry: zip.Entry | undefined;
|
||||
|
|
|
|||
|
|
@ -124,13 +124,8 @@
|
|||
}
|
||||
|
||||
.no-actions-entry {
|
||||
height: 400px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-actions-entry-text {
|
||||
font-weight: bold;
|
||||
font-size: 1.3rem;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,16 +72,7 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||
}}
|
||||
ref={actionListRef}
|
||||
>
|
||||
{actions.length === 0 && <div className='no-actions-entry'>
|
||||
<div>
|
||||
<div className='no-actions-entry-text'>
|
||||
No actions recorded
|
||||
</div>
|
||||
<div>
|
||||
Make sure that the right context was used when recording the trace.
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
{actions.length === 0 && <div className='no-actions-entry'>No actions recorded</div>}
|
||||
{actions.map(action => {
|
||||
const { metadata } = action;
|
||||
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
||||
|
|
|
|||
|
|
@ -66,8 +66,7 @@ export const SnapshotTab: React.FunctionComponent<{
|
|||
if (!iframeRef.current)
|
||||
return;
|
||||
try {
|
||||
const point = pointX === undefined ? undefined : { x: pointX, y: pointY };
|
||||
(iframeRef.current.contentWindow as any).showSnapshot(snapshotUrl, { point });
|
||||
iframeRef.current.src = snapshotUrl + (pointX === undefined ? '' : `&pointX=${pointX}&pointY=${pointY}`);
|
||||
} catch (e) {
|
||||
}
|
||||
})();
|
||||
|
|
@ -102,7 +101,7 @@ export const SnapshotTab: React.FunctionComponent<{
|
|||
height: snapshotSize.height + 'px',
|
||||
transform: `translate(${-snapshotSize.width * (1 - scale) / 2 + (measure.width - scaledSize.width) / 2}px, ${-snapshotSize.height * (1 - scale) / 2 + (measure.height - scaledSize.height) / 2}px) scale(${scale})`,
|
||||
}}>
|
||||
<iframe ref={iframeRef} id='snapshot' name='snapshot' src='/snapshot/'></iframe>
|
||||
<iframe ref={iframeRef} id='snapshot' name='snapshot'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
|
|
|||
|
|
@ -31,19 +31,19 @@ import * as modelUtil from './modelUtil';
|
|||
|
||||
export const Workbench: React.FunctionComponent<{
|
||||
}> = () => {
|
||||
const [traceURL, setTraceURL] = React.useState<string>(new URL(window.location.href).searchParams.get('trace')!);
|
||||
const [contextEntry, setContextEntry] = React.useState<ContextEntry>(emptyContext);
|
||||
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [selectedTab, setSelectedTab] = React.useState<string>('logs');
|
||||
const trace = new URL(window.location.href).searchParams.get('trace');
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const contextEntry = (await fetch(`/context?trace=${trace}`).then(response => response.json())) as ContextEntry;
|
||||
const contextEntry = (await fetch(`/context?trace=${traceURL}`).then(response => response.json())) as ContextEntry;
|
||||
modelUtil.indexModel(contextEntry);
|
||||
setContextEntry(contextEntry);
|
||||
})();
|
||||
}, [trace]);
|
||||
}, [traceURL]);
|
||||
|
||||
const actions = React.useMemo(() => {
|
||||
const actions: ActionTraceEvent[] = [];
|
||||
|
|
@ -61,7 +61,13 @@ export const Workbench: React.FunctionComponent<{
|
|||
const consoleCount = errors + warnings;
|
||||
const networkCount = selectedAction ? modelUtil.resourcesForAction(selectedAction).length : 0;
|
||||
|
||||
return <div className='vbox workbench'>
|
||||
return <div className='vbox workbench'
|
||||
onDragOver={event => { event.preventDefault(); }}
|
||||
onDrop={event => {
|
||||
event.preventDefault();
|
||||
const url = URL.createObjectURL(event.dataTransfer.files[0]);
|
||||
setTraceURL(url.toString());
|
||||
}}>
|
||||
<div className='hbox header'>
|
||||
<div className='logo'>🎭</div>
|
||||
<div className='product'>Playwright</div>
|
||||
|
|
|
|||
|
|
@ -84,9 +84,9 @@ class TraceViewerPage {
|
|||
|
||||
async snapshotFrame(actionName: string, ordinal: number = 0, hasSubframe: boolean = false): Promise<Frame> {
|
||||
await this.selectAction(actionName, ordinal);
|
||||
while (this.page.frames().length < (hasSubframe ? 4 : 3))
|
||||
while (this.page.frames().length < (hasSubframe ? 3 : 2))
|
||||
await this.page.waitForEvent('frameattached');
|
||||
return this.page.mainFrame().childFrames()[0].childFrames()[0];
|
||||
return this.page.mainFrame().childFrames()[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue