diff --git a/dev-packages/e2e-tests/test-applications/astro-5/.gitignore b/dev-packages/e2e-tests/test-applications/astro-5/.gitignore
new file mode 100644
index 000000000000..560782d47d98
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/.gitignore
@@ -0,0 +1,26 @@
+# build output
+dist/
+
+# generated types
+.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
+
+# jetbrains setting folder
+.idea/
+
+test-results
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/.npmrc b/dev-packages/e2e-tests/test-applications/astro-5/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://127.0.0.1:4873
+@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/README.md b/dev-packages/e2e-tests/test-applications/astro-5/README.md
new file mode 100644
index 000000000000..ff19a3e7ece8
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/README.md
@@ -0,0 +1,48 @@
+# Astro Starter Kit: Basics
+
+```sh
+npm create astro@latest -- --template basics
+```
+
+[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
+[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
+[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
+
+> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
+
+
+
+## 🚀 Project Structure
+
+Inside of your Astro project, you'll see the following folders and files:
+
+```text
+/
+├── public/
+│ └── favicon.svg
+├── src/
+│ ├── layouts/
+│ │ └── Layout.astro
+│ └── pages/
+│ └── index.astro
+└── package.json
+```
+
+To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
+
+## 🧞 Commands
+
+All commands are run from the root of the project, from a terminal:
+
+| Command | Action |
+| :------------------------ | :----------------------------------------------- |
+| `npm install` | Installs dependencies |
+| `npm run dev` | Starts local dev server at `localhost:4321` |
+| `npm run build` | Build your production site to `./dist/` |
+| `npm run preview` | Preview your build locally, before deploying |
+| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
+| `npm run astro -- --help` | Get help using the Astro CLI |
+
+## 👀 Want to learn more?
+
+Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs
new file mode 100644
index 000000000000..f38dac6171bf
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs
@@ -0,0 +1,21 @@
+import sentry from '@sentry/astro';
+// @ts-check
+import { defineConfig } from 'astro/config';
+
+import node from '@astrojs/node';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [
+ sentry({
+ debug: true,
+ sourceMapsUploadOptions: {
+ enabled: false,
+ },
+ }),
+ ],
+ output: 'server',
+ adapter: node({
+ mode: 'standalone',
+ }),
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json
new file mode 100644
index 000000000000..a41a919b0283
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "astro-5",
+ "type": "module",
+ "version": "0.0.1",
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro",
+ "test:build": "pnpm install && npx playwright install && pnpm build",
+ "test:assert": "TEST_ENV=production playwright test"
+ },
+ "dependencies": {
+ "@astrojs/internal-helpers": "^0.4.2",
+ "@astrojs/node": "^9.0.0",
+ "@playwright/test": "^1.46.0",
+ "@sentry-internal/test-utils": "link:../../../test-utils",
+ "@sentry/astro": "^8.42.0",
+ "astro": "^5.0.3"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/astro-5/playwright.config.mjs
new file mode 100644
index 000000000000..cd6ed611fb4a
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/playwright.config.mjs
@@ -0,0 +1,13 @@
+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: 'node ./dist/server/entry.mjs',
+});
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/public/favicon.svg b/dev-packages/e2e-tests/test-applications/astro-5/public/favicon.svg
new file mode 100644
index 000000000000..f157bd1c5e28
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/public/favicon.svg
@@ -0,0 +1,9 @@
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/sentry.client.config.js b/dev-packages/e2e-tests/test-applications/astro-5/sentry.client.config.js
new file mode 100644
index 000000000000..7bb40f0c60d4
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/sentry.client.config.js
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/astro';
+
+Sentry.init({
+ dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
+ environment: 'qa',
+ tracesSampleRate: 1.0,
+ tunnel: 'http://localhost:3031/', // proxy server
+ integrations: [Sentry.browserTracingIntegration()],
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/sentry.server.config.js b/dev-packages/e2e-tests/test-applications/astro-5/sentry.server.config.js
new file mode 100644
index 000000000000..2b79ec0ed337
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/sentry.server.config.js
@@ -0,0 +1,8 @@
+import * as Sentry from '@sentry/astro';
+
+Sentry.init({
+ dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
+ environment: 'qa',
+ tracesSampleRate: 1.0,
+ tunnel: 'http://localhost:3031/', // proxy server
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/assets/astro.svg b/dev-packages/e2e-tests/test-applications/astro-5/src/assets/astro.svg
new file mode 100644
index 000000000000..8cf8fb0c7da6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/assets/astro.svg
@@ -0,0 +1 @@
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/assets/background.svg b/dev-packages/e2e-tests/test-applications/astro-5/src/assets/background.svg
new file mode 100644
index 000000000000..4b2be0ac0e47
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/assets/background.svg
@@ -0,0 +1 @@
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/components/Avatar.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/components/Avatar.astro
new file mode 100644
index 000000000000..09a539f14e64
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/components/Avatar.astro
@@ -0,0 +1,3 @@
+---
+---
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/components/Welcome.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/components/Welcome.astro
new file mode 100644
index 000000000000..6b7b9c70e869
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/components/Welcome.astro
@@ -0,0 +1,209 @@
+---
+import astroLogo from '../assets/astro.svg';
+import background from '../assets/background.svg';
+---
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/layouts/Layout.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/layouts/Layout.astro
new file mode 100644
index 000000000000..e455c6106729
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/layouts/Layout.astro
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+ Astro Basics
+
+
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/client-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/client-error/index.astro
new file mode 100644
index 000000000000..facd6f077a6e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/client-error/index.astro
@@ -0,0 +1,11 @@
+---
+import Layout from "../../layouts/Layout.astro";
+---
+
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/api.ts b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/api.ts
new file mode 100644
index 000000000000..a76accdba010
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/api.ts
@@ -0,0 +1,15 @@
+import type { APIRoute } from 'astro';
+
+export const prerender = false;
+
+export const GET: APIRoute = ({ request, url }) => {
+ if (url.searchParams.has('error')) {
+ throw new Error('Endpoint Error');
+ }
+ return new Response(
+ JSON.stringify({
+ search: url.search,
+ sp: url.searchParams,
+ }),
+ );
+};
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/index.astro
new file mode 100644
index 000000000000..f025c76f8365
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/endpoint-error/index.astro
@@ -0,0 +1,9 @@
+---
+import Layout from "../../layouts/Layout.astro";
+
+export const prerender = false;
+---
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro
new file mode 100644
index 000000000000..457d94f43457
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro
@@ -0,0 +1,21 @@
+---
+import Welcome from '../components/Welcome.astro';
+import Layout from '../layouts/Layout.astro';
+
+// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
+// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
+---
+
+
+
+ Astro E2E Test App
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/server-island/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/server-island/index.astro
new file mode 100644
index 000000000000..d0544ac4f32f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/server-island/index.astro
@@ -0,0 +1,16 @@
+---
+import Avatar from '../../components/Avatar.astro';
+import Layout from '../../layouts/Layout.astro';
+
+export const prerender = true;
+---
+
+
+ This page is static, except for the avatar which is loaded dynamically from the server
+
+
+ Fallback
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/ssr-error/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/ssr-error/index.astro
new file mode 100644
index 000000000000..4ecb7466de70
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/ssr-error/index.astro
@@ -0,0 +1,13 @@
+---
+import Layout from "../../layouts/Layout.astro";
+
+const a = {} as any;
+console.log(a.foo.x);
+export const prerender = false;
+---
+
+
+
+ Page with SSR error
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-ssr/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-ssr/index.astro
new file mode 100644
index 000000000000..58f5d80198d7
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-ssr/index.astro
@@ -0,0 +1,15 @@
+---
+import Layout from "../../layouts/Layout.astro"
+
+export const prerender = false
+---
+
+
+
+
+ This is a server page
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-static/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-static/index.astro
new file mode 100644
index 000000000000..f71bf00c9adf
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/test-static/index.astro
@@ -0,0 +1,15 @@
+---
+import Layout from "../../layouts/Layout.astro";
+
+export const prerender = true;
+---
+
+
+
+
+ This is a static page
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/astro-5/start-event-proxy.mjs
new file mode 100644
index 000000000000..875a9a2afac1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/test-utils';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'astro-5',
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.client.test.ts
new file mode 100644
index 000000000000..22572d009202
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.client.test.ts
@@ -0,0 +1,80 @@
+import { expect, test } from '@playwright/test';
+import { waitForError } from '@sentry-internal/test-utils';
+
+test.describe('client-side errors', () => {
+ test('captures error thrown on click', async ({ page }) => {
+ const errorEventPromise = waitForError('astro-5', errorEvent => {
+ return errorEvent?.exception?.values?.[0]?.value === 'client error';
+ });
+
+ await page.goto('/client-error');
+
+ await page.getByText('Throw Error').click();
+
+ const errorEvent = await errorEventPromise;
+
+ const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames;
+
+ expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual(
+ expect.objectContaining({
+ colno: expect.any(Number),
+ lineno: expect.any(Number),
+ filename: expect.stringContaining('/client-error'),
+ function: 'HTMLButtonElement.onclick',
+ in_app: true,
+ }),
+ );
+
+ expect(errorEvent).toMatchObject({
+ exception: {
+ values: [
+ {
+ mechanism: {
+ handled: false,
+ type: 'onerror',
+ },
+ type: 'Error',
+ value: 'client error',
+ stacktrace: expect.any(Object), // detailed check above
+ },
+ ],
+ },
+ level: 'error',
+ platform: 'javascript',
+ request: {
+ url: expect.stringContaining('/client-error'),
+ headers: {
+ 'User-Agent': expect.any(String),
+ },
+ },
+ event_id: expect.stringMatching(/[a-f0-9]{32}/),
+ timestamp: expect.any(Number),
+ sdk: {
+ integrations: expect.arrayContaining([
+ 'InboundFilters',
+ 'FunctionToString',
+ 'BrowserApiErrors',
+ 'Breadcrumbs',
+ 'GlobalHandlers',
+ 'LinkedErrors',
+ 'Dedupe',
+ 'HttpContext',
+ 'BrowserSession',
+ 'BrowserTracing',
+ ]),
+ name: 'sentry.javascript.astro',
+ version: expect.any(String),
+ packages: expect.any(Array),
+ },
+ transaction: '/client-error',
+ contexts: {
+ trace: {
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ },
+ },
+ environment: 'qa',
+ });
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.server.test.ts
new file mode 100644
index 000000000000..d6a9514da1d1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/errors.server.test.ts
@@ -0,0 +1,164 @@
+import { expect, test } from '@playwright/test';
+import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
+
+test.describe('server-side errors', () => {
+ test('captures SSR error', async ({ page }) => {
+ const errorEventPromise = waitForError('astro-5', errorEvent => {
+ return errorEvent?.exception?.values?.[0]?.value === "Cannot read properties of undefined (reading 'x')";
+ });
+
+ const transactionEventPromise = waitForTransaction('astro-5', transactionEvent => {
+ return transactionEvent.transaction === 'GET /ssr-error';
+ });
+
+ await page.goto('/ssr-error');
+
+ const errorEvent = await errorEventPromise;
+ const transactionEvent = await transactionEventPromise;
+
+ expect(transactionEvent).toMatchObject({
+ transaction: 'GET /ssr-error',
+ spans: [],
+ });
+
+ const traceId = transactionEvent.contexts?.trace?.trace_id;
+ const spanId = transactionEvent.contexts?.trace?.span_id;
+
+ expect(traceId).toMatch(/[a-f0-9]{32}/);
+ expect(spanId).toMatch(/[a-f0-9]{16}/);
+ expect(transactionEvent.contexts?.trace?.parent_span_id).toBeUndefined();
+
+ expect(errorEvent).toMatchObject({
+ contexts: {
+ app: expect.any(Object),
+ cloud_resource: expect.any(Object),
+ culture: expect.any(Object),
+ device: expect.any(Object),
+ os: expect.any(Object),
+ runtime: expect.any(Object),
+ trace: {
+ span_id: spanId,
+ trace_id: traceId,
+ },
+ },
+ environment: 'qa',
+ event_id: expect.stringMatching(/[a-f0-9]{32}/),
+ exception: {
+ values: [
+ {
+ mechanism: {
+ data: {
+ function: 'astroMiddleware',
+ },
+ handled: false,
+ type: 'astro',
+ },
+ stacktrace: expect.any(Object),
+ type: 'TypeError',
+ value: "Cannot read properties of undefined (reading 'x')",
+ },
+ ],
+ },
+ platform: 'node',
+ request: {
+ cookies: {},
+ headers: expect.objectContaining({
+ // demonstrates that requestData integration is getting data
+ host: 'localhost:3030',
+ 'user-agent': expect.any(String),
+ }),
+ method: 'GET',
+ url: expect.stringContaining('/ssr-error'),
+ },
+ sdk: {
+ integrations: expect.any(Array),
+ name: 'sentry.javascript.astro',
+ packages: expect.any(Array),
+ version: expect.any(String),
+ },
+ server_name: expect.any(String),
+ timestamp: expect.any(Number),
+ transaction: 'GET /ssr-error',
+ });
+ });
+
+ test('captures endpoint error', async ({ page }) => {
+ const errorEventPromise = waitForError('astro-5', errorEvent => {
+ return errorEvent?.exception?.values?.[0]?.value === 'Endpoint Error';
+ });
+ const transactionEventApiPromise = waitForTransaction('astro-5', transactionEvent => {
+ return transactionEvent.transaction === 'GET /endpoint-error/api';
+ });
+ const transactionEventEndpointPromise = waitForTransaction('astro-5', transactionEvent => {
+ return transactionEvent.transaction === 'GET /endpoint-error';
+ });
+
+ await page.goto('/endpoint-error');
+ await page.getByText('Get Data').click();
+
+ const errorEvent = await errorEventPromise;
+ const transactionEventApi = await transactionEventApiPromise;
+ const transactionEventEndpoint = await transactionEventEndpointPromise;
+
+ expect(transactionEventEndpoint).toMatchObject({
+ transaction: 'GET /endpoint-error',
+ spans: [],
+ });
+
+ const traceId = transactionEventEndpoint.contexts?.trace?.trace_id;
+ const endpointSpanId = transactionEventApi.contexts?.trace?.span_id;
+
+ expect(traceId).toMatch(/[a-f0-9]{32}/);
+ expect(endpointSpanId).toMatch(/[a-f0-9]{16}/);
+
+ expect(transactionEventApi).toMatchObject({
+ transaction: 'GET /endpoint-error/api',
+ spans: [],
+ });
+
+ const spanId = transactionEventApi.contexts?.trace?.span_id;
+ const parentSpanId = transactionEventApi.contexts?.trace?.parent_span_id;
+
+ expect(spanId).toMatch(/[a-f0-9]{16}/);
+ // TODO: This is incorrect, for whatever reason, it should be the endpointSpanId ideally
+ expect(parentSpanId).toMatch(/[a-f0-9]{16}/);
+ expect(parentSpanId).not.toEqual(endpointSpanId);
+
+ expect(errorEvent).toMatchObject({
+ contexts: {
+ trace: {
+ parent_span_id: parentSpanId,
+ span_id: spanId,
+ trace_id: traceId,
+ },
+ },
+ exception: {
+ values: [
+ {
+ mechanism: {
+ data: {
+ function: 'astroMiddleware',
+ },
+ handled: false,
+ type: 'astro',
+ },
+ stacktrace: expect.any(Object),
+ type: 'Error',
+ value: 'Endpoint Error',
+ },
+ ],
+ },
+ platform: 'node',
+ request: {
+ cookies: {},
+ headers: expect.objectContaining({
+ accept: expect.any(String),
+ }),
+ method: 'GET',
+ query_string: 'error=1',
+ url: expect.stringContaining('endpoint-error/api?error=1'),
+ },
+ transaction: 'GET /endpoint-error/api',
+ });
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
new file mode 100644
index 000000000000..8c0e2c0c8850
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
@@ -0,0 +1,123 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test.describe('tracing in dynamically rendered (ssr) routes', () => {
+ test('sends server and client pageload spans with the same trace id', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction === '/test-ssr';
+ });
+
+ const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction === 'GET /test-ssr';
+ });
+
+ await page.goto('/test-ssr');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+ const serverPageRequestTxn = await serverPageRequestTxnPromise;
+
+ const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id;
+ const clientPageloadParentSpanId = clientPageloadTxn.contexts?.trace?.parent_span_id;
+
+ const serverPageRequestTraceId = serverPageRequestTxn.contexts?.trace?.trace_id;
+ const serverPageloadSpanId = serverPageRequestTxn.contexts?.trace?.span_id;
+
+ expect(clientPageloadTraceId).toEqual(serverPageRequestTraceId);
+ expect(clientPageloadParentSpanId).toEqual(serverPageloadSpanId);
+
+ expect(clientPageloadTxn).toMatchObject({
+ contexts: {
+ trace: {
+ data: expect.objectContaining({
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.browser',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'url',
+ }),
+ op: 'pageload',
+ origin: 'auto.pageload.browser',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ },
+ },
+ environment: 'qa',
+ event_id: expect.stringMatching(/[a-f0-9]{32}/),
+ measurements: expect.any(Object),
+ platform: 'javascript',
+ request: expect.any(Object),
+ sdk: {
+ integrations: expect.any(Array),
+ name: 'sentry.javascript.astro',
+ packages: expect.any(Array),
+ version: expect.any(String),
+ },
+ spans: expect.any(Array),
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ transaction: '/test-ssr',
+ transaction_info: {
+ source: 'url',
+ },
+ type: 'transaction',
+ });
+
+ expect(serverPageRequestTxn).toMatchObject({
+ breadcrumbs: expect.any(Array),
+ contexts: {
+ app: expect.any(Object),
+ cloud_resource: expect.any(Object),
+ culture: expect.any(Object),
+ device: expect.any(Object),
+ os: expect.any(Object),
+ otel: expect.any(Object),
+ runtime: expect.any(Object),
+ trace: {
+ data: {
+ 'http.response.status_code': 200,
+ method: 'GET',
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ url: expect.stringContaining('/test-ssr'),
+ },
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ status: 'ok',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ },
+ },
+ environment: 'qa',
+ event_id: expect.stringMatching(/[a-f0-9]{32}/),
+ platform: 'node',
+ request: {
+ cookies: {},
+ headers: expect.objectContaining({
+ // demonstrates that request data integration can extract headers
+ accept: expect.any(String),
+ 'accept-encoding': expect.any(String),
+ 'user-agent': expect.any(String),
+ }),
+ method: 'GET',
+ url: expect.stringContaining('/test-ssr'),
+ },
+ sdk: {
+ integrations: expect.any(Array),
+ name: 'sentry.javascript.astro',
+ packages: expect.any(Array),
+ version: expect.any(String),
+ },
+ server_name: expect.any(String),
+ spans: expect.any(Array),
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ transaction: 'GET /test-ssr',
+ transaction_info: {
+ source: 'route',
+ },
+ type: 'transaction',
+ });
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts
new file mode 100644
index 000000000000..a6b288f4de71
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts
@@ -0,0 +1,99 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test.describe('tracing in static routes with server islands', () => {
+ test('only sends client pageload transaction and server island endpoint transaction', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction === '/server-island';
+ });
+
+ const serverIslandEndpointTxnPromise = waitForTransaction('astro-5', evt => {
+ return !!evt.transaction?.startsWith('GET /_server-islands');
+ });
+
+ await page.goto('/server-island');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+
+ const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id;
+ const clientPageloadParentSpanId = clientPageloadTxn.contexts?.trace?.parent_span_id;
+
+ const sentryTraceMetaTagContent = await page.locator('meta[name="sentry-trace"]').getAttribute('content');
+ const baggageMetaTagContent = await page.locator('meta[name="baggage"]').getAttribute('content');
+
+ const [metaTraceId, metaParentSpanId, metaSampled] = sentryTraceMetaTagContent?.split('-') || [];
+
+ expect(clientPageloadTraceId).toMatch(/[a-f0-9]{32}/);
+ expect(clientPageloadParentSpanId).toMatch(/[a-f0-9]{16}/);
+ expect(metaSampled).toBe('1');
+
+ expect(clientPageloadTxn).toMatchObject({
+ contexts: {
+ trace: {
+ data: expect.objectContaining({
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.browser',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'url',
+ }),
+ op: 'pageload',
+ origin: 'auto.pageload.browser',
+ parent_span_id: metaParentSpanId,
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: metaTraceId,
+ },
+ },
+ platform: 'javascript',
+ transaction: '/server-island',
+ transaction_info: {
+ source: 'url',
+ },
+ type: 'transaction',
+ });
+
+ const pageloadSpans = clientPageloadTxn.spans;
+
+ // pageload transaction contains a resource link span for the preloaded server island request
+ expect(pageloadSpans).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ op: 'resource.link',
+ origin: 'auto.resource.browser.metrics',
+ description: expect.stringMatching(/\/_server-islands\/Avatar.*$/),
+ }),
+ ]),
+ );
+
+ expect(baggageMetaTagContent).toContain('sentry-transaction=GET%20%2Fserver-island%2F'); // URL-encoded for 'GET /test-static/'
+ expect(baggageMetaTagContent).toContain('sentry-sampled=true');
+
+ const serverIslandEndpointTxn = await serverIslandEndpointTxnPromise;
+
+ expect(serverIslandEndpointTxn).toMatchObject({
+ contexts: {
+ trace: {
+ data: expect.objectContaining({
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ },
+ },
+ transaction: 'GET /_server-islands/[name]',
+ });
+
+ const serverIslandEndpointTraceId = serverIslandEndpointTxn.contexts?.trace?.trace_id;
+
+ // unfortunately, the server island trace id is not the same as the client pageload trace id
+ // this is because the server island endpoint request is made as a resource link request,
+ // meaning our fetch instrumentation can't attach headers to the request :(
+ expect(serverIslandEndpointTraceId).not.toBe(clientPageloadTraceId);
+
+ await page.waitForTimeout(1000); // wait another sec to ensure no server transaction is sent
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts
new file mode 100644
index 000000000000..9c202da53542
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts
@@ -0,0 +1,62 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test.describe('tracing in static/pre-rendered routes', () => {
+ test('only sends client pageload span with traceId from pre-rendered tags', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction === '/test-static';
+ });
+
+ waitForTransaction('astro-5', evt => {
+ if (evt.platform !== 'javascript') {
+ throw new Error('Server transaction should not be sent');
+ }
+ return false;
+ });
+
+ await page.goto('/test-static');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+
+ const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id;
+ const clientPageloadParentSpanId = clientPageloadTxn.contexts?.trace?.parent_span_id;
+
+ const sentryTraceMetaTagContent = await page.locator('meta[name="sentry-trace"]').getAttribute('content');
+ const baggageMetaTagContent = await page.locator('meta[name="baggage"]').getAttribute('content');
+
+ const [metaTraceId, metaParentSpanId, metaSampled] = sentryTraceMetaTagContent?.split('-') || [];
+
+ expect(clientPageloadTraceId).toMatch(/[a-f0-9]{32}/);
+ expect(clientPageloadParentSpanId).toMatch(/[a-f0-9]{16}/);
+ expect(metaSampled).toBe('1');
+
+ expect(clientPageloadTxn).toMatchObject({
+ contexts: {
+ trace: {
+ data: expect.objectContaining({
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.browser',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'url',
+ }),
+ op: 'pageload',
+ origin: 'auto.pageload.browser',
+ parent_span_id: metaParentSpanId,
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: metaTraceId,
+ },
+ },
+ platform: 'javascript',
+ transaction: '/test-static',
+ transaction_info: {
+ source: 'url',
+ },
+ type: 'transaction',
+ });
+
+ expect(baggageMetaTagContent).toContain('sentry-transaction=GET%20%2Ftest-static%2F'); // URL-encoded for 'GET /test-static/'
+ expect(baggageMetaTagContent).toContain('sentry-sampled=true');
+
+ await page.waitForTimeout(1000); // wait another sec to ensure no server transaction is sent
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tsconfig.json b/dev-packages/e2e-tests/test-applications/astro-5/tsconfig.json
new file mode 100644
index 000000000000..8bf91d3bb997
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist"]
+}
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 48d2424414ba..a678100bbd90 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -4,22 +4,14 @@
"description": "Official Sentry SDK for Astro",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro",
- "keywords": [
- "withastro",
- "astro-component",
- "astro-integration",
- "sentry",
- "apm"
- ],
+ "keywords": ["withastro", "astro-component", "astro-integration", "sentry", "apm"],
"author": "Sentry",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"type": "module",
- "files": [
- "/build"
- ],
+ "files": ["/build"],
"main": "build/cjs/index.client.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
@@ -53,7 +45,7 @@
"access": "public"
},
"peerDependencies": {
- "astro": ">=3.x || >=4.0.0-beta"
+ "astro": ">=3.x || >=4.0.0-beta || >=5.x"
},
"dependencies": {
"@sentry/browser": "8.42.0",