diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ec44bc343fc1..c4122a129a8b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -140,7 +140,6 @@ jobs:
- 'packages/profiling-node/**'
- 'dev-packages/e2e-tests/test-applications/node-profiling/**'
profiling_node_bindings:
- - *workflow
- 'packages/profiling-node/**'
- 'dev-packages/e2e-tests/test-applications/node-profiling/**'
deno:
@@ -888,8 +887,6 @@ jobs:
remix: 1
- node: 16
remix: 1
- - tracingIntegration: true
- remix: 2
steps:
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
uses: actions/checkout@v4
@@ -907,7 +904,6 @@ jobs:
env:
NODE_VERSION: ${{ matrix.node }}
REMIX_VERSION: ${{ matrix.remix }}
- TRACING_INTEGRATION: ${{ matrix.tracingIntegration }}
run: |
cd packages/remix
yarn test:integration:ci
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx
index a43de2e086de..4eb7e3d3553f 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx
@@ -19,6 +19,7 @@ Sentry.init({
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
+ tunnel: 'http://localhost:3031/', // proxy server
});
Sentry.addEventProcessor(event => {
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx
index c3deb6369af3..d228a7606ac6 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx
@@ -17,6 +17,7 @@ Sentry.init({
dsn: process.env.E2E_TEST_DSN,
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
+ tunnel: 'http://localhost:3031/', // proxy server
});
export const handleError = Sentry.wrapRemixHandleError;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx
index 8907ef7816fd..b646c62ee4da 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx
@@ -1,7 +1,13 @@
-import { Link } from '@remix-run/react';
+import { Link, useSearchParams } from '@remix-run/react';
import * as Sentry from '@sentry/remix';
export default function Index() {
+ const [searchParams] = useSearchParams();
+
+ if (searchParams.get('tag')) {
+ Sentry.setTag('sentry_test', searchParams.get('tag'));
+ }
+
return (
{
+ const eventCallbackListeners: Set<(data: string) => void> = new Set();
+
+ const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
+ const proxyRequestChunks: Uint8Array[] = [];
+
+ proxyRequest.addListener('data', (chunk: Buffer) => {
+ proxyRequestChunks.push(chunk);
+ });
+
+ proxyRequest.addListener('error', err => {
+ throw err;
+ });
+
+ proxyRequest.addListener('end', () => {
+ const proxyRequestBody =
+ proxyRequest.headers['content-encoding'] === 'gzip'
+ ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
+ : Buffer.concat(proxyRequestChunks).toString();
+
+ let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
+
+ if (!envelopeHeader.dsn) {
+ throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.');
+ }
+
+ const { origin, pathname, host } = new URL(envelopeHeader.dsn);
+
+ const projectId = pathname.substring(1);
+ const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
+
+ proxyRequest.headers.host = host;
+
+ const sentryResponseChunks: Uint8Array[] = [];
+
+ const sentryRequest = https.request(
+ sentryIngestUrl,
+ { headers: proxyRequest.headers, method: proxyRequest.method },
+ sentryResponse => {
+ sentryResponse.addListener('data', (chunk: Buffer) => {
+ proxyResponse.write(chunk, 'binary');
+ sentryResponseChunks.push(chunk);
+ });
+
+ sentryResponse.addListener('end', () => {
+ eventCallbackListeners.forEach(listener => {
+ const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
+
+ const data: SentryRequestCallbackData = {
+ envelope: parseEnvelope(proxyRequestBody),
+ rawProxyRequestBody: proxyRequestBody,
+ rawSentryResponseBody,
+ sentryResponseStatusCode: sentryResponse.statusCode,
+ };
+
+ listener(Buffer.from(JSON.stringify(data)).toString('base64'));
+ });
+ proxyResponse.end();
+ });
+
+ sentryResponse.addListener('error', err => {
+ throw err;
+ });
+
+ proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
+ },
+ );
+
+ sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
+ sentryRequest.end();
+ });
+ });
+
+ const proxyServerStartupPromise = new Promise
(resolve => {
+ proxyServer.listen(options.port, () => {
+ resolve();
+ });
+ });
+
+ const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
+ eventCallbackResponse.statusCode = 200;
+ eventCallbackResponse.setHeader('connection', 'keep-alive');
+
+ const callbackListener = (data: string): void => {
+ eventCallbackResponse.write(data.concat('\n'), 'utf8');
+ };
+
+ eventCallbackListeners.add(callbackListener);
+
+ eventCallbackRequest.on('close', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+
+ eventCallbackRequest.on('error', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+ });
+
+ const eventCallbackServerStartupPromise = new Promise(resolve => {
+ eventCallbackServer.listen(0, () => {
+ const port = String((eventCallbackServer.address() as AddressInfo).port);
+ void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
+ });
+ });
+
+ await eventCallbackServerStartupPromise;
+ await proxyServerStartupPromise;
+ return;
+}
+
+export async function waitForRequest(
+ proxyServerName: string,
+ callback: (eventData: SentryRequestCallbackData) => Promise | boolean,
+): Promise {
+ const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName);
+
+ return new Promise((resolve, reject) => {
+ const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => {
+ let eventContents = '';
+
+ response.on('error', err => {
+ reject(err);
+ });
+
+ response.on('data', (chunk: Buffer) => {
+ const chunkString = chunk.toString('utf8');
+ chunkString.split('').forEach(char => {
+ if (char === '\n') {
+ const eventCallbackData: SentryRequestCallbackData = JSON.parse(
+ Buffer.from(eventContents, 'base64').toString('utf8'),
+ );
+ const callbackResult = callback(eventCallbackData);
+ if (typeof callbackResult !== 'boolean') {
+ callbackResult.then(
+ match => {
+ if (match) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ },
+ err => {
+ throw err;
+ },
+ );
+ } else if (callbackResult) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ eventContents = '';
+ } else {
+ eventContents = eventContents.concat(char);
+ }
+ });
+ });
+ });
+
+ request.end();
+ });
+}
+
+export function waitForEnvelopeItem(
+ proxyServerName: string,
+ callback: (envelopeItem: EnvelopeItem) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForRequest(proxyServerName, async eventData => {
+ const envelopeItems = eventData.envelope[1];
+ for (const envelopeItem of envelopeItems) {
+ if (await callback(envelopeItem)) {
+ resolve(envelopeItem);
+ return true;
+ }
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForError(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForTransaction(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+const TEMP_FILE_PREFIX = 'event-proxy-server-';
+
+async function registerCallbackServerPort(serverName: string, port: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ await writeFile(tmpFilePath, port, { encoding: 'utf8' });
+}
+
+function retrieveCallbackServerPort(serverName: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ return readFile(tmpFilePath, 'utf8');
+}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
index 17a21f6e0b5e..4310039b0638 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
@@ -27,6 +27,8 @@
"devDependencies": {
"@playwright/test": "^1.36.2",
"@remix-run/dev": "^2.7.2",
+ "@sentry/types": "latest || *",
+ "@sentry/utils": "latest || *",
"@types/compression": "^1.7.5",
"@types/express": "^4.17.20",
"@types/morgan": "^1.9.9",
@@ -43,7 +45,8 @@
"eslint-plugin-react-hooks": "^4.6.0",
"typescript": "^5.1.6",
"vite": "^5.1.0",
- "vite-tsconfig-paths": "^4.2.1"
+ "vite-tsconfig-paths": "^4.2.1",
+ "ts-node": "10.9.1"
},
"engines": {
"node": ">=18.0.0"
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts
index 9f04a7ee7896..dd495b0c9f98 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts
@@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const port = 3030;
+const eventProxyPort = 3031;
/**
* See https://playwright.dev/docs/test-configuration.
@@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
+
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${port}`,
},
/* Configure projects for major browsers */
@@ -44,15 +48,19 @@ const config: PlaywrightTestConfig = {
...devices['Desktop Chrome'],
},
},
- // For now we only test Chrome!
],
/* Run your local dev server before starting the tests */
- webServer: {
- // This test app is testing the Vite dev server, so we need to run it before the tests.
- command: `PORT=${port} pnpm dev`,
- port,
- },
+ webServer: [
+ {
+ command: 'pnpm ts-node --project="tsconfig.event-proxy-server.json" ./start-event-proxy.ts',
+ port: eventProxyPort,
+ },
+ {
+ command: `PORT=${port} pnpm dev`,
+ port: port,
+ },
+ ],
};
export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts
new file mode 100644
index 000000000000..e56a52190e63
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts
@@ -0,0 +1,5 @@
+import { startEventProxyServer } from './event-proxy-server';
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'create-remix-app-express-vite-dev',
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts
new file mode 100644
index 000000000000..6a36f85cbfd0
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts
@@ -0,0 +1,35 @@
+import { expect, test } from '@playwright/test';
+import { uuid4 } from '@sentry/utils';
+
+import { waitForTransaction } from '../event-proxy-server';
+
+test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => {
+ // We use this to identify the transactions
+ const testTag = uuid4();
+
+ // no server span here!
+
+ const pageLoadTransactionPromise = waitForTransaction('create-remix-app-express-vite-dev', transactionEvent => {
+ return (
+ transactionEvent.type === 'transaction' &&
+ transactionEvent.contexts?.trace?.op === 'pageload' &&
+ transactionEvent.tags?.['sentry_test'] === testTag
+ );
+ });
+
+ page.goto(`/?tag=${testTag}`);
+
+ const pageloadTransaction = await pageLoadTransactionPromise;
+
+ expect(pageloadTransaction).toBeDefined();
+
+ const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
+ const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
+ const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
+
+ expect(pageloadTransaction.transaction).toBe('routes/_index');
+
+ expect(pageLoadTraceId).toBeDefined();
+ expect(pageLoadParentSpanId).toBeUndefined();
+ expect(pageLoadSpanId).toBeDefined();
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json
new file mode 100644
index 000000000000..bd49b1f0c16f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "module": "CommonJS",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "allowImportingTsExtensions": true
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json
index 77291a910914..0a6c9071cb90 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json
@@ -1,5 +1,6 @@
{
"include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["event-proxy-server.ts", "start-event-proxy.ts"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx
index 361d2ab9935b..b3b5db3d9b3d 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx
@@ -25,6 +25,7 @@ Sentry.init({
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
+ tunnel: 'http://localhost:3031/', // proxy server
});
Sentry.addEventProcessor(event => {
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx
index d8e63095fa43..98ab60159b70 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx
@@ -23,6 +23,7 @@ Sentry.init({
dsn: process.env.E2E_TEST_DSN,
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
+ tunnel: 'http://localhost:3031/', // proxy server
});
export const handleError = Sentry.wrapRemixHandleError;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx
index 8907ef7816fd..b646c62ee4da 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx
@@ -1,7 +1,13 @@
-import { Link } from '@remix-run/react';
+import { Link, useSearchParams } from '@remix-run/react';
import * as Sentry from '@sentry/remix';
export default function Index() {
+ const [searchParams] = useSearchParams();
+
+ if (searchParams.get('tag')) {
+ Sentry.setTag('sentry_test', searchParams.get('tag'));
+ }
+
return (
{
+ const eventCallbackListeners: Set<(data: string) => void> = new Set();
+
+ const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
+ const proxyRequestChunks: Uint8Array[] = [];
+
+ proxyRequest.addListener('data', (chunk: Buffer) => {
+ proxyRequestChunks.push(chunk);
+ });
+
+ proxyRequest.addListener('error', err => {
+ throw err;
+ });
+
+ proxyRequest.addListener('end', () => {
+ const proxyRequestBody =
+ proxyRequest.headers['content-encoding'] === 'gzip'
+ ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
+ : Buffer.concat(proxyRequestChunks).toString();
+
+ let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
+
+ if (!envelopeHeader.dsn) {
+ throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.');
+ }
+
+ const { origin, pathname, host } = new URL(envelopeHeader.dsn);
+
+ const projectId = pathname.substring(1);
+ const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
+
+ proxyRequest.headers.host = host;
+
+ const sentryResponseChunks: Uint8Array[] = [];
+
+ const sentryRequest = https.request(
+ sentryIngestUrl,
+ { headers: proxyRequest.headers, method: proxyRequest.method },
+ sentryResponse => {
+ sentryResponse.addListener('data', (chunk: Buffer) => {
+ proxyResponse.write(chunk, 'binary');
+ sentryResponseChunks.push(chunk);
+ });
+
+ sentryResponse.addListener('end', () => {
+ eventCallbackListeners.forEach(listener => {
+ const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
+
+ const data: SentryRequestCallbackData = {
+ envelope: parseEnvelope(proxyRequestBody),
+ rawProxyRequestBody: proxyRequestBody,
+ rawSentryResponseBody,
+ sentryResponseStatusCode: sentryResponse.statusCode,
+ };
+
+ listener(Buffer.from(JSON.stringify(data)).toString('base64'));
+ });
+ proxyResponse.end();
+ });
+
+ sentryResponse.addListener('error', err => {
+ throw err;
+ });
+
+ proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
+ },
+ );
+
+ sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
+ sentryRequest.end();
+ });
+ });
+
+ const proxyServerStartupPromise = new Promise
(resolve => {
+ proxyServer.listen(options.port, () => {
+ resolve();
+ });
+ });
+
+ const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
+ eventCallbackResponse.statusCode = 200;
+ eventCallbackResponse.setHeader('connection', 'keep-alive');
+
+ const callbackListener = (data: string): void => {
+ eventCallbackResponse.write(data.concat('\n'), 'utf8');
+ };
+
+ eventCallbackListeners.add(callbackListener);
+
+ eventCallbackRequest.on('close', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+
+ eventCallbackRequest.on('error', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+ });
+
+ const eventCallbackServerStartupPromise = new Promise(resolve => {
+ eventCallbackServer.listen(0, () => {
+ const port = String((eventCallbackServer.address() as AddressInfo).port);
+ void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
+ });
+ });
+
+ await eventCallbackServerStartupPromise;
+ await proxyServerStartupPromise;
+ return;
+}
+
+export async function waitForRequest(
+ proxyServerName: string,
+ callback: (eventData: SentryRequestCallbackData) => Promise | boolean,
+): Promise {
+ const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName);
+
+ return new Promise((resolve, reject) => {
+ const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => {
+ let eventContents = '';
+
+ response.on('error', err => {
+ reject(err);
+ });
+
+ response.on('data', (chunk: Buffer) => {
+ const chunkString = chunk.toString('utf8');
+ chunkString.split('').forEach(char => {
+ if (char === '\n') {
+ const eventCallbackData: SentryRequestCallbackData = JSON.parse(
+ Buffer.from(eventContents, 'base64').toString('utf8'),
+ );
+ const callbackResult = callback(eventCallbackData);
+ if (typeof callbackResult !== 'boolean') {
+ callbackResult.then(
+ match => {
+ if (match) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ },
+ err => {
+ throw err;
+ },
+ );
+ } else if (callbackResult) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ eventContents = '';
+ } else {
+ eventContents = eventContents.concat(char);
+ }
+ });
+ });
+ });
+
+ request.end();
+ });
+}
+
+export function waitForEnvelopeItem(
+ proxyServerName: string,
+ callback: (envelopeItem: EnvelopeItem) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForRequest(proxyServerName, async eventData => {
+ const envelopeItems = eventData.envelope[1];
+ for (const envelopeItem of envelopeItems) {
+ if (await callback(envelopeItem)) {
+ resolve(envelopeItem);
+ return true;
+ }
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForError(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForTransaction(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+const TEMP_FILE_PREFIX = 'event-proxy-server-';
+
+async function registerCallbackServerPort(serverName: string, port: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ await writeFile(tmpFilePath, port, { encoding: 'utf8' });
+}
+
+function retrieveCallbackServerPort(serverName: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ return readFile(tmpFilePath, 'utf8');
+}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
index 71ff5ff39803..646bc5f21e25 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
@@ -24,10 +24,13 @@
"@playwright/test": "^1.36.2",
"@remix-run/dev": "2.7.2",
"@remix-run/eslint-config": "2.7.2",
+ "@sentry/types": "latest || *",
+ "@sentry/utils": "latest || *",
"@types/react": "^18.0.35",
"@types/react-dom": "^18.0.11",
"eslint": "^8.38.0",
- "typescript": "^5.0.4"
+ "typescript": "^5.0.4",
+ "ts-node": "10.9.1"
},
"engines": {
"node": ">=18.0.0"
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts
index 79efcbc22c1a..429baa2db33f 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts
@@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const port = 3030;
+const eventProxyPort = 3031;
/**
* See https://playwright.dev/docs/test-configuration.
@@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
+
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${port}`,
},
/* Configure projects for major browsers */
@@ -44,14 +48,19 @@ const config: PlaywrightTestConfig = {
...devices['Desktop Chrome'],
},
},
- // For now we only test Chrome!
],
/* Run your local dev server before starting the tests */
- webServer: {
- command: `PORT=${port} pnpm start`,
- port,
- },
+ webServer: [
+ {
+ command: 'pnpm ts-node-script start-event-proxy.ts',
+ port: eventProxyPort,
+ },
+ {
+ command: `PORT=${port} pnpm start`,
+ port: port,
+ },
+ ],
};
export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts
new file mode 100644
index 000000000000..cc810192de58
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts
@@ -0,0 +1,5 @@
+import { startEventProxyServer } from './event-proxy-server';
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'create-remix-app-v2',
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts
new file mode 100644
index 000000000000..992a315af3d3
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts
@@ -0,0 +1,50 @@
+import { expect, test } from '@playwright/test';
+import { uuid4 } from '@sentry/utils';
+
+import { waitForTransaction } from '../event-proxy-server';
+
+test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => {
+ // We use this to identify the transactions
+ const testTag = uuid4();
+
+ const httpServerTransactionPromise = waitForTransaction('create-remix-app-v2', transactionEvent => {
+ return (
+ transactionEvent.type === 'transaction' &&
+ transactionEvent.contexts?.trace?.op === 'http.server' &&
+ transactionEvent.tags?.['sentry_test'] === testTag
+ );
+ });
+
+ const pageLoadTransactionPromise = waitForTransaction('create-remix-app-v2', transactionEvent => {
+ return (
+ transactionEvent.type === 'transaction' &&
+ transactionEvent.contexts?.trace?.op === 'pageload' &&
+ transactionEvent.tags?.['sentry_test'] === testTag
+ );
+ });
+
+ page.goto(`/?tag=${testTag}`);
+
+ const pageloadTransaction = await pageLoadTransactionPromise;
+ const httpServerTransaction = await httpServerTransactionPromise;
+
+ expect(pageloadTransaction).toBeDefined();
+ expect(httpServerTransaction).toBeDefined();
+
+ const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
+ const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
+
+ const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
+ const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
+ const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
+
+ expect(httpServerTransaction.transaction).toBe('routes/_index');
+ expect(pageloadTransaction.transaction).toBe('routes/_index');
+
+ expect(httpServerTraceId).toBeDefined();
+ expect(httpServerSpanId).toBeDefined();
+
+ expect(pageLoadTraceId).toEqual(httpServerTraceId);
+ expect(pageLoadParentSpanId).toEqual(httpServerSpanId);
+ expect(pageLoadSpanId).not.toEqual(httpServerSpanId);
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx
index 5cf3bb788d6b..93eab0f819fb 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx
@@ -18,6 +18,7 @@ Sentry.init({
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
+ tunnel: 'http://localhost:3031/', // proxy server
});
Sentry.addEventProcessor(event => {
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx
index c2de73cdba63..03364b7d1f32 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx
@@ -20,6 +20,7 @@ Sentry.init({
dsn: process.env.E2E_TEST_DSN,
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
+ tunnel: 'http://localhost:3031/', // proxy server
});
export const handleError = Sentry.wrapRemixHandleError;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx
index 8907ef7816fd..b646c62ee4da 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx
@@ -1,7 +1,13 @@
-import { Link } from '@remix-run/react';
+import { Link, useSearchParams } from '@remix-run/react';
import * as Sentry from '@sentry/remix';
export default function Index() {
+ const [searchParams] = useSearchParams();
+
+ if (searchParams.get('tag')) {
+ Sentry.setTag('sentry_test', searchParams.get('tag'));
+ }
+
return (
{
+ const eventCallbackListeners: Set<(data: string) => void> = new Set();
+
+ const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
+ const proxyRequestChunks: Uint8Array[] = [];
+
+ proxyRequest.addListener('data', (chunk: Buffer) => {
+ proxyRequestChunks.push(chunk);
+ });
+
+ proxyRequest.addListener('error', err => {
+ throw err;
+ });
+
+ proxyRequest.addListener('end', () => {
+ const proxyRequestBody =
+ proxyRequest.headers['content-encoding'] === 'gzip'
+ ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
+ : Buffer.concat(proxyRequestChunks).toString();
+
+ let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);
+
+ if (!envelopeHeader.dsn) {
+ throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.');
+ }
+
+ const { origin, pathname, host } = new URL(envelopeHeader.dsn);
+
+ const projectId = pathname.substring(1);
+ const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;
+
+ proxyRequest.headers.host = host;
+
+ const sentryResponseChunks: Uint8Array[] = [];
+
+ const sentryRequest = https.request(
+ sentryIngestUrl,
+ { headers: proxyRequest.headers, method: proxyRequest.method },
+ sentryResponse => {
+ sentryResponse.addListener('data', (chunk: Buffer) => {
+ proxyResponse.write(chunk, 'binary');
+ sentryResponseChunks.push(chunk);
+ });
+
+ sentryResponse.addListener('end', () => {
+ eventCallbackListeners.forEach(listener => {
+ const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();
+
+ const data: SentryRequestCallbackData = {
+ envelope: parseEnvelope(proxyRequestBody),
+ rawProxyRequestBody: proxyRequestBody,
+ rawSentryResponseBody,
+ sentryResponseStatusCode: sentryResponse.statusCode,
+ };
+
+ listener(Buffer.from(JSON.stringify(data)).toString('base64'));
+ });
+ proxyResponse.end();
+ });
+
+ sentryResponse.addListener('error', err => {
+ throw err;
+ });
+
+ proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
+ },
+ );
+
+ sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
+ sentryRequest.end();
+ });
+ });
+
+ const proxyServerStartupPromise = new Promise
(resolve => {
+ proxyServer.listen(options.port, () => {
+ resolve();
+ });
+ });
+
+ const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
+ eventCallbackResponse.statusCode = 200;
+ eventCallbackResponse.setHeader('connection', 'keep-alive');
+
+ const callbackListener = (data: string): void => {
+ eventCallbackResponse.write(data.concat('\n'), 'utf8');
+ };
+
+ eventCallbackListeners.add(callbackListener);
+
+ eventCallbackRequest.on('close', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+
+ eventCallbackRequest.on('error', () => {
+ eventCallbackListeners.delete(callbackListener);
+ });
+ });
+
+ const eventCallbackServerStartupPromise = new Promise(resolve => {
+ eventCallbackServer.listen(0, () => {
+ const port = String((eventCallbackServer.address() as AddressInfo).port);
+ void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
+ });
+ });
+
+ await eventCallbackServerStartupPromise;
+ await proxyServerStartupPromise;
+ return;
+}
+
+export async function waitForRequest(
+ proxyServerName: string,
+ callback: (eventData: SentryRequestCallbackData) => Promise | boolean,
+): Promise {
+ const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName);
+
+ return new Promise((resolve, reject) => {
+ const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => {
+ let eventContents = '';
+
+ response.on('error', err => {
+ reject(err);
+ });
+
+ response.on('data', (chunk: Buffer) => {
+ const chunkString = chunk.toString('utf8');
+ chunkString.split('').forEach(char => {
+ if (char === '\n') {
+ const eventCallbackData: SentryRequestCallbackData = JSON.parse(
+ Buffer.from(eventContents, 'base64').toString('utf8'),
+ );
+ const callbackResult = callback(eventCallbackData);
+ if (typeof callbackResult !== 'boolean') {
+ callbackResult.then(
+ match => {
+ if (match) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ },
+ err => {
+ throw err;
+ },
+ );
+ } else if (callbackResult) {
+ response.destroy();
+ resolve(eventCallbackData);
+ }
+ eventContents = '';
+ } else {
+ eventContents = eventContents.concat(char);
+ }
+ });
+ });
+ });
+
+ request.end();
+ });
+}
+
+export function waitForEnvelopeItem(
+ proxyServerName: string,
+ callback: (envelopeItem: EnvelopeItem) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForRequest(proxyServerName, async eventData => {
+ const envelopeItems = eventData.envelope[1];
+ for (const envelopeItem of envelopeItems) {
+ if (await callback(envelopeItem)) {
+ resolve(envelopeItem);
+ return true;
+ }
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForError(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+export function waitForTransaction(
+ proxyServerName: string,
+ callback: (transactionEvent: Event) => Promise | boolean,
+): Promise {
+ return new Promise((resolve, reject) => {
+ waitForEnvelopeItem(proxyServerName, async envelopeItem => {
+ const [envelopeItemHeader, envelopeItemBody] = envelopeItem;
+ if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) {
+ resolve(envelopeItemBody as Event);
+ return true;
+ }
+ return false;
+ }).catch(reject);
+ });
+}
+
+const TEMP_FILE_PREFIX = 'event-proxy-server-';
+
+async function registerCallbackServerPort(serverName: string, port: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ await writeFile(tmpFilePath, port, { encoding: 'utf8' });
+}
+
+function retrieveCallbackServerPort(serverName: string): Promise {
+ const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
+ return readFile(tmpFilePath, 'utf8');
+}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app/package.json
index 5e3b56b07ee4..365fd9fb0bac 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/package.json
@@ -26,8 +26,11 @@
"@remix-run/eslint-config": "^1.19.3",
"@types/react": "^18.0.35",
"@types/react-dom": "^18.0.11",
+ "@sentry/types": "latest || *",
+ "@sentry/utils": "latest || *",
"eslint": "^8.38.0",
- "typescript": "^5.0.4"
+ "typescript": "^5.0.4",
+ "ts-node": "10.9.1"
},
"engines": {
"node": ">=14.18"
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts
index 785ca43321a4..429baa2db33f 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts
@@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const port = 3030;
+const eventProxyPort = 3031;
/**
* See https://playwright.dev/docs/test-configuration.
@@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
+
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${port}`,
},
/* Configure projects for major browsers */
@@ -44,26 +48,19 @@ const config: PlaywrightTestConfig = {
...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: `PORT=${port} pnpm start`,
- port,
- },
+ webServer: [
+ {
+ command: 'pnpm ts-node-script start-event-proxy.ts',
+ port: eventProxyPort,
+ },
+ {
+ command: `PORT=${port} pnpm start`,
+ port: port,
+ },
+ ],
};
export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts
new file mode 100644
index 000000000000..93755c9d232e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts
@@ -0,0 +1,5 @@
+import { startEventProxyServer } from './event-proxy-server';
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'create-remix-app',
+});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts
new file mode 100644
index 000000000000..d0d737e44a69
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts
@@ -0,0 +1,50 @@
+import { expect, test } from '@playwright/test';
+import { uuid4 } from '@sentry/utils';
+
+import { waitForTransaction } from '../event-proxy-server';
+
+test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => {
+ // We use this to identify the transactions
+ const testTag = uuid4();
+
+ const httpServerTransactionPromise = waitForTransaction('create-remix-app', transactionEvent => {
+ return (
+ transactionEvent.type === 'transaction' &&
+ transactionEvent.contexts?.trace?.op === 'http.server' &&
+ transactionEvent.tags?.['sentry_test'] === testTag
+ );
+ });
+
+ const pageLoadTransactionPromise = waitForTransaction('create-remix-app', transactionEvent => {
+ return (
+ transactionEvent.type === 'transaction' &&
+ transactionEvent.contexts?.trace?.op === 'pageload' &&
+ transactionEvent.tags?.['sentry_test'] === testTag
+ );
+ });
+
+ page.goto(`/?tag=${testTag}`);
+
+ const pageloadTransaction = await pageLoadTransactionPromise;
+ const httpServerTransaction = await httpServerTransactionPromise;
+
+ expect(pageloadTransaction).toBeDefined();
+ expect(httpServerTransaction).toBeDefined();
+
+ const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
+ const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
+
+ const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
+ const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
+ const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
+
+ expect(httpServerTransaction.transaction).toBe('routes/_index');
+ expect(pageloadTransaction.transaction).toBe('routes/_index');
+
+ expect(httpServerTraceId).toBeDefined();
+ expect(httpServerSpanId).toBeDefined();
+
+ expect(pageLoadTraceId).toEqual(httpServerTraceId);
+ expect(pageLoadParentSpanId).toEqual(httpServerSpanId);
+ expect(pageLoadSpanId).not.toEqual(httpServerSpanId);
+});
diff --git a/packages/remix/package.json b/packages/remix/package.json
index be7820075da7..8cc0ce185345 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -85,7 +85,6 @@
"test:integration": "run-s test:integration:v1 test:integration:v2 test:integration:tracingIntegration",
"test:integration:v1": "run-s test:integration:clean test:integration:prepare test:integration:client test:integration:server",
"test:integration:v2": "export REMIX_VERSION=2 && run-s test:integration:v1",
- "test:integration:tracingIntegration": "export TRACING_INTEGRATION=true && run-s test:integration:v2",
"test:integration:ci": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server",
"test:integration:prepare": "(cd test/integration && yarn install)",
"test:integration:clean": "(cd test/integration && rimraf .cache node_modules build)",
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx
deleted file mode 100644
index 7273433127ac..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-import { useEffect } from 'react';
-import { hydrate } from 'react-dom';
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- tracesSampleRate: 1,
- integrations: [Sentry.browserTracingIntegration({ useEffect, useLocation, useMatches })],
-});
-
-hydrate(, document);
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx
deleted file mode 100644
index bba366801092..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { EntryContext } from '@remix-run/node';
-import { RemixServer } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-import { renderToString } from 'react-dom/server';
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- tracesSampleRate: 1,
- tracePropagationTargets: ['example.org'],
- // Disabling to test series of envelopes deterministically.
- autoSessionTracking: false,
-});
-
-export const handleError = Sentry.wrapRemixHandleError;
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- let markup = renderToString();
-
- responseHeaders.set('Content-Type', 'text/html');
-
- return new Response('' + markup, {
- status: responseStatusCode,
- headers: responseHeaders,
- });
-}
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx
deleted file mode 100644
index 15b78b8a6325..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { LoaderFunction, V2_MetaFunction, defer, json, redirect } from '@remix-run/node';
-import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, useRouteError } from '@remix-run/react';
-import { V2_ErrorBoundaryComponent } from '@remix-run/react/dist/routeModules';
-import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';
-
-export const ErrorBoundary: V2_ErrorBoundaryComponent = () => {
- const error = useRouteError();
-
- captureRemixErrorBoundaryError(error);
-
- return error
;
-};
-
-export const meta: V2_MetaFunction = ({ data }) => [
- { charset: 'utf-8' },
- { title: 'New Remix App' },
- { name: 'viewport', content: 'width=device-width,initial-scale=1' },
- { name: 'sentry-trace', content: data.sentryTrace },
- { name: 'baggage', content: data.sentryBaggage },
-];
-
-export const loader: LoaderFunction = async ({ request }) => {
- const url = new URL(request.url);
- const type = url.searchParams.get('type');
-
- switch (type) {
- case 'empty':
- return {};
- case 'plain':
- return {
- data_one: [],
- data_two: 'a string',
- };
- case 'json':
- return json({ data_one: [], data_two: 'a string' }, { headers: { 'Cache-Control': 'max-age=300' } });
- case 'defer':
- return defer({ data_one: [], data_two: 'a string' });
- case 'null':
- return null;
- case 'undefined':
- return undefined;
- case 'throwRedirect':
- throw redirect('/?type=plain');
- case 'returnRedirect':
- return redirect('/?type=plain');
- case 'throwRedirectToExternal':
- throw redirect('https://example.com');
- case 'returnRedirectToExternal':
- return redirect('https://example.com');
- default: {
- return {};
- }
- }
-};
-
-function App() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default withSentry(App);
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx
deleted file mode 100644
index 7a00bfb2bfe7..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/action-json-response.$id';
-export { default } from '../../common/routes/action-json-response.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx
deleted file mode 100644
index 1ba745d2e63d..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/capture-exception';
-export { default } from '../../common/routes/capture-exception';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx
deleted file mode 100644
index 9dae2318cc14..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/capture-message';
-export { default } from '../../common/routes/capture-message';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx
deleted file mode 100644
index 011f92462069..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/error-boundary-capture.$id';
-export { default } from '../../common/routes/error-boundary-capture.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx
deleted file mode 100644
index 22c086a4c2cf..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/index';
-export { default } from '../../common/routes/index';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx
deleted file mode 100644
index 69499e594ccc..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/loader-defer-response.$id';
-export { default } from '../../common/routes/loader-defer-response.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx
deleted file mode 100644
index 7761875bdb76..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/loader-json-response.$id';
-export { default } from '../../common/routes/loader-json-response.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx
deleted file mode 100644
index 6b9a6a85cbef..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/loader-throw-response.$id';
-export { default } from '../../common/routes/loader-throw-response.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx
deleted file mode 100644
index a7cfebe4ed46..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/manual-tracing.$id';
-export { default } from '../../common/routes/manual-tracing.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx
deleted file mode 100644
index 5ba2376f0339..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/scope-bleed.$id';
-export { default } from '../../common/routes/scope-bleed.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx
deleted file mode 100644
index d9571c68ddd5..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/server-side-unexpected-errors.$id';
-export { default } from '../../common/routes/server-side-unexpected-errors.$id';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx
deleted file mode 100644
index 627f7e126871..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/ssr-error';
-export { default } from '../../common/routes/ssr-error';
diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx
deleted file mode 100644
index 4425f3432b58..000000000000
--- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from '../../common/routes/throw-redirect';
-export { default } from '../../common/routes/throw-redirect';
diff --git a/packages/remix/test/integration/remix.config.js b/packages/remix/test/integration/remix.config.js
index 418d3690f696..b4c7ac0837b8 100644
--- a/packages/remix/test/integration/remix.config.js
+++ b/packages/remix/test/integration/remix.config.js
@@ -1,9 +1,8 @@
/** @type {import('@remix-run/dev').AppConfig} */
const useV2 = process.env.REMIX_VERSION === '2';
-const useBrowserTracing = process.env.TRACING_INTEGRATION === 'true';
module.exports = {
- appDirectory: useBrowserTracing ? 'app_v2_tracingIntegration' : useV2 ? 'app_v2' : 'app_v1',
+ appDirectory: useV2 ? 'app_v2' : 'app_v1',
assetsBuildDirectory: 'public/build',
serverBuildPath: 'build/index.js',
publicPath: '/build/',