playwright/packages/trace-viewer/src/ui/uiModeTraceView.tsx

119 lines
3.9 KiB
TypeScript

/**
* 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 { artifactsFolderName } from '@testIsomorphic/folders';
import type { TreeItem } from '@testIsomorphic/testTree';
import '@web/common.css';
import '@web/third_party/vscode/codicon.css';
import type * as reporterTypes from 'playwright/types/testReporter';
import React from 'react';
import type { ContextEntry } from '../types/entries';
import type { SourceLocation } from './modelUtil';
import { MultiTraceModel } from './modelUtil';
import { Workbench } from './workbench';
export const TraceView: React.FC<{
item: { treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase },
rootDir?: string,
onOpenExternally?: (location: SourceLocation) => void,
revealSource?: boolean,
pathSeparator: string,
}> = ({ item, rootDir, onOpenExternally, revealSource, pathSeparator }) => {
const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean } | undefined>();
const [counter, setCounter] = React.useState(0);
const pollTimer = React.useRef<NodeJS.Timeout | null>(null);
const { outputDir } = React.useMemo(() => {
const outputDir = item.testCase ? outputDirForTestCase(item.testCase) : undefined;
return { outputDir };
}, [item]);
React.useEffect(() => {
if (pollTimer.current)
clearTimeout(pollTimer.current);
const result = item.testCase?.results[0];
if (!result) {
setModel(undefined);
return;
}
// Test finished.
const attachment = result && result.duration >= 0 && result.attachments.find(a => a.name === 'trace');
if (attachment && attachment.path) {
loadSingleTraceFile(attachment.path).then(model => setModel({ model, isLive: false }));
return;
}
if (!outputDir) {
setModel(undefined);
return;
}
const traceLocation = [
outputDir,
artifactsFolderName(result!.workerIndex),
'traces',
`${item.testCase?.id}.json`
].join(pathSeparator);
// Start polling running test.
pollTimer.current = setTimeout(async () => {
try {
const model = await loadSingleTraceFile(traceLocation);
setModel({ model, isLive: true });
} catch {
setModel(undefined);
} finally {
setCounter(counter + 1);
}
}, 500);
return () => {
if (pollTimer.current)
clearTimeout(pollTimer.current);
};
}, [outputDir, item, setModel, counter, setCounter, pathSeparator]);
return <Workbench
key='workbench'
model={model?.model}
showSourcesFirst={true}
rootDir={rootDir}
fallbackLocation={item.testFile}
isLive={model?.isLive}
status={item.treeItem?.status}
annotations={item.testCase?.annotations || []}
onOpenExternally={onOpenExternally}
revealSource={revealSource}
/>;
};
const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefined => {
for (let suite: reporterTypes.Suite | undefined = testCase.parent; suite; suite = suite.parent) {
if (suite.project())
return suite.project()?.outputDir;
}
return undefined;
};
async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> {
const params = new URLSearchParams();
params.set('trace', url);
params.set('limit', '1');
const response = await fetch(`contexts?${params.toString()}`);
const contextEntries = await response.json() as ContextEntry[];
return new MultiTraceModel(contextEntries);
}