Skip to content

Commit 6cee1bf

Browse files
authored
test(e2e): Add Next.js Turbopack E2E tests (#14031)
1 parent eb17d62 commit 6cee1bf

19 files changed

+294
-0
lines changed

.github/workflows/build.yml

+7
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,7 @@ jobs:
904904
'nextjs-13',
905905
'nextjs-14',
906906
'nextjs-15',
907+
'nextjs-turbo',
907908
'nextjs-t3',
908909
'react-17',
909910
'react-19',
@@ -1126,6 +1127,12 @@ jobs:
11261127
- test-application: 'nextjs-15'
11271128
build-command: 'test:build-latest'
11281129
label: 'nextjs-15 (latest)'
1130+
- test-application: 'nextjs-turbo'
1131+
build-command: 'test:build-canary'
1132+
label: 'nextjs-turbo (canary)'
1133+
- test-application: 'nextjs-turbo'
1134+
build-command: 'test:build-latest'
1135+
label: 'nextjs-turbo (latest)'
11291136

11301137
steps:
11311138
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})

.github/workflows/canary.yml

+6
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ jobs:
9696
- test-application: 'nextjs-15'
9797
build-command: 'test:build-latest'
9898
label: 'nextjs-15 (latest)'
99+
- test-application: 'nextjs-turbo'
100+
build-command: 'test:build-canary'
101+
label: 'nextjs-turbo (canary)'
102+
- test-application: 'nextjs-turbo'
103+
build-command: 'test:build-latest'
104+
label: 'nextjs-turbo (latest)'
99105
- test-application: 'react-create-hash-router'
100106
build-command: 'test:build-canary'
101107
label: 'react-create-hash-router (canary)'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
38+
!*.d.ts
39+
40+
# Sentry
41+
.sentryclirc
42+
43+
.vscode
44+
45+
test-results
46+
event-dumps
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const dynamic = 'force-dynamic';
2+
3+
export default function Page() {
4+
if (Math.random() > -1) {
5+
throw new Error('page rsc render error');
6+
}
7+
8+
return null;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client';
2+
3+
import * as Sentry from '@sentry/nextjs';
4+
import NextError from 'next/error';
5+
import { useEffect } from 'react';
6+
7+
export default function GlobalError({
8+
error,
9+
}: {
10+
error: Error & { digest?: string };
11+
}) {
12+
useEffect(() => {
13+
Sentry.captureException(error);
14+
}, [error]);
15+
16+
return (
17+
<html>
18+
<body>
19+
{/* `NextError` is the default Next.js error page component. Its type
20+
definition requires a `statusCode` prop. However, since the App Router
21+
does not expose status codes for errors, we simply pass 0 to render a
22+
generic error message. */}
23+
<NextError statusCode={0} />
24+
</body>
25+
</html>
26+
);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Layout({ children }: { children: React.ReactNode }) {
2+
return (
3+
<html lang="en">
4+
<body>{children}</body>
5+
</html>
6+
);
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface Window {
2+
recordedTransactions?: string[];
3+
capturedExceptionId?: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
export async function register() {
4+
if (process.env.NEXT_RUNTIME === 'nodejs') {
5+
await import('./sentry.server.config');
6+
}
7+
8+
if (process.env.NEXT_RUNTIME === 'edge') {
9+
await import('./sentry.edge.config');
10+
}
11+
}
12+
13+
export const onRequestError = Sentry.captureRequestError;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { withSentryConfig } = require('@sentry/nextjs');
2+
3+
/** @type {import('next').NextConfig} */
4+
const nextConfig = {
5+
experimental: {
6+
turbo: {}, // Enables Turbopack for builds
7+
},
8+
};
9+
10+
module.exports = withSentryConfig(nextConfig, {
11+
silent: true,
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "create-next-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
7+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
8+
"test:prod": "TEST_ENV=production playwright test",
9+
"test:dev": "TEST_ENV=development playwright test",
10+
"test:build": "pnpm install && npx playwright install && pnpm build",
11+
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@canary && pnpm add react-dom@canary && npx playwright install && pnpm build",
12+
"test:build-latest": "pnpm install && pnpm add next@latest && pnpm add react@rc && pnpm add react-dom@rc && npx playwright install && pnpm build",
13+
"test:assert": "pnpm test:prod && pnpm test:dev"
14+
},
15+
"dependencies": {
16+
"@sentry/nextjs": "latest || *",
17+
"@types/node": "18.11.17",
18+
"@types/react": "18.0.26",
19+
"@types/react-dom": "18.0.9",
20+
"next": "15.0.0",
21+
"react": "rc",
22+
"react-dom": "rc",
23+
"typescript": "4.9.5"
24+
},
25+
"devDependencies": {
26+
"@playwright/test": "^1.44.1",
27+
"@sentry-internal/test-utils": "link:../../../test-utils",
28+
"@sentry-internal/feedback": "latest || *",
29+
"@sentry-internal/replay-canvas": "latest || *",
30+
"@sentry-internal/browser-utils": "latest || *",
31+
"@sentry/browser": "latest || *",
32+
"@sentry/core": "latest || *",
33+
"@sentry/nextjs": "latest || *",
34+
"@sentry/node": "latest || *",
35+
"@sentry/opentelemetry": "latest || *",
36+
"@sentry/react": "latest || *",
37+
"@sentry-internal/replay": "latest || *",
38+
"@sentry/types": "latest || *",
39+
"@sentry/utils": "latest || *",
40+
"@sentry/vercel-edge": "latest || *",
41+
"import-in-the-middle": "1.11.2"
42+
},
43+
"overrides": {
44+
"import-in-the-middle": "1.11.2"
45+
},
46+
"volta": {
47+
"extends": "../../package.json"
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
const testEnv = process.env.TEST_ENV;
3+
4+
if (!testEnv) {
5+
throw new Error('No test env defined');
6+
}
7+
8+
const config = getPlaywrightConfig(
9+
{
10+
startCommand: testEnv === 'development' ? 'pnpm next dev -p 3030 --turbo' : 'pnpm next start -p 3030',
11+
port: 3030,
12+
},
13+
{
14+
// This comes with the risk of tests leaking into each other but the tests run quite slow so we should parallelize
15+
workers: '100%',
16+
},
17+
);
18+
19+
export default config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
transportOptions: {
10+
// We are doing a lot of events at once in this test
11+
bufferSize: 1000,
12+
},
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
transportOptions: {
10+
// We are doing a lot of events at once in this test
11+
bufferSize: 1000,
12+
},
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { startEventProxyServer } from '@sentry-internal/test-utils';
4+
5+
const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json')));
6+
7+
startEventProxyServer({
8+
port: 3031,
9+
proxyServerName: 'nextjs-turbo',
10+
envelopeDumpPath: path.join(
11+
process.cwd(),
12+
`event-dumps/nextjs-turbo-${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`,
13+
),
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
test('Should capture errors from server components', async ({ page }) => {
5+
const errorEventPromise = waitForError('nextjs-turbo', errorEvent => {
6+
return !!errorEvent?.exception?.values?.some(value => value.value === 'page rsc render error');
7+
});
8+
9+
await page.goto(`/123/rsc-page-error`);
10+
11+
const errorEvent = await errorEventPromise;
12+
13+
expect(errorEvent).toBeDefined();
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2018",
4+
"lib": ["dom", "dom.iterable", "esnext"],
5+
"allowJs": true,
6+
"skipLibCheck": true,
7+
"strict": true,
8+
"forceConsistentCasingInFileNames": true,
9+
"noEmit": true,
10+
"esModuleInterop": true,
11+
"module": "esnext",
12+
"moduleResolution": "node",
13+
"resolveJsonModule": true,
14+
"isolatedModules": true,
15+
"jsx": "preserve",
16+
"plugins": [
17+
{
18+
"name": "next"
19+
}
20+
],
21+
"incremental": true
22+
},
23+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js", ".next/types/**/*.ts"],
24+
"exclude": ["node_modules", "playwright.config.ts"]
25+
}

0 commit comments

Comments
 (0)