Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add time information to Call and Network tabs in Trace Viewer #33935

Merged
merged 8 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
72 changes: 45 additions & 27 deletions packages/trace-viewer/src/ui/callTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,50 +27,68 @@ import type { ActionTraceEventInContext } from './modelUtil';

export const CallTab: React.FunctionComponent<{
action: ActionTraceEventInContext | undefined,
executionStartTime: number,
executionStartWallTime: number,
sdkLanguage: Language | undefined,
}> = ({ action, sdkLanguage }) => {
}> = ({ action, executionStartTime, executionStartWallTime, sdkLanguage }) => {
// We never need the waitForEventInfo (`info`).
const paramKeys = React.useMemo(() => Object.keys(action?.params ?? {}).filter(name => name !== 'info'), [action]);

if (!action)
return <PlaceholderPanel text='No action selected' />;
const params = { ...action.params };
// Strip down the waitForEventInfo data, we never need it.
delete params.info;
const paramKeys = Object.keys(params);
const timeMillis = action.startTime + (action.context.wallTime - action.context.startTime);
const wallTime = new Date(timeMillis).toLocaleString();

// Calculate execution time relative to the test runner's start time
const startTimeMillis = action.startTime - executionStartTime;
const startTime = msToString(startTimeMillis);

const wallTimeMillis = startTimeMillis + executionStartWallTime;
agg23 marked this conversation as resolved.
Show resolved Hide resolved
const wallTime = new Date(wallTimeMillis).toLocaleString(undefined, { timeZoneName: 'short' });

const duration = action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out';

return <div className='call-tab'>
<div className='call-line'>{action.apiName}</div>
{<>
<div className='call-section'>Time</div>
{wallTime && <div className='call-line'>wall time:<span className='call-value datetime' title={wallTime}>{wallTime}</span></div>}
<div className='call-line'>duration:<span className='call-value datetime' title={duration}>{duration}</span></div>
</>}
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }
{
!!paramKeys.length && paramKeys.map((name, index) => renderProperty(propertyToString(action, name, params[name], sdkLanguage), 'param-' + index))
}
{ !!action.result && <div className='call-section'>Return value</div> }
{
!!action.result && Object.keys(action.result).map((name, index) =>
renderProperty(propertyToString(action, name, action.result[name], sdkLanguage), 'result-' + index)
)
}
</div>;
return (
<div className='call-tab'>
<div className='call-line'>{action.apiName}</div>
{
<>
<div className='call-section'>Time</div>
{wallTime && <DateTimeCallLine name='wall time:' value={wallTime}/>}
agg23 marked this conversation as resolved.
Show resolved Hide resolved
<DateTimeCallLine name='start:' value={startTime}/>
<DateTimeCallLine name='duration:' value={duration}/>
</>
}
{
!!paramKeys.length && <>
<div className='call-section'>Parameters</div>
{paramKeys.map(name => renderProperty(propertyToString(action, name, action.params[name], sdkLanguage)))}
</>
}
{
!!action.result && <>
<div className='call-section'>Return value</div>
{Object.keys(action.result).map(name =>
renderProperty(propertyToString(action, name, action.result[name], sdkLanguage))
)}
</>
}
</div>
);
};

const DateTimeCallLine: React.FC<{ name: string, value: string }> = ({ name, value }) => <div className='call-line'>{name}<span className='call-value datetime' title={value}>{value}</span></div>;

type Property = {
name: string;
type: 'string' | 'number' | 'object' | 'locator' | 'handle' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'function';
text: string;
};

function renderProperty(property: Property, key: string) {
function renderProperty(property: Property) {
let text = property.text.replace(/\n/g, '↵');
if (property.type === 'string')
text = `"${text}"`;
return (
<div key={key} className='call-line'>
<div key={property.name} className='call-line'>
{property.name}:<span className={clsx('call-value', property.type)} title={property.text}>{text}</span>
{ ['string', 'number', 'object', 'locator'].includes(property.type) &&
<CopyToClipboard value={property.text} />
Expand Down
4 changes: 3 additions & 1 deletion packages/trace-viewer/src/ui/metadataView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ export const MetadataView: React.FunctionComponent<{
if (!model)
return <></>;

const wallTime = model.wallTime !== undefined ? new Date(model.wallTime).toLocaleString(undefined, { timeZoneName: 'short' }) : undefined;

return <div data-testid='metadata-view' className='vbox' style={{ flexShrink: 0 }}>
<div className='call-section' style={{ paddingTop: 2 }}>Time</div>
{!!model.wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={new Date(model.wallTime).toLocaleString()}>{new Date(model.wallTime).toLocaleString()}</span></div>}
{!!wallTime && <div className='call-line'>start time:<span className='call-value datetime' title={wallTime}>{wallTime}</span></div>}
<div className='call-line'>duration:<span className='call-value number' title={msToString(model.endTime - model.startTime)}>{msToString(model.endTime - model.startTime)}</span></div>
<div className='call-section'>Browser</div>
<div className='call-line'>engine:<span className='call-value string' title={model.browserName}>{model.browserName}</span></div>
Expand Down
17 changes: 13 additions & 4 deletions packages/trace-viewer/src/ui/networkResourceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import { generateCurlCommand, generateFetchCall } from '../third_party/devtools'
import { CopyToClipboardTextButton } from './copyToClipboard';
import { getAPIRequestCodeGen } from './codegen';
import type { Language } from '@isomorphic/locatorGenerators';
import { msToString } from '@web/uiUtils';

export const NetworkResourceDetails: React.FunctionComponent<{
resource: ResourceSnapshot;
onClose: () => void;
sdkLanguage: Language;
}> = ({ resource, onClose, sdkLanguage }) => {
startTimeOffset: number;
onClose: () => void;
}> = ({ resource, sdkLanguage, startTimeOffset, onClose }) => {
const [selectedTab, setSelectedTab] = React.useState('request');

return <TabbedPane
Expand All @@ -39,7 +41,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
{
id: 'request',
title: 'Request',
render: () => <RequestTab resource={resource} sdkLanguage={sdkLanguage} />,
render: () => <RequestTab resource={resource} sdkLanguage={sdkLanguage} startTimeOffset={startTimeOffset} />,
},
{
id: 'response',
Expand All @@ -59,9 +61,12 @@ export const NetworkResourceDetails: React.FunctionComponent<{
const RequestTab: React.FunctionComponent<{
resource: ResourceSnapshot;
sdkLanguage: Language;
}> = ({ resource, sdkLanguage }) => {
startTimeOffset: number;
}> = ({ resource, sdkLanguage, startTimeOffset }) => {
const [requestBody, setRequestBody] = React.useState<{ text: string, mimeType?: string } | null>(null);

const wallTimeString = React.useMemo(() => resource.startedDateTime.length > 0 ? new Date(resource.startedDateTime).toLocaleString(undefined, { timeZoneName: 'short' }) : '-', [resource.startedDateTime]);

React.useEffect(() => {
const readResources = async () => {
if (resource.request.postData) {
Expand Down Expand Up @@ -94,6 +99,10 @@ const RequestTab: React.FunctionComponent<{
{resource.request.queryString.map(param => `${param.name}: ${param.value}`).join('\n')}
</div>
</> : null}
<div className='network-request-details-header'>Time</div>
agg23 marked this conversation as resolved.
Show resolved Hide resolved
<div className='network-request-details-general'>{`Wall Time: ${wallTimeString}`}</div>
<div className='network-request-details-general'>{`Start: ${msToString(startTimeOffset)}`}</div>
<div className='network-request-details-general'>{`Duration: ${msToString(resource.time)}`}</div>
<div className='network-request-details-header'>Request Headers</div>
<div className='network-request-details-headers'>{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}</div>

Expand Down
2 changes: 1 addition & 1 deletion packages/trace-viewer/src/ui/networkTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const NetworkTab: React.FunctionComponent<{
sidebarIsFirst={true}
orientation='horizontal'
settingName='networkResourceDetails'
main={<NetworkResourceDetails resource={selectedEntry.resource} onClose={() => setSelectedEntry(undefined)} sdkLanguage={sdkLanguage} />}
main={<NetworkResourceDetails resource={selectedEntry.resource} sdkLanguage={sdkLanguage} startTimeOffset={selectedEntry.start} onClose={() => setSelectedEntry(undefined)} />}
sidebar={grid}
/>}
</>;
Expand Down
2 changes: 1 addition & 1 deletion packages/trace-viewer/src/ui/workbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const Workbench: React.FunctionComponent<{
const callTab: TabbedPaneTabModel = {
id: 'call',
title: 'Call',
render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} />
render: () => <CallTab action={activeAction} executionStartTime={model?.startTime ?? 0} executionStartWallTime={model?.wallTime ?? Date.now()} sdkLanguage={sdkLanguage} />
agg23 marked this conversation as resolved.
Show resolved Hide resolved
};
const logTab: TabbedPaneTabModel = {
id: 'log',
Expand Down
3 changes: 3 additions & 0 deletions tests/library/trace-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ test('should show params and return value', async ({ showTraceViewer }) => {
await expect(traceViewer.callLines).toHaveText([
/page.evaluate/,
/wall time:[0-9/:,APM ]+/,
/start:[\d\.]+m?s/,
/duration:[\d]+ms/,
/expression:"\({↵ a↵ }\) => {↵ console\.log\(\'Info\'\);↵ console\.warn\(\'Warning\'\);↵ console/,
'isFunction:true',
Expand All @@ -252,6 +253,7 @@ test('should show params and return value', async ({ showTraceViewer }) => {
await expect(traceViewer.callLines).toContainText([
/expect.toHaveText/,
/wall time:[0-9/:,APM ]+/,
/start:[\d\.]+m?s/,
/duration:[\d]+ms/,
/locator:locator\('button'\)/,
/expression:"to.have.text"/,
Expand All @@ -267,6 +269,7 @@ test('should show null as a param', async ({ showTraceViewer, browserName }) =>
await expect(traceViewer.callLines).toHaveText([
/page.evaluate/,
/wall time:[0-9/:,APM ]+/,
/start:[\d\.]+m?s/,
/duration:[\d]+ms/,
'expression:"() => 1 + 1"',
'isFunction:true',
Expand Down
Loading