Skip to content

Commit d1ddeff

Browse files
authored
test(nextjs): Add NextJS server-side E2E tests. (#6829)
1 parent bee8d52 commit d1ddeff

File tree

9 files changed

+131
-15
lines changed

9 files changed

+131
-15
lines changed

packages/e2e-tests/test-applications/create-next-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint",
10-
"test": "TEST_MODE=build playwright test",
10+
"test": "test:prod && test:dev",
11+
"test:prod": "TEST_MODE=prod playwright test",
1112
"test:dev": "TEST_MODE=dev playwright test"
1213
},
1314
"dependencies": {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
3+
import type { NextApiRequest, NextApiResponse } from 'next';
4+
5+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6+
const exceptionId = Sentry.captureException(new Error('This is an error'));
7+
8+
await Sentry.flush(2000);
9+
10+
res.status(200).json({ exceptionId });
11+
}

packages/e2e-tests/test-applications/create-next-app/pages/api/hello.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2+
import type { NextApiRequest, NextApiResponse } from 'next';
3+
import * as Sentry from '@sentry/nextjs';
4+
5+
export default function handler(req: NextApiRequest, res: NextApiResponse) {
6+
const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' });
7+
Sentry.getCurrentHub().configureScope(scope => scope.setSpan(transaction));
8+
9+
const span = transaction.startChild();
10+
11+
span.finish();
12+
transaction.finish();
13+
14+
Sentry.flush().then(() => {
15+
res.status(200).json({
16+
transactionIds: global.transactionIds,
17+
});
18+
});
19+
}

packages/e2e-tests/test-applications/create-next-app/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const config: PlaywrightTestConfig = {
6161

6262
/* Run your local dev server before starting the tests */
6363
webServer: {
64-
command: process.env.TEST_MODE === 'build' ? 'yarn start' : 'yarn dev',
64+
command: process.env.TEST_MODE === 'prod' ? 'yarn start' : 'yarn dev',
6565
port: 3000,
6666
},
6767
};

packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
import * as Sentry from '@sentry/nextjs';
66

7+
declare global {
8+
namespace globalThis {
9+
var transactionIds: string[];
10+
}
11+
}
12+
713
Sentry.init({
814
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
915
// Adjust this value in production, or use tracesSampler for greater control
@@ -13,3 +19,17 @@ Sentry.init({
1319
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
1420
// that it will also get attached to your source maps
1521
});
22+
23+
Sentry.addGlobalEventProcessor(event => {
24+
global.transactionIds = global.transactionIds || [];
25+
26+
if (event.type === 'transaction') {
27+
const eventId = event.event_id;
28+
29+
if (eventId) {
30+
global.transactionIds.push(eventId);
31+
}
32+
}
33+
34+
return event;
35+
});

packages/e2e-tests/test-applications/create-next-app/test-recipe.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build",
55
"tests": [
66
{
7-
"testName": "Playwright tests - Build Mode",
8-
"testCommand": "yarn test"
7+
"testName": "Playwright tests - Prod Mode",
8+
"testCommand": "yarn test:prod"
99
},
1010
{
1111
"testName": "Playwright tests - Dev Mode",

packages/e2e-tests/test-applications/create-next-app/tests/client/behaviour.test.ts renamed to packages/e2e-tests/test-applications/create-next-app/tests/behaviour-client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
66
const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
77
const EVENT_POLLING_TIMEOUT = 30_000;
88

9-
test('Sends an exception to Sentry', async ({ page, baseURL }) => {
9+
test('Sends a client-side exception to Sentry', async ({ page }) => {
1010
await page.goto('/');
1111

1212
const exceptionButton = page.locator('id=exception-button');
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { test, expect } from '@playwright/test';
2+
import axios, { AxiosError } from 'axios';
3+
4+
const authToken = process.env.E2E_TEST_AUTH_TOKEN;
5+
const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG;
6+
const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT;
7+
const EVENT_POLLING_TIMEOUT = 30_000;
8+
9+
test('Sends a server-side exception to Sentry', async ({ baseURL }) => {
10+
const { data } = await axios.get(`${baseURL}/api/error`);
11+
const { exceptionId } = data;
12+
13+
const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`;
14+
15+
console.log(`Polling for error eventId: ${exceptionId}`);
16+
17+
await expect
18+
.poll(
19+
async () => {
20+
try {
21+
const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } });
22+
23+
return response.status;
24+
} catch (e) {
25+
if (e instanceof AxiosError && e.response) {
26+
if (e.response.status !== 404) {
27+
throw e;
28+
} else {
29+
return e.response.status;
30+
}
31+
} else {
32+
throw e;
33+
}
34+
}
35+
},
36+
{ timeout: EVENT_POLLING_TIMEOUT },
37+
)
38+
.toBe(200);
39+
});
40+
41+
test('Sends server-side transactions to Sentry', async ({ baseURL }) => {
42+
const { data } = await axios.get(`${baseURL}/api/success`);
43+
const { transactionIds } = data;
44+
45+
console.log(`Polling for transaction eventIds: ${JSON.stringify(transactionIds)}`);
46+
47+
await Promise.all(
48+
transactionIds.map(async (transactionId: string) => {
49+
const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionId}/`;
50+
51+
await expect
52+
.poll(
53+
async () => {
54+
try {
55+
const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } });
56+
57+
return response.status;
58+
} catch (e) {
59+
if (e instanceof AxiosError && e.response) {
60+
if (e.response.status !== 404) {
61+
throw e;
62+
} else {
63+
return e.response.status;
64+
}
65+
} else {
66+
throw e;
67+
}
68+
}
69+
},
70+
{ timeout: EVENT_POLLING_TIMEOUT },
71+
)
72+
.toBe(200);
73+
}),
74+
);
75+
});

0 commit comments

Comments
 (0)