Skip to content
Draft
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 { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router/dom';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
// todo: get this from env
dsn: 'https://username@domain/123',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,46 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
import { APP_NAME } from '../constants';

test.describe('client - navigation performance', () => {
test('should update navigation transaction for dynamic routes', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return (
transactionEvent.transaction === '/performance/with/:param' &&
transactionEvent.contexts?.trace?.op === 'navigation'
);
test('should update navigation span for dynamic routes', async ({ page }) => {
const navigationSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance/with/:param' && getSpanOp(span) === 'navigation' && span.is_segment;
});

const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
const pageloadSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/performance`); // pageload
await pageloadTxPromise;
await pageloadSpanPromise;
await page.getByRole('link', { name: 'With Param Page' }).click(); // navigation

const transaction = await txPromise;
const span = await navigationSpanPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.navigation.react_router',
'sentry.op': 'navigation',
'sentry.segment.name.source': 'route',
'url.template': '/performance/with/:param',
'url.path': '/performance/with/sentry',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
},
op: 'navigation',
origin: 'auto.navigation.react_router',
},
},
spans: expect.any(Array),
expect(span).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/performance/with/:param',
type: 'transaction',
transaction_info: { source: 'route' },
platform: 'javascript',
request: {
url: expect.stringContaining('/performance/with/sentry'),
headers: expect.any(Object),
},
event_id: expect.any(String),
environment: 'qa',
sdk: {
integrations: expect.arrayContaining([expect.any(String)]),
name: 'sentry.javascript.react-router',
version: expect.any(String),
packages: [
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
{ name: 'npm:@sentry/browser', version: expect.any(String) },
],
end_timestamp: expect.any(Number),
is_segment: true,
status: 'ok',
});

expect(span.attributes).toMatchObject({
'sentry.origin': { value: 'auto.navigation.react_router', type: 'string' },
'sentry.op': { value: 'navigation', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
'sentry.sdk.integrations': { value: expect.arrayContaining([expect.any(String)]), type: 'array' },
'url.template': { value: '/performance/with/:param', type: 'string' },
'url.path': { value: '/performance/with/sentry', type: 'string' },
'url.full': {
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
type: 'string',
},
tags: { runtime: 'browser' },
});
});
});
Original file line number Diff line number Diff line change
@@ -1,116 +1,63 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
import { APP_NAME } from '../constants';

test.describe('client - pageload performance', () => {
test('should send pageload transaction', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
test('should send pageload span', async ({ page }) => {
const spanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/performance`);
await page.getByRole('heading', { name: 'Performance Page' }).waitFor();

const transaction = await txPromise;
const span = await spanPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.pageload.react_router',
'sentry.op': 'pageload',
'sentry.segment.name.source': 'route',
'url.template': '/performance',
'url.path': '/performance',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance$/),
},
op: 'pageload',
origin: 'auto.pageload.react_router',
},
},
spans: expect.any(Array),
expect(span).toMatchObject({
span_id: expect.any(String),
trace_id: expect.any(String),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/performance',
type: 'transaction',
transaction_info: { source: 'route' },
measurements: expect.any(Object),
platform: 'javascript',
request: {
url: expect.stringContaining('/performance'),
headers: expect.any(Object),
},
event_id: expect.any(String),
environment: 'qa',
sdk: {
integrations: expect.arrayContaining([expect.any(String)]),
name: 'sentry.javascript.react-router',
version: expect.any(String),
packages: [
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
{ name: 'npm:@sentry/browser', version: expect.any(String) },
],
},
tags: { runtime: 'browser' },
end_timestamp: expect.any(Number),
is_segment: true,
status: 'ok',
});

expect(span.attributes).toMatchObject({
'sentry.origin': { value: 'auto.pageload.react_router', type: 'string' },
'sentry.op': { value: 'pageload', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
'sentry.sdk.integrations': { value: expect.arrayContaining([expect.any(String)]), type: 'array' },
'url.template': { value: '/performance', type: 'string' },
'url.path': { value: '/performance', type: 'string' },
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance$/), type: 'string' },
});
});

test('should update pageload transaction for dynamic routes', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return (
transactionEvent.transaction === '/performance/with/:param' &&
transactionEvent.contexts?.trace?.op === 'pageload'
);
test('should update pageload span for dynamic routes', async ({ page }) => {
const spanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance/with/:param' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/performance/with/sentry`);
await page.getByRole('heading', { name: 'Dynamic Parameter Page' }).waitFor();

const transaction = await txPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
data: {
'sentry.origin': 'auto.pageload.react_router',
'sentry.op': 'pageload',
'sentry.segment.name.source': 'route',
'url.template': '/performance/with/:param',
'url.path': '/performance/with/sentry',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
},
op: 'pageload',
origin: 'auto.pageload.react_router',
},
},
spans: expect.any(Array),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/performance/with/:param',
type: 'transaction',
transaction_info: { source: 'route' },
measurements: expect.any(Object),
platform: 'javascript',
request: {
url: expect.stringContaining('/performance/with/sentry'),
headers: expect.any(Object),
},
event_id: expect.any(String),
environment: 'qa',
sdk: {
integrations: expect.arrayContaining([expect.any(String)]),
name: 'sentry.javascript.react-router',
version: expect.any(String),
packages: [
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
{ name: 'npm:@sentry/browser', version: expect.any(String) },
],
const span = await spanPromise;

expect(span.attributes).toMatchObject({
'sentry.origin': { value: 'auto.pageload.react_router', type: 'string' },
'sentry.op': { value: 'pageload', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
'sentry.environment': { value: 'qa', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
'url.template': { value: '/performance/with/:param', type: 'string' },
'url.path': { value: '/performance/with/sentry', type: 'string' },
'url.full': {
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
type: 'string',
},
tags: { runtime: 'browser' },
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { hydrateRoot } from 'react-dom/client';
import { HydratedRouter } from 'react-router/dom';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
// todo: get this from env
dsn: 'https://username@domain/123',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/react-router';

Sentry.init({
traceLifecycle: 'static',
dsn: 'https://username@domain/123',
environment: 'qa', // dynamic sampling bias to keep transactions
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import type { SerializedStreamedSpan } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan, waitForStreamedSpans } from '@sentry-internal/test-utils';
import { APP_NAME } from '../constants';

test.describe('low-quality transaction filter', () => {
test('does not send a server transaction for /__manifest? requests', async ({ page }) => {
const serverTxns: Array<{ contexts?: { trace?: { data?: Record<string, unknown> } } }> = [];
test.describe('low-quality span filter', () => {
test('does not send a server span for /__manifest? requests', async ({ page }) => {
const streamedSpans: SerializedStreamedSpan[] = [];

const navigationPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return (
transactionEvent.transaction === '/performance/ssr' && transactionEvent.contexts?.trace?.op === 'navigation'
);
const navigationPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance/ssr' && getSpanOp(span) === 'navigation' && span.is_segment;
});

waitForTransaction(APP_NAME, async evt => {
serverTxns.push(evt);
waitForStreamedSpans(APP_NAME, spans => {
streamedSpans.push(...spans);
return false;
});

Expand All @@ -23,12 +22,13 @@ test.describe('low-quality transaction filter', () => {

await navigationPromise;

// Force the server to flush any in-flight transactions before we assert
// Force the server to flush any in-flight spans before we assert
await page.evaluate(() => fetch('/__sentry-flush'));

const targetIsManifest = (t: (typeof serverTxns)[number]) =>
typeof t.contexts?.trace?.data?.['url.path'] === 'string' &&
(t.contexts.trace.data['url.path'] as string).includes('/__manifest');
expect(serverTxns.some(targetIsManifest)).toBe(false);
const targetIsManifest = (span: SerializedStreamedSpan) => {
const urlPath = span.attributes['url.path']?.value;
return typeof urlPath === 'string' && urlPath.includes('/__manifest');
};
expect(streamedSpans.some(targetIsManifest)).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { collectStreamedTrace, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
import { APP_NAME } from '../constants';

test.describe('server - middleware', () => {
test('should send middleware transaction on pageload', async ({ page }) => {
const serverTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === 'GET /performance/with-middleware';
});

const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance/with-middleware';
});

const customMiddlewareTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === 'authMiddleware';
test('should send middleware span on pageload', async ({ page }) => {
const serverSpansPromise = collectStreamedTrace(
APP_NAME,
span => span.name === 'GET /performance/with-middleware',
spansOfTrace => spansOfTrace.some(span => span.name === 'authMiddleware'),
);

const pageloadSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.name === '/performance/with-middleware' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/performance/with-middleware`);

const serverTx = await serverTxPromise;
const pageloadTx = await pageloadTxPromise;
const customMiddlewareTx = await customMiddlewareTxPromise;
const serverSpans = await serverSpansPromise;
const pageloadSpan = await pageloadSpanPromise;

const traceIds = {
server: serverTx?.contexts?.trace?.trace_id,
pageload: pageloadTx?.contexts?.trace?.trace_id,
customMiddleware: customMiddlewareTx?.contexts?.trace?.trace_id,
};
const serverSpan = serverSpans.find(span => span.is_segment)!;
const customMiddlewareSpan = serverSpans.find(span => span.name === 'authMiddleware')!;

expect(pageloadTx).toBeDefined();
expect(customMiddlewareTx).toBeDefined();
expect(pageloadSpan).toBeDefined();
expect(customMiddlewareSpan).toBeDefined();

// Assert that all transactions belong to the same trace
expect(traceIds.server).toBe(traceIds.pageload);
expect(traceIds.server).toBe(traceIds.customMiddleware);
// Assert that all spans belong to the same trace
expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id);
expect(serverSpan.trace_id).toBe(customMiddlewareSpan.trace_id);
});
});
Loading
Loading