Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions packages/trace-viewer/src/sw/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function loadTrace(clientId: string, url: URL, isContextRequest: boolean, progre
return loadedTrace;
const promise = innerLoadTrace(traceUrl, progress);
loadedTraces.set(traceUrl, promise);
promise.catch(() => loadedTraces.delete(traceUrl));
return promise;
}

Expand All @@ -119,7 +120,14 @@ async function innerLoadTrace(traceUrl: string, progress: Progress): Promise<Loa
throw new Error('Could not load trace. Did you upload a Playwright HTML report instead? Make sure to extract the archive first and then double-click the index.html file or put it on a web server.');
if (error instanceof TraceVersionError)
throw new Error(`Could not load trace from ${traceUrl}. ${error.message}`);
throw new Error(`Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`);

let message = `Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`;

const lnaPermission = await navigator.permissions.query({ name: 'local-network-access' as PermissionName }).catch(() => { });
if (lnaPermission && lnaPermission.state !== 'granted')
message += `\n\nIf your trace is in a local or private network, please grant permission for Local Network Access.`; // workbenchLoader.tsx opens the prompt when it sees this message.

throw new Error(message);
}
const snapshotServer = new SnapshotServer(traceLoader.storage(), sha1 => traceLoader.resourceForSha1(sha1));
return { traceLoader, snapshotServer };
Expand All @@ -132,12 +140,6 @@ async function doFetch(event: FetchEvent): Promise<Response> {
if (request.url.startsWith('chrome-extension://'))
return fetch(request);

if (request.headers.get('x-pw-serviceworker') === 'forward') {
const request = new Request(event.request);
request.headers.delete('x-pw-serviceworker');
return fetch(request);
}

const url = new URL(request.url);
let relativePath: string | undefined;
if (request.url.startsWith(self.registration.scope))
Expand Down Expand Up @@ -282,5 +284,8 @@ function isLiveTrace(traceUrl: string): boolean {
}

self.addEventListener('fetch', function(event: FetchEvent) {
if (event.request.headers.get('x-pw-serviceworker') === 'skip')
return false;

event.respondWith(doFetch(event));
});
1 change: 1 addition & 0 deletions packages/trace-viewer/src/ui/workbenchLoader.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ body .drop-target {
font-weight: bold;
text-align: center;
margin: 30px;
white-space: pre-line;
}

.drop-target input {
Expand Down
36 changes: 24 additions & 12 deletions packages/trace-viewer/src/ui/workbenchLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,22 @@ export const WorkbenchLoader: React.FunctionComponent<{
}
}, []);

const fetchTrace = React.useCallback(async (traceURL: string): Promise<string | undefined> => {
const params = new URLSearchParams();
params.set('trace', traceURL);
const response = await fetch(`contexts?${params.toString()}`);
if (!response.ok) {
const { error } = await response.json();
setProcessingErrorMessage(error);
return error;
}
const contextEntries = await response.json();
const model = new TraceModel(traceURL, contextEntries);
setProgress({ done: 0, total: 0 });
setProcessingErrorMessage(null);
setModel(model);
}, []);

React.useEffect(() => {
(async () => {
if (!traceURL) {
Expand All @@ -138,25 +154,21 @@ export const WorkbenchLoader: React.FunctionComponent<{
try {
navigator.serviceWorker.addEventListener('message', swListener);
setProgress({ done: 0, total: 1 });

const params = new URLSearchParams();
params.set('trace', traceURL);
const response = await fetch(`contexts?${params.toString()}`);
if (!response.ok) {
let error = await fetchTrace(traceURL);
if (error?.includes('please grant permission for Local Network Access')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We control the type, let's return a dedicated status. error: { message: string, lanError?: boolean }.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a pretty big code change, let's discuss it tonight

// fetching the asset opens the permission prompt. but only from window, not from SW (https://issues.chromium.org/issues/460180743)
await fetch(traceURL, { method: 'HEAD', headers: { 'x-pw-serviceworker': 'skip' } });
error = await fetchTrace(traceURL);
}
if (error) {
if (!isServer)
setTraceURL(undefined);
setProcessingErrorMessage((await response.json()).error);
return;
}
const contextEntries = await response.json();
const model = new TraceModel(traceURL, contextEntries);
setProgress({ done: 0, total: 0 });
setModel(model);
} finally {
navigator.serviceWorker.removeEventListener('message', swListener);
}
})();
}, [isServer, traceURL, uploadedTraceName]);
}, [isServer, traceURL, uploadedTraceName, fetchTrace]);

const showLoading = progress.done !== progress.total && progress.total !== 0 && !processingErrorMessage;

Expand Down
Loading