diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json
index 16fd64cdb58c..965637b71600 100644
--- a/dev-packages/e2e-tests/package.json
+++ b/dev-packages/e2e-tests/package.json
@@ -14,7 +14,7 @@
"test:prepare": "ts-node prepare.ts",
"test:validate": "run-s test:validate-configuration test:validate-test-app-setups",
"clean": "rimraf tmp node_modules pnpm-lock.yaml && yarn clean:test-applications",
- "clean:test-applications": "rimraf test-applications/**/{node_modules,dist,build,.next,.sveltekit,pnpm-lock.yaml} .last-run.json"
+ "clean:test-applications": "rimraf test-applications/**/{node_modules,dist,build,.next,.sveltekit,pnpm-lock.yaml} .last-run.json && pnpm store prune"
},
"devDependencies": {
"@types/glob": "8.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/globals.d.ts b/dev-packages/e2e-tests/test-applications/create-next-app/globals.d.ts
index 109dbcd55648..e69de29bb2d1 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/globals.d.ts
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/globals.d.ts
@@ -1,4 +0,0 @@
-interface Window {
- recordedTransactions?: string[];
- capturedExceptionId?: string;
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/instrumentation.ts b/dev-packages/e2e-tests/test-applications/create-next-app/instrumentation.ts
index 5ddf6e7b823a..dd214cc7a7bc 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/instrumentation.ts
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/instrumentation.ts
@@ -1,11 +1,5 @@
import * as Sentry from '@sentry/nextjs';
-declare global {
- namespace globalThis {
- var transactionIds: string[];
- }
-}
-
export function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
Sentry.init({
@@ -13,21 +7,7 @@ export function register() {
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
- integrations: [Sentry.localVariablesIntegration()],
- });
-
- Sentry.addEventProcessor(event => {
- global.transactionIds = global.transactionIds || [];
-
- if (event.type === 'transaction') {
- const eventId = event.event_id;
-
- if (eventId) {
- global.transactionIds.push(eventId);
- }
- }
-
- return event;
+ tunnel: 'http://localhost:3031',
});
}
}
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/package.json b/dev-packages/e2e-tests/test-applications/create-next-app/package.json
index 330e8b4097ee..2c0945051ee5 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build": "next build",
- "clean": "npx rimraf node_modules pnpm-lock.yaml",
+ "clean": "npx rimraf node_modules pnpm-lock.yaml .next",
"test:prod": "TEST_ENV=prod playwright test",
"test:dev": "TEST_ENV=dev playwright test",
"test:build": "pnpm install && npx playwright install && pnpm build",
@@ -23,7 +23,8 @@
"typescript": "4.9.5"
},
"devDependencies": {
- "@playwright/test": "^1.44.1"
+ "@playwright/test": "^1.44.1",
+ "@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
"extends": "../../package.json"
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts
index 5440074c39aa..6debfd151870 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts
@@ -1,11 +1,6 @@
-import * as Sentry from '@sentry/nextjs';
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- const exceptionId = Sentry.captureException(new Error('This is an error'));
-
- await Sentry.flush(2000);
-
- res.status(200).json({ exceptionId });
+ throw new Error('I am a server error!');
}
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts
index 94f7b003ffcb..d3504dc73d98 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts
@@ -3,11 +3,9 @@ import * as Sentry from '@sentry/nextjs';
import type { NextApiRequest, NextApiResponse } from 'next';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
- Sentry.startSpan({ name: 'test-span' }, span => undefined);
+ Sentry.startSpan({ name: 'test-span' }, () => undefined);
Sentry.flush().then(() => {
- res.status(200).json({
- transactionIds: global.transactionIds,
- });
+ res.status(200).json({});
});
}
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/pages/index.tsx b/dev-packages/e2e-tests/test-applications/create-next-app/pages/index.tsx
index 059230d236a5..6bb62f18deb4 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/pages/index.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/pages/index.tsx
@@ -1,4 +1,3 @@
-import * as Sentry from '@sentry/nextjs';
import Head from 'next/head';
import Link from 'next/link';
@@ -17,8 +16,7 @@ export default function Home() {
value="Capture Exception"
id="exception-button"
onClick={() => {
- const eventId = Sentry.captureException(new Error('I am an error!'));
- window.capturedExceptionId = eventId;
+ throw new Error('I am an error!');
}}
/>
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.mjs
new file mode 100644
index 000000000000..f97facbf0cc5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.mjs
@@ -0,0 +1,13 @@
+import { getPlaywrightConfig } from '@sentry-internal/test-utils';
+
+const testEnv = process.env.TEST_ENV;
+
+if (!testEnv) {
+ throw new Error('No test env defined');
+}
+
+const config = getPlaywrightConfig({
+ startCommand: testEnv === 'development' ? `pnpm next dev -p 3030` : `pnpm next start -p 3030`,
+});
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts
deleted file mode 100644
index b29068c3141c..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-next-app/playwright.config.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import type { PlaywrightTestConfig } from '@playwright/test';
-import { devices } from '@playwright/test';
-
-const testEnv = process.env.TEST_ENV;
-
-if (!testEnv) {
- throw new Error('No test env defined');
-}
-
-const port = 3030;
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-const config: PlaywrightTestConfig = {
- testDir: './tests',
- /* Maximum time one test can run for. */
- timeout: 150_000,
- expect: {
- /**
- * Maximum time expect() should wait for the condition to be met.
- * For example in `await expect(locator).toHaveText();`
- */
- timeout: 5000,
- },
- /* Run tests in files in parallel */
- fullyParallel: true,
- /* Fail the build on CI if you accidentally left test.only in the source code. */
- forbidOnly: !!process.env.CI,
- /* Retry on CI only */
- retries: 0,
- /* Opt out of parallel tests on CI. */
- workers: 1,
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: 'list',
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
- actionTimeout: 0,
-
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: 'on-first-retry',
- },
-
- /* Configure projects for major browsers */
- projects: [
- {
- name: 'chromium',
- use: {
- ...devices['Desktop Chrome'],
- },
- },
- // For now we only test Chrome!
- // {
- // name: 'firefox',
- // use: {
- // ...devices['Desktop Firefox'],
- // },
- // },
- // {
- // name: 'webkit',
- // use: {
- // ...devices['Desktop Safari'],
- // },
- // },
- ],
-
- /* Run your local dev server before starting the tests */
- webServer: {
- command: testEnv === 'development' ? `pnpm next dev -p ${port}` : `pnpm next start -p ${port}`,
- port,
- },
-};
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts
index 1318bf32e5ac..e24170711a83 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/sentry.client.config.ts
@@ -7,26 +7,6 @@ import * as Sentry from '@sentry/nextjs';
Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
- // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
-
- // ...
- // Note: if you want to override the automatic release value, do not set a
- // `release` value here - use the environment variable `SENTRY_RELEASE`, so
- // that it will also get attached to your source maps
-});
-
-Sentry.addEventProcessor(event => {
- if (
- event.type === 'transaction' &&
- (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
- ) {
- const eventId = event.event_id;
- if (eventId) {
- window.recordedTransactions = window.recordedTransactions || [];
- window.recordedTransactions.push(eventId);
- }
- }
-
- return event;
+ tunnel: 'http://localhost:3031',
});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/create-next-app/start-event-proxy.mjs
new file mode 100644
index 000000000000..db6c74e4afe3
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/test-utils';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'create-next-app',
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts
deleted file mode 100644
index 23d1d7126e16..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import { expect, test } from '@playwright/test';
-
-const authToken = process.env.E2E_TEST_AUTH_TOKEN;
-const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
-const sentryTestProject = process.env.E2E_TEST_SENTRY_PROJECT;
-const EVENT_POLLING_TIMEOUT = 90_000;
-
-test('Sends a client-side exception to Sentry', async ({ page }) => {
- await page.goto('/');
-
- const exceptionButton = page.locator('id=exception-button');
- await exceptionButton.click();
-
- const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId);
- const exceptionEventId = await exceptionIdHandle.jsonValue();
-
- console.log(`Polling for error eventId: ${exceptionEventId}`);
-
- await expect
- .poll(
- async () => {
- const response = await fetch(
- `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionEventId}/`,
- { headers: { Authorization: `Bearer ${authToken}` } },
- );
-
- return response.status;
- },
- {
- timeout: EVENT_POLLING_TIMEOUT,
- },
- )
- .toBe(200);
-});
-
-test('Sends a pageload transaction to Sentry', async ({ page }) => {
- await page.goto('/');
-
- const recordedTransactionsHandle = await page.waitForFunction(() => {
- if (window.recordedTransactions && window.recordedTransactions?.length >= 1) {
- return window.recordedTransactions;
- } else {
- return undefined;
- }
- });
- const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue();
-
- if (recordedTransactionEventIds === undefined) {
- throw new Error("Application didn't record any transaction event IDs.");
- }
-
- let hadPageLoadTransaction = false;
-
- console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`);
-
- await Promise.all(
- recordedTransactionEventIds.map(async transactionEventId => {
- await expect
- .poll(
- async () => {
- const response = await fetch(
- `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
- { headers: { Authorization: `Bearer ${authToken}` } },
- );
-
- if (response.ok) {
- const data = await response.json();
- if (data.contexts.trace.op === 'pageload') {
- hadPageLoadTransaction = true;
- }
- }
-
- return response.status;
- },
- {
- timeout: EVENT_POLLING_TIMEOUT,
- },
- )
- .toBe(200);
- }),
- );
-
- expect(hadPageLoadTransaction).toBe(true);
-});
-
-test('Sends a navigation transaction to Sentry', async ({ page }) => {
- await page.goto('/');
-
- // Give pageload transaction time to finish
- await page.waitForTimeout(4000);
-
- const linkElement = page.locator('id=navigation');
- await linkElement.click();
-
- const recordedTransactionsHandle = await page.waitForFunction(() => {
- if (window.recordedTransactions && window.recordedTransactions?.length >= 2) {
- return window.recordedTransactions;
- } else {
- return undefined;
- }
- });
- const recordedTransactionEventIds = await recordedTransactionsHandle.jsonValue();
-
- if (recordedTransactionEventIds === undefined) {
- throw new Error("Application didn't record any transaction event IDs.");
- }
-
- let hadPageNavigationTransaction = false;
-
- console.log(`Polling for transaction eventIds: ${JSON.stringify(recordedTransactionEventIds)}`);
-
- await Promise.all(
- recordedTransactionEventIds.map(async transactionEventId => {
- await expect
- .poll(
- async () => {
- const response = await fetch(
- `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
- { headers: { Authorization: `Bearer ${authToken}` } },
- );
-
- if (response.ok) {
- const data = await response.json();
- if (data.contexts.trace.op === 'navigation') {
- hadPageNavigationTransaction = true;
- }
- }
-
- return response.status;
- },
- {
- timeout: EVENT_POLLING_TIMEOUT,
- },
- )
- .toBe(200);
- }),
- );
-
- expect(hadPageNavigationTransaction).toBe(true);
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts
deleted file mode 100644
index ef30d70c0a52..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { expect, test } from '@playwright/test';
-
-const authToken = process.env.E2E_TEST_AUTH_TOKEN;
-const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
-const sentryTestProject = process.env.E2E_TEST_SENTRY_PROJECT;
-const EVENT_POLLING_TIMEOUT = 90_000;
-
-test('Sends a server-side exception to Sentry', async ({ baseURL }) => {
- const response = await fetch(`${baseURL}/api/error`);
- const data = await response.json();
- const { exceptionId } = data;
-
- const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`;
-
- console.log(`Polling for error eventId: ${exceptionId}`);
-
- await expect
- .poll(
- async () => {
- const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } });
- return response.status;
- },
- { timeout: EVENT_POLLING_TIMEOUT },
- )
- .toBe(200);
-});
-
-test('Sends server-side transactions to Sentry', async ({ baseURL }) => {
- const response = await fetch(`${baseURL}/api/success`);
- const data = await response.json();
- const { transactionIds } = data;
-
- console.log(`Polling for transaction eventIds: ${JSON.stringify(transactionIds)}`);
-
- await Promise.all(
- transactionIds.map(async (transactionId: string) => {
- const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionId}/`;
-
- await expect
- .poll(
- async () => {
- const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` } });
- return response.status;
- },
- { timeout: EVENT_POLLING_TIMEOUT },
- )
- .toBe(200);
- }),
- );
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-errors.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-errors.test.ts
new file mode 100644
index 000000000000..5e06086f1a29
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-errors.test.ts
@@ -0,0 +1,30 @@
+import { expect, test } from '@playwright/test';
+import { waitForError } from '@sentry-internal/test-utils';
+
+test('Sends a client-side exception to Sentry', async ({ page }) => {
+ const errorEventPromise = waitForError('create-next-app', event => {
+ return event.exception?.values?.[0]?.value === 'I am an error!';
+ });
+
+ await page.goto('/');
+
+ const exceptionButton = page.locator('id=exception-button');
+ await exceptionButton.click();
+
+ const errorEvent = await errorEventPromise;
+
+ expect(errorEvent.exception?.values).toHaveLength(1);
+ expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!');
+
+ expect(errorEvent.request).toEqual({
+ headers: expect.any(Object),
+ url: 'http://localhost:3030/',
+ });
+
+ expect(errorEvent.transaction).toEqual('/');
+
+ expect(errorEvent.contexts?.trace).toEqual({
+ trace_id: expect.any(String),
+ span_id: expect.any(String),
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-transactions.test.ts
new file mode 100644
index 000000000000..7af02f35fa47
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/client-transactions.test.ts
@@ -0,0 +1,85 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Sends a pageload transaction to Sentry', async ({ page }) => {
+ const pageloadTransactionEventPromise = waitForTransaction('create-next-app', transactionEvent => {
+ return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === '/';
+ });
+
+ await page.goto('/');
+
+ const transactionEvent = await pageloadTransactionEventPromise;
+
+ expect(transactionEvent).toEqual(
+ expect.objectContaining({
+ transaction: '/',
+ tags: { runtime: 'browser' },
+ transaction_info: { source: 'route' },
+ type: 'transaction',
+ contexts: {
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ op: 'pageload',
+ origin: 'auto.pageload.nextjs.pages_router_instrumentation',
+ data: expect.objectContaining({
+ 'sentry.idle_span_finish_reason': 'idleTimeout',
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ },
+ },
+ request: {
+ headers: {
+ 'User-Agent': expect.any(String),
+ },
+ url: 'http://localhost:3030/',
+ },
+ }),
+ );
+});
+
+test('captures a navigation transcation to Sentry', async ({ page }) => {
+ const clientNavigationTxnEventPromise = waitForTransaction('create-next-app', txnEvent => {
+ return txnEvent?.transaction === '/user/[id]';
+ });
+
+ await page.goto('/');
+
+ // navigation to page
+ const clickPromise = page.getByText('navigate').click();
+
+ const [clientTxnEvent, serverTxnEvent, _1] = await Promise.all([clientNavigationTxnEventPromise, clickPromise]);
+
+ expect(clientTxnEvent).toEqual(
+ expect.objectContaining({
+ transaction: '/user/[id]',
+ tags: { runtime: 'browser' },
+ transaction_info: { source: 'route' },
+ type: 'transaction',
+ contexts: {
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ op: 'navigation',
+ origin: 'auto.navigation.nextjs.pages_router_instrumentation',
+ data: expect.objectContaining({
+ 'sentry.idle_span_finish_reason': 'idleTimeout',
+ 'sentry.op': 'navigation',
+ 'sentry.origin': 'auto.navigation.nextjs.pages_router_instrumentation',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ },
+ },
+ request: {
+ headers: {
+ 'User-Agent': expect.any(String),
+ },
+ url: 'http://localhost:3030/user/5',
+ },
+ }),
+ );
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-errors.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-errors.test.ts
new file mode 100644
index 000000000000..ed3263f5eb8b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-errors.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from '@playwright/test';
+import { waitForError } from '@sentry-internal/test-utils';
+
+test('Sends a server-side exception to Sentry', async ({ baseURL }) => {
+ const errorEventPromise = waitForError('create-next-app', event => {
+ return event.exception?.values?.[0]?.value === 'I am a server error!';
+ });
+
+ const response = await fetch(`${baseURL}/api/error`);
+
+ expect(response.status).toBe(500);
+
+ const errorEvent = await errorEventPromise;
+
+ expect(errorEvent.exception?.values).toHaveLength(1);
+ expect(errorEvent.exception?.values?.[0]?.value).toBe('I am a server error!');
+
+ expect(errorEvent.request).toEqual({
+ headers: expect.any(Object),
+ cookies: {},
+ method: 'GET',
+ url: expect.stringContaining('/api/error'),
+ });
+
+ expect(errorEvent.transaction).toEqual('GET /api/error');
+
+ expect(errorEvent.contexts?.trace).toEqual({
+ trace_id: expect.any(String),
+ span_id: expect.any(String),
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts
new file mode 100644
index 000000000000..01bd22f3cae0
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts
@@ -0,0 +1,69 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+const authToken = process.env.E2E_TEST_AUTH_TOKEN;
+const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
+const sentryTestProject = process.env.E2E_TEST_SENTRY_PROJECT;
+const EVENT_POLLING_TIMEOUT = 90_000;
+
+test('Sends server-side transactions to Sentry', async ({ baseURL }) => {
+ const transactionEventPromise = waitForTransaction('create-next-app', transactionEvent => {
+ return (
+ transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/success'
+ );
+ });
+
+ await fetch(`${baseURL}/api/success`);
+
+ const transactionEvent = await transactionEventPromise;
+
+ expect(transactionEvent).toEqual(
+ expect.objectContaining({
+ transaction: 'GET /api/success',
+ transaction_info: { source: 'route' },
+ type: 'transaction',
+ contexts: expect.objectContaining({
+ trace: {
+ span_id: expect.any(String),
+ trace_id: expect.any(String),
+ op: 'http.server',
+ origin: 'auto.http.nextjs',
+ data: expect.objectContaining({
+ 'http.response.status_code': 200,
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.nextjs',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ status: 'ok',
+ },
+ runtime: {
+ name: 'node',
+ version: expect.any(String),
+ },
+ }),
+ spans: [
+ {
+ data: {
+ 'otel.kind': 'INTERNAL',
+ 'sentry.origin': 'manual',
+ },
+ description: 'test-span',
+ origin: 'manual',
+ parent_span_id: transactionEvent.contexts?.trace?.span_id,
+ span_id: expect.any(String),
+ start_timestamp: expect.any(Number),
+ status: 'ok',
+ timestamp: expect.any(Number),
+ trace_id: transactionEvent.contexts?.trace?.trace_id,
+ },
+ ],
+ request: {
+ headers: expect.any(Object),
+ method: 'GET',
+ cookies: {},
+ url: expect.stringContaining('/api/success'),
+ },
+ }),
+ );
+});
diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts
index eb1a9eb869e0..3ef2bba479db 100644
--- a/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/errors.test.ts
@@ -25,6 +25,5 @@ test('Sends correct error event', async ({ baseURL }) => {
expect(errorEvent.contexts?.trace).toEqual({
trace_id: expect.any(String),
span_id: expect.any(String),
- parent_span_id: expect.any(String),
});
});
diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts
index aca04522f8fc..1fbb1fd42613 100644
--- a/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/errors.test.ts
@@ -25,6 +25,5 @@ test('Sends correct error event', async ({ baseURL }) => {
expect(errorEvent.contexts?.trace).toEqual({
trace_id: expect.any(String),
span_id: expect.any(String),
- parent_span_id: expect.any(String),
});
});
diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts
index 0c03bc8f0ec9..b996b6af1877 100644
--- a/packages/nextjs/src/common/utils/tracingUtils.ts
+++ b/packages/nextjs/src/common/utils/tracingUtils.ts
@@ -1,6 +1,6 @@
-import { Scope, getCurrentScope, withActiveSpan } from '@sentry/core';
+import { Scope, startNewTrace } from '@sentry/core';
import type { PropagationContext } from '@sentry/types';
-import { GLOBAL_OBJ, logger, uuid4 } from '@sentry/utils';
+import { GLOBAL_OBJ, logger } from '@sentry/utils';
import { DEBUG_BUILD } from '../debug-build';
const commonPropagationContextMap = new WeakMap