base 64 encode/decode urls with s3
This commit is contained in:
parent
ee8208beda
commit
7f25d1ac1a
|
|
@ -23,7 +23,7 @@
|
||||||
navigator.serviceWorker.register('sw.bundle.js');
|
navigator.serviceWorker.register('sw.bundle.js');
|
||||||
if (!navigator.serviceWorker.controller)
|
if (!navigator.serviceWorker.controller)
|
||||||
await new Promise(f => navigator.serviceWorker.oncontrollerchange = f);
|
await new Promise(f => navigator.serviceWorker.oncontrollerchange = f);
|
||||||
const traceUrl = new URL(location.href).searchParams.get('trace');
|
let traceUrl = new URL(location.href).searchParams.get('trace');
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set('trace', traceUrl);
|
params.set('trace', traceUrl);
|
||||||
await fetch('contexts?' + params.toString()).then(r => r.json());
|
await fetch('contexts?' + params.toString()).then(r => r.json());
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,11 @@ import { splitProgress } from './progress';
|
||||||
import { unwrapPopoutUrl } from './snapshotRenderer';
|
import { unwrapPopoutUrl } from './snapshotRenderer';
|
||||||
import { SnapshotServer } from './snapshotServer';
|
import { SnapshotServer } from './snapshotServer';
|
||||||
import { TraceModel } from './traceModel';
|
import { TraceModel } from './traceModel';
|
||||||
import { FetchTraceModelBackend, TraceViewerServer, ZipTraceModelBackend } from './traceModelBackends';
|
import {
|
||||||
|
FetchTraceModelBackend,
|
||||||
|
TraceViewerServer,
|
||||||
|
ZipTraceModelBackend,
|
||||||
|
} from './traceModelBackends';
|
||||||
import { TraceVersionError } from './traceModernizer';
|
import { TraceVersionError } from './traceModernizer';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -34,20 +38,47 @@ self.addEventListener('activate', function(event: any) {
|
||||||
|
|
||||||
const scopePath = new URL(self.registration.scope).pathname;
|
const scopePath = new URL(self.registration.scope).pathname;
|
||||||
|
|
||||||
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>();
|
const loadedTraces = new Map<
|
||||||
|
string,
|
||||||
|
{ traceModel: TraceModel; snapshotServer: SnapshotServer }
|
||||||
|
>();
|
||||||
|
|
||||||
const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string>, traceViewerServer: TraceViewerServer }>();
|
const clientIdToTraceUrls = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
limit: number | undefined;
|
||||||
|
traceUrls: Set<string>;
|
||||||
|
traceViewerServer: TraceViewerServer;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
async function loadTrace(traceUrl: string, traceFileName: string | null, client: any | undefined, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
|
async function loadTrace(
|
||||||
|
traceUrl: string,
|
||||||
|
traceFileName: string | null,
|
||||||
|
client: any | undefined,
|
||||||
|
limit: number | undefined,
|
||||||
|
progress: (done: number, total: number) => undefined
|
||||||
|
): Promise<TraceModel> {
|
||||||
await gc();
|
await gc();
|
||||||
const clientId = client?.id ?? '';
|
const clientId = client?.id ?? '';
|
||||||
let data = clientIdToTraceUrls.get(clientId);
|
let data = clientIdToTraceUrls.get(clientId);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
let traceViewerServerBaseUrl = new URL('../', client?.url ?? self.registration.scope);
|
let traceViewerServerBaseUrl = new URL(
|
||||||
if (traceViewerServerBaseUrl.searchParams.has('server'))
|
'../',
|
||||||
traceViewerServerBaseUrl = new URL(traceViewerServerBaseUrl.searchParams.get('server')!, traceViewerServerBaseUrl);
|
client?.url ?? self.registration.scope
|
||||||
|
);
|
||||||
|
if (traceViewerServerBaseUrl.searchParams.has('server')) {
|
||||||
|
traceViewerServerBaseUrl = new URL(
|
||||||
|
traceViewerServerBaseUrl.searchParams.get('server')!,
|
||||||
|
traceViewerServerBaseUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
data = { limit, traceUrls: new Set(), traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl) };
|
data = {
|
||||||
|
limit,
|
||||||
|
traceUrls: new Set(),
|
||||||
|
traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl),
|
||||||
|
};
|
||||||
clientIdToTraceUrls.set(clientId, data);
|
clientIdToTraceUrls.set(clientId, data);
|
||||||
}
|
}
|
||||||
data.traceUrls.add(traceUrl);
|
data.traceUrls.add(traceUrl);
|
||||||
|
|
@ -55,21 +86,49 @@ async function loadTrace(traceUrl: string, traceFileName: string | null, client:
|
||||||
const traceModel = new TraceModel();
|
const traceModel = new TraceModel();
|
||||||
try {
|
try {
|
||||||
// Allow 10% to hop from sw to page.
|
// Allow 10% to hop from sw to page.
|
||||||
const [fetchProgress, unzipProgress] = splitProgress(progress, [0.5, 0.4, 0.1]);
|
const [fetchProgress, unzipProgress] = splitProgress(
|
||||||
const backend = traceUrl.endsWith('json') ? new FetchTraceModelBackend(traceUrl, data.traceViewerServer) : new ZipTraceModelBackend(traceUrl, data.traceViewerServer, fetchProgress);
|
progress,
|
||||||
|
[0.5, 0.4, 0.1]
|
||||||
|
);
|
||||||
|
const backend = traceUrl.endsWith('json')
|
||||||
|
? new FetchTraceModelBackend(traceUrl, data.traceViewerServer)
|
||||||
|
: new ZipTraceModelBackend(
|
||||||
|
traceUrl,
|
||||||
|
data.traceViewerServer,
|
||||||
|
fetchProgress
|
||||||
|
);
|
||||||
await traceModel.load(backend, unzipProgress);
|
await traceModel.load(backend, unzipProgress);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error?.message?.includes('Cannot find .trace file') && await traceModel.hasEntry('index.html'))
|
if (
|
||||||
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.');
|
error?.message?.includes('Cannot find .trace file') &&
|
||||||
if (error instanceof TraceVersionError)
|
(await traceModel.hasEntry('index.html'))
|
||||||
throw new Error(`Could not load trace from ${traceFileName || traceUrl}. ${error.message}`);
|
) {
|
||||||
if (traceFileName)
|
throw new Error(
|
||||||
throw new Error(`Could not load trace from ${traceFileName}. Make sure to upload a valid Playwright trace.`);
|
'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.'
|
||||||
throw new Error(`Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`);
|
);
|
||||||
|
}
|
||||||
|
if (error instanceof TraceVersionError) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not load trace from ${traceFileName || traceUrl}. ${
|
||||||
|
error.message
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (traceFileName) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not load trace from ${traceFileName}. Make sure to upload a valid Playwright trace.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const snapshotServer = new SnapshotServer(traceModel.storage(), sha1 => traceModel.resourceForSha1(sha1));
|
const snapshotServer = new SnapshotServer(traceModel.storage(), sha1 =>
|
||||||
|
traceModel.resourceForSha1(sha1)
|
||||||
|
);
|
||||||
|
|
||||||
loadedTraces.set(traceUrl, { traceModel, snapshotServer });
|
loadedTraces.set(traceUrl, { traceModel, snapshotServer });
|
||||||
return traceModel;
|
return traceModel;
|
||||||
}
|
}
|
||||||
|
|
@ -98,28 +157,43 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
return new Response(null, { status: 200 });
|
return new Response(null, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const traceUrl = url.searchParams.get('trace');
|
let traceUrl = '';
|
||||||
|
try {
|
||||||
|
traceUrl = atob(url.searchParams.get('trace') ?? '');
|
||||||
|
} catch (error) {
|
||||||
|
traceUrl = url.searchParams.get('trace') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (relativePath === '/contexts') {
|
if (relativePath === '/contexts') {
|
||||||
try {
|
try {
|
||||||
const limit = url.searchParams.has('limit') ? +url.searchParams.get('limit')! : undefined;
|
const limit = url.searchParams.has('limit')
|
||||||
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), client, limit, (done: number, total: number) => {
|
? +url.searchParams.get('limit')!
|
||||||
client.postMessage({ method: 'progress', params: { done, total } });
|
: undefined;
|
||||||
});
|
const traceModel = await loadTrace(
|
||||||
|
traceUrl!,
|
||||||
|
url.searchParams.get('traceFileName'),
|
||||||
|
client,
|
||||||
|
limit,
|
||||||
|
(done: number, total: number) => {
|
||||||
|
client.postMessage({ method: 'progress', params: { done, total } });
|
||||||
|
}
|
||||||
|
);
|
||||||
return new Response(JSON.stringify(traceModel!.contextEntries), {
|
return new Response(JSON.stringify(traceModel!.contextEntries), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new Response(JSON.stringify({ error: error?.message }), {
|
return new Response(JSON.stringify({ error: error?.message }), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/snapshotInfo/')) {
|
if (relativePath.startsWith('/snapshotInfo/')) {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
|
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
return snapshotServer.serveSnapshotInfo(relativePath, url.searchParams);
|
return snapshotServer.serveSnapshotInfo(relativePath, url.searchParams);
|
||||||
|
|
@ -129,9 +203,17 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
const response = snapshotServer.serveSnapshot(relativePath, url.searchParams, url.href);
|
const response = snapshotServer.serveSnapshot(
|
||||||
if (isDeployedAsHttps)
|
relativePath,
|
||||||
response.headers.set('Content-Security-Policy', 'upgrade-insecure-requests');
|
url.searchParams,
|
||||||
|
url.href
|
||||||
|
);
|
||||||
|
if (isDeployedAsHttps) {
|
||||||
|
response.headers.set(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
'upgrade-insecure-requests'
|
||||||
|
);
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +221,10 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
const { snapshotServer } = loadedTraces.get(traceUrl!) || {};
|
||||||
if (!snapshotServer)
|
if (!snapshotServer)
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
return snapshotServer.serveClosestScreenshot(relativePath, url.searchParams);
|
return snapshotServer.serveClosestScreenshot(
|
||||||
|
relativePath,
|
||||||
|
url.searchParams
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/sha1/')) {
|
if (relativePath.startsWith('/sha1/')) {
|
||||||
|
|
@ -147,15 +232,21 @@ async function doFetch(event: FetchEvent): Promise<Response> {
|
||||||
const sha1 = relativePath.slice('/sha1/'.length);
|
const sha1 = relativePath.slice('/sha1/'.length);
|
||||||
for (const trace of loadedTraces.values()) {
|
for (const trace of loadedTraces.values()) {
|
||||||
const blob = await trace.traceModel.resourceForSha1(sha1);
|
const blob = await trace.traceModel.resourceForSha1(sha1);
|
||||||
if (blob)
|
if (blob) {
|
||||||
return new Response(blob, { status: 200, headers: downloadHeaders(url.searchParams) });
|
return new Response(blob, {
|
||||||
|
status: 200,
|
||||||
|
headers: downloadHeaders(url.searchParams),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativePath.startsWith('/file/')) {
|
if (relativePath.startsWith('/file/')) {
|
||||||
const path = url.searchParams.get('path')!;
|
const path = url.searchParams.get('path')!;
|
||||||
const traceViewerServer = clientIdToTraceUrls.get(event.clientId ?? '')?.traceViewerServer;
|
const traceViewerServer = clientIdToTraceUrls.get(
|
||||||
|
event.clientId ?? ''
|
||||||
|
)?.traceViewerServer;
|
||||||
if (!traceViewerServer)
|
if (!traceViewerServer)
|
||||||
throw new Error('client is not initialized');
|
throw new Error('client is not initialized');
|
||||||
const response = await traceViewerServer.readFile(path);
|
const response = await traceViewerServer.readFile(path);
|
||||||
|
|
@ -186,7 +277,12 @@ function downloadHeaders(searchParams: URLSearchParams): Headers | undefined {
|
||||||
if (!name)
|
if (!name)
|
||||||
return;
|
return;
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set('Content-Disposition', `attachment; filename="attachment"; filename*=UTF-8''${encodeURIComponent(name)}`);
|
headers.set(
|
||||||
|
'Content-Disposition',
|
||||||
|
`attachment; filename="attachment"; filename*=UTF-8''${encodeURIComponent(
|
||||||
|
name
|
||||||
|
)}`
|
||||||
|
);
|
||||||
if (contentType)
|
if (contentType)
|
||||||
headers.set('Content-Type', contentType);
|
headers.set('Content-Type', contentType);
|
||||||
return headers;
|
return headers;
|
||||||
|
|
@ -214,6 +310,7 @@ async function gc() {
|
||||||
if (!usedTraces.has(traceUrl))
|
if (!usedTraces.has(traceUrl))
|
||||||
loadedTraces.delete(traceUrl);
|
loadedTraces.delete(traceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
import type * as actionTypes from '@recorder/actions';
|
import type * as actionTypes from '@recorder/actions';
|
||||||
import { SourceChooser } from '@web/components/sourceChooser';
|
import { SourceChooser } from '@web/components/sourceChooser';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
|
|
@ -31,12 +32,10 @@ import type * as modelUtil from '../modelUtil';
|
||||||
import type { SourceLocation } from '../modelUtil';
|
import type { SourceLocation } from '../modelUtil';
|
||||||
import { NetworkTab, useNetworkTabModel } from '../networkTab';
|
import { NetworkTab, useNetworkTabModel } from '../networkTab';
|
||||||
import { collectSnapshots, extendSnapshot, SnapshotView } from '../snapshotTab';
|
import { collectSnapshots, extendSnapshot, SnapshotView } from '../snapshotTab';
|
||||||
import { SourceTab } from '../sourceTab';
|
|
||||||
import { ModelContext, ModelProvider } from './modelContext';
|
|
||||||
import './recorderView.css';
|
|
||||||
import { ActionListView } from './actionListView';
|
import { ActionListView } from './actionListView';
|
||||||
import { BackendContext, BackendProvider } from './backendContext';
|
import { BackendContext, BackendProvider } from './backendContext';
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
import { ModelContext, ModelProvider } from './modelContext';
|
||||||
|
import './recorderView.css';
|
||||||
|
|
||||||
export const RecorderView: React.FunctionComponent = () => {
|
export const RecorderView: React.FunctionComponent = () => {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -219,15 +218,15 @@ const PropertiesView: React.FunctionComponent<{
|
||||||
setHighlightedLocator={setHighlightedLocator} />,
|
setHighlightedLocator={setHighlightedLocator} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sourceTab: TabbedPaneTabModel = {
|
// const sourceTab: TabbedPaneTabModel = {
|
||||||
id: 'source',
|
// id: 'source',
|
||||||
title: 'Source',
|
// title: 'Source',
|
||||||
render: () => <SourceTab
|
// render: () => <SourceTab
|
||||||
sources={sourceModel.current}
|
// sources={sourceModel.current}
|
||||||
stackFrameLocation={'right'}
|
// stackFrameLocation={'right'}
|
||||||
fallbackLocation={sourceLocation}
|
// fallbackLocation={sourceLocation}
|
||||||
/>
|
// />
|
||||||
};
|
// };
|
||||||
const consoleTab: TabbedPaneTabModel = {
|
const consoleTab: TabbedPaneTabModel = {
|
||||||
id: 'console',
|
id: 'console',
|
||||||
title: 'Console',
|
title: 'Console',
|
||||||
|
|
@ -242,7 +241,6 @@ const PropertiesView: React.FunctionComponent<{
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs: TabbedPaneTabModel[] = [
|
const tabs: TabbedPaneTabModel[] = [
|
||||||
sourceTab,
|
|
||||||
inspectorTab,
|
inspectorTab,
|
||||||
consoleTab,
|
consoleTab,
|
||||||
networkTab,
|
networkTab,
|
||||||
|
|
@ -283,6 +281,7 @@ const TraceView: React.FunctionComponent<{
|
||||||
return snapshot ? extendSnapshot(snapshot) : undefined;
|
return snapshot ? extendSnapshot(snapshot) : undefined;
|
||||||
}, [snapshot]);
|
}, [snapshot]);
|
||||||
|
|
||||||
|
|
||||||
return <SnapshotView
|
return <SnapshotView
|
||||||
sdkLanguage={sdkLanguage}
|
sdkLanguage={sdkLanguage}
|
||||||
testIdAttributeName='data-testid'
|
testIdAttributeName='data-testid'
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,22 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import './snapshotTab.css';
|
import ConsoleAPI from '@injected/consoleApi';
|
||||||
import * as React from 'react';
|
import { InjectedScript } from '@injected/injectedScript';
|
||||||
|
import { Recorder } from '@injected/recorder/recorder';
|
||||||
|
import type { Language } from '@isomorphic/locatorGenerators';
|
||||||
|
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||||
|
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
||||||
|
import type { ElementInfo } from '@recorder/recorderTypes';
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent } from '@trace/trace';
|
||||||
import { context, type MultiTraceModel, prevInList } from './modelUtil';
|
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
||||||
import { Toolbar } from '@web/components/toolbar';
|
import { Toolbar } from '@web/components/toolbar';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { clsx, useMeasure } from '@web/uiUtils';
|
import { clsx, useMeasure } from '@web/uiUtils';
|
||||||
import { InjectedScript } from '@injected/injectedScript';
|
import * as React from 'react';
|
||||||
import { Recorder } from '@injected/recorder/recorder';
|
|
||||||
import ConsoleAPI from '@injected/consoleApi';
|
|
||||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
|
||||||
import type { Language } from '@isomorphic/locatorGenerators';
|
|
||||||
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
|
||||||
import { TabbedPaneTab } from '@web/components/tabbedPane';
|
|
||||||
import { BrowserFrame } from './browserFrame';
|
import { BrowserFrame } from './browserFrame';
|
||||||
import type { ElementInfo } from '@recorder/recorderTypes';
|
import { context, type MultiTraceModel, prevInList } from './modelUtil';
|
||||||
|
import './snapshotTab.css';
|
||||||
|
|
||||||
export const SnapshotTabsView: React.FunctionComponent<{
|
export const SnapshotTabsView: React.FunctionComponent<{
|
||||||
action: ActionTraceEvent | undefined,
|
action: ActionTraceEvent | undefined,
|
||||||
|
|
@ -329,7 +329,7 @@ const serverParam = new URLSearchParams(window.location.search).get('server');
|
||||||
|
|
||||||
export function extendSnapshot(snapshot: Snapshot): SnapshotUrls {
|
export function extendSnapshot(snapshot: Snapshot): SnapshotUrls {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set('trace', context(snapshot.action).traceUrl);
|
params.set('trace', btoa(context(snapshot.action).traceUrl));
|
||||||
params.set('name', snapshot.snapshotName);
|
params.set('name', snapshot.snapshotName);
|
||||||
if (isUnderTest)
|
if (isUnderTest)
|
||||||
params.set('isUnderTest', 'true');
|
params.set('isUnderTest', 'true');
|
||||||
|
|
@ -360,6 +360,7 @@ export async function fetchSnapshotInfo(snapshotInfoUrl: string | undefined) {
|
||||||
const result = { url: '', viewport: kDefaultViewport, timestamp: undefined, wallTime: undefined };
|
const result = { url: '', viewport: kDefaultViewport, timestamp: undefined, wallTime: undefined };
|
||||||
if (snapshotInfoUrl) {
|
if (snapshotInfoUrl) {
|
||||||
const response = await fetch(snapshotInfoUrl);
|
const response = await fetch(snapshotInfoUrl);
|
||||||
|
|
||||||
const info = await response.json();
|
const info = await response.json();
|
||||||
if (!info.error) {
|
if (!info.error) {
|
||||||
result.url = info.url;
|
result.url = info.url;
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,18 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { StackFrame } from '@protocol/channels';
|
||||||
|
import type { SourceHighlight } from '@web/components/codeMirrorWrapper';
|
||||||
|
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
import * as React from 'react';
|
import { Toolbar } from '@web/components/toolbar';
|
||||||
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
import { useAsyncMemo } from '@web/uiUtils';
|
import { useAsyncMemo } from '@web/uiUtils';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
|
import type { SourceLocation, SourceModel } from './modelUtil';
|
||||||
import './sourceTab.css';
|
import './sourceTab.css';
|
||||||
import { StackTraceView } from './stackTrace';
|
import { StackTraceView } from './stackTrace';
|
||||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
|
||||||
import type { SourceHighlight } from '@web/components/codeMirrorWrapper';
|
|
||||||
import type { SourceLocation, SourceModel } from './modelUtil';
|
|
||||||
import type { StackFrame } from '@protocol/channels';
|
|
||||||
import { CopyToClipboard } from './copyToClipboard';
|
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
|
||||||
import { Toolbar } from '@web/components/toolbar';
|
|
||||||
|
|
||||||
export const SourceTab: React.FunctionComponent<{
|
export const SourceTab: React.FunctionComponent<{
|
||||||
stack?: StackFrame[],
|
stack?: StackFrame[],
|
||||||
|
|
@ -98,7 +98,7 @@ export const SourceTab: React.FunctionComponent<{
|
||||||
|
|
||||||
const showStackFrames = (stack?.length ?? 0) > 1;
|
const showStackFrames = (stack?.length ?? 0) > 1;
|
||||||
const shortFileName = getFileName(fileName);
|
const shortFileName = getFileName(fileName);
|
||||||
|
return null;
|
||||||
return <SplitView
|
return <SplitView
|
||||||
sidebarSize={200}
|
sidebarSize={200}
|
||||||
orientation={stackFrameLocation === 'bottom' ? 'vertical' : 'horizontal'}
|
orientation={stackFrameLocation === 'bottom' ? 'vertical' : 'horizontal'}
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,33 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Entry } from '@trace/har';
|
||||||
|
import type { AfterActionTraceEventAttachment } from '@trace/trace';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
|
import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
||||||
|
import { TabbedPane } from '@web/components/tabbedPane';
|
||||||
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
|
import { clsx, msToString, useSetting } from '@web/uiUtils';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ActionList } from './actionList';
|
import { ActionList } from './actionList';
|
||||||
|
import { AnnotationsTab } from './annotationsTab';
|
||||||
|
import { AttachmentsTab } from './attachmentsTab';
|
||||||
import { CallTab } from './callTab';
|
import { CallTab } from './callTab';
|
||||||
import { LogTab } from './logTab';
|
|
||||||
import { ErrorsTab, useErrorsTabModel } from './errorsTab';
|
|
||||||
import type { ErrorDescription } from './errorsTab';
|
|
||||||
import type { ConsoleEntry } from './consoleTab';
|
import type { ConsoleEntry } from './consoleTab';
|
||||||
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
|
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
|
||||||
|
import type { ErrorDescription } from './errorsTab';
|
||||||
|
import { ErrorsTab, useErrorsTabModel } from './errorsTab';
|
||||||
|
import type { Boundaries } from './geometry';
|
||||||
|
import { InspectorTab } from './inspectorTab';
|
||||||
|
import { LogTab } from './logTab';
|
||||||
|
import { MetadataView } from './metadataView';
|
||||||
import type * as modelUtil from './modelUtil';
|
import type * as modelUtil from './modelUtil';
|
||||||
import { NetworkTab, useNetworkTabModel } from './networkTab';
|
import { NetworkTab, useNetworkTabModel } from './networkTab';
|
||||||
import { SnapshotTabsView } from './snapshotTab';
|
import { SnapshotTabsView } from './snapshotTab';
|
||||||
import { SourceTab } from './sourceTab';
|
|
||||||
import { TabbedPane } from '@web/components/tabbedPane';
|
|
||||||
import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
|
||||||
import { Timeline } from './timeline';
|
|
||||||
import { MetadataView } from './metadataView';
|
|
||||||
import { AttachmentsTab } from './attachmentsTab';
|
|
||||||
import { AnnotationsTab } from './annotationsTab';
|
|
||||||
import type { Boundaries } from './geometry';
|
|
||||||
import { InspectorTab } from './inspectorTab';
|
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
|
||||||
import { useSetting, msToString, clsx } from '@web/uiUtils';
|
|
||||||
import type { Entry } from '@trace/har';
|
|
||||||
import './workbench.css';
|
|
||||||
import { testStatusIcon, testStatusText } from './testUtils';
|
|
||||||
import type { UITestStatus } from './testUtils';
|
import type { UITestStatus } from './testUtils';
|
||||||
import type { AfterActionTraceEventAttachment } from '@trace/trace';
|
import { testStatusIcon, testStatusText } from './testUtils';
|
||||||
|
import { Timeline } from './timeline';
|
||||||
|
import './workbench.css';
|
||||||
|
|
||||||
export const Workbench: React.FunctionComponent<{
|
export const Workbench: React.FunctionComponent<{
|
||||||
model?: modelUtil.MultiTraceModel,
|
model?: modelUtil.MultiTraceModel,
|
||||||
|
|
@ -202,19 +201,19 @@ export const Workbench: React.FunctionComponent<{
|
||||||
if (!selectedAction && fallbackLocation)
|
if (!selectedAction && fallbackLocation)
|
||||||
fallbackSourceErrorCount = fallbackLocation.source?.errors.length;
|
fallbackSourceErrorCount = fallbackLocation.source?.errors.length;
|
||||||
|
|
||||||
const sourceTab: TabbedPaneTabModel = {
|
// const sourceTab: TabbedPaneTabModel = {
|
||||||
id: 'source',
|
// id: 'source',
|
||||||
title: 'Source',
|
// title: 'Source',
|
||||||
errorCount: fallbackSourceErrorCount,
|
// errorCount: fallbackSourceErrorCount,
|
||||||
render: () => <SourceTab
|
// render: () => <SourceTab
|
||||||
stack={revealedStack}
|
// stack={revealedStack}
|
||||||
sources={sources}
|
// sources={sources}
|
||||||
rootDir={rootDir}
|
// rootDir={rootDir}
|
||||||
stackFrameLocation={sidebarLocation === 'bottom' ? 'right' : 'bottom'}
|
// stackFrameLocation={sidebarLocation === 'bottom' ? 'right' : 'bottom'}
|
||||||
fallbackLocation={fallbackLocation}
|
// fallbackLocation={fallbackLocation}
|
||||||
onOpenExternally={onOpenExternally}
|
// onOpenExternally={onOpenExternally}
|
||||||
/>
|
// />
|
||||||
};
|
// };
|
||||||
const consoleTab: TabbedPaneTabModel = {
|
const consoleTab: TabbedPaneTabModel = {
|
||||||
id: 'console',
|
id: 'console',
|
||||||
title: 'Console',
|
title: 'Console',
|
||||||
|
|
@ -247,7 +246,6 @@ export const Workbench: React.FunctionComponent<{
|
||||||
errorsTab,
|
errorsTab,
|
||||||
consoleTab,
|
consoleTab,
|
||||||
networkTab,
|
networkTab,
|
||||||
sourceTab,
|
|
||||||
attachmentsTab,
|
attachmentsTab,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,28 @@ body.dark-mode .drop-target {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-inset {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.inner-progress {
|
.inner-progress {
|
||||||
background-color: var(--vscode-progressBar-background);
|
background-color: var(--vscode-progressBar-background);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
|
import { toggleTheme } from '@web/theme';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { ContextEntry } from '../types/entries';
|
import type { ContextEntry } from '../types/entries';
|
||||||
import { MultiTraceModel } from './modelUtil';
|
import { MultiTraceModel } from './modelUtil';
|
||||||
import './workbenchLoader.css';
|
|
||||||
import { toggleTheme } from '@web/theme';
|
|
||||||
import { Workbench } from './workbench';
|
import { Workbench } from './workbench';
|
||||||
import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection';
|
import './workbenchLoader.css';
|
||||||
|
|
||||||
export const WorkbenchLoader: React.FunctionComponent<{
|
export const WorkbenchLoader: React.FunctionComponent<{
|
||||||
}> = () => {
|
}> = () => {
|
||||||
|
|
@ -166,6 +166,9 @@ export const WorkbenchLoader: React.FunctionComponent<{
|
||||||
<div className='progress'>
|
<div className='progress'>
|
||||||
<div className='inner-progress' style={{ width: progress.total ? (100 * progress.done / progress.total) + '%' : 0 }}></div>
|
<div className='inner-progress' style={{ width: progress.total ? (100 * progress.done / progress.total) + '%' : 0 }}></div>
|
||||||
</div>
|
</div>
|
||||||
|
{progress.done < progress.total && <div className='loading-inset'>
|
||||||
|
<div className='spinner'></div>
|
||||||
|
</div>}
|
||||||
<Workbench model={model} inert={showFileUploadDropArea} />
|
<Workbench model={model} inert={showFileUploadDropArea} />
|
||||||
{fileForLocalModeError && <div className='drop-target'>
|
{fileForLocalModeError && <div className='drop-target'>
|
||||||
<div>Trace Viewer uses Service Workers to show traces. To view trace:</div>
|
<div>Trace Viewer uses Service Workers to show traces. To view trace:</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue