mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00: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,28 +72,55 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||||||
newIndex = Math.max(index - 1, 0);
|
newIndex = Math.max(index - 1, 0);
|
||||||
}
|
}
|
||||||
const element = actionListRef.current?.children.item(newIndex);
|
const element = actionListRef.current?.children.item(newIndex);
|
||||||
if ((element as any)?.scrollIntoViewIfNeeded)
|
scrollIntoViewIfNeeded(element);
|
||||||
(element as any).scrollIntoViewIfNeeded(false);
|
|
||||||
else
|
|
||||||
element?.scrollIntoView();
|
|
||||||
onSelected(actions[newIndex]);
|
onSelected(actions[newIndex]);
|
||||||
}}
|
}}
|
||||||
ref={actionListRef}
|
ref={actionListRef}
|
||||||
>
|
>
|
||||||
{actions.length === 0 && <div className='no-actions-entry'>No actions recorded</div>}
|
{actions.length === 0 && <div className='no-actions-entry'>No actions recorded</div>}
|
||||||
{actions.map(action => {
|
{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 { metadata } = action;
|
||||||
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
const selectedSuffix = action === selectedAction ? ' selected' : '';
|
||||||
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
|
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
|
||||||
const error = metadata.error?.error?.message;
|
const error = metadata.error?.error?.message;
|
||||||
const { errors, warnings } = modelUtil.stats(action);
|
const { errors, warnings } = modelUtil.stats(action);
|
||||||
const locator = metadata.params.selector ? asLocator(sdkLanguage || 'javascript', metadata.params.selector) : undefined;
|
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
|
return <div
|
||||||
className={'action-entry' + selectedSuffix + highlightedSuffix}
|
className={'action-entry' + selectedSuffix + highlightedSuffix}
|
||||||
key={metadata.id}
|
key={metadata.id}
|
||||||
onClick={() => onSelected(action)}
|
onClick={() => onSelected(action)}
|
||||||
onMouseEnter={() => onHighlighted(action)}
|
onMouseEnter={() => onHighlighted(action)}
|
||||||
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
|
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
|
||||||
|
ref={divRef}
|
||||||
>
|
>
|
||||||
<div className='action-title'>
|
<div className='action-title'>
|
||||||
<span>{metadata.apiName}</span>
|
<span>{metadata.apiName}</span>
|
||||||
@ -107,7 +134,13 @@ export const ActionList: React.FC<ActionListProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{error && <div className='codicon codicon-issues' title={error} />}
|
{error && <div className='codicon codicon-issues' title={error} />}
|
||||||
</div>;
|
</div>;
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</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;
|
rightTime: number;
|
||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
title: string;
|
||||||
className: string;
|
className: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
leftPosition: timeToPosition(measure.width, boundaries, entry.metadata.startTime),
|
leftPosition: timeToPosition(measure.width, boundaries, entry.metadata.startTime),
|
||||||
rightPosition: timeToPosition(measure.width, boundaries, entry.metadata.endTime),
|
rightPosition: timeToPosition(measure.width, boundaries, entry.metadata.endTime),
|
||||||
label: entry.metadata.apiName + ' ' + detail,
|
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,
|
type: entry.metadata.type + '.' + entry.metadata.method,
|
||||||
className: `${entry.metadata.type}_${entry.metadata.method}`.toLowerCase()
|
className: `${entry.metadata.type}_${entry.metadata.method}`.toLowerCase()
|
||||||
});
|
});
|
||||||
@ -81,6 +83,7 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
leftPosition: timeToPosition(measure.width, boundaries, startTime),
|
leftPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||||
rightPosition: timeToPosition(measure.width, boundaries, startTime),
|
rightPosition: timeToPosition(measure.width, boundaries, startTime),
|
||||||
label: event.metadata.method,
|
label: event.metadata.method,
|
||||||
|
title: event.metadata.endTime ? msToString(event.metadata.endTime - event.metadata.startTime) : 'Timed Out',
|
||||||
type: event.metadata.type + '.' + event.metadata.method,
|
type: event.metadata.type + '.' + event.metadata.method,
|
||||||
className: `${event.metadata.type}_${event.metadata.method}`.toLowerCase()
|
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',
|
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
||||||
top: barTop(bar) + 'px',
|
top: barTop(bar) + 'px',
|
||||||
}}
|
}}
|
||||||
|
title={bar.title}
|
||||||
></div>;
|
></div>;
|
||||||
})
|
})
|
||||||
}</div>
|
}</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user