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
Expand Up @@ -4,7 +4,6 @@ import { solidRouterBrowserTracingIntegration } from '@sentry/solidstart/solidro
import { StartClient, mount } from '@solidjs/start/client';

Sentry.init({
traceLifecycle: 'static',
// We can't use env variables here, seems like they are stripped
// out in production builds.
dsn: 'https://public@dsn.ingest.sentry.io/1337',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/solidstart';

Sentry.init({
traceLifecycle: 'static',
dsn: process.env.E2E_TEST_DSN,
environment: 'qa', // dynamic sampling bias to keep transactions
tracesSampleRate: 1.0, // Capture 100% of the transactions
Expand Down
143 changes: 80 additions & 63 deletions dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,105 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';

test('Instruments ioredis automatically via build-time orchestrion', async ({ baseURL }) => {
const transactionEventPromise = waitForTransaction('solidstart', transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'http.server' && !!transactionEvent.transaction?.includes('db-ioredis')
);
});
const spansPromise = collectStreamedSpans(
'solidstart',
spans =>
spans.some(
span =>
span.is_segment &&
getSpanOp(span) === 'http.server' &&
String(span.attributes['url.path']?.value ?? '').includes('db-ioredis'),
) &&
spans.some(span => span.attributes['db.query.text']?.value === 'set test-key [1 other arguments]') &&
spans.some(span => span.attributes['db.query.text']?.value === 'get test-key'),
);

await fetch(`${baseURL}/api/db-ioredis`);

const transactionEvent = await transactionEventPromise;
const spans = transactionEvent.spans || [];
const spans = await spansPromise;
// ioredis also emits handshake commands (SETINFO, INFO) as db.query spans.
const redisSpans = spans.filter(
span =>
getSpanOp(span) === 'db.query' &&
(span.attributes['db.operation.name']?.value === 'set' || span.attributes['db.operation.name']?.value === 'get'),
);

expect(spans).toContainEqual(
expect(redisSpans).toHaveLength(2);
expect(redisSpans).toContainEqual(
expect.objectContaining({
op: 'db.query',
origin: 'auto.db.redis',
description: 'set test-key [1 other arguments]',
name: 'set test-key [1 other arguments]',
status: 'ok',
data: expect.objectContaining({
'db.system.name': 'redis',
'db.operation.name': 'set',
'db.query.text': 'set test-key [1 other arguments]',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'db.query' },
'sentry.origin': { type: 'string', value: 'auto.db.redis' },
'db.system.name': { type: 'string', value: 'redis' },
'db.operation.name': { type: 'string', value: 'set' },
'db.query.text': { type: 'string', value: 'set test-key [1 other arguments]' },
}),
}),
);
expect(spans).toContainEqual(
expect(redisSpans).toContainEqual(
expect.objectContaining({
op: 'db.query',
origin: 'auto.db.redis',
description: 'get test-key',
name: 'get test-key',
status: 'ok',
data: expect.objectContaining({
'db.system.name': 'redis',
'db.operation.name': 'get',
'db.query.text': 'get test-key',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'db.query' },
'sentry.origin': { type: 'string', value: 'auto.db.redis' },
'db.system.name': { type: 'string', value: 'redis' },
'db.operation.name': { type: 'string', value: 'get' },
'db.query.text': { type: 'string', value: 'get test-key' },
}),
}),
);
});

test('Instruments mysql automatically via build-time orchestrion', async ({ baseURL }) => {
const transactionEventPromise = waitForTransaction('solidstart', transactionEvent => {
return (
transactionEvent.contexts?.trace?.op === 'http.server' && !!transactionEvent.transaction?.includes('db-mysql')
);
});
const spansPromise = collectStreamedSpans(
'solidstart',
spans =>
spans.some(
span =>
span.is_segment &&
getSpanOp(span) === 'http.server' &&
String(span.attributes['url.path']?.value ?? '').includes('db-mysql'),
) &&
spans.some(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution') &&
spans.some(span => span.attributes['db.query.text']?.value === 'SELECT NOW()'),
);

await fetch(`${baseURL}/api/db-mysql`);

const transactionEvent = await transactionEventPromise;
const spans = transactionEvent.spans || [];
const spans = await spansPromise;
const mysqlSpans = spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.db.mysql');

expect(spans).toContainEqual(
expect.objectContaining({
op: 'db',
origin: 'auto.db.mysql',
description: 'SELECT 1 + 1 AS solution',
status: 'ok',
data: expect.objectContaining({
'db.system.name': 'mysql',
'db.query.text': 'SELECT 1 + 1 AS solution',
'db.user': 'root',
'db.connection_string': expect.any(String),
'server.address': expect.any(String),
'server.port': 3306,
}),
}),
);
expect(spans).toContainEqual(
expect.objectContaining({
op: 'db',
origin: 'auto.db.mysql',
description: 'SELECT NOW()',
status: 'ok',
data: expect.objectContaining({
'db.system.name': 'mysql',
'db.query.text': 'SELECT NOW()',
'db.user': 'root',
'db.connection_string': expect.any(String),
'server.address': expect.any(String),
'server.port': 3306,
}),
}),
);
const firstQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution');
expect(firstQuery).toBeDefined();
expect(firstQuery!.name).toBe('SELECT');
expect(firstQuery!.status).toBe('ok');
expect(firstQuery!.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'db' },
'sentry.origin': { type: 'string', value: 'auto.db.mysql' },
'db.system.name': { type: 'string', value: 'mysql' },
'db.query.text': { type: 'string', value: 'SELECT 1 + 1 AS solution' },
'db.user': { type: 'string', value: 'root' },
'db.connection_string': { type: 'string', value: expect.any(String) },
'server.address': { type: 'string', value: expect.any(String) },
'server.port': { type: 'integer', value: 3306 },
});

const secondQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT NOW()');
expect(secondQuery).toBeDefined();
expect(secondQuery!.name).toBe('SELECT');
expect(secondQuery!.status).toBe('ok');
expect(secondQuery!.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'db' },
'sentry.origin': { type: 'string', value: 'auto.db.mysql' },
'db.system.name': { type: 'string', value: 'mysql' },
'db.query.text': { type: 'string', value: 'SELECT NOW()' },
'db.user': { type: 'string', value: 'root' },
'db.connection_string': { type: 'string', value: expect.any(String) },
'server.address': { type: 'string', value: expect.any(String) },
'server.port': { type: 'integer', value: 3306 },
});
});
Original file line number Diff line number Diff line change
@@ -1,119 +1,81 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test('sends a pageload transaction', async ({ page }) => {
const transactionPromise = waitForTransaction('solidstart', async transactionEvent => {
return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'pageload';
test('sends a pageload span', async ({ page }) => {
const spanPromise = waitForStreamedSpan('solidstart', span => {
return span.name === '/' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto('/');
const pageloadTransaction = await transactionPromise;
const pageloadSpan = await spanPromise;

expect(pageloadTransaction).toMatchObject({
contexts: {
trace: {
op: 'pageload',
origin: 'auto.pageload.browser',
data: {
'sentry.segment.name.source': 'route',
'url.template': '/',
'url.path': '/',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/),
},
},
},
transaction: '/',
transaction_info: {
source: 'route',
},
expect(getSpanOp(pageloadSpan)).toBe('pageload');
expect(pageloadSpan.attributes).toMatchObject({
'sentry.origin': { value: 'auto.pageload.browser', type: 'string' },
'sentry.op': { value: 'pageload', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'url.template': { value: '/', type: 'string' },
'url.path': { value: '/', type: 'string' },
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), type: 'string' },
});
});

test('sends a navigation transaction with parametrized route', async ({ page }) => {
const transactionPromise = waitForTransaction('solidstart', async transactionEvent => {
return transactionEvent?.transaction === '/users/:id' && transactionEvent.contexts?.trace?.op === 'navigation';
test('sends a navigation span with parametrized route', async ({ page }) => {
const spanPromise = waitForStreamedSpan('solidstart', span => {
return span.name === '/users/:id' && getSpanOp(span) === 'navigation' && span.is_segment;
});

await page.goto(`/`);
await page.locator('#navLink').click();
const navigationTransaction = await transactionPromise;
const navigationSpan = await spanPromise;

expect(navigationTransaction).toMatchObject({
contexts: {
trace: {
op: 'navigation',
origin: 'auto.navigation.solidstart.solidrouter',
data: {
'sentry.segment.name.source': 'route',
'url.template': '/users/:id',
'url.path': '/users/5',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/5$/),
},
},
},
transaction: '/users/:id',
transaction_info: {
source: 'route',
},
expect(getSpanOp(navigationSpan)).toBe('navigation');
expect(navigationSpan.attributes).toMatchObject({
'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' },
'sentry.op': { value: 'navigation', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'url.template': { value: '/users/:id', type: 'string' },
'url.path': { value: '/users/5', type: 'string' },
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/5$/), type: 'string' },
});
});

test('updates the transaction when using the back button', async ({ page }) => {
test('updates the span when using the back button', async ({ page }) => {
// Solid Router sends a `-1` navigation when using the back button.
// The sentry solidRouterBrowserTracingIntegration tries to update such
// transactions with the proper name once the `useLocation` hook triggers.
const navigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => {
return transactionEvent?.transaction === '/users/:id' && transactionEvent.contexts?.trace?.op === 'navigation';
// spans with the proper name once the `useLocation` hook triggers.
const navigationSpanPromise = waitForStreamedSpan('solidstart', span => {
return span.name === '/users/:id' && getSpanOp(span) === 'navigation' && span.is_segment;
});

await page.goto(`/back-navigation`);
await page.locator('#navLink').click();
const navigationTxn = await navigationTxnPromise;
const navigationSpan = await navigationSpanPromise;

expect(navigationTxn).toMatchObject({
contexts: {
trace: {
op: 'navigation',
origin: 'auto.navigation.solidstart.solidrouter',
data: {
'sentry.segment.name.source': 'route',
'url.template': '/users/:id',
'url.path': '/users/6',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/6$/),
},
},
},
transaction: '/users/:id',
transaction_info: {
source: 'route',
},
expect(getSpanOp(navigationSpan)).toBe('navigation');
expect(navigationSpan.attributes).toMatchObject({
'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' },
'sentry.op': { value: 'navigation', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'url.template': { value: '/users/:id', type: 'string' },
'url.path': { value: '/users/6', type: 'string' },
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/6$/), type: 'string' },
});

const backNavigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => {
return (
transactionEvent?.transaction === '/back-navigation' && transactionEvent.contexts?.trace?.op === 'navigation'
);
const backNavigationSpanPromise = waitForStreamedSpan('solidstart', span => {
return span.name === '/back-navigation' && getSpanOp(span) === 'navigation' && span.is_segment;
});

await page.goBack();
const backNavigationTxn = await backNavigationTxnPromise;
const backNavigationSpan = await backNavigationSpanPromise;

expect(backNavigationTxn).toMatchObject({
contexts: {
trace: {
op: 'navigation',
origin: 'auto.navigation.solidstart.solidrouter',
data: {
'sentry.segment.name.source': 'route',
'url.template': '/back-navigation',
'url.path': '/back-navigation',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/back-navigation$/),
},
},
},
transaction: '/back-navigation',
transaction_info: {
source: 'route',
},
expect(getSpanOp(backNavigationSpan)).toBe('navigation');
expect(backNavigationSpan.attributes).toMatchObject({
'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' },
'sentry.op': { value: 'navigation', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'url.template': { value: '/back-navigation', type: 'string' },
'url.path': { value: '/back-navigation', type: 'string' },
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/back-navigation$/), type: 'string' },
});
});
Loading
Loading