Skip to content

test(nextjs): Add NextJS client-side E2E tests #6669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fields:

- The `buildCommand` command runs only once before any of the tests and is supposed to build the test application. If
this command returns a non-zero exit code, it counts as a failed test and the test application's tests are not run. In
the example above, we use the `--pure-lockfile` flag to install depencies without modifiying the lockfile so that
the example above, we use the `--pure-lockfile` flag to install dependencies without modifiying the lockfile so that
there aren't any changes in the git worktree after running the tests.
- The `testCommand` command is supposed to run tests on the test application. If the configured command returns a
non-zero exit code, it counts as a failed test.
Expand Down Expand Up @@ -107,7 +107,7 @@ A standardized frontend test application has the following features:
`standard-frontend-nextjs`.
- A page at path `/`
- Having a `<input type="button" id="exception-button">` that captures an Exception when clicked. The returned
`eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It doesn not
`eventId` from the `Sentry.captureException()` call must be written to `window.capturedExceptionId`. It does not
matter what the captured error looks like.
- Having an link with `id="navigation"` that navigates to `/user/5`. It doesn't have to be an `<a>` tag, for example
if a framework has another way of doing routing, the important part is that the element to click for navigation has
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"devDependencies": {
"@types/glob": "8.0.0",
"@types/node": "^14.6.4",
"dotenv": "16.0.3",
"glob": "8.0.3",
"ts-node": "10.9.1",
"typescript": "3.8.3",
Expand Down
5 changes: 5 additions & 0 deletions packages/e2e-tests/run.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
/* eslint-disable max-lines */
/* eslint-disable no-console */
import * as childProcess from 'child_process';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as glob from 'glob';
import * as path from 'path';

// Load environment variables from .env file locally
dotenv.config();

const repositoryRoot = path.resolve(__dirname, '../..');

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

const envVarsToInject = {
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
};

// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines
Expand Down
41 changes: 41 additions & 0 deletions packages/e2e-tests/test-applications/create-next-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

!*.d.ts

# Sentry
.sentryclirc
2 changes: 2 additions & 0 deletions packages/e2e-tests/test-applications/create-next-app/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://localhost:4873
@sentry-internal:registry=http://localhost:4873
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface Window {
recordedTransactions?: string[];
capturedExceptionId?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This file sets a custom webpack configuration to use your Next.js app
// with Sentry.
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

const { withSentryConfig } = require('@sentry/nextjs');

const moduleExports = {
// Your existing module.exports

sentry: {
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
// for client-side builds. (This will be the default starting in
// `@sentry/nextjs` version 8.0.0.) See
// https://webpack.js.org/configuration/devtool/ and
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
// for more information.
hideSourceMaps: true,
},
};

const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that
// the following options are set automatically, and overriding them is not
// recommended:
// release, url, org, project, authToken, configFile, stripPrefix,
// urlPrefix, include, ignore

silent: true, // Suppresses all logs
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options.

// We're not testing source map uploads at the moment.
dryRun: true,
};

// Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
27 changes: 27 additions & 0 deletions packages/e2e-tests/test-applications/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "create-next-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "TEST_MODE=build playwright test",
"test:dev": "TEST_MODE=dev playwright test"
},
"dependencies": {
"@next/font": "13.0.7",
"@sentry/nextjs": "*",
"@types/node": "18.11.17",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"next": "13.0.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "4.9.4"
},
"devDependencies": {
"@playwright/test": "^1.27.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
*
* NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
* penultimate line in `CustomErrorComponent`.
*
* This page is loaded by Nextjs:
* - on the server, when data-fetching methods throw or reject
* - on the client, when `getInitialProps` throws or rejects
* - on the client, when a React lifecycle method throws or rejects, and it's
* caught by the built-in Nextjs error boundary
*
* See:
* - https://nextjs.org/docs/basic-features/data-fetching/overview
* - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
* - https://reactjs.org/docs/error-boundaries.html
*/

import * as Sentry from '@sentry/nextjs';
import NextErrorComponent from 'next/error';
import { NextPageContext } from 'next';

const CustomErrorComponent = (props: { statusCode: any }) => {
// If you're using a Nextjs version prior to 12.2.1, uncomment this to
// compensate for https://github.com/vercel/next.js/issues/8592
// Sentry.captureUnderscoreErrorException(props);

return <NextErrorComponent statusCode={props.statusCode} />;
};

CustomErrorComponent.getInitialProps = async (contextData: NextPageContext) => {
// In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData);

// This will contain the status code of the response
return NextErrorComponent.getInitialProps(contextData);
};

export default CustomErrorComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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<Data>) {
res.status(200).json({ name: 'John Doe' });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Head from 'next/head';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdyt about replacing the contents of this page with the contents of client.tsx and deleting client.tsx? Just so we conform to the standard test defined in the README.

Copy link
Collaborator Author

@onurtemizkan onurtemizkan Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was mainly to have separate client / server contexts, but we can discuss it when working on server-side tests. Moved to index -> a9772ae

import Link from 'next/link';
import * as Sentry from '@sentry/nextjs';

export default function Home() {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<input
type="button"
value="Capture Exception"
id="exception-button"
onClick={() => {
const eventId = Sentry.captureException(new Error('I am an error!'));
window.capturedExceptionId = eventId;
}}
/>
<Link href="/user/5" id="navigation">
navigate
</Link>
</main>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const User = () => {
return <p>I am a blank page :)</p>;
};

export default User;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 60 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'dot',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
// For now we only test Chrome!
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
// {
// name: 'webkit',
// use: {
// ...devices['Desktop Safari'],
// },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: process.env.TEST_MODE === 'build' ? 'yarn start' : 'yarn dev',
port: 3000,
},
};

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

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

Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});

Sentry.addGlobalEventProcessor(event => {
if (
event.type === 'transaction' &&
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
) {
const eventId = event.event_id;
if (eventId) {
window.recordedTransactions = window.recordedTransactions || [];
window.recordedTransactions.push(eventId);
}
}

return event;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

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

Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});
Loading