1- import type { RunnerTestCase , TestArtifact } from 'vitest'
1+ import type { RunnerTestCase , RunnerTestFile , TestArtifact } from 'vitest'
22import type { BrowserTraceData , BrowserTraceEntry } from '../../../browser/src/client/tester/trace'
33import { ref , watch , watchEffect } from 'vue'
44import { browserState , client , config } from './client'
@@ -8,6 +8,15 @@ import { selectedTest } from './params'
88export interface TraceSelection {
99 test : RunnerTestCase
1010 attemptKey ?: string
11+ selectedStepIndex : number
12+ }
13+
14+ export interface TraceEditorMarker {
15+ file : string
16+ line : number
17+ stepIndex : number
18+ entry : BrowserTraceEntry
19+ active ?: boolean
1120}
1221
1322export const activeTraceView = ref < TraceSelection > ( )
@@ -85,26 +94,93 @@ export function getSelectedTrace(selection: TraceSelection): BrowserTraceData |
8594 : Object . values ( attempts ) [ 0 ]
8695}
8796
97+ export function getTraceEditorMarkersForFile (
98+ selection : TraceSelection ,
99+ file : string ,
100+ ) : TraceEditorMarker [ ] {
101+ const trace = getSelectedTrace ( selection )
102+ return getTraceEditorMarkers ( trace ?. entries ?? [ ] )
103+ . filter ( marker => marker . file === file )
104+ . map ( marker => ( {
105+ ...marker ,
106+ active : marker . stepIndex === selection . selectedStepIndex ,
107+ } ) )
108+ }
109+
110+ function getTraceEditorMarkers ( entries : BrowserTraceEntry [ ] ) : TraceEditorMarker [ ] {
111+ const markers : TraceEditorMarker [ ] = [ ]
112+ const seen = new Set < string > ( )
113+
114+ for ( const [ stepIndex , entry ] of entries . entries ( ) ) {
115+ const location = entry . location
116+ if ( ! location ) {
117+ continue
118+ }
119+
120+ const key = `${ location . file } :${ location . line } `
121+ if ( seen . has ( key ) ) {
122+ continue
123+ }
124+
125+ seen . add ( key )
126+ markers . push ( {
127+ file : location . file ,
128+ line : location . line ,
129+ stepIndex,
130+ entry,
131+ } )
132+ }
133+
134+ return markers
135+ }
136+
137+ export function getTraceEntryClass ( entry : BrowserTraceEntry ) {
138+ if ( entry . range ?. phase === 'start' ) {
139+ return 'text-yellow-500'
140+ }
141+ if ( entry . status === 'fail' ) {
142+ return 'text-red-500'
143+ }
144+ if ( entry . kind === 'action' ) {
145+ return 'text-blue-500'
146+ }
147+ if ( entry . kind === 'expect' ) {
148+ return 'text-green-500'
149+ }
150+ if ( entry . kind === 'mark' ) {
151+ return 'text-amber-500'
152+ }
153+ return 'text-gray-400 dark:text-gray-500'
154+ }
155+
88156export function openTrace ( trace : BrowserTraceData , test : RunnerTestCase ) {
89157 detailsPosition . value = 'bottom'
90158 activeTraceView . value = {
91159 test,
92160 attemptKey : getTraceAttemptKey ( trace ) ,
161+ selectedStepIndex : 0 ,
93162 }
94163}
95164
96165export function closeTrace ( ) {
97166 activeTraceView . value = undefined
98167}
99168
169+ export function selectActiveTraceStep ( index : number ) {
170+ const selection = activeTraceView . value
171+ if ( selection ) {
172+ selection . selectedStepIndex = index
173+ }
174+ }
175+
100176// Open/close only on selected-test navigation so the close button can clear the
101177// trace view without being auto-opened again for the same selected test.
102178watch ( selectedTest , ( testId ) => {
103179 if ( testId ) {
104180 const test = client . state . idMap . get ( testId )
105- if ( test ?. type === 'test' && isTraceViewEnabled ( test ) ) {
181+ if ( test ?. type === 'test' && isTraceViewEnabled ( test . file ) ) {
106182 // Auto-open trace view when selecting a trace-enabled test.
107- activeTraceView . value = { test }
183+ activeTraceView . value = { test, selectedStepIndex : 0 }
108184 return
109185 }
110186 }
@@ -122,12 +198,12 @@ watchEffect(() => {
122198 const test = client . state . idMap . get ( testId )
123199 if ( test ?. type === 'test' && active . test !== test ) {
124200 // Rerun produced a fresh test object; reset attempt selection.
125- activeTraceView . value = { test }
201+ activeTraceView . value = { test, selectedStepIndex : 0 }
126202 }
127203 }
128204} )
129205
130- function isTraceViewEnabled ( test : RunnerTestCase ) : boolean {
206+ export function isTraceViewEnabled ( test : RunnerTestFile ) : boolean {
131207 const project = getProjectConfigByTest ( test )
132208 const traceView
133209 = browserState ?. config . browser ?. traceView
@@ -136,7 +212,7 @@ function isTraceViewEnabled(test: RunnerTestCase): boolean {
136212 return traceView ?. enabled ?? false
137213}
138214
139- function getProjectConfigByTest ( test : RunnerTestCase ) {
215+ function getProjectConfigByTest ( test : RunnerTestFile ) {
140216 const projectName = test . file . projectName || ''
141217 return config . value . projects ?. find ( project => project . name === projectName )
142218}
0 commit comments