mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: preserve selected trace action in live trace (#32630)
This commit is contained in:
parent
2a347b5494
commit
3bff7b6ab1
@ -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[];
|
||||
};
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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');
|
||||
}} />
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user