Skip to content

Commit 97694e7

Browse files
Merge pull request #8286 from getsentry/priscila/feat/add-remix-e2e-test
2 parents 0008d94 + befe6fa commit 97694e7

File tree

12 files changed

+288
-0
lines changed

12 files changed

+288
-0
lines changed

packages/e2e-tests/run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async function run(): Promise<void> {
1717

1818
const envVarsToInject = {
1919
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
20+
REMIX_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2021
NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2122
PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2223
BASE_PORT: '27496', // just some random port
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('eslint').Linter.Config} */
2+
module.exports = {
3+
extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'],
4+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
/public/build
6+
.env
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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* By default, Remix will handle hydrating your app on the client for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.client
5+
*/
6+
7+
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
8+
import { startTransition, StrictMode, useEffect } from 'react';
9+
import { hydrateRoot } from 'react-dom/client';
10+
import * as Sentry from '@sentry/remix';
11+
12+
Sentry.init({
13+
dsn: process.env.REMIX_APP_E2E_TEST_DSN,
14+
integrations: [
15+
new Sentry.BrowserTracing({
16+
routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches),
17+
}),
18+
new Sentry.Replay(),
19+
],
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
// Session Replay
23+
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
24+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
25+
});
26+
27+
startTransition(() => {
28+
hydrateRoot(
29+
document,
30+
<StrictMode>
31+
<RemixBrowser />
32+
</StrictMode>,
33+
);
34+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* By default, Remix will handle generating the HTTP Response for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.server
5+
*/
6+
7+
import { PassThrough } from 'node:stream';
8+
9+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
10+
import { Response } from '@remix-run/node';
11+
import { RemixServer } from '@remix-run/react';
12+
import isbot from 'isbot';
13+
import { renderToPipeableStream } from 'react-dom/server';
14+
import * as Sentry from '@sentry/remix';
15+
16+
const ABORT_DELAY = 5_000;
17+
18+
Sentry.init({
19+
dsn: process.env.REMIX_APP_E2E_TEST_DSN,
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
});
23+
24+
export default function handleRequest(
25+
request: Request,
26+
responseStatusCode: number,
27+
responseHeaders: Headers,
28+
remixContext: EntryContext,
29+
loadContext: AppLoadContext,
30+
) {
31+
return isbot(request.headers.get('user-agent'))
32+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
33+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
34+
}
35+
36+
function handleBotRequest(
37+
request: Request,
38+
responseStatusCode: number,
39+
responseHeaders: Headers,
40+
remixContext: EntryContext,
41+
) {
42+
return new Promise((resolve, reject) => {
43+
const { pipe, abort } = renderToPipeableStream(
44+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
45+
{
46+
onAllReady() {
47+
const body = new PassThrough();
48+
49+
responseHeaders.set('Content-Type', 'text/html');
50+
51+
resolve(
52+
new Response(body, {
53+
headers: responseHeaders,
54+
status: responseStatusCode,
55+
}),
56+
);
57+
58+
pipe(body);
59+
},
60+
onShellError(error: unknown) {
61+
reject(error);
62+
},
63+
onError(error: unknown) {
64+
responseStatusCode = 500;
65+
console.error(error);
66+
},
67+
},
68+
);
69+
70+
setTimeout(abort, ABORT_DELAY);
71+
});
72+
}
73+
74+
function handleBrowserRequest(
75+
request: Request,
76+
responseStatusCode: number,
77+
responseHeaders: Headers,
78+
remixContext: EntryContext,
79+
) {
80+
return new Promise((resolve, reject) => {
81+
const { pipe, abort } = renderToPipeableStream(
82+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
83+
{
84+
onShellReady() {
85+
const body = new PassThrough();
86+
87+
responseHeaders.set('Content-Type', 'text/html');
88+
89+
resolve(
90+
new Response(body, {
91+
headers: responseHeaders,
92+
status: responseStatusCode,
93+
}),
94+
);
95+
96+
pipe(body);
97+
},
98+
onShellError(error: unknown) {
99+
reject(error);
100+
},
101+
onError(error: unknown) {
102+
console.error(error);
103+
responseStatusCode = 500;
104+
},
105+
},
106+
);
107+
108+
setTimeout(abort, ABORT_DELAY);
109+
});
110+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { cssBundleHref } from '@remix-run/css-bundle';
2+
import type { LinksFunction } from '@remix-run/node';
3+
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
4+
import { withSentry } from '@sentry/remix';
5+
6+
export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])];
7+
8+
function App() {
9+
return (
10+
<html lang="en">
11+
<head>
12+
<meta charSet="utf-8" />
13+
<meta name="viewport" content="width=device-width,initial-scale=1" />
14+
<Meta />
15+
<Links />
16+
</head>
17+
<body>
18+
<Outlet />
19+
<ScrollRestoration />
20+
<Scripts />
21+
<LiveReload />
22+
</body>
23+
</html>
24+
);
25+
}
26+
27+
export default withSentry(App);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { V2_MetaFunction } from '@remix-run/node';
2+
3+
export const meta: V2_MetaFunction = () => {
4+
return [{ title: 'New Remix App' }, { name: 'description', content: 'Welcome to Remix!' }];
5+
};
6+
7+
export default function Index() {
8+
return (
9+
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.8' }}>
10+
<h1>Welcome to Remix</h1>
11+
<ul>
12+
<li>
13+
<a target="_blank" href="https://remix.run/tutorials/blog" rel="noreferrer">
14+
15m Quickstart Blog Tutorial
15+
</a>
16+
</li>
17+
<li>
18+
<a target="_blank" href="https://remix.run/tutorials/jokes" rel="noreferrer">
19+
Deep Dive Jokes App Tutorial
20+
</a>
21+
</li>
22+
<li>
23+
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
24+
Remix Docs
25+
</a>
26+
</li>
27+
</ul>
28+
</div>
29+
);
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"private": true,
3+
"sideEffects": false,
4+
"scripts": {
5+
"build": "remix build",
6+
"dev": "remix dev",
7+
"start": "remix-serve build",
8+
"typecheck": "tsc"
9+
},
10+
"dependencies": {
11+
"@sentry/remix": "*",
12+
"@remix-run/css-bundle": "^1.16.1",
13+
"@remix-run/node": "^1.16.1",
14+
"@remix-run/react": "^1.16.1",
15+
"@remix-run/serve": "^1.16.1",
16+
"isbot": "^3.6.8",
17+
"react": "^18.2.0",
18+
"react-dom": "^18.2.0"
19+
},
20+
"devDependencies": {
21+
"@remix-run/dev": "^1.16.1",
22+
"@remix-run/eslint-config": "^1.16.1",
23+
"@types/react": "^18.0.35",
24+
"@types/react-dom": "^18.0.11",
25+
"eslint": "^8.38.0",
26+
"typescript": "^5.0.4"
27+
},
28+
"engines": {
29+
"node": ">=14"
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/** @type {import('@remix-run/dev').AppConfig} */
2+
module.exports = {
3+
ignoredRouteFiles: ['**/.*'],
4+
// appDirectory: "app",
5+
// assetsBuildDirectory: "public/build",
6+
// serverBuildPath: "build/index.js",
7+
// publicPath: "/build/",
8+
serverModuleFormat: 'cjs',
9+
future: {
10+
v2_errorBoundary: true,
11+
v2_meta: true,
12+
v2_normalizeFormMethod: true,
13+
v2_routeConvention: true,
14+
},
15+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$schema": "../../test-recipe-schema.json",
3+
"testApplicationName": "create-remix-app",
4+
"buildCommand": "pnpm install && pnpm build && pnpm start",
5+
"tests": []
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
3+
"compilerOptions": {
4+
"lib": ["DOM", "DOM.Iterable", "ES2019"],
5+
"isolatedModules": true,
6+
"esModuleInterop": true,
7+
"jsx": "react-jsx",
8+
"moduleResolution": "node",
9+
"resolveJsonModule": true,
10+
"target": "ES2019",
11+
"strict": true,
12+
"allowJs": true,
13+
"forceConsistentCasingInFileNames": true,
14+
"baseUrl": ".",
15+
"paths": {
16+
"~/*": ["./app/*"]
17+
},
18+
19+
// Remix takes care of building everything in `remix build`.
20+
"noEmit": true
21+
}
22+
}

0 commit comments

Comments
 (0)