Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/nitro';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
dsn: process.env.E2E_TEST_TUNNEL ? 'http://public@localhost:3031/1337' : process.env.E2E_TEST_DSN,
tunnel: process.env.E2E_TEST_TUNNEL ? true : `http://localhost:3031/`, // proxy server
tracesSampleRate: 1,
});
32 changes: 31 additions & 1 deletion dev-packages/e2e-tests/test-applications/nitro-3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
"clean": "npx rimraf node_modules pnpm-lock.yaml .output",
"test": "playwright test",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test"
"test:assert": "pnpm test",
"test:build:tunnel": "pnpm install && E2E_TEST_TUNNEL=1 pnpm build",
"test:build:tunnel-basepath": "pnpm install && E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app pnpm build",
"test:assert:tunnel": "E2E_TEST_TUNNEL=1 playwright test tunnel",
"test:assert:tunnel-basepath": "E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app playwright test tunnel",
"test:assert:tunnel-dev": "TEST_ENV=development E2E_TEST_TUNNEL=1 playwright test tunnel",
"test:assert:tunnel-basepath-dev": "TEST_ENV=development E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app playwright test tunnel"
},
"dependencies": {
"@sentry/browser": "latest || *",
Expand All @@ -25,5 +31,29 @@
},
"volta": {
"extends": "../../package.json"
},
"sentryTest": {
"variants": [
{
"build-command": "pnpm test:build:tunnel",
"assert-command": "pnpm test:assert:tunnel",
"label": "nitro-3 (tunnel)"
},
{
"build-command": "pnpm test:build:tunnel-basepath",
"assert-command": "pnpm test:assert:tunnel-basepath",
"label": "nitro-3 (tunnel-basepath)"
},
{
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-dev",
"label": "nitro-3 (tunnel-dev)"
},
{
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-basepath-dev",
"label": "nitro-3 (tunnel-basepath-dev)"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const config = getPlaywrightConfig({
startCommand: `pnpm start`,
startCommand:
process.env.TEST_ENV === 'development'
? `PORT=3030 NODE_OPTIONS='--import ./instrument.mjs' pnpm exec vite dev --port 3030`
: `pnpm start`,
});

export default config;
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { defineHandler, getQuery, setResponseHeader } from 'nitro/h3';
import { defineHandler, getQuery, HTTPError, setResponseHeader } from 'nitro/h3';

export default defineHandler(event => {
// The tunnel variant checks that the managed tunnel answers before an auth middleware like this one.
if (process.env.E2E_TEST_TUNNEL && event.req.method === 'POST' && event.req.headers.get('x-auth') !== 'ok') {
throw new HTTPError({ status: 401, statusText: 'blocked by user auth' });
}

setResponseHeader(event, 'x-sentry-test-middleware', 'executed');

const query = getQuery(event);
Expand Down
12 changes: 10 additions & 2 deletions dev-packages/e2e-tests/test-applications/nitro-3/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import * as Sentry from '@sentry/browser';

declare const __E2E_TEST_TUNNEL__: boolean;
declare const __E2E_TEST_BASE_PATH__: string;

// Let's us test trace propagation
Sentry.init({
environment: 'qa',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tunnel: 'http://localhost:3031/', // proxy server
dsn: __E2E_TEST_TUNNEL__ ? 'http://public@localhost:3031/1337' : 'https://public@dsn.ingest.sentry.io/1337',
// Plain Nitro has no client SDK to pass `baseURL` on, so the browser names it. The server picks it up itself.
tunnel: __E2E_TEST_TUNNEL__
? __E2E_TEST_BASE_PATH__
? { basePath: __E2E_TEST_BASE_PATH__ }
: true
: 'http://localhost:3031/', // proxy server
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test.skip(!process.env.E2E_TEST_TUNNEL, 'Only runs in the tunnel variant');

const BASE_PATH = process.env.E2E_TEST_BASE_PATH ?? '';
const TUNNEL_PATH = new RegExp(`^${BASE_PATH}/[a-z0-9]{1,7}$`);

test('answers the managed tunnel before the app auth middleware', async ({ page }) => {
// The variant's auth middleware rejects every other POST.
expect((await page.request.post(`${BASE_PATH}/api/anything`, { data: 'x' })).status()).toBe(401);

const tunnelServerSpanPromise = waitForStreamedSpan(
'nitro-3',
span => getSpanOp(span) === 'http.server' && TUNNEL_PATH.test(String(span.attributes['url.path']?.value ?? '')),
);
const tunnelResponsePromise = page.waitForResponse(
response => response.request().method() === 'POST' && TUNNEL_PATH.test(new URL(response.url()).pathname),
);

await page.goto(`${BASE_PATH}/`);
const tunnelResponse = await tunnelResponsePromise;

expect(tunnelResponse.status()).toBe(200);
expect(new URL(tunnelResponse.url()).origin).toBe(new URL(page.url()).origin);

// Anchor on a regular server span requested after the tunnel traffic: spans flush in order, so a tunnel span
// would arrive first.
const anchorSpanPromise = waitForStreamedSpan(
'nitro-3',
span =>
getSpanOp(span) === 'http.server' &&
String(span.attributes['url.path']?.value ?? '') === `${BASE_PATH}/api/test-transaction`,
);
await page.request.get(`${BASE_PATH}/api/test-transaction`);

const winner = await Promise.race([
tunnelServerSpanPromise.then(() => 'tunnel' as const),
anchorSpanPromise.then(() => 'anchor' as const),
]);
expect(winner).toBe('anchor');
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import { nitro } from 'nitro/vite';
import { defineConfig } from 'vite';

export default defineConfig({
define: {
__E2E_TEST_TUNNEL__: JSON.stringify(!!process.env.E2E_TEST_TUNNEL),
__E2E_TEST_BASE_PATH__: JSON.stringify(process.env.E2E_TEST_BASE_PATH ?? ''),
},
plugins: [
nitro(
// FIXME: Nitro plugin has a type issue
// @ts-expect-error
withSentryConfig({
serverDir: './server',
...(process.env.E2E_TEST_BASE_PATH && { baseURL: `${process.env.E2E_TEST_BASE_PATH}/` }),
}),
),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
const tunnelVariant = !!process.env.E2E_TEST_TUNNEL;

export default defineNuxtConfig({
compatibilityDate: '2025-06-06',
imports: { autoImport: false },
Expand All @@ -12,12 +14,17 @@ export default defineNuxtConfig({
'/rendering-modes/pre-rendered-page': { prerender: true },
},

// The base-path variant serves the app, and so the tunnel, under `/app`.
...(process.env.E2E_TEST_BASE_PATH && { app: { baseURL: `${process.env.E2E_TEST_BASE_PATH}/` } }),

modules: ['@pinia/nuxt', '@sentry/nuxt/module'],
runtimeConfig: {
public: {
sentry: {
dsn: 'https://public@dsn.ingest.sentry.io/1337',
// The tunnel variant forwards to the test proxy, so the DSN has to point there.
dsn: tunnelVariant ? 'http://public@localhost:3031/1337' : 'https://public@dsn.ingest.sentry.io/1337',
},
e2eTunnel: tunnelVariant,
},
},
nitro: {
Expand Down
28 changes: 27 additions & 1 deletion dev-packages/e2e-tests/test-applications/nuxt-4/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
"test:build": "pnpm install && node ./scripts/build-with-prerender-event-sink.mjs",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitropack@npm:nitropack-nightly@latest && pnpm install --force && node ./scripts/build-with-prerender-event-sink.mjs",
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:prod:import": "TEST_ENV=production-import playwright test"
"test:prod:import": "TEST_ENV=production-import playwright test",
"test:build:tunnel": "pnpm install && E2E_TEST_TUNNEL=1 node ./scripts/build-with-prerender-event-sink.mjs",
"test:build:tunnel-basepath": "pnpm install && E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app node ./scripts/build-with-prerender-event-sink.mjs",
"test:assert:tunnel": "E2E_TEST_TUNNEL=1 TEST_ENV=production playwright test tunnel",
"test:assert:tunnel-basepath": "E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app TEST_ENV=production playwright test tunnel",
"test:assert:tunnel-dev": "E2E_TEST_TUNNEL=1 TEST_ENV=development playwright test tunnel",
"test:assert:tunnel-basepath-dev": "E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app TEST_ENV=development playwright test tunnel"
},
"//": "Pin ioredis to 5.10.1 because that's the last version before it publishes its own diagnostics channels",
"dependencies": {
Expand Down Expand Up @@ -45,6 +51,26 @@
"build-command": "pnpm test:build",
"assert-command": "pnpm test:prod:import",
"label": "nuxt-4 (--import compat)"
},
{
"build-command": "pnpm test:build:tunnel",
"assert-command": "pnpm test:assert:tunnel",
"label": "nuxt-4 (tunnel)"
},
{
"build-command": "pnpm test:build:tunnel-basepath",
"assert-command": "pnpm test:assert:tunnel-basepath",
"label": "nuxt-4 (tunnel-basepath)"
},
{
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-dev",
"label": "nuxt-4 (tunnel-dev)"
},
{
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-basepath-dev",
"label": "nuxt-4 (tunnel-basepath-dev)"
}
],
"optionalVariants": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { usePinia, useRuntimeConfig } from '#imports';

Sentry.init({
dsn: useRuntimeConfig().public.sentry.dsn,
tunnel: `http://localhost:3031/`, // proxy server
tunnel: useRuntimeConfig().public.e2eTunnel ? true : `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
integrations: [
Sentry.piniaIntegration(usePinia(), {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as Sentry from '@sentry/nuxt';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
dsn: process.env.E2E_TEST_TUNNEL ? 'http://public@localhost:3031/1337' : 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0, // Capture 100% of the transactions
tunnel: 'http://localhost:3031/', // proxy server
tunnel: process.env.E2E_TEST_TUNNEL ? true : 'http://localhost:3031/', // proxy server
// Opt into the Sentry OpenTelemetry tracer provider in the "(tracer provider)" e2e variant.
// Leaving it `undefined` otherwise keeps the SDK's default (no provider).
enableOpenTelemetrySetup: process.env.E2E_TEST_OTEL_SETUP === 'true' ? true : undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { defineEventHandler, setHeader, getQuery } from '#imports';
import { createError, defineEventHandler, getHeader, setHeader, getQuery } from '#imports';

export default defineEventHandler(async event => {
// The tunnel variant checks that the managed tunnel answers before an auth middleware like this one.
if (process.env.E2E_TEST_TUNNEL && event.method === 'POST' && getHeader(event, 'x-auth') !== 'ok') {
throw createError({ statusCode: 401, statusMessage: 'blocked by user auth' });
}

// Check if we should throw an error
const query = getQuery(event);
if (query.throwError === 'true') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test.skip(!process.env.E2E_TEST_TUNNEL, 'Only runs in the tunnel variant');

const BASE_PATH = process.env.E2E_TEST_BASE_PATH ?? '';
const TUNNEL_PATH = new RegExp(`^${BASE_PATH}/[a-z0-9]{1,7}$`);

test('answers the managed tunnel before the app auth middleware', async ({ page }) => {
// The variant's auth middleware rejects every other POST.
expect((await page.request.post(`${BASE_PATH}/api/anything`, { data: 'x' })).status()).toBe(401);

const tunnelServerSpanPromise = waitForStreamedSpan(
'nuxt-4',
span => getSpanOp(span) === 'http.server' && TUNNEL_PATH.test(String(span.attributes['url.path']?.value ?? '')),
);
const tunnelResponsePromise = page.waitForResponse(
response => response.request().method() === 'POST' && TUNNEL_PATH.test(new URL(response.url()).pathname),
);

await page.goto(`${BASE_PATH}/`);
const tunnelResponse = await tunnelResponsePromise;

expect(tunnelResponse.status()).toBe(200);
expect(new URL(tunnelResponse.url()).origin).toBe(new URL(page.url()).origin);

// Anchor on a regular server span requested after the tunnel traffic: spans flush in order, so a tunnel span
// would arrive first.
const anchorSpanPromise = waitForStreamedSpan(
'nuxt-4',
span =>
getSpanOp(span) === 'http.server' &&
String(span.attributes['url.path']?.value ?? '') === `${BASE_PATH}/api/test-param/123`,
);
await page.request.get(`${BASE_PATH}/api/test-param/123`);

const winner = await Promise.race([
tunnelServerSpanPromise.then(() => 'tunnel' as const),
anchorSpanPromise.then(() => 'anchor' as const),
]);
expect(winner).toBe('anchor');
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
const tunnelVariant = !!process.env.E2E_TEST_TUNNEL;

export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
imports: { autoImport: false },
Expand All @@ -12,12 +14,17 @@ export default defineNuxtConfig({
'/rendering-modes/pre-rendered-page': { prerender: true },
},

// The base-path variant serves the app, and so the tunnel, under `/app`.
...(process.env.E2E_TEST_BASE_PATH && { app: { baseURL: `${process.env.E2E_TEST_BASE_PATH}/` } }),

modules: ['@pinia/nuxt', '@sentry/nuxt/module'],
runtimeConfig: {
public: {
sentry: {
dsn: 'https://public@dsn.ingest.sentry.io/1337',
// The tunnel variant forwards to the test proxy, so the DSN has to point there.
dsn: tunnelVariant ? 'http://public@localhost:3031/1337' : 'https://public@dsn.ingest.sentry.io/1337',
},
e2eTunnel: tunnelVariant,
},
},
nitro: {
Expand Down
26 changes: 24 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
"test:dev": "TEST_ENV=development playwright test environment",
"test:build": "pnpm install && pnpm build",
"test:build-canary": "pnpm add nuxt@npm:nuxt-nightly@latest && pnpm add nitro@npm:nitro-nightly@latest && pnpm install --force && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
"test:assert": "pnpm test:prod && pnpm test:dev",
"test:build:tunnel": "pnpm install && E2E_TEST_TUNNEL=1 pnpm build",
"test:build:tunnel-basepath": "pnpm install && E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app pnpm build",
"test:assert:tunnel": "E2E_TEST_TUNNEL=1 TEST_ENV=production playwright test tunnel",
"test:assert:tunnel-basepath": "E2E_TEST_TUNNEL=1 E2E_TEST_BASE_PATH=/app TEST_ENV=production playwright test tunnel",
"test:assert:tunnel-dev": "E2E_TEST_TUNNEL=1 TEST_ENV=development playwright test tunnel"
},
"//": [
"Currently, we need to install the latest version of Nitro and the Nuxt nightlies as those contain Nuxt v5",
Expand All @@ -38,7 +43,24 @@
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"sentryTest": {
"optional": true
"optional": true,
"variants": [
{
"build-command": "pnpm test:build:tunnel",
"assert-command": "pnpm test:assert:tunnel",
"label": "nuxt-5 (tunnel)"
},
{
"build-command": "pnpm test:build:tunnel-basepath",
"assert-command": "pnpm test:assert:tunnel-basepath",
"label": "nuxt-5 (tunnel-basepath)"
},
{
"build-command": "pnpm install",
"assert-command": "pnpm test:assert:tunnel-dev",
"label": "nuxt-5 (tunnel-dev)"
}
]
},
"volta": {
"extends": "../../package.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { /* usePinia,*/ useRuntimeConfig } from '#imports';

Sentry.init({
dsn: useRuntimeConfig().public.sentry.dsn,
tunnel: `http://localhost:3031/`, // proxy server
tunnel: useRuntimeConfig().public.e2eTunnel ? true : `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
integrations: [
/* Sentry.piniaIntegration(usePinia(), {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as Sentry from '@sentry/nuxt';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
dsn: process.env.E2E_TEST_TUNNEL ? 'http://public@localhost:3031/1337' : 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0, // Capture 100% of the transactions
tunnel: 'http://localhost:3031/', // proxy server
tunnel: process.env.E2E_TEST_TUNNEL ? true : 'http://localhost:3031/', // proxy server
// Several tests trigger the same server error one after another, which Dedupe would drop.
integrations: integrations => integrations.filter(integration => integration.name !== 'Dedupe'),
});
Loading
Loading