@@ -36,14 +36,49 @@ function extractMetricsFromRequest(req: Request): MetricItem[] {
3636 }
3737}
3838
39- function isElementTimingMetricRequest ( req : Request ) : boolean {
40- if ( ! req . url ( ) . includes ( '/api/1337/envelope/' ) ) return false ;
41- const metrics = extractMetricsFromRequest ( req ) ;
42- return metrics . some ( m => m . name . startsWith ( 'element_timing.' ) ) ;
43- }
39+ /**
40+ * Collects element timing metrics from envelope requests on the page.
41+ * Returns a function to get all collected metrics so far and a function
42+ * that waits until all expected identifiers have been seen in render_time metrics.
43+ */
44+ function createMetricCollector ( page : Page ) {
45+ const collectedRequests : Request [ ] = [ ] ;
46+
47+ page . on ( 'request' , req => {
48+ if ( ! req . url ( ) . includes ( '/api/1337/envelope/' ) ) return ;
49+ const metrics = extractMetricsFromRequest ( req ) ;
50+ if ( metrics . some ( m => m . name . startsWith ( 'element_timing.' ) ) ) {
51+ collectedRequests . push ( req ) ;
52+ }
53+ } ) ;
54+
55+ function getAll ( ) : MetricItem [ ] {
56+ return collectedRequests . flatMap ( req => extractMetricsFromRequest ( req ) ) ;
57+ }
58+
59+ async function waitForIdentifiers ( identifiers : string [ ] , timeout = 30_000 ) : Promise < void > {
60+ const deadline = Date . now ( ) + timeout ;
61+ while ( Date . now ( ) < deadline ) {
62+ const all = getAll ( ) . filter ( m => m . name === 'element_timing.render_time' ) ;
63+ const seen = new Set ( all . map ( m => m . attributes [ 'element.identifier' ] ?. value ) ) ;
64+ if ( identifiers . every ( id => seen . has ( id ) ) ) {
65+ return ;
66+ }
67+ await page . waitForTimeout ( 500 ) ;
68+ }
69+ // Final check with assertion for clear error message
70+ const all = getAll ( ) . filter ( m => m . name === 'element_timing.render_time' ) ;
71+ const seen = all . map ( m => m . attributes [ 'element.identifier' ] ?. value ) ;
72+ for ( const id of identifiers ) {
73+ expect ( seen ) . toContain ( id ) ;
74+ }
75+ }
4476
45- function waitForElementTimingMetrics ( page : Page ) : Promise < Request > {
46- return page . waitForRequest ( req => isElementTimingMetricRequest ( req ) , { timeout : 15_000 } ) ;
77+ function reset ( ) : void {
78+ collectedRequests . length = 0 ;
79+ }
80+
81+ return { getAll, waitForIdentifiers, reset } ;
4782}
4883
4984sentryTest (
@@ -56,32 +91,23 @@ sentryTest(
5691 serveAssets ( page ) ;
5792
5893 const url = await getLocalTestUrl ( { testDir : __dirname } ) ;
59-
60- // Collect all metric requests
61- const allMetricRequests : Request [ ] = [ ] ;
62- page . on ( 'request' , req => {
63- if ( req . url ( ) . includes ( '/api/1337/envelope/' ) ) {
64- const metrics = extractMetricsFromRequest ( req ) ;
65- if ( metrics . some ( m => m . name . startsWith ( 'element_timing.' ) ) ) {
66- allMetricRequests . push ( req ) ;
67- }
68- }
69- } ) ;
94+ const collector = createMetricCollector ( page ) ;
7095
7196 await page . goto ( url ) ;
7297
73- // Wait for at least one element timing metric envelope to arrive
74- await waitForElementTimingMetrics ( page ) ;
98+ // Wait until all expected element identifiers have been flushed as metrics
99+ await collector . waitForIdentifiers ( [
100+ 'image-fast' ,
101+ 'text1' ,
102+ 'button1' ,
103+ 'image-slow' ,
104+ 'lazy-image' ,
105+ 'lazy-text' ,
106+ ] ) ;
75107
76- // Wait a bit more for slow images and lazy content + flush interval
77- await page . waitForTimeout ( 8000 ) ;
78-
79- // Extract all element timing metrics from all collected requests
80- const allMetrics = allMetricRequests . flatMap ( req => extractMetricsFromRequest ( req ) ) ;
81- const elementTimingMetrics = allMetrics . filter ( m => m . name . startsWith ( 'element_timing.' ) ) ;
82-
83- const renderTimeMetrics = elementTimingMetrics . filter ( m => m . name === 'element_timing.render_time' ) ;
84- const loadTimeMetrics = elementTimingMetrics . filter ( m => m . name === 'element_timing.load_time' ) ;
108+ const allMetrics = collector . getAll ( ) . filter ( m => m . name . startsWith ( 'element_timing.' ) ) ;
109+ const renderTimeMetrics = allMetrics . filter ( m => m . name === 'element_timing.render_time' ) ;
110+ const loadTimeMetrics = allMetrics . filter ( m => m . name === 'element_timing.load_time' ) ;
85111
86112 const renderIdentifiers = renderTimeMetrics . map ( m => m . attributes [ 'element.identifier' ] ?. value ) ;
87113 const loadIdentifiers = loadTimeMetrics . map ( m => m . attributes [ 'element.identifier' ] ?. value ) ;
@@ -128,30 +154,23 @@ sentryTest('emits element timing metrics after navigation', async ({ getLocalTes
128154 serveAssets ( page ) ;
129155
130156 const url = await getLocalTestUrl ( { testDir : __dirname } ) ;
157+ const collector = createMetricCollector ( page ) ;
131158
132159 await page . goto ( url ) ;
133160
134- // Wait for pageload content to settle and flush
135- await page . waitForTimeout ( 8000 ) ;
161+ // Wait for pageload element timing metrics to arrive before navigating
162+ await collector . waitForIdentifiers ( [ 'image-fast' , 'text1' ] ) ;
136163
137- // Now collect only post-navigation metrics
138- const postNavMetricRequests : Request [ ] = [ ] ;
139- page . on ( 'request' , req => {
140- if ( req . url ( ) . includes ( '/api/1337/envelope/' ) ) {
141- const metrics = extractMetricsFromRequest ( req ) ;
142- if ( metrics . some ( m => m . name . startsWith ( 'element_timing.' ) ) ) {
143- postNavMetricRequests . push ( req ) ;
144- }
145- }
146- } ) ;
164+ // Reset so we only capture post-navigation metrics
165+ collector . reset ( ) ;
147166
148167 // Trigger navigation
149168 await page . locator ( '#button1' ) . click ( ) ;
150169
151- // Wait for navigation elements to render + flush interval
152- await page . waitForTimeout ( 8000 ) ;
170+ // Wait for navigation element timing metrics
171+ await collector . waitForIdentifiers ( [ 'navigation-image' , 'navigation-text' ] ) ;
153172
154- const allMetrics = postNavMetricRequests . flatMap ( req => extractMetricsFromRequest ( req ) ) ;
173+ const allMetrics = collector . getAll ( ) ;
155174 const renderTimeMetrics = allMetrics . filter ( m => m . name === 'element_timing.render_time' ) ;
156175 const renderIdentifiers = renderTimeMetrics . map ( m => m . attributes [ 'element.identifier' ] ?. value ) ;
157176
0 commit comments