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
	 Pavel Feldman
						Pavel Feldman