chore: preserve selected trace action in live trace (#32630)

This commit is contained in:
Pavel Feldman 2024-09-16 17:33:52 -07:00 committed by GitHub
parent 2a347b5494
commit 3bff7b6ab1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 40 deletions

View File

@ -22,7 +22,7 @@ import { renderAction } from './actionList';
import type { Language } from '@isomorphic/locatorGenerators';
import type { StackFrame } from '@protocol/channels';
type ErrorDescription = {
export type ErrorDescription = {
action?: modelUtil.ActionTraceEventInContext;
stack?: StackFrame[];
};

View File

@ -342,10 +342,6 @@ export function buildActionTree(actions: ActionTraceEventInContext[]): { rootIte
return { rootItem, itemMap };
}
export function idForAction(action: ActionTraceEvent) {
return `${action.pageId || 'none'}:${action.callId}`;
}
export function context(action: ActionTraceEvent | trace.EventTraceEvent | ResourceSnapshot): ContextEntry {
return (action as any)[contextSymbol];
}

View File

@ -16,14 +16,13 @@
import { artifactsFolderName } from '@testIsomorphic/folders';
import type { TreeItem } from '@testIsomorphic/testTree';
import type { ActionTraceEvent } from '@trace/trace';
import '@web/common.css';
import '@web/third_party/vscode/codicon.css';
import type * as reporterTypes from 'playwright/types/testReporter';
import React from 'react';
import type { ContextEntry } from '../entries';
import type { SourceLocation } from './modelUtil';
import { idForAction, MultiTraceModel } from './modelUtil';
import { MultiTraceModel } from './modelUtil';
import { Workbench } from './workbench';
export const TraceView: React.FC<{
@ -42,12 +41,6 @@ export const TraceView: React.FC<{
return { outputDir };
}, [item]);
// Preserve user selection upon live-reloading trace model by persisting the action id.
// This avoids auto-selection of the last action every time we reload the model.
const [selectedActionId, setSelectedActionId] = React.useState<string | undefined>();
const onSelectionChanged = React.useCallback((action: ActionTraceEvent) => setSelectedActionId(idForAction(action)), [setSelectedActionId]);
const initialSelection = selectedActionId ? model?.model.actions.find(a => idForAction(a) === selectedActionId) : undefined;
React.useEffect(() => {
if (pollTimer.current)
clearTimeout(pollTimer.current);
@ -98,8 +91,6 @@ export const TraceView: React.FC<{
model={model?.model}
showSourcesFirst={true}
rootDir={rootDir}
initialSelection={initialSelection}
onSelectionChanged={onSelectionChanged}
fallbackLocation={item.testFile}
isLive={model?.isLive}
status={item.treeItem?.status}

View File

@ -20,11 +20,11 @@ import { ActionList } from './actionList';
import { CallTab } from './callTab';
import { LogTab } from './logTab';
import { ErrorsTab, useErrorsTabModel } from './errorsTab';
import type { ErrorDescription } from './errorsTab';
import type { ConsoleEntry } from './consoleTab';
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
import type * as modelUtil from './modelUtil';
import { isRouteAction } from './modelUtil';
import type { StackFrame } from '@protocol/channels';
import { NetworkTab, useNetworkTabModel } from './networkTab';
import { SnapshotTab } from './snapshotTab';
import { SourceTab } from './sourceTab';
@ -49,8 +49,6 @@ export const Workbench: React.FunctionComponent<{
showSourcesFirst?: boolean,
rootDir?: string,
fallbackLocation?: modelUtil.SourceLocation,
initialSelection?: modelUtil.ActionTraceEventInContext,
onSelectionChanged?: (action: modelUtil.ActionTraceEventInContext) => void,
isLive?: boolean,
status?: UITestStatus,
annotations?: { type: string; description?: string; }[];
@ -59,9 +57,10 @@ export const Workbench: React.FunctionComponent<{
onOpenExternally?: (location: modelUtil.SourceLocation) => void,
revealSource?: boolean,
showSettings?: boolean,
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, annotations, inert, openPage, onOpenExternally, revealSource, showSettings }) => {
const [selectedAction, setSelectedActionImpl] = React.useState<modelUtil.ActionTraceEventInContext | undefined>(undefined);
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, isLive, status, annotations, inert, openPage, onOpenExternally, revealSource, showSettings }) => {
const [selectedCallId, setSelectedCallId] = React.useState<string | undefined>(undefined);
const [revealedError, setRevealedError] = React.useState<ErrorDescription | undefined>(undefined);
const [highlightedAction, setHighlightedAction] = React.useState<modelUtil.ActionTraceEventInContext | undefined>();
const [highlightedEntry, setHighlightedEntry] = React.useState<Entry | undefined>();
const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>();
@ -69,38 +68,39 @@ export const Workbench: React.FunctionComponent<{
const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('propertiesTab', showSourcesFirst ? 'source' : 'call');
const [isInspecting, setIsInspectingState] = React.useState(false);
const [highlightedLocator, setHighlightedLocator] = React.useState<string>('');
const activeAction = model ? highlightedAction || selectedAction : undefined;
const [selectedTime, setSelectedTime] = React.useState<Boundaries | undefined>();
const [sidebarLocation, setSidebarLocation] = useSetting<'bottom' | 'right'>('propertiesSidebarLocation', 'bottom');
const [showRouteActions, setShowRouteActions] = useSetting('show-route-actions', true);
const [showScreenshot, setShowScreenshot] = useSetting('screenshot-instead-of-snapshot', false);
const filteredActions = React.useMemo(() => {
return (model?.actions || []).filter(action => showRouteActions || !isRouteAction(action));
}, [model, showRouteActions]);
const setSelectedAction = React.useCallback((action: modelUtil.ActionTraceEventInContext | undefined) => {
setSelectedActionImpl(action);
setRevealedStack(action?.stack);
}, [setSelectedActionImpl, setRevealedStack]);
setSelectedCallId(action?.callId);
setRevealedError(undefined);
}, []);
const sources = React.useMemo(() => model?.sources || new Map<string, modelUtil.SourceModel>(), [model]);
React.useEffect(() => {
setSelectedTime(undefined);
setRevealedStack(undefined);
setRevealedError(undefined);
}, [model]);
React.useEffect(() => {
if (selectedAction && model?.actions.includes(selectedAction))
return;
const selectedAction = React.useMemo(() => {
if (selectedCallId) {
const action = model?.actions.find(a => a.callId === selectedCallId);
if (action)
return action;
}
const failedAction = model?.failedAction();
if (initialSelection && model?.actions.includes(initialSelection)) {
setSelectedAction(initialSelection);
} else if (failedAction) {
setSelectedAction(failedAction);
} else if (model?.actions.length) {
if (failedAction)
return failedAction;
if (model?.actions.length) {
// Select the last non-after hooks item.
let index = model.actions.length - 1;
for (let i = 0; i < model.actions.length; ++i) {
@ -109,15 +109,24 @@ export const Workbench: React.FunctionComponent<{
break;
}
}
setSelectedAction(model.actions[index]);
return model.actions[index];
}
}, [model, selectedAction, setSelectedAction, initialSelection]);
}, [model, selectedCallId]);
const revealedStack = React.useMemo(() => {
if (revealedError)
return revealedError.stack;
return selectedAction?.stack;
}, [selectedAction, revealedError]);
const activeAction = React.useMemo(() => {
return highlightedAction || selectedAction;
}, [selectedAction, highlightedAction]);
const onActionSelected = React.useCallback((action: modelUtil.ActionTraceEventInContext) => {
setSelectedAction(action);
setHighlightedAction(undefined);
onSelectionChanged?.(action);
}, [setSelectedAction, onSelectionChanged, setHighlightedAction]);
}, [setSelectedAction, setHighlightedAction]);
const selectPropertiesTab = React.useCallback((tab: string) => {
setSelectedPropertiesTab(tab);
@ -177,7 +186,7 @@ export const Workbench: React.FunctionComponent<{
if (error.action)
setSelectedAction(error.action);
else
setRevealedStack(error.stack);
setRevealedError(error);
selectPropertiesTab('source');
}} />
};