Skip to content
Open
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
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/astro';

Sentry.init({
traceLifecycle: 'static',
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/astro';

Sentry.init({
traceLifecycle: 'static',
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: 'qa',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

const APP_NAME = 'astro-7';

// Astro 7 "advanced routing": the app owns the request pipeline via `src/fetch.ts`.
// These tests verify Sentry behaves correctly both when the app delegates to
Expand All @@ -17,28 +19,24 @@ test.describe('astro 7 advanced routing (src/fetch.ts)', () => {
test('Sentry middleware still parametrizes routes when run through the full pipeline', async ({ page }) => {
// Hit a dynamic route so we exercise the actual URL -> route parametrization
// (`/user-page/myUsername123` -> `/user-page/[userId]`), not just a static route name.
const serverPageRequestTxnPromise = waitForTransaction('astro-7', txnEvent => {
return txnEvent?.transaction === 'GET /user-page/[userId]';
const serverPageRequestSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return getSpanOp(span) === 'http.server' && span.is_segment && span.name === 'GET /user-page/[userId]';
});

const response = await page.goto('/user-page/myUsername123');

// Proves the request flowed through our custom `astro(state)` pipeline wrapper.
expect(response?.headers()['x-astro-advanced-routing']).toBe('pipeline');

const serverPageRequestTxn = await serverPageRequestTxnPromise;

// The parametrized transaction name proves Sentry's auto-injected middleware
// ran inside the user-owned pipeline AND resolved the dynamic segment to
// `[userId]` via Astro's route manifest (rather than leaving the raw URL).
expect(serverPageRequestTxn.transaction).toBe('GET /user-page/[userId]');
expect(serverPageRequestTxn.contexts?.trace).toMatchObject({
op: 'http.server',
origin: 'auto.http.astro',
data: expect.objectContaining({
'sentry.segment.name.source': 'route',
'url.full': expect.stringContaining('/user-page/myUsername123'),
}),
const serverPageRequestSpan = await serverPageRequestSpanPromise;

// The parametrized span name proves Sentry's auto-injected middleware ran inside the user-owned
// pipeline AND resolved the dynamic segment to `[userId]` via Astro's route manifest (rather
// than leaving the raw URL).
expect(serverPageRequestSpan.attributes).toMatchObject({
'sentry.origin': { value: 'auto.http.astro', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'url.full': { value: expect.stringContaining('/user-page/myUsername123'), type: 'string' },
});
});
});
100 changes: 39 additions & 61 deletions dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,67 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils';

const APP_NAME = 'astro-7';

test('Instruments ioredis automatically', async ({ baseURL }) => {
const transactionEventPromise = waitForTransaction('astro-7', transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /db-ioredis';
});
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, '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 || [];
const spans = await spansPromise;
const redisSpans = spans.filter(span => getSpanOp(span) === 'db.query');

expect(spans).toContainEqual(
// With span streaming `db.query.text` carries the key, so the command span is named
// `{db.operation.name} {server.address}:{server.port}` instead.
expect(redisSpans).toContainEqual(
expect.objectContaining({
op: 'db.query',
origin: 'auto.db.redis',
description: '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.origin': { value: 'auto.db.redis', type: 'string' },
'db.system.name': { value: 'redis', type: 'string' },
'db.operation.name': { value: 'set', type: 'string' },
'db.query.text': { value: 'set test-key [1 other arguments]', type: 'string' },
}),
}),
);
expect(spans).toContainEqual(
expect(redisSpans).toContainEqual(
expect.objectContaining({
op: 'db.query',
origin: 'auto.db.redis',
description: '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.origin': { value: 'auto.db.redis', type: 'string' },
'db.system.name': { value: 'redis', type: 'string' },
'db.operation.name': { value: 'get', type: 'string' },
'db.query.text': { value: 'get test-key', type: 'string' },
}),
}),
);
});

test('Instruments mysql automatically', async ({ baseURL }) => {
const transactionEventPromise = waitForTransaction('astro-7', transactionEvent => {
return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /db-mysql';
});
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /db-mysql');

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

const transactionEvent = await transactionEventPromise;
const spans = await spansPromise;
const mysqlSpans = spans.filter(span => getSpanOp(span) === 'db');

const spans = transactionEvent.spans || [];

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(
// With span streaming the span name is the low-cardinality query summary; the statement stays in
// `db.query.text`.
const expectedQuerySpan = (query: string) =>
expect.objectContaining({
op: 'db',
origin: 'auto.db.mysql',
description: 'SELECT NOW()',
name: 'SELECT',
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,
attributes: expect.objectContaining({
'sentry.origin': { value: 'auto.db.mysql', type: 'string' },
'db.system.name': { value: 'mysql', type: 'string' },
'db.query.text': { value: query, type: 'string' },
'db.user': { value: 'root', type: 'string' },
'db.connection_string': { value: expect.any(String), type: 'string' },
'server.address': { value: expect.any(String), type: 'string' },
'server.port': { value: 3306, type: 'integer' },
}),
}),
);
});

expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT 1 + 1 AS solution'));
expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT NOW()'));
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';

test.describe('server-side errors', () => {
test('captures SSR error', async ({ page }) => {
const errorEventPromise = waitForError('astro-7', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === "Cannot read properties of undefined (reading 'x')";
});

const transactionEventPromise = waitForTransaction('astro-7', transactionEvent => {
return transactionEvent.transaction === 'GET /ssr-error';
const spanPromise = waitForStreamedSpan('astro-7', span => {
return getSpanOp(span) === 'http.server' && span.is_segment && span.name === 'GET /ssr-error';
});

// This page returns an error status code, so we need to catch the navigation error
Expand All @@ -17,19 +17,14 @@ test.describe('server-side errors', () => {
});

const errorEvent = await errorEventPromise;
const transactionEvent = await transactionEventPromise;
const span = await spanPromise;

expect(transactionEvent).toMatchObject({
transaction: 'GET /ssr-error',
spans: [],
});

const traceId = transactionEvent.contexts?.trace?.trace_id;
const spanId = transactionEvent.contexts?.trace?.span_id;
const traceId = span.trace_id;
const spanId = span.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(span.parent_span_id).toBeUndefined();

expect(errorEvent).toMatchObject({
contexts: {
Expand Down Expand Up @@ -86,38 +81,28 @@ test.describe('server-side errors', () => {
const errorEventPromise = waitForError('astro-7', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Endpoint Error';
});
const transactionEventApiPromise = waitForTransaction('astro-7', transactionEvent => {
return transactionEvent.transaction === 'GET /endpoint-error/api';
const apiSpanPromise = waitForStreamedSpan('astro-7', span => {
return getSpanOp(span) === 'http.server' && span.name === 'GET /endpoint-error/api';
});
const transactionEventEndpointPromise = waitForTransaction('astro-7', transactionEvent => {
return transactionEvent.transaction === 'GET /endpoint-error';
const endpointSpanPromise = waitForStreamedSpan('astro-7', span => {
return getSpanOp(span) === 'http.server' && span.is_segment && span.name === '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 apiSpan = await apiSpanPromise;
const endpointSpan = await endpointSpanPromise;

const traceId = transactionEventEndpoint.contexts?.trace?.trace_id;
const endpointSpanId = transactionEventApi.contexts?.trace?.span_id;
const traceId = endpointSpan.trace_id;
const endpointSpanId = apiSpan.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;
const spanId = apiSpan.span_id;
const parentSpanId = apiSpan.parent_span_id;

expect(spanId).toMatch(/[a-f0-9]{16}/);
expect(parentSpanId).toMatch(/[a-f0-9]{16}/);
Expand Down
Loading
Loading