@@ -305,78 +305,175 @@ function Visualizer(props: VisualizerProps): JSX.Element {
305305}
306306
307307export function App ( ) {
308+ /**
309+ * Parse attributes from a dump script element.
310+ */
311+ function parseAttributesFromElement ( el : Element ) : PlaywrightTaskAttributes {
312+ const attributes : Partial < PlaywrightTaskAttributes > & Record < string , any > =
313+ {
314+ playwright_test_description : '' ,
315+ playwright_test_id : '' ,
316+ playwright_test_title : '' ,
317+ playwright_test_status : undefined ,
318+ playwright_test_duration : 0 ,
319+ } ;
320+ Array . from ( el . attributes ) . forEach ( ( attr ) => {
321+ const { name, value } = attr ;
322+ const valueDecoded = decodeURIComponent ( value ) ;
323+ if ( name . startsWith ( 'playwright_' ) ) {
324+ if ( name === 'playwright_test_duration' ) {
325+ attributes [ name ] = Number ( valueDecoded ) || 0 ;
326+ } else {
327+ attributes [ name ] = valueDecoded ;
328+ }
329+ }
330+ } ) ;
331+ return attributes as PlaywrightTaskAttributes ;
332+ }
333+
334+ /**
335+ * Build a PlaywrightTasks entry from a single dump element (original behavior).
336+ */
337+ function buildPlaywrightTaskFromElement ( el : Element ) : PlaywrightTasks {
338+ const attributes = parseAttributesFromElement ( el ) ;
339+ let cachedJsonContent : GroupedActionDump | null = null ;
340+ let isParsed = false ;
341+
342+ return {
343+ get : ( ) => {
344+ if ( ! isParsed ) {
345+ try {
346+ console . time ( 'parse_dump' ) ;
347+ const content = antiEscapeScriptTag ( el . textContent || '' ) ;
348+ const parsed = JSON . parse ( content ) ;
349+ const restored = restoreImageReferences (
350+ parsed ,
351+ resolveImageFromDom ,
352+ ) ;
353+ cachedJsonContent = GroupedActionDump . fromJSON ( restored ) ;
354+ console . timeEnd ( 'parse_dump' ) ;
355+ ( cachedJsonContent as any ) . attributes = attributes ;
356+ isParsed = true ;
357+ } catch ( e ) {
358+ console . error ( el ) ;
359+ console . error ( 'failed to parse json content' , e ) ;
360+ cachedJsonContent = new GroupedActionDump ( {
361+ sdkVersion : '' ,
362+ groupName : '' ,
363+ modelBriefs : [ ] ,
364+ executions : [ ] ,
365+ } ) ;
366+ ( cachedJsonContent as any ) . attributes = attributes ;
367+ ( cachedJsonContent as any ) . error = 'Failed to parse JSON content' ;
368+ isParsed = true ;
369+ }
370+ }
371+ return cachedJsonContent ! ;
372+ } ,
373+ attributes,
374+ } ;
375+ }
376+
308377 function getDumpElements ( ) : PlaywrightTasks [ ] {
309378 const dumpElements = document . querySelectorAll (
310379 'script[type="midscene_web_dump"]' ,
311380 ) ;
312- const reportDump : PlaywrightTasks [ ] = [ ] ;
313- Array . from ( dumpElements )
314- . filter ( ( el ) => {
315- const textContent = el . textContent ;
316- if ( ! textContent ) {
317- console . warn ( 'empty content in script tag' , el ) ;
381+ const validElements = Array . from ( dumpElements ) . filter ( ( el ) => {
382+ const textContent = el . textContent ;
383+ if ( ! textContent ) {
384+ console . warn ( 'empty content in script tag' , el ) ;
385+ }
386+ return ! ! textContent ;
387+ } ) ;
388+
389+ // Group elements by data-group-id
390+ const groupMap = new Map < string , Element [ ] > ( ) ;
391+ const ungrouped : Element [ ] = [ ] ;
392+
393+ for ( const el of validElements ) {
394+ const groupId = el . getAttribute ( 'data-group-id' ) ;
395+ if ( groupId ) {
396+ const decodedGroupId = decodeURIComponent ( groupId ) ;
397+ if ( ! groupMap . has ( decodedGroupId ) ) {
398+ groupMap . set ( decodedGroupId , [ ] ) ;
318399 }
319- return ! ! textContent ;
320- } )
321- . forEach ( ( el ) => {
322- const attributes : Partial < PlaywrightTaskAttributes > &
323- Record < string , any > = {
324- playwright_test_description : '' ,
325- playwright_test_id : '' ,
326- playwright_test_title : '' ,
327- playwright_test_status : undefined ,
328- playwright_test_duration : 0 ,
329- } ;
330- Array . from ( el . attributes ) . forEach ( ( attr ) => {
331- const { name, value } = attr ;
332- const valueDecoded = decodeURIComponent ( value ) ;
333- if ( name . startsWith ( 'playwright_' ) ) {
334- if ( name === 'playwright_test_duration' ) {
335- attributes [ name ] = Number ( valueDecoded ) || 0 ;
336- } else {
337- attributes [ name ] = valueDecoded ;
338- }
339- }
340- } ) ;
400+ groupMap . get ( decodedGroupId ) ! . push ( el ) ;
401+ } else {
402+ ungrouped . push ( el ) ;
403+ }
404+ }
341405
342- // Lazy loading: Store raw content and parse only when get() is called
343- let cachedJsonContent : GroupedActionDump | null = null ;
344- let isParsed = false ;
406+ const result : PlaywrightTasks [ ] = [ ] ;
345407
346- reportDump . push ( {
347- get : ( ) => {
348- if ( ! isParsed ) {
349- try {
350- console . time ( 'parse_dump' ) ;
351- const content = antiEscapeScriptTag ( el . textContent || '' ) ;
408+ // Process grouped dump tags — merge into one PlaywrightTasks per group
409+ for ( const [ , elements ] of groupMap ) {
410+ const attributes = parseAttributesFromElement ( elements [ 0 ] ) ;
411+ let cachedJsonContent : GroupedActionDump | null = null ;
412+ let isParsed = false ;
413+
414+ result . push ( {
415+ get : ( ) => {
416+ if ( ! isParsed ) {
417+ try {
418+ console . time ( 'parse_grouped_dump' ) ;
419+ const allExecutions : any [ ] = [ ] ;
420+ let baseDump : GroupedActionDump | null = null ;
352421
422+ for ( const el of elements ) {
423+ const content = antiEscapeScriptTag ( el . textContent || '' ) ;
353424 const parsed = JSON . parse ( content ) ;
354425 const restored = restoreImageReferences (
355426 parsed ,
356427 resolveImageFromDom ,
357428 ) ;
358- cachedJsonContent = GroupedActionDump . fromJSON ( restored ) ;
359-
360- console . timeEnd ( 'parse_dump' ) ;
361- ( cachedJsonContent as any ) . attributes = attributes ;
362- isParsed = true ;
363- } catch ( e ) {
364- console . error ( el ) ;
365- console . error ( 'failed to parse json content' , e ) ;
366- // Return a fallback object to prevent crashes
367- cachedJsonContent = {
368- attributes,
369- error : 'Failed to parse JSON content' ,
370- } as any ;
371- isParsed = true ;
429+ const dump = GroupedActionDump . fromJSON ( restored ) ;
430+ if ( ! baseDump ) {
431+ baseDump = dump ;
432+ }
433+ allExecutions . push ( ...dump . executions ) ;
434+ }
435+
436+ if ( baseDump ) {
437+ baseDump . executions = allExecutions ;
438+ cachedJsonContent = baseDump ;
439+ } else {
440+ cachedJsonContent = new GroupedActionDump ( {
441+ sdkVersion : '' ,
442+ groupName : '' ,
443+ modelBriefs : [ ] ,
444+ executions : [ ] ,
445+ } ) ;
372446 }
447+
448+ console . timeEnd ( 'parse_grouped_dump' ) ;
449+ ( cachedJsonContent as any ) . attributes = attributes ;
450+ isParsed = true ;
451+ } catch ( e ) {
452+ console . error ( 'failed to parse grouped dump content' , e ) ;
453+ cachedJsonContent = new GroupedActionDump ( {
454+ sdkVersion : '' ,
455+ groupName : '' ,
456+ modelBriefs : [ ] ,
457+ executions : [ ] ,
458+ } ) ;
459+ ( cachedJsonContent as any ) . attributes = attributes ;
460+ ( cachedJsonContent as any ) . error =
461+ 'Failed to parse grouped JSON content' ;
462+ isParsed = true ;
373463 }
374- return cachedJsonContent ;
375- } ,
376- attributes : attributes as PlaywrightTaskAttributes ,
377- } ) ;
464+ }
465+ return cachedJsonContent ! ;
466+ } ,
467+ attributes ,
378468 } ) ;
379- return reportDump ;
469+ }
470+
471+ // Process ungrouped dump tags — original behavior (backward compatible)
472+ for ( const el of ungrouped ) {
473+ result . push ( buildPlaywrightTaskFromElement ( el ) ) ;
474+ }
475+
476+ return result ;
380477 }
381478
382479 const [ reportDump , setReportDump ] = useState < PlaywrightTasks [ ] > ( [ ] ) ;
0 commit comments