Skip to content

test(e2e): Add standard frontend test app specification #5874

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 7 commits into from
Oct 4, 2022
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
41 changes: 41 additions & 0 deletions packages/e2e-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,44 @@ add Sentry dependencies to your test application, you should set the dependency
```

All that is left for you to do now is to create a test app and run `yarn test:e2e`.

## Standardized Test Apps

For some of our E2E tests we define a standard for test applications as to how they should look and behave. Standardized
test apps enables us to reuse the same test suite over a number of different frameworks/SDKs.

### Standardized Frontend Test Apps

A standardized frontend test application has the following features:

- Just for the sake of consistency we prefix the standardized frontend tests with `standard-frontend-`. For example
`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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We write it to window.capturedExceptionId so we can read it with playwrights waitForFunction https://playwright.dev/docs/api/class-page#page-wait-for-function

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
the correct `id`. Text of the link doesn't matter.
- An empty page at `/user/5`
- Apps should write all pageload and navigation transaction IDs into an array at `window.recordedTransactions`. This can
be done with an event processor:

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

return event;
});
```

### Standardized Backend Test Apps

TBD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

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

!*.d.ts
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,44 @@
{
"name": "standard-frontend-react-test",
"version": "0.1.0",
"private": true,
"dependencies": {
"@sentry/react": "*",
"@sentry/tracing": "*",
"@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "13.0.0",
"@testing-library/user-event": "13.2.1",
"@types/jest": "27.0.1",
"@types/node": "16.7.13",
"@types/react": "18.0.0",
"@types/react-dom": "18.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-router-dom": "^6.4.1",
"react-scripts": "5.0.1",
"typescript": "4.4.2",
"web-vitals": "2.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import {
Routes,
BrowserRouter,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
Route,
} from 'react-router-dom';
import Index from './pages/Index';
import User from './pages/User';

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [
new BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
),
}),
],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
release: 'e2e-test',
});

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

return event;
});

const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<BrowserRouter>
<SentryRoutes>
<Route path="/" element={<Index />} />
<Route path="/user/:id" element={<User />} />
</SentryRoutes>
</BrowserRouter>,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';
import * as Sentry from '@sentry/react';
import { Link } from 'react-router-dom';

const Index = () => {
return (
<>
<input
type="button"
value="Capture Exception"
id="exception-button"
onClick={() => {
const eventId = Sentry.captureException(new Error('I am an error!'));
// @ts-ignore
window.capturedExceptionId = eventId;
}}
/>
<Link to="/user/5" id="navigation">
navigate
</Link>
</>
);
};

export default Index;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';

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 @@
/// <reference types="react-scripts" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "../../test-recipe-schema.json",
"testApplicationName": "standard-frontend-react",
"buildCommand": "yarn install && yarn build",
"tests": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
Loading