From 3918e33c91fe4ced5f64bbc76a6ae43cbb2b6b76 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 10 Jan 2023 18:33:20 +0100 Subject: [PATCH] chore: trace viewer UX (auto scroll to action + timeline duration) (#20001) Fixes https://github.com/microsoft/playwright/issues/19916 --- packages/trace-viewer/src/ui/actionList.tsx | 95 ++++++++++++++------- packages/trace-viewer/src/ui/timeline.tsx | 4 + 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index fc203976d9..40d7138a28 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -72,42 +72,75 @@ export const ActionList: React.FC = ({ 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 &&
No actions recorded
} - {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
onSelected(action)} - onMouseEnter={() => onHighlighted(action)} - onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)} - > -
- {metadata.apiName} - {locator &&
{locator}
} - {metadata.method === 'goto' && metadata.params.url &&
{metadata.params.url}
} -
-
{metadata.endTime ? msToString(metadata.endTime - metadata.startTime) : 'Timed Out'}
-
setSelectedTab('console')}> - {!!errors &&
{errors}
} - {!!warnings &&
{warnings}
} -
- {error &&
} -
; - })} + {actions.map(action => )}
; }; + +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(null); + + React.useEffect(() => { + if (divRef.current && selectedAction === action) + scrollIntoViewIfNeeded(divRef.current); + }, [selectedAction, action]); + + return
onSelected(action)} + onMouseEnter={() => onHighlighted(action)} + onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)} + ref={divRef} + > +
+ {metadata.apiName} + {locator &&
{locator}
} + {metadata.method === 'goto' && metadata.params.url &&
{metadata.params.url}
} +
+
{metadata.endTime ? msToString(metadata.endTime - metadata.startTime) : 'Timed Out'}
+
setSelectedTab('console')}> + {!!errors &&
{errors}
} + {!!warnings &&
{warnings}
} +
+ {error &&
} +
; +}; + +function scrollIntoViewIfNeeded(element?: Element | null) { + if (!element) + return; + if ((element as any)?.scrollIntoViewIfNeeded) + (element as any).scrollIntoViewIfNeeded(false); + else + element?.scrollIntoView(); +} diff --git a/packages/trace-viewer/src/ui/timeline.tsx b/packages/trace-viewer/src/ui/timeline.tsx index f23b4e8445..227cb728f5 100644 --- a/packages/trace-viewer/src/ui/timeline.tsx +++ b/packages/trace-viewer/src/ui/timeline.tsx @@ -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} >
; }) }