From 7ee8566e7011c2f27fd306bd9ce9d12181632acf Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:11:50 +0200 Subject: [PATCH 1/2] test(e2e): Add test for SvelteKit with orchestrion --- .../sveltekit-2-orchestrion/.gitignore | 11 +++ .../docker-compose.yml | 31 +++++++ .../sveltekit-2-orchestrion/global-setup.mjs | 14 +++ .../global-teardown.mjs | 12 +++ .../sveltekit-2-orchestrion/package.json | 43 +++++++++ .../playwright.config.mjs | 11 +++ .../sveltekit-2-orchestrion/src/app.html | 12 +++ .../src/hooks.client.ts | 12 +++ .../src/hooks.server.ts | 21 +++++ .../src/lib/assets/favicon.svg | 1 + .../src/routes/+layout.svelte | 19 ++++ .../src/routes/+page.svelte | 5 ++ .../src/routes/db-ioredis/+server.ts | 18 ++++ .../src/routes/db-mysql/+server.ts | 25 ++++++ .../start-event-proxy.mjs | 6 ++ .../sveltekit-2-orchestrion/svelte.config.js | 12 +++ .../sveltekit-2-orchestrion/tests/db.test.ts | 87 +++++++++++++++++++ .../sveltekit-2-orchestrion/tsconfig.json | 21 +++++ .../sveltekit-2-orchestrion/vite.config.ts | 19 ++++ 19 files changed, 380 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/docker-compose.yml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-setup.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-teardown.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/package.json create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/app.html create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.client.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/lib/assets/favicon.svg create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+layout.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-ioredis/+server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/svelte.config.js create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tests/db.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/vite.config.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/.gitignore b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/.gitignore new file mode 100644 index 000000000000..20ce1b370ee2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +test-results diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/docker-compose.yml b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/docker-compose.yml new file mode 100644 index 000000000000..c9836eff1d71 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/docker-compose.yml @@ -0,0 +1,31 @@ +services: + db: + image: mysql:8.0 + restart: always + container_name: e2e-tests-sveltekit-2-orchestrion-mysql + # The `mysql` 2.x driver doesn't speak MySQL 8's default + # `caching_sha2_password` auth, so force the legacy plugin. + command: ['--default-authentication-plugin=mysql_native_password'] + ports: + - '3306:3306' + environment: + MYSQL_ROOT_PASSWORD: docker + healthcheck: + test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -uroot -pdocker'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 10s + + redis: + image: redis:7 + restart: always + container_name: e2e-tests-sveltekit-2-orchestrion-redis + ports: + - '6379:6379' + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 5s diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-setup.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-setup.mjs new file mode 100644 index 000000000000..cb48d539c466 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-setup.mjs @@ -0,0 +1,14 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalSetup() { + // Start MySQL + Redis via Docker Compose. `--wait` blocks until the + // healthchecks in docker-compose.yml pass, so the app can connect immediately. + execSync('docker compose up -d --wait', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-teardown.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-teardown.mjs new file mode 100644 index 000000000000..2742279431ad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/global-teardown.mjs @@ -0,0 +1,12 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalTeardown() { + execSync('docker compose down --volumes', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/package.json new file mode 100644 index 000000000000..63bdd148f9ac --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/package.json @@ -0,0 +1,43 @@ +{ + "name": "sveltekit-2-orchestrion", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "start": "node build", + "proxy": "node start-event-proxy.mjs", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "test:prod": "TEST_ENV=production playwright test", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test:prod" + }, + "//": "Need to use ioredis 5.10.1 because that's the last version before they support tracing channels", + "dependencies": { + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", + "@sentry/sveltekit": "file:../../packed/sentry-sveltekit-packed.tgz", + "ioredis": "5.10.1", + "mysql": "^2.18.1" + }, + "devDependencies": { + "@apm-js-collab/code-transformer-bundler-plugins": "^0.5.0", + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-node": "^5.5.7", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "typescript": "^6.0.3", + "vite": "^8.0.16" + }, + "volta": { + "extends": "../../package.json", + "node": "22.22.0" + } +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/playwright.config.mjs new file mode 100644 index 000000000000..7db9f644ceac --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/playwright.config.mjs @@ -0,0 +1,11 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'node build', +}); + +export default { + ...config, + globalSetup: './global-setup.mjs', + globalTeardown: './global-teardown.mjs', +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/app.html b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/app.html new file mode 100644 index 000000000000..023060c2d49d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.client.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.client.ts new file mode 100644 index 000000000000..5b1665875c8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.client.ts @@ -0,0 +1,12 @@ +import { env } from '$env/dynamic/public'; +import * as Sentry from '@sentry/sveltekit'; + +Sentry.init({ + environment: 'qa', + dsn: env.PUBLIC_E2E_TEST_DSN, + debug: !!env.PUBLIC_DEBUG, + tunnel: `http://localhost:3031/`, + tracesSampleRate: 1.0, +}); + +export const handleError = Sentry.handleErrorWithSentry(); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.server.ts new file mode 100644 index 000000000000..1da7f92c8850 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/hooks.server.ts @@ -0,0 +1,21 @@ +import { E2E_TEST_DSN } from '$env/static/private'; +import * as Sentry from '@sentry/sveltekit'; + +// Opt into diagnostics-channel-based auto-instrumentation. This registers the +// channel subscribers (e.g. for mysql and ioredis) that turn the +// diagnostics-channel events — injected at build time by the orchestrion Vite +// plugin (see vite.config.ts) — into Sentry spans. Must run before Sentry.init(). +Sentry.experimentalUseDiagnosticsChannelInjection(); + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: E2E_TEST_DSN, + debug: !!process.env.DEBUG, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, +}); + +// not logging anything to console to avoid noise in the test output +export const handleError = Sentry.handleErrorWithSentry(() => {}); + +export const handle = Sentry.sentryHandle(); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/lib/assets/favicon.svg b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/lib/assets/favicon.svg new file mode 100644 index 000000000000..cc5dc66a3a52 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+layout.svelte new file mode 100644 index 000000000000..1fd1f01400bc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+layout.svelte @@ -0,0 +1,19 @@ + + + + + + +

SvelteKit Orchestrion E2E Test app

+
+ {@render children()} +
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+page.svelte new file mode 100644 index 000000000000..bdd8b4212ed8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/+page.svelte @@ -0,0 +1,5 @@ +

Welcome

+ diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-ioredis/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-ioredis/+server.ts new file mode 100644 index 000000000000..c7348b20f084 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-ioredis/+server.ts @@ -0,0 +1,18 @@ +import { json } from '@sveltejs/kit'; +import Redis from 'ioredis'; + +export const GET = async () => { + const redis = new Redis({ + // Don't keep retrying forever if Redis goes away (e.g. on test teardown) + maxRetriesPerRequest: 1, + retryStrategy: () => null, + }); + + try { + await redis.set('test-key', 'test-value'); + const value = await redis.get('test-key'); + return json({ value }); + } finally { + redis.disconnect(); + } +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts new file mode 100644 index 000000000000..866a821803d0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts @@ -0,0 +1,25 @@ +import { json } from '@sveltejs/kit'; +import mysql from 'mysql'; + +const connection = mysql.createConnection({ + user: 'root', + password: 'docker', +}); + +export const GET = async () => { + const connection = mysql.createConnection({ user: 'root', password: 'docker' }); + try { + await new Promise((resolve, reject) => { + connection.query('SELECT 1 + 1 AS solution', err1 => { + if (err1) return reject(err1); + connection.query('SELECT NOW()', ['1', '2'], err2 => { + if (err2) return reject(err2); + resolve(); + }); + }); + }); + return json({ status: 'ok' }); + } finally { + connection.end(() => {}); + } +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/start-event-proxy.mjs new file mode 100644 index 000000000000..95156d8d34c5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'sveltekit-2-orchestrion', +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/svelte.config.js new file mode 100644 index 000000000000..9e49a2d64461 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + }, +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tests/db.test.ts new file mode 100644 index 000000000000..529a43f45344 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tests/db.test.ts @@ -0,0 +1,87 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Instruments ioredis automatically', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('sveltekit-2-orchestrion', transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /db-ioredis'; + }); + + await fetch(`${baseURL}/db-ioredis`); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.contexts?.trace?.op).toEqual('http.server'); + expect(transactionEvent.transaction).toEqual('GET /db-ioredis'); + + const spans = transactionEvent.spans || []; + + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.redis', + description: 'set test-key [1 other arguments]', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'redis', + 'db.statement': 'set test-key [1 other arguments]', + }), + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.redis', + description: 'get test-key', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'redis', + 'db.statement': 'get test-key', + }), + }), + ); +}); + +test('Instruments mysql automatically', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('sveltekit-2-orchestrion', transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /db-mysql'; + }); + + await fetch(`${baseURL}/db-mysql`); + + const transactionEvent = await transactionEventPromise; + + const spans = transactionEvent.spans || []; + + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.mysql', + description: 'SELECT 1 + 1 AS solution', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'mysql', + 'db.statement': 'SELECT 1 + 1 AS solution', + 'db.user': 'root', + 'db.connection_string': expect.any(String), + 'net.peer.name': expect.any(String), + 'net.peer.port': 3306, + }), + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + op: 'db', + origin: 'auto.db.orchestrion.mysql', + description: 'SELECT NOW()', + status: 'ok', + data: expect.objectContaining({ + 'db.system': 'mysql', + 'db.statement': 'SELECT NOW()', + 'db.user': 'root', + 'db.connection_string': expect.any(String), + 'net.peer.name': expect.any(String), + 'net.peer.port': 3306, + }), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tsconfig.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tsconfig.json new file mode 100644 index 000000000000..5e69e0350b19 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/vite.config.ts new file mode 100644 index 000000000000..9a08b8d48133 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/vite.config.ts @@ -0,0 +1,19 @@ +import { sentrySvelteKit } from '@sentry/sveltekit'; +import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + sentrySvelteKit({ + autoUploadSourceMaps: false, + }), + + // Runs the orchestrion code transform on the SvelteKit SSR bundle so + // instrumented DB drivers (mysql, ioredis) get `diagnostics_channel` + // publishers injected at build time. + sentryOrchestrionPlugin(), + + sveltekit(), + ], +}); From 24a5e19cbec22de6068479b0660584cc754d5c5f Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:29:29 +0200 Subject: [PATCH 2/2] fix mysql --- .../sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts index 866a821803d0..f6df545e43c4 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-orchestrion/src/routes/db-mysql/+server.ts @@ -1,11 +1,6 @@ import { json } from '@sveltejs/kit'; import mysql from 'mysql'; -const connection = mysql.createConnection({ - user: 'root', - password: 'docker', -}); - export const GET = async () => { const connection = mysql.createConnection({ user: 'root', password: 'docker' }); try {