mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 05:37:20 +03:00
chore: trace viewer UX (auto scroll to action + timeline duration) (#20001)
Fixes https://github.com/microsoft/playwright/issues/19916
This commit is contained in:
parent
0fe327c21b
commit
3918e33c91
@ -72,42 +72,75 @@ export const ActionList: React.FC<ActionListProps> = ({
|
||||
newIndex = Math.max(index - 1, 0);
|
||||
}
|
||||
const element = actionListRef.current?.children.item(newIndex);
|
||||
if ((element as any)?.scrollIntoViewIfNeeded)
|
||||
(element as any).scrollIntoViewIfNeeded(false);
|
||||
else
|
||||
element?.scrollIntoView();
|
||||
scrollIntoViewIfNeeded(element);
|
||||
onSelected(actions[newIndex]);
|
||||
}}
|
||||
ref={actionListRef}
|
||||
>
|
||||
{actions.length === 0 && <div className='no-actions-entry'>No actions recorded</div>}
|
||||
{actions.map(action => {
|
||||
const { metadata } = action;
|
||||
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
||||
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
|
||||
const error = metadata.error?.error?.message;
|
||||
const { errors, warnings } = modelUtil.stats(action);
|
||||
const locator = metadata.params.selector ? asLocator(sdkLanguage || 'javascript', metadata.params.selector) : undefined;
|
||||
return <div
|
||||
className={'action-entry' + selectedSuffix + highlightedSuffix}
|
||||
key={metadata.id}
|
||||
onClick={() => onSelected(action)}
|
||||
onMouseEnter={() => onHighlighted(action)}
|
||||
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
|
||||
>
|
||||
<div className='action-title'>
|
||||
<span>{metadata.apiName}</span>
|
||||
{locator && <div className='action-selector' title={locator}>{locator}</div>}
|
||||
{metadata.method === 'goto' && metadata.params.url && <div className='action-url' title={metadata.params.url}>{metadata.params.url}</div>}
|
||||
</div>
|
||||
<div className='action-duration' style={{ flex: 'none' }}>{metadata.endTime ? msToString(metadata.endTime - metadata.startTime) : 'Timed Out'}</div>
|
||||
<div className='action-icons' onClick={() => setSelectedTab('console')}>
|
||||
{!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>}
|
||||
{!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>}
|
||||
</div>
|
||||
{error && <div className='codicon codicon-issues' title={error} />}
|
||||
</div>;
|
||||
})}
|
||||
{actions.map(action => <ActionListItem
|
||||
action={action}
|
||||
highlightedAction={highlightedAction}
|
||||
onSelected={onSelected}
|
||||
onHighlighted={onHighlighted}
|
||||
selectedAction={selectedAction}
|
||||
sdkLanguage={sdkLanguage}
|
||||
setSelectedTab={setSelectedTab}
|
||||
/>)}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const ActionListItem: React.FC<{
|
||||
action: ActionTraceEvent,
|
||||
highlightedAction: ActionTraceEvent | undefined,
|
||||
onSelected: (action: ActionTraceEvent) => void,
|
||||
onHighlighted: (action: ActionTraceEvent | undefined) => void,
|
||||
selectedAction: ActionTraceEvent | undefined,
|
||||
sdkLanguage: Language | undefined,
|
||||
setSelectedTab: (tab: string) => void,
|
||||
}> = ({ action, onSelected, onHighlighted, highlightedAction, selectedAction, sdkLanguage, setSelectedTab }) => {
|
||||
const { metadata } = action;
|
||||
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
||||
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
|
||||
const error = metadata.error?.error?.message;
|
||||
const { errors, warnings } = modelUtil.stats(action);
|
||||
const locator = metadata.params.selector ? asLocator(sdkLanguage || 'javascript', metadata.params.selector) : undefined;
|
||||
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (divRef.current && selectedAction === action)
|
||||
scrollIntoViewIfNeeded(divRef.current);
|
||||
}, [selectedAction, action]);
|
||||
|
||||
return <div
|
||||
className={'action-entry' + selectedSuffix + highlightedSuffix}
|
||||
key={metadata.id}
|
||||
onClick={() => onSelected(action)}
|
||||
onMouseEnter={() => onHighlighted(action)}
|
||||
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
|
||||
ref={divRef}
|
||||
>
|
||||
<div className='action-title'>
|
||||
<span>{metadata.apiName}</span>
|
||||
{locator && <div className='action-selector' title={locator}>{locator}</div>}
|
||||
{metadata.method === 'goto' && metadata.params.url && <div className='action-url' title={metadata.params.url}>{metadata.params.url}</div>}
|
||||
</div>
|
||||
<div className='action-duration' style={{ flex: 'none' }}>{metadata.endTime ? msToString(metadata.endTime - metadata.startTime) : 'Timed Out'}</div>
|
||||
<div className='action-icons' onClick={() => setSelectedTab('console')}>
|
||||
{!!errors && <div className='action-icon'><span className={'codicon codicon-error'}></span><span className="action-icon-value">{errors}</span></div>}
|
||||
{!!warnings && <div className='action-icon'><span className={'codicon codicon-warning'}></span><span className="action-icon-value">{warnings}</span></div>}
|
||||
</div>
|
||||
{error && <div className='codicon codicon-issues' title={error} />}
|
||||
</div>;
|
||||
};
|
||||
|
||||
function scrollIntoViewIfNeeded(element?: Element | null) {
|
||||
if (!element)
|
||||
return;
|
||||
if ((element as any)?.scrollIntoViewIfNeeded)
|
||||
(element as any).scrollIntoViewIfNeeded(false);
|
||||
else
|
||||
element?.scrollIntoView();
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ type TimelineBar = {
|
||||
rightTime: number;
|
||||
type: string;
|
||||
label: string;
|
||||
title: string;
|
||||
className: string;
|
||||
};
|
||||
|
||||
@ -67,6 +68,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
leftPosition: timeToPosition(measure.width, boundaries, entry.metadata.startTime),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, entry.metadata.endTime),
|
||||
label: entry.metadata.apiName + ' ' + detail,
|
||||
title: entry.metadata.endTime ? msToString(entry.metadata.endTime - entry.metadata.startTime) : 'Timed Out',
|
||||
type: entry.metadata.type + '.' + entry.metadata.method,
|
||||
className: `${entry.metadata.type}_${entry.metadata.method}`.toLowerCase()
|
||||
});
|
||||
@ -81,6 +83,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
leftPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||
rightPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||
label: event.metadata.method,
|
||||
title: event.metadata.endTime ? msToString(event.metadata.endTime - event.metadata.startTime) : 'Timed Out',
|
||||
type: event.metadata.type + '.' + event.metadata.method,
|
||||
className: `${event.metadata.type}_${event.metadata.method}`.toLowerCase()
|
||||
});
|
||||
@ -183,6 +186,7 @@ export const Timeline: React.FunctionComponent<{
|
||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||
top: barTop(bar) + 'px',
|
||||
}}
|
||||
title={bar.title}
|
||||
></div>;
|
||||
})
|
||||
}</div>
|
||||
|
Loading…
Reference in New Issue
Block a user