Skip to content

Commit ecc9a0b

Browse files
committed
test(nextjs): Add NextJS client-side E2E tests
1 parent 3b1bcaf commit ecc9a0b

31 files changed

+1958
-3
lines changed

packages/e2e-tests/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fields:
6666

6767
- The `buildCommand` command runs only once before any of the tests and is supposed to build the test application. If
6868
this command returns a non-zero exit code, it counts as a failed test and the test application's tests are not run. In
69-
the example above, we use the `--pure-lockfile` flag to install depencies without modifiying the lockfile so that
69+
the example above, we use the `--pure-lockfile` flag to install dependencies without modifiying the lockfile so that
7070
there aren't any changes in the git worktree after running the tests.
7171
- The `testCommand` command is supposed to run tests on the test application. If the configured command returns a
7272
non-zero exit code, it counts as a failed test.
@@ -107,7 +107,7 @@ A standardized frontend test application has the following features:
107107
`standard-frontend-nextjs`.
108108
- A page at path `/`
109109
- Having a `<input type="button" id="exception-button">` that captures an Exception when clicked. The returned
110-
`eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It doesn not
110+
`eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It does not
111111
matter what the captured error looks like.
112112
- Having an link with `id="navigation"` that navigates to `/user/5`. It doesn't have to be an `<a>` tag, for example
113113
if a framework has another way of doing routing, the important part is that the element to click for navigation has

packages/e2e-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"devDependencies": {
2222
"@types/glob": "8.0.0",
2323
"@types/node": "^14.6.4",
24+
"dotenv": "16.0.3",
2425
"glob": "8.0.3",
2526
"ts-node": "10.9.1",
2627
"typescript": "3.8.3",

packages/e2e-tests/run.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
/* eslint-disable max-lines */
22
/* eslint-disable no-console */
33
import * as childProcess from 'child_process';
4+
import * as dotenv from 'dotenv';
45
import * as fs from 'fs';
56
import * as glob from 'glob';
67
import * as path from 'path';
78

9+
// Load environment variables from .env file locally
10+
dotenv.config();
11+
812
const repositoryRoot = path.resolve(__dirname, '../..');
913

1014
const TEST_REGISTRY_CONTAINER_NAME = 'verdaccio-e2e-test-registry';
@@ -51,6 +55,7 @@ if (missingEnvVar) {
5155

5256
const envVarsToInject = {
5357
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
58+
NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
5459
};
5560

5661
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://localhost:4873
2+
@sentry-internal:registry=http://localhost:4873
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface Window {
2+
recordedTransactions?: string[];
3+
capturedExceptionId?: string;
4+
}
Lines changed: 5 additions & 0 deletions
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/basic-features/typescript for more information.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// This file sets a custom webpack configuration to use your Next.js app
2+
// with Sentry.
3+
// https://nextjs.org/docs/api-reference/next.config.js/introduction
4+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
5+
6+
const { withSentryConfig } = require('@sentry/nextjs');
7+
8+
const moduleExports = {
9+
// Your existing module.exports
10+
11+
sentry: {
12+
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
13+
// for client-side builds. (This will be the default starting in
14+
// `@sentry/nextjs` version 8.0.0.) See
15+
// https://webpack.js.org/configuration/devtool/ and
16+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
17+
// for more information.
18+
hideSourceMaps: true,
19+
},
20+
};
21+
22+
const sentryWebpackPluginOptions = {
23+
// Additional config options for the Sentry Webpack plugin. Keep in mind that
24+
// the following options are set automatically, and overriding them is not
25+
// recommended:
26+
// release, url, org, project, authToken, configFile, stripPrefix,
27+
// urlPrefix, include, ignore
28+
29+
silent: true, // Suppresses all logs
30+
// For all available options, see:
31+
// https://github.com/getsentry/sentry-webpack-plugin#options.
32+
};
33+
34+
// Make sure adding Sentry options is the last code to run before exporting, to
35+
// ensure that your source maps include changes from all other Webpack plugins
36+
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "create-next-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint",
10+
"test": "TEST_MODE=build playwright test",
11+
"test:dev": "TEST_MODE=dev playwright test"
12+
},
13+
"dependencies": {
14+
"@next/font": "13.0.7",
15+
"@sentry/nextjs": "*",
16+
"@types/node": "18.11.17",
17+
"@types/react": "18.0.26",
18+
"@types/react-dom": "18.0.9",
19+
"next": "13.0.7",
20+
"react": "18.2.0",
21+
"react-dom": "18.2.0",
22+
"typescript": "4.9.4"
23+
},
24+
"devDependencies": {
25+
"@playwright/test": "^1.27.1"
26+
}
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import '../styles/globals.css'
2+
import type { AppProps } from 'next/app'
3+
4+
export default function App({ Component, pageProps }: AppProps) {
5+
return <Component {...pageProps} />
6+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Html, Head, Main, NextScript } from 'next/document'
2+
3+
export default function Document() {
4+
return (
5+
<Html lang="en">
6+
<Head />
7+
<body>
8+
<Main />
9+
<NextScript />
10+
</body>
11+
</Html>
12+
)
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
3+
*
4+
* NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
5+
* penultimate line in `CustomErrorComponent`.
6+
*
7+
* This page is loaded by Nextjs:
8+
* - on the server, when data-fetching methods throw or reject
9+
* - on the client, when `getInitialProps` throws or rejects
10+
* - on the client, when a React lifecycle method throws or rejects, and it's
11+
* caught by the built-in Nextjs error boundary
12+
*
13+
* See:
14+
* - https://nextjs.org/docs/basic-features/data-fetching/overview
15+
* - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
16+
* - https://reactjs.org/docs/error-boundaries.html
17+
*/
18+
19+
import * as Sentry from '@sentry/nextjs';
20+
import NextErrorComponent from 'next/error';
21+
import { NextPageContext } from 'next';
22+
23+
const CustomErrorComponent = (props: { statusCode: any }) => {
24+
// If you're using a Nextjs version prior to 12.2.1, uncomment this to
25+
// compensate for https://github.com/vercel/next.js/issues/8592
26+
// Sentry.captureUnderscoreErrorException(props);
27+
28+
return <NextErrorComponent statusCode={props.statusCode} />;
29+
};
30+
31+
CustomErrorComponent.getInitialProps = async (contextData: NextPageContext) => {
32+
// In case this is running in a serverless function, await this in order to give Sentry
33+
// time to send the error before the lambda exits
34+
await Sentry.captureUnderscoreErrorException(contextData);
35+
36+
// This will contain the status code of the response
37+
return NextErrorComponent.getInitialProps(contextData);
38+
};
39+
40+
export default CustomErrorComponent;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2+
import type { NextApiRequest, NextApiResponse } from 'next'
3+
4+
type Data = {
5+
name: string
6+
}
7+
8+
export default function handler(
9+
req: NextApiRequest,
10+
res: NextApiResponse<Data>
11+
) {
12+
res.status(200).json({ name: 'John Doe' })
13+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Link from 'next/link';
2+
import * as Sentry from '@sentry/nextjs';
3+
4+
const ClientError = (): JSX.Element => (
5+
<>
6+
<input
7+
type="button"
8+
value="Capture Exception"
9+
id="exception-button"
10+
onClick={() => {
11+
const eventId = Sentry.captureException(new Error('I am an error!'));
12+
window.capturedExceptionId = eventId;
13+
}}
14+
/>
15+
<Link href="/user/5" id="navigation">
16+
navigate
17+
</Link>
18+
</>
19+
);
20+
21+
export default ClientError;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Head from 'next/head';
2+
import Image from 'next/image';
3+
import { Inter } from '@next/font/google';
4+
import styles from '../styles/Home.module.css';
5+
6+
const inter = Inter({ subsets: ['latin'] });
7+
8+
export default function Home() {
9+
return (
10+
<>
11+
<Head>
12+
<title>Create Next App</title>
13+
<meta name="description" content="Generated by create next app" />
14+
<meta name="viewport" content="width=device-width, initial-scale=1" />
15+
<link rel="icon" href="/favicon.ico" />
16+
</Head>
17+
<main className={styles.main}>
18+
<div className={styles.description}>
19+
<p>
20+
Get started by editing&nbsp;
21+
<code className={styles.code}>pages/index.tsx</code>
22+
</p>
23+
<div>
24+
<a
25+
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
26+
target="_blank"
27+
rel="noopener noreferrer"
28+
>
29+
By{' '}
30+
<Image
31+
src="/vercel.svg"
32+
alt="Vercel Logo"
33+
className={styles.vercelLogo}
34+
width={100}
35+
height={24}
36+
priority
37+
/>
38+
</a>
39+
</div>
40+
</div>
41+
42+
<div className={styles.center}>
43+
<Image className={styles.logo} src="/next.svg" alt="Next.js Logo" width={180} height={37} priority />
44+
<div className={styles.thirteen}>
45+
<Image src="/thirteen.svg" alt="13" width={40} height={31} priority />
46+
</div>
47+
</div>
48+
49+
<div className={styles.grid}>
50+
<a
51+
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
52+
className={styles.card}
53+
target="_blank"
54+
rel="noopener noreferrer"
55+
>
56+
<h2 className={inter.className}>
57+
Docs <span>-&gt;</span>
58+
</h2>
59+
<p className={inter.className}>Find in-depth information about Next.js features and&nbsp;API.</p>
60+
</a>
61+
62+
<a
63+
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
64+
className={styles.card}
65+
target="_blank"
66+
rel="noopener noreferrer"
67+
>
68+
<h2 className={inter.className}>
69+
Learn <span>-&gt;</span>
70+
</h2>
71+
<p className={inter.className}>Learn about Next.js in an interactive course with&nbsp;quizzes!</p>
72+
</a>
73+
74+
<a
75+
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
76+
className={styles.card}
77+
target="_blank"
78+
rel="noopener noreferrer"
79+
>
80+
<h2 className={inter.className}>
81+
Templates <span>-&gt;</span>
82+
</h2>
83+
<p className={inter.className}>Discover and deploy boilerplate example Next.js&nbsp;projects.</p>
84+
</a>
85+
86+
<a
87+
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
88+
className={styles.card}
89+
target="_blank"
90+
rel="noopener noreferrer"
91+
>
92+
<h2 className={inter.className}>
93+
Deploy <span>-&gt;</span>
94+
</h2>
95+
<p className={inter.className}>Instantly deploy your Next.js site to a shareable URL with&nbsp;Vercel.</p>
96+
</a>
97+
</div>
98+
</main>
99+
</>
100+
);
101+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const User = () => {
2+
return <p>I am a blank page :)</p>;
3+
};
4+
5+
export default User;

0 commit comments

Comments
 (0)