Skip to content

test(nextjs): Add e2e test for orpc #16462

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 2 commits into from
Jun 3, 2025
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
45 changes: 45 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-orpc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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

.vscode

test-results
2 changes: 2 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-orpc/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
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/app/building-your-application/configuring/typescript for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import("next").NextConfig} */
const config = {};

import { withSentryConfig } from '@sentry/nextjs';

export default withSentryConfig(config, {
disableLogger: true,
});
45 changes: 45 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-orpc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "next-orpc",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "next build",
"dev": "next dev -p 3030",
"start": "next start -p 3030",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test",
"test:build": "pnpm install && pnpm build",
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && pnpm build",
"test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
},
"dependencies": {
"@sentry/nextjs": "latest || *",
"@orpc/server": "latest",
"@orpc/client": "latest",
"next": "14.2.29",
"react": "18.3.1",
"react-dom": "18.3.1",
"server-only": "^0.0.1"
},
"devDependencies": {
"@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@types/eslint": "^8.56.10",
"@types/node": "^18.19.1",
"@types/react": "18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.4",
"postcss": "^8.4.39",
"prettier": "^3.3.2",
"typescript": "^5.5.3"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
const testEnv = process.env.TEST_ENV;

if (!testEnv) {
throw new Error('No test env defined');
}

const config = getPlaywrightConfig(
{
startCommand: testEnv === 'development' ? 'pnpm next dev -p 3030' : 'pnpm next start -p 3030',
port: 3030,
},
{
// This comes with the risk of tests leaking into each other but the tests run quite slow so we should parallelize
workers: '100%',
},
);

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

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

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FindPlanet } from '~/components/FindPlanet';

export default async function ClientErrorPage() {
return (
<main>
<FindPlanet withError />
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FindPlanet } from '~/components/FindPlanet';

export default async function ClientPage() {
return (
<main>
<FindPlanet />
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import * as Sentry from '@sentry/nextjs';
import NextError from 'next/error';
import { useEffect } from 'react';

export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);

return (
<html>
<body>
{/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
<NextError statusCode={0} />
</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import '../orpc/server';
import * as Sentry from '@sentry/nextjs';

import { type Metadata } from 'next';

export function generateMetadata(): Metadata {
return {
other: {
...Sentry.getTraceData(),
},
};
}

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Link from 'next/link';
import { client } from '~/orpc/client';

export default async function Home() {
const planets = await client.planet.list({ limit: 10 });

return (
<main>
<h1>Planets</h1>
<ul>
{planets.map(planet => (
<li key={planet.id}>{planet.name}</li>
))}
</ul>
<Link href={'/client'}>Client</Link>
<Link href={'/client-error'}>Error</Link>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RPCHandler } from '@orpc/server/fetch';
import { router } from '~/orpc/router';

const handler = new RPCHandler(router);

async function handleRequest(request: Request) {
const { response } = await handler.handle(request, {
prefix: '/rpc',
context: {
headers: Object.fromEntries(request.headers.entries()),
},
});

return response ?? new Response('Not found', { status: 404 });
}

export const HEAD = handleRequest;
export const GET = handleRequest;
export const POST = handleRequest;
export const PUT = handleRequest;
export const PATCH = handleRequest;
export const DELETE = handleRequest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { client } from '~/orpc/client';
import { useEffect, useState } from 'react';

type Planet = {
id: number;
name: string;
description?: string;
};

export function FindPlanet({ withError = false }: { withError?: boolean }) {
const [planet, setPlanet] = useState<Planet>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
async function fetchPlanet() {
const data = withError ? await client.planet.findWithError({ id: 1 }) : await client.planet.find({ id: 1 });
setPlanet(data);
}

setLoading(true);
fetchPlanet();
setLoading(false);
}, []);

if (loading) {
return <div>Loading planet...</div>;
}

if (error) {
return <div>Error: {error}</div>;
}

return (
<div>
<h1>Planet</h1>
<div>{planet?.name}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1,
debug: false,
});

export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/nextjs';

export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
}
}

export const onRequestError = Sentry.captureRequestError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import { RouterClient } from '@orpc/server';
import type { headers } from 'next/headers';
import { router } from './router';

declare global {
var $headers: typeof headers;
}

const link = new RPCLink({
url: new URL('/rpc', typeof window !== 'undefined' ? window.location.href : 'http://localhost:3030'),
headers: async () => {
return globalThis.$headers
? Object.fromEntries(await globalThis.$headers()) // ssr
: {}; // browser
},
});

export const client: RouterClient<typeof router> = createORPCClient(link);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ORPCError, os } from '@orpc/server';
import { z } from 'zod';
import { sentryTracingMiddleware } from './sentry-middleware';

const PlanetSchema = z.object({
id: z.number().int().min(1),
name: z.string(),
description: z.string().optional(),
});

export const base = os.use(sentryTracingMiddleware);

export const listPlanet = base
.input(
z.object({
limit: z.number().int().min(1).max(100).optional(),
cursor: z.number().int().min(0).default(0),
}),
)
.handler(async ({ input }) => {
return [
{ id: 1, name: 'name' },
{ id: 2, name: 'another name' },
];
});

export const findPlanet = base.input(PlanetSchema.pick({ id: true })).handler(async ({ input }) => {
await new Promise(resolve => setTimeout(resolve, 500));
return { id: 1, name: 'name' };
});

export const throwingFindPlanet = base.input(PlanetSchema.pick({ id: true })).handler(async ({ input }) => {
throw new ORPCError('OH_OH', {
message: 'You are hitting an error',
data: { some: 'data' },
});
});

export const router = {
planet: {
list: listPlanet,
find: findPlanet,
findWithError: throwingFindPlanet,
},
};
Loading
Loading