diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json index a0e7c162f0f0..6a602f574863 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json @@ -47,7 +47,9 @@ ] }, "devDependencies": { - "@playwright/test": "1.26.1", + "@playwright/test": "^1.43.1", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", + "ts-node": "^10.9.2", "axios": "1.6.0", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts index 5f93f826ebf0..abee4975ed4e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts @@ -1,6 +1,9 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; +const reactPort = 3030; +const eventProxyPort = 3031; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -32,6 +35,8 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + baseURL: `http://localhost:${reactPort}`, }, /* Configure projects for major browsers */ @@ -58,13 +63,20 @@ const config: PlaywrightTestConfig = { ], /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - port: 3030, - env: { - PORT: '3030', + + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, }, - }, + { + command: 'pnpm start', + port: reactPort, + env: { + PORT: `${reactPort}`, + }, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx index bf68d694d0f7..6340fca3f04b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx @@ -35,6 +35,8 @@ Sentry.init({ // Always capture replays, so we can test this properly replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 0.0, + + tunnel: 'http://localhost:3031', // proxy server }); Object.defineProperty(window, 'sentryReplayId', { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts new file mode 100644 index 000000000000..a836ebb7baa6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-router-6-use-routes', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts rename to dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts index 20f1c75ac222..1729850e778a 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts @@ -110,7 +110,7 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await page.goto('/'); // Give pageload transaction time to finish - page.waitForTimeout(4000); + await page.waitForTimeout(4000); const linkElement = page.locator('id=navigation'); await linkElement.click(); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts new file mode 100644 index 000000000000..baecddb9b96d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('Sends correct error event', async ({ page }) => { + const errorEventPromise = waitForError('react-router-6-use-routes', event => { + return !event.type && 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), + }); +}); + +test('Sets correct transactionName', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorEventPromise = waitForError('react-router-6-use-routes', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + const transactionEvent = await transactionPromise; + + // Only capture error once transaction was sent + 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.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: transactionEvent.contexts?.trace?.trace_id, + span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts index 0b454ba12214..554fac59f88e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts @@ -175,6 +175,7 @@ export const ReplayRecordingData = [ decodedBodySize: expect.any(Number), encodedBodySize: expect.any(Number), size: expect.any(Number), + statusCode: 200, }, }, }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts new file mode 100644 index 000000000000..75b42ebe6c0a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto(`/`); + + const rootSpan = await transactionPromise; + + expect(rootSpan).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.react.reactrouter_v6', + }, + }, + transaction: '/', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const pageloadTxnPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const linkElement = page.locator('id=navigation'); + + const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.react.reactrouter_v6', + }, + }, + transaction: '/user/:id', + transaction_info: { + source: 'route', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json index 4cc95dc2689a..c137d51512ef 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json @@ -16,5 +16,10 @@ "noEmit": true, "jsx": "react" }, - "include": ["src", "tests"] + "include": ["src", "tests"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } }