chore: render time in the trace viewer log (#27825)
This commit is contained in:
parent
89e0978da4
commit
ff206bd9c1
31
packages/trace-viewer/src/ui/logTab.css
Normal file
31
packages/trace-viewer/src/ui/logTab.css
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.log-list-duration {
|
||||||
|
display: flex;
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--vscode-editorCodeLens-foreground);
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-list-item {
|
||||||
|
text-wrap: wrap;
|
||||||
|
user-select: text;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
@ -18,17 +18,51 @@ import type { ActionTraceEventInContext } from './modelUtil';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ListView } from '@web/components/listView';
|
import { ListView } from '@web/components/listView';
|
||||||
import { PlaceholderPanel } from './placeholderPanel';
|
import { PlaceholderPanel } from './placeholderPanel';
|
||||||
|
import { msToString } from '@web/uiUtils';
|
||||||
|
import './logTab.css';
|
||||||
|
|
||||||
const LogList = ListView<{ message: string, time: number }>;
|
const LogList = ListView<{ message: string, time: string }>;
|
||||||
|
|
||||||
export const LogTab: React.FunctionComponent<{
|
export const LogTab: React.FunctionComponent<{
|
||||||
action: ActionTraceEventInContext | undefined,
|
action: ActionTraceEventInContext | undefined,
|
||||||
}> = ({ action }) => {
|
isLive: boolean | undefined,
|
||||||
if (!action?.log.length)
|
}> = ({ action, isLive }) => {
|
||||||
|
const entries = React.useMemo(() => {
|
||||||
|
if (!action || !action.log.length)
|
||||||
|
return [];
|
||||||
|
const log = action.log;
|
||||||
|
const wallTimeOffset = action.wallTime - action.startTime;
|
||||||
|
const entries: { message: string, time: string }[] = [];
|
||||||
|
for (let i = 0; i < log.length; ++i) {
|
||||||
|
let time = '';
|
||||||
|
if (log[i].time !== -1) {
|
||||||
|
const timeStart = log[i]?.time;
|
||||||
|
if (i + 1 < log.length)
|
||||||
|
time = msToString(log[i + 1].time - timeStart);
|
||||||
|
else if (action.endTime > 0)
|
||||||
|
time = msToString(action.endTime - timeStart);
|
||||||
|
else if (isLive)
|
||||||
|
time = msToString(Date.now() - wallTimeOffset - timeStart);
|
||||||
|
else
|
||||||
|
time = '-';
|
||||||
|
}
|
||||||
|
entries.push({
|
||||||
|
message: log[i].message,
|
||||||
|
time,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}, [action]);
|
||||||
|
if (!entries.length)
|
||||||
return <PlaceholderPanel text='No log entries' />;
|
return <PlaceholderPanel text='No log entries' />;
|
||||||
|
|
||||||
return <LogList
|
return <LogList
|
||||||
name='log'
|
name='log'
|
||||||
items={action?.log || []}
|
items={entries}
|
||||||
render={logLine => logLine.message}
|
render={entry => <div className='log-list-item'>
|
||||||
|
<span className='log-list-duration'>{entry.time}</span>
|
||||||
|
{entry.message}
|
||||||
|
</div>}
|
||||||
|
noHighlightOnHover={true}
|
||||||
/>;
|
/>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
const logTab: TabbedPaneTabModel = {
|
const logTab: TabbedPaneTabModel = {
|
||||||
id: 'log',
|
id: 'log',
|
||||||
title: 'Log',
|
title: 'Log',
|
||||||
render: () => <LogTab action={activeAction} />
|
render: () => <LogTab action={activeAction} isLive={isLive} />
|
||||||
};
|
};
|
||||||
const errorsTab: TabbedPaneTabModel = {
|
const errorsTab: TabbedPaneTabModel = {
|
||||||
id: 'errors',
|
id: 'errors',
|
||||||
|
|
@ -199,7 +199,7 @@ export const Workbench: React.FunctionComponent<{
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
let time: number = 0;
|
let time: number = 0;
|
||||||
if (model && model.endTime >= 0)
|
if (!isLive && model && model.endTime >= 0)
|
||||||
time = model.endTime - model.startTime;
|
time = model.endTime - model.startTime;
|
||||||
else if (model && model.wallTime)
|
else if (model && model.wallTime)
|
||||||
time = Date.now() - model.wallTime;
|
time = Date.now() - model.wallTime;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export type ListViewProps<T> = {
|
||||||
onIconClicked?: (item: T, index: number) => void,
|
onIconClicked?: (item: T, index: number) => void,
|
||||||
noItemsMessage?: string,
|
noItemsMessage?: string,
|
||||||
dataTestId?: string,
|
dataTestId?: string,
|
||||||
|
noHighlightOnHover?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollPositions = new Map<string, number>();
|
const scrollPositions = new Map<string, number>();
|
||||||
|
|
@ -57,6 +58,7 @@ export function ListView<T>({
|
||||||
onIconClicked,
|
onIconClicked,
|
||||||
noItemsMessage,
|
noItemsMessage,
|
||||||
dataTestId,
|
dataTestId,
|
||||||
|
noHighlightOnHover,
|
||||||
}: ListViewProps<T>) {
|
}: ListViewProps<T>) {
|
||||||
const itemListRef = React.useRef<HTMLDivElement>(null);
|
const itemListRef = React.useRef<HTMLDivElement>(null);
|
||||||
const [highlightedItem, setHighlightedItem] = React.useState<any>();
|
const [highlightedItem, setHighlightedItem] = React.useState<any>();
|
||||||
|
|
@ -131,7 +133,7 @@ export function ListView<T>({
|
||||||
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
|
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const selectedSuffix = selectedItem === item ? ' selected' : '';
|
const selectedSuffix = selectedItem === item ? ' selected' : '';
|
||||||
const highlightedSuffix = highlightedItem === item ? ' highlighted' : '';
|
const highlightedSuffix = !noHighlightOnHover && highlightedItem === item ? ' highlighted' : '';
|
||||||
const errorSuffix = isError?.(item, index) ? ' error' : '';
|
const errorSuffix = isError?.(item, index) ? ' error' : '';
|
||||||
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
|
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
|
||||||
const indentation = indent?.(item, index) || 0;
|
const indentation = indent?.(item, index) || 0;
|
||||||
|
|
|
||||||
|
|
@ -123,10 +123,10 @@ test('should contain action info', async ({ showTraceViewer }) => {
|
||||||
const traceViewer = await showTraceViewer([traceFile]);
|
const traceViewer = await showTraceViewer([traceFile]);
|
||||||
await traceViewer.selectAction('locator.click');
|
await traceViewer.selectAction('locator.click');
|
||||||
await traceViewer.page.getByText('Log', { exact: true }).click();
|
await traceViewer.page.getByText('Log', { exact: true }).click();
|
||||||
const logLines = await traceViewer.logLines.allTextContents();
|
await expect(traceViewer.logLines).toContainText([
|
||||||
expect(logLines.length).toBeGreaterThan(10);
|
/\d+m?sattempting click action/,
|
||||||
expect(logLines).toContain('attempting click action');
|
/\d+m?s click action done/,
|
||||||
expect(logLines).toContain(' click action done');
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render network bars', async ({ page, runAndTrace, server }) => {
|
test('should render network bars', async ({ page, runAndTrace, server }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue