Skip to content

Commit 995f788

Browse files
authored
test(e2e): Add e2e metrics tests in Next.js 16 (#18643)
closes #18186
1 parent 363a910 commit 995f788

File tree

5 files changed

+228
-1
lines changed

5 files changed

+228
-1
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use client';
2+
3+
import * as Sentry from '@sentry/nextjs';
4+
5+
export default function Page() {
6+
const handleClick = async () => {
7+
Sentry.metrics.count('test.page.count', 1, {
8+
attributes: {
9+
page: '/metrics',
10+
'random.attribute': 'Apples',
11+
},
12+
});
13+
Sentry.metrics.distribution('test.page.distribution', 100, {
14+
attributes: {
15+
page: '/metrics',
16+
'random.attribute': 'Manzanas',
17+
},
18+
});
19+
Sentry.metrics.gauge('test.page.gauge', 200, {
20+
attributes: {
21+
page: '/metrics',
22+
'random.attribute': 'Mele',
23+
},
24+
});
25+
await fetch('/metrics/route-handler');
26+
};
27+
28+
return (
29+
<div>
30+
<h1>Metrics page</h1>
31+
<button onClick={handleClick}>Emit</button>
32+
</div>
33+
);
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
export const GET = async () => {
4+
Sentry.metrics.count('test.route.handler.count', 1, {
5+
attributes: {
6+
endpoint: '/metrics/route-handler',
7+
'random.attribute': 'Potatoes',
8+
},
9+
});
10+
Sentry.metrics.distribution('test.route.handler.distribution', 100, {
11+
attributes: {
12+
endpoint: '/metrics/route-handler',
13+
'random.attribute': 'Patatas',
14+
},
15+
});
16+
Sentry.metrics.gauge('test.route.handler.gauge', 200, {
17+
attributes: {
18+
endpoint: '/metrics/route-handler',
19+
'random.attribute': 'Patate',
20+
},
21+
});
22+
return Response.json({ message: 'Bueno' });
23+
};
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForMetric } from '@sentry-internal/test-utils';
3+
4+
test('Should emit metrics from server and client', async ({ request, page }) => {
5+
const clientCountPromise = waitForMetric('nextjs-16', async metric => {
6+
return metric.name === 'test.page.count';
7+
});
8+
9+
const clientDistributionPromise = waitForMetric('nextjs-16', async metric => {
10+
return metric.name === 'test.page.distribution';
11+
});
12+
13+
const clientGaugePromise = waitForMetric('nextjs-16', async metric => {
14+
return metric.name === 'test.page.gauge';
15+
});
16+
17+
const serverCountPromise = waitForMetric('nextjs-16', async metric => {
18+
return metric.name === 'test.route.handler.count';
19+
});
20+
21+
const serverDistributionPromise = waitForMetric('nextjs-16', async metric => {
22+
return metric.name === 'test.route.handler.distribution';
23+
});
24+
25+
const serverGaugePromise = waitForMetric('nextjs-16', async metric => {
26+
return metric.name === 'test.route.handler.gauge';
27+
});
28+
29+
await page.goto('/metrics');
30+
await page.getByText('Emit').click();
31+
const clientCount = await clientCountPromise;
32+
const clientDistribution = await clientDistributionPromise;
33+
const clientGauge = await clientGaugePromise;
34+
const serverCount = await serverCountPromise;
35+
const serverDistribution = await serverDistributionPromise;
36+
const serverGauge = await serverGaugePromise;
37+
38+
expect(clientCount).toMatchObject({
39+
timestamp: expect.any(Number),
40+
trace_id: expect.any(String),
41+
span_id: expect.any(String),
42+
name: 'test.page.count',
43+
type: 'counter',
44+
value: 1,
45+
attributes: {
46+
page: { value: '/metrics', type: 'string' },
47+
'random.attribute': { value: 'Apples', type: 'string' },
48+
'sentry.environment': { value: 'qa', type: 'string' },
49+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
50+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
51+
},
52+
});
53+
54+
expect(clientDistribution).toMatchObject({
55+
timestamp: expect.any(Number),
56+
trace_id: expect.any(String),
57+
span_id: expect.any(String),
58+
name: 'test.page.distribution',
59+
type: 'distribution',
60+
value: 100,
61+
attributes: {
62+
page: { value: '/metrics', type: 'string' },
63+
'random.attribute': { value: 'Manzanas', type: 'string' },
64+
'sentry.environment': { value: 'qa', type: 'string' },
65+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
66+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
67+
},
68+
});
69+
70+
expect(clientGauge).toMatchObject({
71+
timestamp: expect.any(Number),
72+
trace_id: expect.any(String),
73+
span_id: expect.any(String),
74+
name: 'test.page.gauge',
75+
type: 'gauge',
76+
value: 200,
77+
attributes: {
78+
page: { value: '/metrics', type: 'string' },
79+
'random.attribute': { value: 'Mele', type: 'string' },
80+
'sentry.environment': { value: 'qa', type: 'string' },
81+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
82+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
83+
},
84+
});
85+
86+
expect(serverCount).toMatchObject({
87+
timestamp: expect.any(Number),
88+
trace_id: expect.any(String),
89+
name: 'test.route.handler.count',
90+
type: 'counter',
91+
value: 1,
92+
attributes: {
93+
'server.address': { value: expect.any(String), type: 'string' },
94+
'random.attribute': { value: 'Potatoes', type: 'string' },
95+
endpoint: { value: '/metrics/route-handler', type: 'string' },
96+
'sentry.environment': { value: 'qa', type: 'string' },
97+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
98+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
99+
},
100+
});
101+
102+
expect(serverDistribution).toMatchObject({
103+
timestamp: expect.any(Number),
104+
trace_id: expect.any(String),
105+
name: 'test.route.handler.distribution',
106+
type: 'distribution',
107+
value: 100,
108+
attributes: {
109+
'server.address': { value: expect.any(String), type: 'string' },
110+
'random.attribute': { value: 'Patatas', type: 'string' },
111+
endpoint: { value: '/metrics/route-handler', type: 'string' },
112+
'sentry.environment': { value: 'qa', type: 'string' },
113+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
114+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
115+
},
116+
});
117+
118+
expect(serverGauge).toMatchObject({
119+
timestamp: expect.any(Number),
120+
trace_id: expect.any(String),
121+
name: 'test.route.handler.gauge',
122+
type: 'gauge',
123+
value: 200,
124+
attributes: {
125+
'server.address': { value: expect.any(String), type: 'string' },
126+
'random.attribute': { value: 'Patate', type: 'string' },
127+
endpoint: { value: '/metrics/route-handler', type: 'string' },
128+
'sentry.environment': { value: 'qa', type: 'string' },
129+
'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' },
130+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
131+
},
132+
});
133+
});

dev-packages/test-utils/src/event-proxy-server.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
/* eslint-disable max-lines */
2-
import type { Envelope, EnvelopeItem, Event, SerializedSession } from '@sentry/core';
2+
import type {
3+
Envelope,
4+
EnvelopeItem,
5+
Event,
6+
SerializedMetric,
7+
SerializedMetricContainer,
8+
SerializedSession,
9+
} from '@sentry/core';
310
import { parseEnvelope } from '@sentry/core';
411
import * as fs from 'fs';
512
import * as http from 'http';
@@ -391,6 +398,35 @@ export function waitForTransaction(
391398
});
392399
}
393400

401+
/**
402+
* Wait for metric items to be sent.
403+
*/
404+
export function waitForMetric(
405+
proxyServerName: string,
406+
callback: (metricEvent: SerializedMetric) => Promise<boolean> | boolean,
407+
): Promise<SerializedMetric> {
408+
const timestamp = getNanosecondTimestamp();
409+
return new Promise((resolve, reject) => {
410+
waitForEnvelopeItem(
411+
proxyServerName,
412+
async envelopeItem => {
413+
const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
414+
const metricContainer = envelopeItemBody as SerializedMetricContainer;
415+
if (envelopeItemHeader.type === 'trace_metric') {
416+
for (const metric of metricContainer.items) {
417+
if (await callback(metric)) {
418+
resolve(metric);
419+
return true;
420+
}
421+
}
422+
}
423+
return false;
424+
},
425+
timestamp,
426+
).catch(reject);
427+
});
428+
}
429+
394430
const TEMP_FILE_PREFIX = 'event-proxy-server-';
395431

396432
async function registerCallbackServerPort(serverName: string, port: string): Promise<void> {

dev-packages/test-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
waitForTransaction,
88
waitForSession,
99
waitForPlainRequest,
10+
waitForMetric,
1011
} from './event-proxy-server';
1112

1213
export { getPlaywrightConfig } from './playwright-config';

0 commit comments

Comments
 (0)