Skip to content

Commit 72cb01f

Browse files
author
Luca Forstner
authored
feat(nextjs): Automatically resolve source of errors in dev mode (#7294)
1 parent b887fb7 commit 72cb01f

File tree

16 files changed

+292
-58
lines changed

16 files changed

+292
-58
lines changed

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
@@ -24,7 +24,7 @@ const config: PlaywrightTestConfig = {
2424
/* Opt out of parallel tests on CI. */
2525
workers: 1,
2626
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
27-
reporter: 'dot',
27+
reporter: 'list',
2828
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
2929
use: {
3030
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
3+
export default function Page() {
4+
return (
5+
<div>
6+
<p>Press to throw:</p>
7+
<button
8+
id="exception-button"
9+
onClick={() => {
10+
throw new Error('client-component-button-click-error');
11+
}}
12+
>
13+
throw
14+
</button>
15+
</div>
16+
);
17+
}

packages/e2e-tests/test-applications/nextjs-13-app-dir/app/head.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/e2e-tests/test-applications/nextjs-13-app-dir/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": "playwright test"
10+
"test:prod": "TEST_ENV=production playwright test",
11+
"test:dev": "TEST_ENV=development playwright test"
1112
},
1213
"dependencies": {
1314
"@next/font": "13.0.7",

packages/e2e-tests/test-applications/nextjs-13-app-dir/playwright.config.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { PlaywrightTestConfig } from '@playwright/test';
22
import { devices } from '@playwright/test';
33

4+
const testEnv = process.env.TEST_ENV;
5+
6+
if (!testEnv) {
7+
throw new Error('No test env defined');
8+
}
9+
410
/**
511
* See https://playwright.dev/docs/test-configuration.
612
*/
@@ -13,18 +19,18 @@ const config: PlaywrightTestConfig = {
1319
* Maximum time expect() should wait for the condition to be met.
1420
* For example in `await expect(locator).toHaveText();`
1521
*/
16-
timeout: 5000,
22+
timeout: 10000,
1723
},
1824
/* Run tests in files in parallel */
1925
fullyParallel: true,
2026
/* Fail the build on CI if you accidentally left test.only in the source code. */
2127
forbidOnly: !!process.env.CI,
22-
/* Retry on CI only */
23-
retries: 0,
28+
/* `next dev` is incredibly buggy with the app dir */
29+
retries: testEnv === 'development' ? 3 : 0,
2430
/* Opt out of parallel tests on CI. */
2531
workers: 1,
2632
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
27-
reporter: 'dot',
33+
reporter: 'list',
2834
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
2935
use: {
3036
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
@@ -49,7 +55,7 @@ const config: PlaywrightTestConfig = {
4955
/* Run your local dev server before starting the tests */
5056
webServer: [
5157
{
52-
command: 'yarn start',
58+
command: testEnv === 'development' ? 'yarn dev' : 'yarn start',
5359
port: 3000,
5460
},
5561
{

packages/e2e-tests/test-applications/nextjs-13-app-dir/start-event-proxy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { startEventProxyServer, waitForTransaction } from '../../test-utils/event-proxy-server';
1+
import { startEventProxyServer } from '../../test-utils/event-proxy-server';
22

33
startEventProxyServer({
44
port: 27496,

packages/e2e-tests/test-applications/nextjs-13-app-dir/test-recipe.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@
44
"buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build",
55
"tests": [
66
{
7-
"testName": "Playwright tests",
8-
"testCommand": "yarn test"
7+
"testName": "Prod Mode",
8+
"testCommand": "yarn test:prod"
9+
},
10+
{
11+
"testName": "Dev Mode",
12+
"testCommand": "yarn test:dev"
13+
}
14+
],
15+
"canaryVersions": [
16+
{
17+
"dependencyOverrides": {
18+
"next": "latest"
19+
}
920
}
1021
]
1122
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { test, expect } from '@playwright/test';
2+
import { waitForError } from '../../../test-utils/event-proxy-server';
3+
4+
test.describe('dev mode error symbolification', () => {
5+
if (process.env.TEST_ENV !== 'development') {
6+
test.skip('should be skipped for non-dev mode', () => {});
7+
return;
8+
}
9+
10+
test('should have symbolicated dev errors', async ({ page }) => {
11+
await page.goto('/client-component');
12+
13+
const errorEventPromise = waitForError('nextjs-13-app-dir', errorEvent => {
14+
return errorEvent?.exception?.values?.[0]?.value === 'client-component-button-click-error';
15+
});
16+
17+
await page.locator('id=exception-button').click();
18+
19+
const errorEvent = await errorEventPromise;
20+
const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames;
21+
22+
expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual(
23+
expect.objectContaining({
24+
filename: 'app/client-component/page.tsx',
25+
abs_path: 'webpack-internal:///(app-client)/./app/client-component/page.tsx',
26+
function: 'onClick',
27+
in_app: true,
28+
lineno: 10,
29+
colno: 16,
30+
pre_context: [' id="exception-button"', ' onClick={() => {'],
31+
context_line: " throw new Error('client-component-button-click-error');",
32+
post_context: [' }}', ' >', ' throw'],
33+
}),
34+
);
35+
});
36+
});

packages/e2e-tests/test-applications/nextjs-13-app-dir/tests/transactions.test.ts

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,44 +46,47 @@ test('Sends a pageload transaction', async ({ page }) => {
4646
.toBe(200);
4747
});
4848

49-
test('Sends a transaction for a server component', async ({ page }) => {
50-
const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', transactionEvent => {
51-
return (
52-
transactionEvent?.contexts?.trace?.op === 'function.nextjs' &&
53-
transactionEvent?.transaction === 'Page Server Component (/user/[id])'
54-
);
55-
});
49+
if (process.env.TEST_ENV === 'production') {
50+
// TODO: Fix that this is flakey on dev server - might be an SDK bug
51+
test('Sends a transaction for a server component', async ({ page }) => {
52+
const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', transactionEvent => {
53+
return (
54+
transactionEvent?.contexts?.trace?.op === 'function.nextjs' &&
55+
transactionEvent?.transaction === 'Page Server Component (/user/[id])'
56+
);
57+
});
5658

57-
await page.goto('/user/4');
59+
await page.goto('/user/4');
5860

59-
const transactionEvent = await serverComponentTransactionPromise;
60-
const transactionEventId = transactionEvent.event_id;
61+
const transactionEvent = await serverComponentTransactionPromise;
62+
const transactionEventId = transactionEvent.event_id;
6163

62-
await expect
63-
.poll(
64-
async () => {
65-
try {
66-
const response = await axios.get(
67-
`https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
68-
{ headers: { Authorization: `Bearer ${authToken}` } },
69-
);
64+
await expect
65+
.poll(
66+
async () => {
67+
try {
68+
const response = await axios.get(
69+
`https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`,
70+
{ headers: { Authorization: `Bearer ${authToken}` } },
71+
);
7072

71-
return response.status;
72-
} catch (e) {
73-
if (e instanceof AxiosError && e.response) {
74-
if (e.response.status !== 404) {
75-
throw e;
73+
return response.status;
74+
} catch (e) {
75+
if (e instanceof AxiosError && e.response) {
76+
if (e.response.status !== 404) {
77+
throw e;
78+
} else {
79+
return e.response.status;
80+
}
7681
} else {
77-
return e.response.status;
82+
throw e;
7883
}
79-
} else {
80-
throw e;
8184
}
82-
}
83-
},
84-
{
85-
timeout: EVENT_POLLING_TIMEOUT,
86-
},
87-
)
88-
.toBe(200);
89-
});
85+
},
86+
{
87+
timeout: EVENT_POLLING_TIMEOUT,
88+
},
89+
)
90+
.toBe(200);
91+
});
92+
}

packages/e2e-tests/test-applications/standard-frontend-react/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const config: PlaywrightTestConfig = {
2424
/* Opt out of parallel tests on CI. */
2525
workers: 1,
2626
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
27-
reporter: 'dot',
27+
reporter: 'list',
2828
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
2929
use: {
3030
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */

packages/nextjs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"@sentry/webpack-plugin": "1.20.0",
2929
"chalk": "3.0.0",
3030
"rollup": "2.78.0",
31-
"tslib": "^1.9.3"
31+
"tslib": "^1.9.3",
32+
"stacktrace-parser": "^0.1.10"
3233
},
3334
"devDependencies": {
3435
"@types/webpack": "^4.41.31",

packages/nextjs/rollup.npm.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ export default [
1616

1717
// prevent this internal nextjs code from ending up in our built package (this doesn't happen automatially because
1818
// the name doesn't match an SDK dependency)
19-
packageSpecificConfig: { external: ['next/router', 'next/constants', 'next/headers'] },
19+
packageSpecificConfig: {
20+
external: ['next/router', 'next/constants', 'next/headers', 'stacktrace-parser'],
21+
},
2022
}),
2123
),
2224
...makeNPMConfigVariants(

packages/nextjs/src/client/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { configureScope, init as reactInit, Integrations } from '@sentry/react';
55
import { BrowserTracing, defaultRequestInstrumentationOptions } from '@sentry/tracing';
66
import type { EventProcessor } from '@sentry/types';
77

8+
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
89
import { getVercelEnv } from '../common/getVercelEnv';
910
import { buildMetadata } from '../common/metadata';
1011
import { addOrUpdateIntegration } from '../common/userIntegrations';
@@ -53,6 +54,10 @@ export function init(options: BrowserOptions): void {
5354
event.type === 'transaction' && event.transaction === '/404' ? null : event;
5455
filterTransactions.id = 'NextClient404Filter';
5556
scope.addEventProcessor(filterTransactions);
57+
58+
if (process.env.NODE_ENV === 'development') {
59+
scope.addEventProcessor(devErrorSymbolicationEventProcessor);
60+
}
5661
});
5762
}
5863

0 commit comments

Comments
 (0)