Skip to content

Commit 94b4e5f

Browse files
chargomeclaude
andcommitted
test(remix): Add Remix 3 e2e test application
Registers a Remix 3 app in the e2e matrix with two smoke tests, so later pull requests add instrumentation and its tests together instead of also introducing a new CI surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 95e9e36 commit 94b4e5f

11 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { createController } from 'remix/router';
2+
import type { Handle } from 'remix/ui';
3+
4+
import { assets, entryHref, entryPreloads } from '../assets.ts';
5+
import { routes } from '../routes.ts';
6+
7+
function HomePage(handle: Handle<Record<string, never>>) {
8+
return () => (
9+
<html lang="en">
10+
<head>
11+
<meta charSet="utf-8" />
12+
<title>Sentry Remix 3</title>
13+
{entryPreloads.map(href => (
14+
<link key={href} rel="modulepreload" href={href} />
15+
))}
16+
<script type="module" src={entryHref}></script>
17+
</head>
18+
<body>
19+
<h1 id="home">Sentry Remix 3</h1>
20+
</body>
21+
</html>
22+
);
23+
}
24+
25+
export default createController(routes, {
26+
actions: {
27+
async assets(context) {
28+
return (await assets.fetch(context.request)) ?? new Response('Not Found', { status: 404 });
29+
},
30+
home(context) {
31+
return context.render(<HomePage />);
32+
},
33+
},
34+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { run } from 'remix/ui';
2+
3+
// No Sentry here yet: `@sentry/remix/v3/client` does not export `init` until the browser SDK lands.
4+
export const app = run({
5+
async loadModule(moduleUrl, exportName) {
6+
let mod = await import(moduleUrl);
7+
return mod[exportName];
8+
},
9+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createAssetServer } from 'remix/assets';
2+
3+
export const assets = createAssetServer({
4+
basePath: '/assets',
5+
rootDir: process.cwd(),
6+
allowFiles: ['app/routes.ts', 'app/**/public/**'],
7+
allowPackages: ['remix', '@sentry/remix'],
8+
minify: true,
9+
watch: false,
10+
});
11+
12+
const entry = 'app/actions/public/entry.ts';
13+
14+
export const entryHref = await assets.getHref(entry);
15+
export const entryPreloads = await assets.getPreloads(entry);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createRouter, type MiddlewareContext } from 'remix/router';
2+
import { render } from 'remix/middleware/render';
3+
4+
import controller from './actions/controller.tsx';
5+
import { assets } from './assets.ts';
6+
import { routes } from './routes.ts';
7+
8+
const renderMiddleware = render({ assets });
9+
type AppContext = MiddlewareContext<[typeof renderMiddleware]>;
10+
11+
declare module 'remix/router' {
12+
interface RouterTypes {
13+
context: AppContext;
14+
}
15+
}
16+
17+
export const router = createRouter<AppContext>({
18+
middleware: [renderMiddleware],
19+
});
20+
21+
router.map(routes, controller);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { get, route } from 'remix/routes';
2+
3+
export const routes = route({
4+
assets: get('/assets/*path'),
5+
home: '/',
6+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "remix-v3-app",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"start": "NODE_ENV=production node --import @sentry/remix/v3/node server.ts",
8+
"test": "playwright test",
9+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
10+
"test:build": "pnpm install",
11+
"test:assert": "pnpm test"
12+
},
13+
"dependencies": {
14+
"@sentry/remix": "file:../../packed/sentry-remix-packed.tgz",
15+
"remix": "3.0.0-rc.1"
16+
},
17+
"devDependencies": {
18+
"@playwright/test": "~1.56.0",
19+
"@sentry-internal/test-utils": "link:../../../test-utils",
20+
"@sentry/core": "file:../../packed/sentry-core-packed.tgz",
21+
"@types/node": "^24.6.0",
22+
"typescript": "~5.9.0"
23+
},
24+
"volta": {
25+
"node": "24.18.0",
26+
"pnpm": "9.15.9"
27+
}
28+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: `pnpm start`,
5+
port: 3060,
6+
eventProxyPort: 3061,
7+
});
8+
9+
export default config;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as Sentry from '@sentry/remix/v3';
2+
import * as http from 'node:http';
3+
import { createRequestListener } from 'remix/node-fetch-server';
4+
5+
import { router } from './app/router.ts';
6+
7+
Sentry.init({
8+
dsn: process.env.E2E_TEST_DSN,
9+
tunnel: 'http://localhost:3061/',
10+
tracesSampleRate: 1.0,
11+
});
12+
13+
const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3060;
14+
15+
const server = http.createServer(
16+
createRequestListener(async request => {
17+
try {
18+
return await router.fetch(request);
19+
} catch {
20+
return new Response('Internal Server Error', { status: 500 });
21+
}
22+
}),
23+
);
24+
25+
server.listen(port, () => {
26+
console.log(`Server listening on http://localhost:${port}`);
27+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { startEventProxyServer } from '@sentry-internal/test-utils';
2+
3+
startEventProxyServer({
4+
port: 3061,
5+
proxyServerName: 'remix-v3',
6+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForStreamedSpan } from '@sentry-internal/test-utils';
3+
4+
// There is no instrumentation yet. This app exists so later pull requests add instrumentation and its
5+
// tests together, rather than also introducing a new CI surface.
6+
test('the app boots under the Sentry --import entry', async ({ page }) => {
7+
await page.goto('/');
8+
9+
await expect(page.locator('#home')).toBeVisible();
10+
});
11+
12+
test('Sentry.init from the v3 subpath reports a server span', async ({ baseURL }) => {
13+
// Names are still URL based. This only proves the subpath resolves and the SDK is live.
14+
const spanPromise = waitForStreamedSpan('remix-v3', span => span.is_segment === true);
15+
16+
await fetch(`${baseURL}/`);
17+
18+
const span = await spanPromise;
19+
expect(span.attributes?.['http.request.method']?.value).toBe('GET');
20+
});

0 commit comments

Comments
 (0)