diff --git a/packages/remix/test/integration/app/routes/capture-exception.tsx b/packages/remix/test/integration/app/routes/capture-exception.tsx
new file mode 100644
index 000000000000..296d50af21f4
--- /dev/null
+++ b/packages/remix/test/integration/app/routes/capture-exception.tsx
@@ -0,0 +1,7 @@
+import * as Sentry from '@sentry/remix';
+
+export default function ErrorBoundaryCapture() {
+ Sentry.captureException(new Error('Sentry Manually Captured Error'));
+
+ return
;
+}
diff --git a/packages/remix/test/integration/app/routes/capture-message.tsx b/packages/remix/test/integration/app/routes/capture-message.tsx
new file mode 100644
index 000000000000..459e25e1b4ee
--- /dev/null
+++ b/packages/remix/test/integration/app/routes/capture-message.tsx
@@ -0,0 +1,7 @@
+import * as Sentry from '@sentry/remix';
+
+export default function ErrorBoundaryCapture() {
+ Sentry.captureMessage('Sentry Manually Captured Message');
+
+ return ;
+}
diff --git a/packages/remix/test/integration/app/routes/error-boundary-capture/$id.tsx b/packages/remix/test/integration/app/routes/error-boundary-capture/$id.tsx
new file mode 100644
index 000000000000..4e5330621191
--- /dev/null
+++ b/packages/remix/test/integration/app/routes/error-boundary-capture/$id.tsx
@@ -0,0 +1,13 @@
+import { useState } from 'react';
+
+export default function ErrorBoundaryCapture() {
+ const [count, setCount] = useState(0);
+
+ if (count > 0) {
+ throw new Error('Sentry React Component Error');
+ } else {
+ setTimeout(() => setCount(count + 1), 0);
+ }
+
+ return {count}
;
+}
diff --git a/packages/remix/test/integration/app/routes/manual-tracing/$id.tsx b/packages/remix/test/integration/app/routes/manual-tracing/$id.tsx
new file mode 100644
index 000000000000..75cf8574819a
--- /dev/null
+++ b/packages/remix/test/integration/app/routes/manual-tracing/$id.tsx
@@ -0,0 +1,7 @@
+import * as Sentry from '@sentry/remix';
+
+export default function ManualTracing() {
+ const transaction = Sentry.startTransaction({ name: 'test_transaction_1' });
+ transaction.finish();
+ return ;
+}
diff --git a/packages/remix/test/integration/test/client/capture-exception.test.ts b/packages/remix/test/integration/test/client/capture-exception.test.ts
new file mode 100644
index 000000000000..8f2db9d8704b
--- /dev/null
+++ b/packages/remix/test/integration/test/client/capture-exception.test.ts
@@ -0,0 +1,25 @@
+import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
+import { test, expect } from '@playwright/test';
+import { Event } from '@sentry/types';
+
+test('should report a manually captured error.', async ({ page }) => {
+ const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url: '/capture-exception' });
+
+ const [errorEnvelope, pageloadEnvelope] = envelopes;
+
+ expect(errorEnvelope.level).toBe('error');
+ expect(errorEnvelope.tags?.transaction).toBe('/capture-exception');
+ expect(errorEnvelope.exception?.values).toMatchObject([
+ {
+ type: 'Error',
+ value: 'Sentry Manually Captured Error',
+ stacktrace: { frames: expect.any(Array) },
+ mechanism: { type: 'generic', handled: true },
+ },
+ ]);
+
+ expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload');
+ expect(pageloadEnvelope.tags?.['routing.instrumentation']).toBe('remix-router');
+ expect(pageloadEnvelope.type).toBe('transaction');
+ expect(pageloadEnvelope.transaction).toBe('routes/capture-exception');
+});
diff --git a/packages/remix/test/integration/test/client/capture-message.test.ts b/packages/remix/test/integration/test/client/capture-message.test.ts
new file mode 100644
index 000000000000..8ae743f8419a
--- /dev/null
+++ b/packages/remix/test/integration/test/client/capture-message.test.ts
@@ -0,0 +1,18 @@
+import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
+import { test, expect } from '@playwright/test';
+import { Event } from '@sentry/types';
+
+test('should report a manually captured message.', async ({ page }) => {
+ const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url: '/capture-message' });
+
+ const [messageEnvelope, pageloadEnvelope] = envelopes;
+
+ expect(messageEnvelope.level).toBe('info');
+ expect(messageEnvelope.tags?.transaction).toBe('/capture-message');
+ expect(messageEnvelope.message).toBe('Sentry Manually Captured Message');
+
+ expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload');
+ expect(pageloadEnvelope.tags?.['routing.instrumentation']).toBe('remix-router');
+ expect(pageloadEnvelope.type).toBe('transaction');
+ expect(pageloadEnvelope.transaction).toBe('routes/capture-message');
+});
diff --git a/packages/remix/test/integration/test/client/errorboundary.test.ts b/packages/remix/test/integration/test/client/errorboundary.test.ts
new file mode 100644
index 000000000000..6bf6314095fd
--- /dev/null
+++ b/packages/remix/test/integration/test/client/errorboundary.test.ts
@@ -0,0 +1,32 @@
+import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
+import { test, expect } from '@playwright/test';
+import { Event } from '@sentry/types';
+
+test('should capture React component errors.', async ({ page }) => {
+ const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, {
+ url: '/error-boundary-capture/0',
+ });
+
+ const [pageloadEnvelope, errorEnvelope] = envelopes;
+
+ expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload');
+ expect(pageloadEnvelope.tags?.['routing.instrumentation']).toBe('remix-router');
+ expect(pageloadEnvelope.type).toBe('transaction');
+ expect(pageloadEnvelope.transaction).toBe('routes/error-boundary-capture/$id');
+
+ expect(errorEnvelope.level).toBe('error');
+ expect(errorEnvelope.sdk?.name).toBe('sentry.javascript.remix');
+ expect(errorEnvelope.exception?.values).toMatchObject([
+ {
+ type: 'React ErrorBoundary Error',
+ value: 'Sentry React Component Error',
+ stacktrace: { frames: expect.any(Array) },
+ },
+ {
+ type: 'Error',
+ value: 'Sentry React Component Error',
+ stacktrace: { frames: expect.any(Array) },
+ mechanism: { type: 'generic', handled: true },
+ },
+ ]);
+});
diff --git a/packages/remix/test/integration/test/client/manualtracing.test.ts b/packages/remix/test/integration/test/client/manualtracing.test.ts
new file mode 100644
index 000000000000..90291cf2dacf
--- /dev/null
+++ b/packages/remix/test/integration/test/client/manualtracing.test.ts
@@ -0,0 +1,21 @@
+import { getMultipleSentryEnvelopeRequests } from './utils/helpers';
+import { test, expect } from '@playwright/test';
+import { Event } from '@sentry/types';
+
+test('should report a manually created / finished transaction.', async ({ page }) => {
+ const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, {
+ url: '/manual-tracing/0',
+ });
+
+ const [manualTransactionEnvelope, pageloadEnvelope] = envelopes;
+
+ expect(manualTransactionEnvelope.transaction).toBe('test_transaction_1');
+ expect(manualTransactionEnvelope.sdk?.name).toBe('sentry.javascript.remix');
+ expect(manualTransactionEnvelope.start_timestamp).toBeDefined();
+ expect(manualTransactionEnvelope.timestamp).toBeDefined();
+
+ expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload');
+ expect(pageloadEnvelope.tags?.['routing.instrumentation']).toBe('remix-router');
+ expect(pageloadEnvelope.type).toBe('transaction');
+ expect(pageloadEnvelope.transaction).toBe('routes/manual-tracing/$id');
+});
diff --git a/packages/remix/test/integration/test/client/meta-tags.test.ts b/packages/remix/test/integration/test/client/meta-tags.test.ts
new file mode 100644
index 000000000000..9ae66122f705
--- /dev/null
+++ b/packages/remix/test/integration/test/client/meta-tags.test.ts
@@ -0,0 +1,29 @@
+import { test, expect } from '@playwright/test';
+
+test('should inject `sentry-trace` and `baggage` meta tags inside the root page.', async ({ page }) => {
+ await page.goto('/');
+
+ const sentryTraceTag = await page.$('meta[name="sentry-trace"]');
+ const sentryTraceContent = await sentryTraceTag?.getAttribute('content');
+
+ expect(sentryTraceContent).toEqual(expect.any(String));
+
+ const sentryBaggageTag = await page.$('meta[name="baggage"]');
+ const sentryBaggageContent = await sentryBaggageTag?.getAttribute('content');
+
+ expect(sentryBaggageContent).toEqual(expect.any(String));
+});
+
+test('should inject `sentry-trace` and `baggage` meta tags inside a parameterized route.', async ({ page }) => {
+ await page.goto('/loader-json-response/0');
+
+ const sentryTraceTag = await page.$('meta[name="sentry-trace"]');
+ const sentryTraceContent = await sentryTraceTag?.getAttribute('content');
+
+ expect(sentryTraceContent).toEqual(expect.any(String));
+
+ const sentryBaggageTag = await page.$('meta[name="baggage"]');
+ const sentryBaggageContent = await sentryBaggageTag?.getAttribute('content');
+
+ expect(sentryBaggageContent).toEqual(expect.any(String));
+});