From c8dfce8f4d722fabf74a8c8632fb6cad5e2a9579 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 23 Jul 2026 10:28:22 +0200 Subject: [PATCH] rm wrappers --- MIGRATION.md | 5 +- .../.gitignore | 32 --- .../app/app.css | 6 - .../app/entry.client.tsx | 23 -- .../app/entry.server.tsx | 18 -- .../app/root.tsx | 67 ------ .../app/routes.ts | 21 -- .../app/routes/errors/client-action.tsx | 18 -- .../app/routes/errors/client-loader.tsx | 16 -- .../app/routes/errors/client-param.tsx | 17 -- .../app/routes/errors/client.tsx | 15 -- .../app/routes/errors/server-action.tsx | 18 -- .../app/routes/errors/server-loader.tsx | 16 -- .../app/routes/home.tsx | 9 - .../app/routes/performance/dynamic-param.tsx | 17 -- .../app/routes/performance/index.tsx | 14 -- .../app/routes/performance/server-action.tsx | 25 -- .../app/routes/performance/server-loader.tsx | 17 -- .../app/routes/performance/ssr.tsx | 7 - .../app/routes/performance/static.tsx | 3 - .../instrument.mjs | 8 - .../package.json | 58 ----- .../playwright.config.mjs | 8 - .../public/favicon.ico | Bin 15086 -> 0 bytes .../react-router.config.ts | 6 - .../start-event-proxy.mjs | 6 - .../tests/constants.ts | 1 - .../tests/errors/errors.client.test.ts | 140 ----------- .../tests/errors/errors.server.test.ts | 100 -------- .../performance/navigation.client.test.ts | 126 ---------- .../tests/performance/pageload.client.test.ts | 148 ------------ .../performance/performance.server.test.ts | 227 ------------------ .../performance/trace-propagation.test.ts | 47 ---- .../tsconfig.json | 21 -- .../vite.config.ts | 6 - packages/react-router/src/server/index.ts | 4 - .../src/server/wrapServerAction.ts | 107 --------- .../src/server/wrapServerLoader.ts | 107 --------- .../createServerInstrumentation.test.ts | 2 +- .../test/server/wrapServerAction.test.ts | 150 ------------ .../test/server/wrapServerLoader.test.ts | 150 ------------ 41 files changed, 5 insertions(+), 1781 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts delete mode 100644 packages/react-router/src/server/wrapServerAction.ts delete mode 100644 packages/react-router/src/server/wrapServerLoader.ts delete mode 100644 packages/react-router/test/server/wrapServerAction.test.ts delete mode 100644 packages/react-router/test/server/wrapServerLoader.test.ts diff --git a/MIGRATION.md b/MIGRATION.md index 05c9062c4ce4..bc2a88363b85 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -402,7 +402,10 @@ Sentry.init({ ### `@sentry/react-router` -- The React Router server request wrappers were removed. +- The deprecated server wrappers `wrapServerLoader` and `wrapServerAction` were removed. Loaders and + actions are instrumented automatically via the instrumentation API - export + `instrumentations = [Sentry.createSentryServerInstrumentation()]` from your `entry.server.tsx` + instead of wrapping them individually. ### `@sentry/profiling-node` diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore deleted file mode 100644 index ebb991370034..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# 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* - -/test-results/ -/playwright-report/ -/playwright/.cache/ - -!*.d.ts - -# react router -.react-router diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css deleted file mode 100644 index b31c3a9d0ddf..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/app.css +++ /dev/null @@ -1,6 +0,0 @@ -html, -body { - @media (prefers-color-scheme: dark) { - color-scheme: dark; - } -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx deleted file mode 100644 index 005268b40ad0..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.client.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from '@sentry/react-router'; -import { StrictMode, startTransition } from 'react'; -import { hydrateRoot } from 'react-dom/client'; -import { HydratedRouter } from 'react-router/dom'; - -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - // todo: get this from env - dsn: 'https://username@domain/123', - tunnel: `http://localhost:3031/`, // proxy server - integrations: [Sentry.reactRouterTracingIntegration()], - tracesSampleRate: 1.0, - tracePropagationTargets: [/^\//], -}); - -startTransition(() => { - hydrateRoot( - document, - - - , - ); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx deleted file mode 100644 index 738cd1515a4d..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/entry.server.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { createReadableStreamFromReadable } from '@react-router/node'; -import * as Sentry from '@sentry/react-router'; -import { renderToPipeableStream } from 'react-dom/server'; -import { ServerRouter } from 'react-router'; -import { type HandleErrorFunction } from 'react-router'; - -const ABORT_DELAY = 5_000; - -const handleRequest = Sentry.createSentryHandleRequest({ - streamTimeout: ABORT_DELAY, - ServerRouter, - renderToPipeableStream, - createReadableStreamFromReadable, -}); - -export default handleRequest; - -export const handleError: HandleErrorFunction = Sentry.createSentryHandleError({ logErrors: true }); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx deleted file mode 100644 index bc1b8f1236c0..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/root.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from 'react-router'; -import type { Route } from './+types/root'; -import stylesheet from './app.css?url'; - -export const links: Route.LinksFunction = () => [ - { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, - { - rel: 'preconnect', - href: 'https://fonts.gstatic.com', - crossOrigin: 'anonymous', - }, - { - rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', - }, - { rel: 'stylesheet', href: stylesheet }, -]; - -export function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); -} - -export default function App() { - return ; -} - -export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - let message = 'Oops!'; - let details = 'An unexpected error occurred.'; - let stack: string | undefined; - - if (isRouteErrorResponse(error)) { - message = error.status === 404 ? '404' : 'Error'; - details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details; - } else if (error && error instanceof Error) { - if (import.meta.env.DEV) { - details = error.message; - stack = error.stack; - } - } - - return ( -
-

{message}

-

{details}

- {stack && ( -
-          {stack}
-        
- )} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts deleted file mode 100644 index b412893def52..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type RouteConfig, index, prefix, route } from '@react-router/dev/routes'; - -export default [ - index('routes/home.tsx'), - ...prefix('errors', [ - route('client', 'routes/errors/client.tsx'), - route('client/:client-param', 'routes/errors/client-param.tsx'), - route('client-loader', 'routes/errors/client-loader.tsx'), - route('server-loader', 'routes/errors/server-loader.tsx'), - route('client-action', 'routes/errors/client-action.tsx'), - route('server-action', 'routes/errors/server-action.tsx'), - ]), - ...prefix('performance', [ - index('routes/performance/index.tsx'), - route('ssr', 'routes/performance/ssr.tsx'), - route('with/:param', 'routes/performance/dynamic-param.tsx'), - route('static', 'routes/performance/static.tsx'), - route('server-loader', 'routes/performance/server-loader.tsx'), - route('server-action', 'routes/performance/server-action.tsx'), - ]), -] satisfies RouteConfig; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx deleted file mode 100644 index d3b2d08eef2e..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-action.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Form } from 'react-router'; - -export function clientAction() { - throw new Error('Madonna mia! Che casino nella Client Action!'); -} - -export default function ClientActionErrorPage() { - return ( -
-

Client Error Action Page

-
- -
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx deleted file mode 100644 index 72d9e62a99dc..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-loader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Route } from './+types/server-loader'; - -export function clientLoader() { - throw new Error('¡Madre mía del client loader!'); - return { data: 'sad' }; -} - -export default function ClientLoaderErrorPage({ loaderData }: Route.ComponentProps) { - const { data } = loaderData; - return ( -
-

Client Loader Error Page

-
{data}
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx deleted file mode 100644 index a2e423391f03..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client-param.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Route } from './+types/client-param'; - -export default function ClientErrorParamPage({ params }: Route.ComponentProps) { - return ( -
-

Client Error Param Page

- -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx deleted file mode 100644 index 190074a5ef09..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/client.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export default function ClientErrorPage() { - return ( -
-

Client Error Page

- -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx deleted file mode 100644 index 863c320f3557..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-action.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Form } from 'react-router'; - -export function action() { - throw new Error('Madonna mia! Che casino nella Server Action!'); -} - -export default function ServerActionErrorPage() { - return ( -
-

Server Error Action Page

-
- -
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx deleted file mode 100644 index cb777686d540..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/errors/server-loader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Route } from './+types/server-loader'; - -export function loader() { - throw new Error('¡Madre mía del server!'); - return { data: 'sad' }; -} - -export default function ServerLoaderErrorPage({ loaderData }: Route.ComponentProps) { - const { data } = loaderData; - return ( -
-

Server Error Page

-
{data}
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx deleted file mode 100644 index 4498e7a0d017..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/home.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { Route } from './+types/home'; - -export function meta({}: Route.MetaArgs) { - return [{ title: 'New React Router App' }, { name: 'description', content: 'Welcome to React Router!' }]; -} - -export default function Home() { - return
home
; -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx deleted file mode 100644 index 1ac02775f2ff..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/dynamic-param.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Route } from './+types/dynamic-param'; - -export async function loader() { - await new Promise(resolve => setTimeout(resolve, 500)); - return { data: 'burritos' }; -} - -export default function DynamicParamPage({ params }: Route.ComponentProps) { - const { param } = params; - - return ( -
-

Dynamic Parameter Page

-

The parameter value is: {param}

-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx deleted file mode 100644 index e5383306625a..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Link } from 'react-router'; - -export default function PerformancePage() { - return ( -
-

Performance Page

- -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx deleted file mode 100644 index f149c5466b5a..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-action.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Form } from 'react-router'; -import type { Route } from './+types/server-action'; -import * as Sentry from '@sentry/react-router'; - -export const action = Sentry.wrapServerAction({}, async ({ request }: Route.ActionArgs) => { - let formData = await request.formData(); - let name = formData.get('name'); - await new Promise(resolve => setTimeout(resolve, 1000)); - return { - greeting: `Hola ${name}`, - }; -}); - -export default function Project({ actionData }: Route.ComponentProps) { - return ( -
-

Server action page

-
- - -
- {actionData ?

{actionData.greeting}

: null} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx deleted file mode 100644 index da688d4dfe3e..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/server-loader.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Route } from './+types/server-loader'; -import * as Sentry from '@sentry/react-router'; - -export const loader = Sentry.wrapServerLoader({}, async ({}: Route.LoaderArgs) => { - await new Promise(resolve => setTimeout(resolve, 500)); - return { data: 'burritos' }; -}); - -export default function ServerLoaderPage({ loaderData }: Route.ComponentProps) { - const { data } = loaderData; - return ( -
-

Server Loader Page

-
{data}
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx deleted file mode 100644 index 253e964ff15d..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/ssr.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function SsrPage() { - return ( -
-

SSR Page

-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx deleted file mode 100644 index 3dea24381fdc..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/app/routes/performance/static.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function StaticPage() { - return

Static Page

; -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs deleted file mode 100644 index c16240141b6d..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/instrument.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import * as Sentry from '@sentry/react-router'; - -Sentry.init({ - dsn: 'https://username@domain/123', - environment: 'qa', // dynamic sampling bias to keep transactions - tracesSampleRate: 1.0, - tunnel: `http://localhost:3031/`, // proxy server -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json deleted file mode 100644 index 20fdccf46f4c..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "react-router-7-framework-custom", - "version": "0.1.0", - "type": "module", - "private": true, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router": "^7.13.0", - "@react-router/node": "^7.13.0", - "@react-router/serve": "^7.13.0", - "@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz", - "isbot": "^5.1.17" - }, - "devDependencies": { - "@types/react": "18.3.1", - "@types/react-dom": "18.3.1", - "@types/node": "^20", - "@react-router/dev": "^7.13.0", - "@playwright/test": "~1.56.0", - "@sentry-internal/test-utils": "link:../../../test-utils", - "typescript": "^5.6.3", - "vite": "^5.4.11" - }, - "scripts": { - "build": "react-router build", - "dev": "NODE_OPTIONS='--import ./instrument.mjs' react-router dev", - "start": "NODE_OPTIONS='--import ./instrument.mjs' react-router-serve ./build/server/index.js", - "proxy": "node start-event-proxy.mjs", - "typecheck": "react-router typegen && tsc", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test:ts && pnpm test:playwright", - "test:ts": "pnpm typecheck", - "test:playwright": "playwright test" - }, - "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" - ] - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs deleted file mode 100644 index 3ed5721107a7..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/playwright.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: `PORT=3030 pnpm start`, - port: 3030, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/public/favicon.ico deleted file mode 100644 index 5dbdfcddcb14182535f6d32d1c900681321b1aa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI33v3ic7{|AFEmuJ-;v>ep_G*NPi6KM`qNryCe1PIJ8siIN1WZ(7qVa)RVtmC% z)Ch?tN+afMKm;5@rvorJk zcXnoOc4q51HBQnQH_jn!cAg&XI1?PlX>Kl^k8qq0;zkha`kY$Fxt#=KNJAE9CMdpW zqr4#g8`nTw191(+H4xW8Tmyru2I^3=J1G3emPxkPXA=3{vvuvse_WWSshqaqls^-m zgB7q8&Vk*aYRe?sn$n53dGH#%3y%^vxv{pL*-h0Z4bmb_(k6{FL7HWIz(V*HT#IcS z-wE{)+0x1U!RUPt3gB97%p}@oHxF4|6S*+Yw=_tLtxZ~`S=z6J?O^AfU>7qOX`JNBbV&8+bO0%@fhQitKIJ^O^ zpgIa__qD_y07t@DFlBJ)8SP_#^j{6jpaXt{U%=dx!qu=4u7^21lWEYHPPY5U3TcoQ zX_7W+lvZi>TapNk_X>k-KO%MC9iZp>1E`N34gHKd9tK&){jq2~7OsJ>!G0FzxQFw6G zm&Vb(2#-T|rM|n3>uAsG_hnbvUKFf3#ay@u4uTzia~NY%XgCHfx4^To4BDU@)HlV? z@EN=g^ymETa1sQK{kRwyE4Ax8?wT&GvaG@ASO}{&a17&^v`y z!oPdiSiia^oov(Z)QhG2&|FgE{M9_4hJROGbnj>#$~ZF$-G^|zPj*QApltKe?;u;uKHJ~-V!=VLkg7Kgct)l7u39f@%VG8e3f$N-B zAu3a4%ZGf)r+jPAYCSLt73m_J3}p>}6Tx0j(wg4vvKhP!DzgiWANiE;Ppvp}P2W@m z-VbYn+NXFF?6ngef5CfY6ZwKnWvNV4z6s^~yMXw2i5mv}jC$6$46g?G|CPAu{W5qF zDobS=zb2ILX9D827g*NtGe5w;>frjanY{f)hrBP_2ehBt1?`~ypvg_Ot4x1V+43P@Ve8>qd)9NX_jWdLo`Zfy zoeam9)@Dpym{4m@+LNxXBPjPKA7{3a&H+~xQvr>C_A;7=JrfK~$M2pCh>|xLz>W6SCs4qC|#V`)# z)0C|?$o>jzh<|-cpf

K7osU{Xp5PG4-K+L2G=)c3f&}H&M3wo7TlO_UJjQ-Oq&_ zjAc9=nNIYz{c3zxOiS5UfcE1}8#iI4@uy;$Q7>}u`j+OU0N<*Ezx$k{x_27+{s2Eg z`^=rhtIzCm!_UcJ?Db~Lh-=_))PT3{Q0{Mwdq;0>ZL%l3+;B&4!&xm#%HYAK|;b456Iv&&f$VQHf` z>$*K9w8T+paVwc7fLfMlhQ4)*zL_SG{~v4QR;IuX-(oRtYAhWOlh`NLoX0k$RUYMi z2Y!bqpdN}wz8q`-%>&Le@q|jFw92ErW-hma-le?S z-@OZt2EEUm4wLsuEMkt4zlyy29_3S50JAcQHTtgTC{P~%-mvCTzrjXOc|{}N`Cz`W zSj7CrXfa7lcsU0J(0uSX6G`54t^7}+OLM0n(|g4waOQ}bd3%!XLh?NX9|8G_|06Ie zD5F1)w5I~!et7lA{G^;uf7aqT`KE&2qx9|~O;s6t!gb`+zVLJyT2T)l*8l(j diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts deleted file mode 100644 index bb1f96469dd2..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/react-router.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Config } from '@react-router/dev/config'; - -export default { - ssr: true, - prerender: ['/performance/static'], -} satisfies Config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs deleted file mode 100644 index fb8dabc7fcfa..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'react-router-7-framework-custom', -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts deleted file mode 100644 index 91653303b335..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const APP_NAME = 'react-router-7-framework-custom'; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts deleted file mode 100644 index c1a7de46f1b6..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.client.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('client-side errors', () => { - const errorMessage = '¡Madre mía!'; - test('captures error thrown on click', async ({ page }) => { - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto(`/errors/client`); - await page.locator('#throw-on-click').click(); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: errorMessage, - mechanism: { - handled: false, - }, - }, - ], - }, - transaction: '/errors/client', - request: { - url: expect.stringContaining('errors/client'), - headers: expect.any(Object), - }, - level: 'error', - platform: 'javascript', - environment: 'qa', - sdk: { - integrations: expect.any(Array), - name: 'sentry.javascript.react-router', - version: expect.any(String), - }, - tags: { runtime: 'browser' }, - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - }, - }, - breadcrumbs: [ - { - category: 'ui.click', - message: 'body > div > button#throw-on-click', - }, - ], - }); - }); - - test('captures error thrown on click from a parameterized route', async ({ page }) => { - const errorMessage = '¡Madre mía de churros!'; - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto('/errors/client/churros'); - await page.locator('#throw-on-click').click(); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: '¡Madre mía de churros!', - mechanism: { - handled: false, - }, - }, - ], - }, - // todo: should be '/errors/client/:client-param' - transaction: '/errors/client/churros', - }); - }); - - test('captures error thrown in a clientLoader', async ({ page }) => { - const errorMessage = '¡Madre mía del client loader!'; - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto('/errors/client-loader'); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: errorMessage, - mechanism: { - handled: false, - type: 'auto.function.react_router.on_error', - }, - }, - ], - }, - transaction: '/errors/client-loader', - }); - }); - - test('captures error thrown in a clientAction', async ({ page }) => { - const errorMessage = 'Madonna mia! Che casino nella Client Action!'; - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto('/errors/client-action'); - await page.locator('#submit').click(); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: errorMessage, - mechanism: { - handled: false, - type: 'auto.function.react_router.on_error', - }, - }, - ], - }, - transaction: '/errors/client-action', - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts deleted file mode 100644 index 2759bfecb67e..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/errors/errors.server.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('server-side errors', () => { - test('captures error thrown in server loader', async ({ page }) => { - const errorMessage = '¡Madre mía del server!'; - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto(`/errors/server-loader`); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: errorMessage, - mechanism: { - handled: false, - type: 'react-router', - }, - }, - ], - }, - // todo: should be 'GET /errors/server-loader' - transaction: 'GET *', - request: { - url: expect.stringContaining('errors/server-loader'), - headers: expect.any(Object), - }, - level: 'error', - platform: 'node', - environment: 'qa', - sdk: { - integrations: expect.any(Array), - name: 'sentry.javascript.react-router', - version: expect.any(String), - }, - tags: { runtime: 'node' }, - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - }, - }, - }); - }); - - test('captures error thrown in server action', async ({ page }) => { - const errorMessage = 'Madonna mia! Che casino nella Server Action!'; - const errorPromise = waitForError(APP_NAME, async errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === errorMessage; - }); - - await page.goto(`/errors/server-action`); - await page.locator('#submit').click(); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: errorMessage, - mechanism: { - handled: false, - type: 'react-router', - }, - }, - ], - }, - // todo: should be 'POST /errors/server-action' - transaction: 'POST *', - request: { - url: expect.stringContaining('errors/server-action'), - headers: expect.any(Object), - }, - level: 'error', - platform: 'node', - environment: 'qa', - sdk: { - integrations: expect.any(Array), - name: 'sentry.javascript.react-router', - version: expect.any(String), - }, - tags: { runtime: 'node' }, - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - }, - }, - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts deleted file mode 100644 index 3432b95ddae3..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('client - navigation performance', () => { - test('should create navigation transaction', async ({ page }) => { - const navigationPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return ( - transactionEvent.transaction === '/performance/ssr' && transactionEvent.contexts?.trace?.op === 'navigation' - ); - }); - - const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload'; - }); - - await page.goto(`/performance`); // pageload - await pageloadTxPromise; - await page.getByRole('link', { name: 'SSR Page' }).click(); // navigation - - const transaction = await navigationPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.navigation.react_router', - 'sentry.op': 'navigation', - 'sentry.source': 'route', - 'url.template': '/performance/ssr', - 'url.path': '/performance/ssr', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/ssr$/), - }, - op: 'navigation', - origin: 'auto.navigation.react_router', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/performance/ssr', - type: 'transaction', - transaction_info: { source: 'route' }, - platform: 'javascript', - request: { - url: expect.stringContaining('/performance/ssr'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/browser', version: expect.any(String) }, - ], - }, - tags: { runtime: 'browser' }, - }); - }); - - test('should update navigation transaction for dynamic routes', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return ( - transactionEvent.transaction === '/performance/with/:param' && - transactionEvent.contexts?.trace?.op === 'navigation' - ); - }); - - const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload'; - }); - - await page.goto(`/performance`); // pageload - await pageloadTxPromise; - await page.getByRole('link', { name: 'With Param Page' }).click(); // navigation - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.navigation.react_router', - 'sentry.op': 'navigation', - 'sentry.source': 'route', - 'url.template': '/performance/with/:param', - 'url.path': '/performance/with/sentry', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/), - }, - op: 'navigation', - origin: 'auto.navigation.react_router', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/performance/with/:param', - type: 'transaction', - transaction_info: { source: 'route' }, - platform: 'javascript', - request: { - url: expect.stringContaining('/performance/with/sentry'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/browser', version: expect.any(String) }, - ], - }, - tags: { runtime: 'browser' }, - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts deleted file mode 100644 index f996989ccbf5..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('client - pageload performance', () => { - test('should send pageload transaction', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload'; - }); - - await page.goto(`/performance`); - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.pageload.react_router', - 'sentry.op': 'pageload', - 'sentry.source': 'route', - 'url.template': '/performance', - // react-router-serve 301-redirects the bare index route to a trailing slash - 'url.path': '/performance/', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/$/), - }, - op: 'pageload', - origin: 'auto.pageload.react_router', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/performance', - type: 'transaction', - transaction_info: { source: 'route' }, - measurements: expect.any(Object), - platform: 'javascript', - request: { - url: expect.stringContaining('/performance'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/browser', version: expect.any(String) }, - ], - }, - tags: { runtime: 'browser' }, - }); - }); - - test('should update pageload transaction for dynamic routes', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return ( - transactionEvent.transaction === '/performance/with/:param' && - transactionEvent.contexts?.trace?.op === 'pageload' - ); - }); - - await page.goto(`/performance/with/sentry`); - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.pageload.react_router', - 'sentry.op': 'pageload', - 'sentry.source': 'route', - 'url.template': '/performance/with/:param', - 'url.path': '/performance/with/sentry', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/), - }, - op: 'pageload', - origin: 'auto.pageload.react_router', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/performance/with/:param', - type: 'transaction', - transaction_info: { source: 'route' }, - measurements: expect.any(Object), - platform: 'javascript', - request: { - url: expect.stringContaining('/performance/with/sentry'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/browser', version: expect.any(String) }, - ], - }, - tags: { runtime: 'browser' }, - }); - }); - - test('should send pageload transaction for prerendered pages', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return ( - transactionEvent.transaction === '/performance/static' && transactionEvent.contexts?.trace?.op === 'pageload' - ); - }); - - await page.goto(`/performance/static`); - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - transaction: '/performance/static', - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.pageload.react_router', - 'sentry.op': 'pageload', - 'sentry.source': 'route', - 'url.template': '/performance/static', - // react-router-serve 301-redirects prerendered routes to a trailing slash - 'url.path': '/performance/static/', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/static\/$/), - }, - op: 'pageload', - origin: 'auto.pageload.react_router', - }, - }, - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts deleted file mode 100644 index 18b7ce9f3c6c..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/performance.server.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('server - performance', () => { - test('should send server transaction on pageload', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET /performance'; - }); - - await page.goto(`/performance`); - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.react_router.request_handler', - 'sentry.source': 'route', - }, - op: 'http.server', - origin: 'auto.http.react_router.request_handler', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: 'GET /performance', - type: 'transaction', - transaction_info: { source: 'route' }, - platform: 'node', - request: { - url: expect.stringContaining('/performance'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/node', version: expect.any(String) }, - ], - }, - tags: { - runtime: 'node', - }, - }); - }); - - test('should send server transaction on parameterized route', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET /performance/with/:param'; - }); - - await page.goto(`/performance/with/some-param`); - - const transaction = await txPromise; - - expect(transaction).toMatchObject({ - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.react_router.request_handler', - 'sentry.source': 'route', - }, - op: 'http.server', - origin: 'auto.http.react_router.request_handler', - }, - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: 'GET /performance/with/:param', - type: 'transaction', - transaction_info: { source: 'route' }, - platform: 'node', - request: { - url: expect.stringContaining('/performance/with/some-param'), - headers: expect.any(Object), - }, - event_id: expect.any(String), - environment: 'qa', - sdk: { - integrations: expect.arrayContaining([expect.any(String)]), - name: 'sentry.javascript.react-router', - version: expect.any(String), - packages: [ - { name: 'npm:@sentry/react-router', version: expect.any(String) }, - { name: 'npm:@sentry/node', version: expect.any(String) }, - ], - }, - tags: { - runtime: 'node', - }, - }); - }); - - test('should instrument wrapped server loader', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET /performance/server-loader.data'; - }); - - await page.goto(`/performance`); - await page.getByRole('link', { name: 'Server Loader' }).click(); - - const transaction = await txPromise; - - expect(transaction).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - op: 'http.server', - origin: 'auto.http.react_router.loader', - parent_span_id: expect.any(String), - status: 'ok', - data: expect.objectContaining({ - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.target': '/performance/server-loader.data', - 'http.url': 'http://localhost:3030/performance/server-loader.data', - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.react_router.loader', - 'sentry.source': 'url', - url: 'http://localhost:3030/performance/server-loader.data', - }), - }, - }), - transaction: 'GET /performance/server-loader.data', - type: 'transaction', - transaction_info: { source: 'url' }, - platform: 'node', - }), - ); - // ensure we do not have a stray, bogus route attribute - expect(transaction.contexts?.trace?.data?.['http.route']).not.toBeDefined(); - - expect(transaction?.spans).toContainEqual({ - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.http.react_router.loader', - 'sentry.op': 'function.react_router.loader', - }, - description: 'Executing Server Loader', - parent_span_id: expect.any(String), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - status: 'ok', - op: 'function.react_router.loader', - origin: 'auto.http.react_router.loader', - }); - }); - - test('should instrument a wrapped server action', async ({ page }) => { - const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'POST /performance/server-action.data'; - }); - - await page.goto(`/performance/server-action`); - await page.getByRole('button', { name: 'Submit' }).click(); - - const transaction = await txPromise; - - expect(transaction).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - op: 'http.server', - origin: 'auto.http.react_router.action', - parent_span_id: expect.any(String), - status: 'ok', - data: expect.objectContaining({ - 'http.method': 'POST', - 'http.response.status_code': 200, - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.target': '/performance/server-action.data', - 'http.url': 'http://localhost:3030/performance/server-action.data', - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.react_router.action', - 'sentry.source': 'url', - url: 'http://localhost:3030/performance/server-action.data', - }), - }, - }), - transaction: 'POST /performance/server-action.data', - type: 'transaction', - transaction_info: { source: 'url' }, - platform: 'node', - }), - ); - // ensure we do not have a stray, bogus route attribute - expect(transaction.contexts?.trace?.data?.['http.route']).not.toBeDefined(); - - expect(transaction?.spans).toContainEqual({ - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - 'sentry.origin': 'auto.http.react_router.action', - 'sentry.op': 'function.react_router.action', - }, - description: 'Executing Server Action', - parent_span_id: expect.any(String), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - status: 'ok', - op: 'function.react_router.action', - origin: 'auto.http.react_router.action', - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts deleted file mode 100644 index e9b2c9409154..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { APP_NAME } from '../constants'; - -test.describe('Trace propagation', () => { - test('should inject metatags in ssr pageload', async ({ page }) => { - await page.goto(`/`); - const sentryTraceContent = await page.getAttribute('meta[name="sentry-trace"]', 'content'); - expect(sentryTraceContent).toBeDefined(); - expect(sentryTraceContent).toMatch(/^[a-f0-9]{32}-[a-f0-9]{16}-[01]$/); - const baggageContent = await page.getAttribute('meta[name="baggage"]', 'content'); - expect(baggageContent).toBeDefined(); - expect(baggageContent).toContain('sentry-environment=qa'); - expect(baggageContent).toContain('sentry-public_key='); - expect(baggageContent).toContain('sentry-trace_id='); - expect(baggageContent).toContain('sentry-transaction='); - expect(baggageContent).toContain('sentry-sampled='); - }); - - test('should have trace connection', async ({ page }) => { - const serverTxPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === 'GET *'; - }); - - const clientTxPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/'; - }); - - await page.goto(`/`); - const serverTx = await serverTxPromise; - const clientTx = await clientTxPromise; - - expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - - const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); - - expect(requestHandlerSpan).toBeDefined(); - expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); - }); - - test('should not have trace connection for prerendered pages', async ({ page }) => { - await page.goto('/performance/static'); - - const sentryTraceElement = await page.$('meta[name="sentry-trace"]'); - expect(sentryTraceElement).toBeNull(); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json deleted file mode 100644 index 1b510b528de9..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["node", "vite/client"], - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "rootDirs": [".", "./.react-router/types"], - "baseUrl": ".", - - "esModuleInterop": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true - }, - "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"], - "exclude": ["tests/**/*"] -} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts deleted file mode 100644 index 68ba30d69397..000000000000 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { reactRouter } from '@react-router/dev/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [reactRouter()], -}); diff --git a/packages/react-router/src/server/index.ts b/packages/react-router/src/server/index.ts index 3f8b7c0e149a..43470cf2c870 100644 --- a/packages/react-router/src/server/index.ts +++ b/packages/react-router/src/server/index.ts @@ -7,10 +7,6 @@ export { init } from './sdk'; // eslint-disable-next-line typescript/no-deprecated export { wrapSentryHandleRequest, sentryHandleRequest } from './wrapSentryHandleRequest'; export { createSentryHandleRequest, type SentryHandleRequestOptions } from './createSentryHandleRequest'; -// eslint-disable-next-line typescript/no-deprecated -export { wrapServerAction } from './wrapServerAction'; -// eslint-disable-next-line typescript/no-deprecated -export { wrapServerLoader } from './wrapServerLoader'; export { createSentryHandleError, type SentryHandleErrorOptions } from './createSentryHandleError'; export { getMetaTagTransformer } from './getMetaTagTransformer'; diff --git a/packages/react-router/src/server/wrapServerAction.ts b/packages/react-router/src/server/wrapServerAction.ts deleted file mode 100644 index 0ebaeca9cdb5..000000000000 --- a/packages/react-router/src/server/wrapServerAction.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { HTTP_TARGET } from '@sentry/conventions/attributes'; -import type { SpanAttributes } from '@sentry/core'; -import { - debug, - flushIfServerless, - getActiveSpan, - getRootSpan, - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - spanToJSON, - startSpan, - updateSpanName, -} from '@sentry/core'; -import type { ActionFunctionArgs } from 'react-router'; -import { DEBUG_BUILD } from '../common/debug-build'; -import { isInstrumentationApiUsed } from './serverGlobals'; - -type SpanOptions = { - name?: string; - attributes?: SpanAttributes; -}; - -// Track if we've already warned about duplicate instrumentation -let hasWarnedAboutDuplicateActionInstrumentation = false; - -// todo(v11): Remove this deprecated wrapper in favor of the instrumentation API (`createSentryServerInstrumentation`). -/** - * Wraps a React Router server action function with Sentry performance monitoring. - * - * @deprecated Use React Router's instrumentation API instead: export - * `instrumentations = [createSentryServerInstrumentation()]` from your `entry.server.tsx` to instrument all server - * actions without wrapping them individually. This manual wrapper will be removed in a future major. - * - * @param options - Optional span configuration options including name, operation, description and attributes - * @param actionFn - The server action function to wrap - * - * @example - * ```ts - * // Wrap an action function with custom span options - * export const action = wrapServerAction( - * { - * name: 'Submit Form Data', - * description: 'Processes form submission data', - * }, - * async ({ request }) => { - * // ... your action logic - * } - * ); - * ``` - */ -export function wrapServerAction( - options: SpanOptions = {}, - actionFn: (args: ActionFunctionArgs) => Promise, -): (args: ActionFunctionArgs) => Promise { - return async function (args: ActionFunctionArgs): Promise { - // Skip instrumentation if instrumentation API is already handling it - if (isInstrumentationApiUsed()) { - if (DEBUG_BUILD && !hasWarnedAboutDuplicateActionInstrumentation) { - hasWarnedAboutDuplicateActionInstrumentation = true; - debug.warn( - 'wrapServerAction is redundant when using the instrumentation API. ' + - 'The action is already instrumented automatically. You can safely remove wrapServerAction.', - ); - } - return actionFn(args); - } - - const name = options.name || 'Executing Server Action'; - const active = getActiveSpan(); - if (active) { - const root = getRootSpan(active); - const spanData = spanToJSON(root); - if (spanData.origin === 'auto.http.otel.http') { - // eslint-disable-next-line typescript/no-deprecated - const target = spanData.data[HTTP_TARGET]; - - if (target) { - // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route - // So we force this to be a more sensible name here - updateSpanName(root, `${args.request.method} ${target}`); - root.setAttributes({ - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action', - }); - } - } - } - - try { - return await startSpan( - { - name, - ...options, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action', - ...options.attributes, - }, - }, - () => actionFn(args), - ); - } finally { - await flushIfServerless(); - } - }; -} diff --git a/packages/react-router/src/server/wrapServerLoader.ts b/packages/react-router/src/server/wrapServerLoader.ts deleted file mode 100644 index ec48746f363b..000000000000 --- a/packages/react-router/src/server/wrapServerLoader.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { HTTP_TARGET } from '@sentry/conventions/attributes'; -import type { SpanAttributes } from '@sentry/core'; -import { - debug, - flushIfServerless, - getActiveSpan, - getRootSpan, - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - spanToJSON, - startSpan, - updateSpanName, -} from '@sentry/core'; -import type { LoaderFunctionArgs } from 'react-router'; -import { DEBUG_BUILD } from '../common/debug-build'; -import { isInstrumentationApiUsed } from './serverGlobals'; - -type SpanOptions = { - name?: string; - attributes?: SpanAttributes; -}; - -// Track if we've already warned about duplicate instrumentation -let hasWarnedAboutDuplicateLoaderInstrumentation = false; - -// todo(v11): Remove this deprecated wrapper in favor of the instrumentation API (`createSentryServerInstrumentation`). -/** - * Wraps a React Router server loader function with Sentry performance monitoring. - * - * @deprecated Use React Router's instrumentation API instead: export - * `instrumentations = [createSentryServerInstrumentation()]` from your `entry.server.tsx` to instrument all server - * loaders without wrapping them individually. This manual wrapper will be removed in a future major. - * - * @param options - Optional span configuration options including name, operation, description and attributes - * @param loaderFn - The server loader function to wrap - * - * @example - * ```ts - * // Wrap a loader function with custom span options - * export const loader = wrapServerLoader( - * { - * name: 'Load Some Data', - * description: 'Loads some data from the db', - * }, - * async ({ params }) => { - * // ... your loader logic - * } - * ); - * ``` - */ -export function wrapServerLoader( - options: SpanOptions = {}, - loaderFn: (args: LoaderFunctionArgs) => Promise, -): (args: LoaderFunctionArgs) => Promise { - return async function (args: LoaderFunctionArgs): Promise { - // Skip instrumentation if instrumentation API is already handling it - if (isInstrumentationApiUsed()) { - if (DEBUG_BUILD && !hasWarnedAboutDuplicateLoaderInstrumentation) { - hasWarnedAboutDuplicateLoaderInstrumentation = true; - debug.warn( - 'wrapServerLoader is redundant when using the instrumentation API. ' + - 'The loader is already instrumented automatically. You can safely remove wrapServerLoader.', - ); - } - return loaderFn(args); - } - - const name = options.name || 'Executing Server Loader'; - const active = getActiveSpan(); - - if (active) { - const root = getRootSpan(active); - const spanData = spanToJSON(root); - if (spanData.origin === 'auto.http.otel.http') { - // eslint-disable-next-line typescript/no-deprecated - const target = spanData.data[HTTP_TARGET]; - - if (target) { - // We cannot rely on the regular span name inferral here, as the express instrumentation sets `*` as the route - // So we force this to be a more sensible name here - updateSpanName(root, `${args.request.method} ${target}`); - root.setAttributes({ - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader', - }); - } - } - } - try { - return await startSpan( - { - name, - ...options, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader', - ...options.attributes, - }, - }, - () => loaderFn(args), - ); - } finally { - await flushIfServerless(); - } - }; -} diff --git a/packages/react-router/test/server/createServerInstrumentation.test.ts b/packages/react-router/test/server/createServerInstrumentation.test.ts index 80b1a4597901..6762be8e4e83 100644 --- a/packages/react-router/test/server/createServerInstrumentation.test.ts +++ b/packages/react-router/test/server/createServerInstrumentation.test.ts @@ -70,7 +70,7 @@ describe('createSentryServerInstrumentation', () => { // Creating the instrumentation must not mark the API active. On React Router versions that // don't support the instrumentations API, the registration callbacks are never invoked, so - // the legacy OTel data-loader path and wrapServerLoader/wrapServerAction must stay active. + // the legacy OTel data-loader path must stay active. expect((globalThis as any).__sentryReactRouterServerInstrumentationUsed).toBeUndefined(); }); diff --git a/packages/react-router/test/server/wrapServerAction.test.ts b/packages/react-router/test/server/wrapServerAction.test.ts deleted file mode 100644 index 149b90d570c0..000000000000 --- a/packages/react-router/test/server/wrapServerAction.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as core from '@sentry/core'; -import type { ActionFunctionArgs } from 'react-router'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { wrapServerAction } from '../../src/server/wrapServerAction'; - -vi.mock('@sentry/core', async () => { - const actual = await vi.importActual('@sentry/core'); - return { - ...actual, - startSpan: vi.fn(), - flushIfServerless: vi.fn(), - debug: { - warn: vi.fn(), - }, - }; -}); - -describe('wrapServerAction', () => { - beforeEach(() => { - vi.clearAllMocks(); - // Reset the global flag and warning state - delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed; - }); - - afterEach(() => { - delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed; - }); - - it('should wrap an action function with default options', async () => { - const mockActionFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedAction = wrapServerAction({}, mockActionFn); - await wrappedAction(mockArgs); - - expect(core.startSpan).toHaveBeenCalledWith( - { - name: 'Executing Server Action', - attributes: { - [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action', - [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action', - }, - }, - expect.any(Function), - ); - expect(mockActionFn).toHaveBeenCalledWith(mockArgs); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should wrap an action function with custom options', async () => { - const customOptions = { - name: 'Custom Action', - attributes: { - 'sentry.custom': 'value', - }, - }; - - const mockActionFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedAction = wrapServerAction(customOptions, mockActionFn); - await wrappedAction(mockArgs); - - expect(core.startSpan).toHaveBeenCalledWith( - { - name: 'Custom Action', - attributes: { - [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.action', - [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.action', - 'sentry.custom': 'value', - }, - }, - expect.any(Function), - ); - expect(mockActionFn).toHaveBeenCalledWith(mockArgs); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should call flushIfServerless on successful execution', async () => { - const mockActionFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedAction = wrapServerAction({}, mockActionFn); - await wrappedAction(mockArgs); - - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should call flushIfServerless even when action throws an error', async () => { - const mockError = new Error('Action failed'); - const mockActionFn = vi.fn().mockRejectedValue(mockError); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedAction = wrapServerAction({}, mockActionFn); - - await expect(wrappedAction(mockArgs)).rejects.toThrow('Action failed'); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should propagate errors from action function', async () => { - const mockError = new Error('Test error'); - const mockActionFn = vi.fn().mockRejectedValue(mockError); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedAction = wrapServerAction({}, mockActionFn); - - await expect(wrappedAction(mockArgs)).rejects.toBe(mockError); - }); - - it('should skip span creation and warn when instrumentation API is used', async () => { - // Reset modules to get a fresh copy with unset warning flag - vi.resetModules(); - const { wrapServerAction: freshWrapServerAction } = await import('../../src/server/wrapServerAction'); - - // Set the global flag indicating instrumentation API is in use - (globalThis as any).__sentryReactRouterServerInstrumentationUsed = true; - - const mockActionFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as ActionFunctionArgs; - - const wrappedAction = freshWrapServerAction({}, mockActionFn); - - // Call multiple times - await wrappedAction(mockArgs); - await wrappedAction(mockArgs); - await wrappedAction(mockArgs); - - // Should warn about redundant wrapper via debug.warn, but only once - expect(core.debug.warn).toHaveBeenCalledTimes(1); - expect(core.debug.warn).toHaveBeenCalledWith( - expect.stringContaining('wrapServerAction is redundant when using the instrumentation API'), - ); - - // Should not create spans (instrumentation API handles it) - expect(core.startSpan).not.toHaveBeenCalled(); - - // Should still execute the action function - expect(mockActionFn).toHaveBeenCalledTimes(3); - }); -}); diff --git a/packages/react-router/test/server/wrapServerLoader.test.ts b/packages/react-router/test/server/wrapServerLoader.test.ts deleted file mode 100644 index ce3be0f4319a..000000000000 --- a/packages/react-router/test/server/wrapServerLoader.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as core from '@sentry/core'; -import type { LoaderFunctionArgs } from 'react-router'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { wrapServerLoader } from '../../src/server/wrapServerLoader'; - -vi.mock('@sentry/core', async () => { - const actual = await vi.importActual('@sentry/core'); - return { - ...actual, - startSpan: vi.fn(), - flushIfServerless: vi.fn(), - debug: { - warn: vi.fn(), - }, - }; -}); - -describe('wrapServerLoader', () => { - beforeEach(() => { - vi.clearAllMocks(); - // Reset the global flag and warning state - delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed; - }); - - afterEach(() => { - delete (globalThis as any).__sentryReactRouterServerInstrumentationUsed; - }); - - it('should wrap a loader function with default options', async () => { - const mockLoaderFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedLoader = wrapServerLoader({}, mockLoaderFn); - await wrappedLoader(mockArgs); - - expect(core.startSpan).toHaveBeenCalledWith( - { - name: 'Executing Server Loader', - attributes: { - [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader', - [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader', - }, - }, - expect.any(Function), - ); - expect(mockLoaderFn).toHaveBeenCalledWith(mockArgs); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should wrap a loader function with custom options', async () => { - const customOptions = { - name: 'Custom Loader', - attributes: { - 'sentry.custom': 'value', - }, - }; - - const mockLoaderFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedLoader = wrapServerLoader(customOptions, mockLoaderFn); - await wrappedLoader(mockArgs); - - expect(core.startSpan).toHaveBeenCalledWith( - { - name: 'Custom Loader', - attributes: { - [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.react_router.loader', - [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.loader', - 'sentry.custom': 'value', - }, - }, - expect.any(Function), - ); - expect(mockLoaderFn).toHaveBeenCalledWith(mockArgs); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should call flushIfServerless on successful execution', async () => { - const mockLoaderFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedLoader = wrapServerLoader({}, mockLoaderFn); - await wrappedLoader(mockArgs); - - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should call flushIfServerless even when loader throws an error', async () => { - const mockError = new Error('Loader failed'); - const mockLoaderFn = vi.fn().mockRejectedValue(mockError); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedLoader = wrapServerLoader({}, mockLoaderFn); - - await expect(wrappedLoader(mockArgs)).rejects.toThrow('Loader failed'); - expect(core.flushIfServerless).toHaveBeenCalled(); - }); - - it('should propagate errors from loader function', async () => { - const mockError = new Error('Test error'); - const mockLoaderFn = vi.fn().mockRejectedValue(mockError); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - (core.startSpan as any).mockImplementation((_: any, fn: any) => fn()); - - const wrappedLoader = wrapServerLoader({}, mockLoaderFn); - - await expect(wrappedLoader(mockArgs)).rejects.toBe(mockError); - }); - - it('should skip span creation and warn when instrumentation API is used', async () => { - // Reset modules to get a fresh copy with unset warning flag - vi.resetModules(); - const { wrapServerLoader: freshWrapServerLoader } = await import('../../src/server/wrapServerLoader'); - - // Set the global flag indicating instrumentation API is in use - (globalThis as any).__sentryReactRouterServerInstrumentationUsed = true; - - const mockLoaderFn = vi.fn().mockResolvedValue('result'); - const mockArgs = { request: new Request('http://test.com') } as LoaderFunctionArgs; - - const wrappedLoader = freshWrapServerLoader({}, mockLoaderFn); - - // Call multiple times - await wrappedLoader(mockArgs); - await wrappedLoader(mockArgs); - await wrappedLoader(mockArgs); - - // Should warn about redundant wrapper via debug.warn, but only once - expect(core.debug.warn).toHaveBeenCalledTimes(1); - expect(core.debug.warn).toHaveBeenCalledWith( - expect.stringContaining('wrapServerLoader is redundant when using the instrumentation API'), - ); - - // Should not create spans (instrumentation API handles it) - expect(core.startSpan).not.toHaveBeenCalled(); - - // Should still execute the loader function - expect(mockLoaderFn).toHaveBeenCalledTimes(3); - }); -});