Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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',
});
}
Original file line number Diff line number Diff line change
@@ -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',
});
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="off">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
@@ -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();
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import favicon from '$lib/assets/favicon.svg';
import { onMount } from 'svelte';
let { children } = $props();
onMount(() => {
document.body.classList.add('hydrated');
});
</script>

<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>

<h1>SvelteKit Orchestrion E2E Test app</h1>
<div data-sveltekit-preload-data="off">
{@render children()}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<h2>Welcome</h2>
<ul>
<li><a href="/db-mysql">db-mysql</a></li>
<li><a href="/db-ioredis">db-ioredis</a></li>
</ul>
Original file line number Diff line number Diff line change
@@ -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();
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { json } from '@sveltejs/kit';
import mysql from 'mysql';

export const GET = async () => {
const connection = mysql.createConnection({ user: 'root', password: 'docker' });
try {
await new Promise<void>((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(() => {});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'sveltekit-2-orchestrion',
});
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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,
}),
}),
);
});
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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(),
],
});
Loading