diff --git a/packages/e2e-tests/test-applications/create-next-app/package.json b/packages/e2e-tests/test-applications/create-next-app/package.json index 4dc80476aee9..5d5e829bace6 100644 --- a/packages/e2e-tests/test-applications/create-next-app/package.json +++ b/packages/e2e-tests/test-applications/create-next-app/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "test": "TEST_MODE=build playwright test", + "test": "test:prod && test:dev", + "test:prod": "TEST_MODE=prod playwright test", "test:dev": "TEST_MODE=dev playwright test" }, "dependencies": { diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts b/packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts new file mode 100644 index 000000000000..5440074c39aa --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/api/error.ts @@ -0,0 +1,11 @@ +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 }); +} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts b/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts deleted file mode 100644 index eb4cc6657b37..000000000000 --- a/packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; - -type Data = { - name: string; -}; - -export default function handler(req: NextApiRequest, res: NextApiResponse) { - res.status(200).json({ name: 'John Doe' }); -} diff --git a/packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts b/packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts new file mode 100644 index 000000000000..847d0ae4821c --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts @@ -0,0 +1,19 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next'; +import * as Sentry from '@sentry/nextjs'; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); + Sentry.getCurrentHub().configureScope(scope => scope.setSpan(transaction)); + + const span = transaction.startChild(); + + span.finish(); + transaction.finish(); + + Sentry.flush().then(() => { + res.status(200).json({ + transactionIds: global.transactionIds, + }); + }); +} diff --git a/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts index 6bccee48f9fc..dabfa9c23619 100644 --- a/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts +++ b/packages/e2e-tests/test-applications/create-next-app/playwright.config.ts @@ -61,7 +61,7 @@ const config: PlaywrightTestConfig = { /* Run your local dev server before starting the tests */ webServer: { - command: process.env.TEST_MODE === 'build' ? 'yarn start' : 'yarn dev', + command: process.env.TEST_MODE === 'prod' ? 'yarn start' : 'yarn dev', port: 3000, }, }; diff --git a/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts b/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts index dfce34424f98..e3d70f17e126 100644 --- a/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts +++ b/packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts @@ -4,6 +4,12 @@ import * as Sentry from '@sentry/nextjs'; +declare global { + namespace globalThis { + var transactionIds: string[]; + } +} + Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, // Adjust this value in production, or use tracesSampler for greater control @@ -13,3 +19,17 @@ Sentry.init({ // `release` value here - use the environment variable `SENTRY_RELEASE`, so // that it will also get attached to your source maps }); + +Sentry.addGlobalEventProcessor(event => { + global.transactionIds = global.transactionIds || []; + + if (event.type === 'transaction') { + const eventId = event.event_id; + + if (eventId) { + global.transactionIds.push(eventId); + } + } + + return event; +}); diff --git a/packages/e2e-tests/test-applications/create-next-app/test-recipe.json b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json index b41a7a4fd05d..cf4bf4431929 100644 --- a/packages/e2e-tests/test-applications/create-next-app/test-recipe.json +++ b/packages/e2e-tests/test-applications/create-next-app/test-recipe.json @@ -4,8 +4,8 @@ "buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build", "tests": [ { - "testName": "Playwright tests - Build Mode", - "testCommand": "yarn test" + "testName": "Playwright tests - Prod Mode", + "testCommand": "yarn test:prod" }, { "testName": "Playwright tests - Dev Mode", diff --git a/packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts b/packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts similarity index 98% rename from packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts rename to packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts index a819664f35e3..d0ede66cf32f 100644 --- a/packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts +++ b/packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts @@ -6,7 +6,7 @@ const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; const EVENT_POLLING_TIMEOUT = 30_000; -test('Sends an exception to Sentry', async ({ page, baseURL }) => { +test('Sends a client-side exception to Sentry', async ({ page }) => { await page.goto('/'); const exceptionButton = page.locator('id=exception-button'); diff --git a/packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts b/packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts new file mode 100644 index 000000000000..3b3d74af1885 --- /dev/null +++ b/packages/e2e-tests/test-applications/create-next-app/tests/behaviour-server.test.ts @@ -0,0 +1,75 @@ +import { test, expect } from '@playwright/test'; +import axios, { AxiosError } from 'axios'; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; +const EVENT_POLLING_TIMEOUT = 30_000; + +test('Sends a server-side exception to Sentry', async ({ baseURL }) => { + const { data } = await axios.get(`${baseURL}/api/error`); + 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 () => { + try { + const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); +}); + +test('Sends server-side transactions to Sentry', async ({ baseURL }) => { + const { data } = await axios.get(`${baseURL}/api/success`); + 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 () => { + try { + const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); + }), + ); +});