Skip to content

Commit f8406f7

Browse files
logaretmclaude
andcommitted
fix: Replace fixed timeouts with deterministic polling in element timing tests
Instead of sleeping 8 seconds and hoping all metrics have arrived, poll until the expected element identifiers appear in the collected metrics. This makes the tests faster (6-13s vs 16-20s) and more reliable — they complete as soon as data arrives and fail with clear assertions when it doesn't. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4016f22 commit f8406f7

1 file changed

Lines changed: 63 additions & 44 deletions

File tree

  • dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing

dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4984
sentryTest(
@@ -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

Comments
 (0)