Skip to content

Commit 4c49615

Browse files
committed
add e2e
1 parent beca740 commit 4c49615

5 files changed

Lines changed: 93 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export function GET() {
6+
return NextResponse.json({ name: 'Jane Doe' });
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export function GET() {
6+
return NextResponse.json({ name: 'John Doe' });
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NextResponse } from 'next/server';
2+
import type { NextRequest } from 'next/server';
3+
4+
export async function middleware(request: NextRequest) {
5+
// Keep this invocation in-flight for a bit so a concurrent request to the other matched endpoint genuinely
6+
// overlaps with it — the concurrency test in tests/middleware.test.ts relies on this overlap.
7+
if (request.nextUrl.pathname === '/api/endpoint-behind-middleware-2') {
8+
await new Promise(resolve => setTimeout(resolve, 300));
9+
}
10+
11+
return NextResponse.next();
12+
}
13+
14+
// See "Matching Paths" below to learn more
15+
export const config = {
16+
matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-middleware-2'],
17+
};

dev-packages/e2e-tests/test-applications/nextjs-15/sentry.edge.config.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@ Sentry.init({
44
environment: 'qa', // dynamic sampling bias to keep transactions
55
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
66
tunnel: `http://localhost:3031/`, // proxy server
7-
tracesSampleRate: 1.0,
7+
tracesSampler: samplingContext => {
8+
if (samplingContext.attributes?.['next.span_type'] === 'Middleware.execute') {
9+
// Only keep the middleware transaction when `normalizedRequest` was available at sampling time and belongs to
10+
// the request that is being sampled (guards against concurrent requests leaking into each other's sampling
11+
// context). The middleware e2e tests time out and fail when the transaction is dropped here.
12+
const { normalizedRequest } = samplingContext;
13+
return Boolean(
14+
normalizedRequest?.method &&
15+
normalizedRequest?.url &&
16+
normalizedRequest.url === samplingContext.attributes['http.target'],
17+
);
18+
}
19+
20+
return 1.0;
21+
},
822
dataCollection: { userInfo: true },
923
transportOptions: {
1024
// We are doing a lot of events at once in this test
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
4+
// The `tracesSampler` in `sentry.edge.config.ts` only samples `Middleware.execute` spans when `normalizedRequest`
5+
// is available at sampling time, so this test times out if the request data does not reach the sampler.
6+
test('tracesSampler receives normalizedRequest for edge middleware', async ({ request }) => {
7+
const middlewareTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
8+
return transactionEvent?.transaction === 'middleware GET';
9+
});
10+
11+
const response = await request.get('/api/endpoint-behind-middleware');
12+
expect(await response.json()).toStrictEqual({ name: 'John Doe' });
13+
14+
const middlewareTransaction = await middlewareTransactionPromise;
15+
16+
expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge');
17+
expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware');
18+
expect(middlewareTransaction.request?.url).toContain('/api/endpoint-behind-middleware');
19+
expect(middlewareTransaction.request?.method).toBe('GET');
20+
});
21+
22+
// The `tracesSampler` additionally asserts that `normalizedRequest.url` matches the sampled span's own
23+
// `http.target`, so a request leaking into the sampling context of a concurrent one drops that transaction
24+
// and times this test out.
25+
test('does not leak normalizedRequest between concurrent middleware invocations', async ({ request }) => {
26+
const firstTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
27+
return (
28+
transactionEvent?.transaction === 'middleware GET' &&
29+
transactionEvent.contexts?.trace?.data?.['http.target'] === '/api/endpoint-behind-middleware'
30+
);
31+
});
32+
33+
const secondTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
34+
return (
35+
transactionEvent?.transaction === 'middleware GET' &&
36+
transactionEvent.contexts?.trace?.data?.['http.target'] === '/api/endpoint-behind-middleware-2'
37+
);
38+
});
39+
40+
await Promise.all([request.get('/api/endpoint-behind-middleware'), request.get('/api/endpoint-behind-middleware-2')]);
41+
42+
const [firstTransaction, secondTransaction] = await Promise.all([firstTransactionPromise, secondTransactionPromise]);
43+
44+
expect(firstTransaction.request?.url).toContain('/api/endpoint-behind-middleware');
45+
expect(firstTransaction.request?.url).not.toContain('/api/endpoint-behind-middleware-2');
46+
expect(secondTransaction.request?.url).toContain('/api/endpoint-behind-middleware-2');
47+
});

0 commit comments

Comments
 (0)