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
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForError, waitForStreamedSpans, waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForError, waitForStreamedSpan, waitForStreamedSpans } from '@sentry-internal/test-utils';

// FIXME: This app uses `ai@^3`, which the channel-based Vercel AI integration doesn't instrument
// (it supports v4-v6 via the orchestrion transform and v7 via the native `ai:telemetry` channel).
// With channel-based instrumentation now the default, no gen_ai spans are produced. Re-enable once
// the app is upgraded to `ai@v7` (or v3 support is restored).
test.fixme('should create AI spans with correct attributes and error linking', async ({ page }) => {
const aiTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent.transaction === 'GET /ai-error-test';
const aiSpanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === 'GET /ai-error-test' && span.is_segment;
});

// gen_ai spans are extracted into a separate span v2 envelope item
Expand All @@ -21,12 +21,12 @@ test.fixme('should create AI spans with correct attributes and error linking', a

await page.goto('/ai-error-test');

const aiTransaction = await aiTransactionPromise;
const aiSpan = await aiSpanPromise;
const genAiSpans = await genAiSpansPromise;
const errorEvent = await errorEventPromise;

expect(aiTransaction).toBeDefined();
expect(aiTransaction.transaction).toBe('GET /ai-error-test');
expect(aiSpan).toBeDefined();
expect(aiSpan.name).toBe('GET /ai-error-test');

// Each generateText call should create 2 spans: one for the pipeline and one for doGenerate
// Plus a span for the tool call
Expand All @@ -44,5 +44,5 @@ test.fixme('should create AI spans with correct attributes and error linking', a
expect(errorEvent).toBeDefined();

//Verify error is linked to the same trace as the transaction
expect(errorEvent?.contexts?.trace?.trace_id).toBe(aiTransaction.contexts?.trace?.trace_id);
expect(errorEvent?.contexts?.trace?.trace_id).toBe(aiSpan.trace_id);
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect, test } from '@playwright/test';
import { getSpanOp, waitForStreamedSpans, waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan, waitForStreamedSpans } from '@sentry-internal/test-utils';

// FIXME: This app uses `ai@^3`, which the channel-based Vercel AI integration doesn't instrument
// (it supports v4-v6 via the orchestrion transform and v7 via the native `ai:telemetry` channel).
// With channel-based instrumentation now the default, no gen_ai spans are produced. Re-enable once
// the app is upgraded to `ai@v7` (or v3 support is restored).
test.fixme('should create AI spans with correct attributes', async ({ page }) => {
const aiTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent.transaction === 'GET /ai-test';
const aiSpanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === 'GET /ai-test' && span.is_segment;
});

// gen_ai spans are extracted into a separate span v2 envelope item
Expand All @@ -17,11 +17,11 @@ test.fixme('should create AI spans with correct attributes', async ({ page }) =>

await page.goto('/ai-test');

const aiTransaction = await aiTransactionPromise;
const aiSpan = await aiSpanPromise;
const genAiSpans = await genAiSpansPromise;

expect(aiTransaction).toBeDefined();
expect(aiTransaction.transaction).toBe('GET /ai-test');
expect(aiSpan).toBeDefined();
expect(aiSpan.name).toBe('GET /ai-test');

// We expect spans for the first 3 AI calls (4th is disabled)
// Each generateText call should create 2 spans: one for the pipeline and one for doGenerate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
import { parseSemver } from '@sentry/core';

const packageJson = require('../package.json');
Expand All @@ -12,29 +12,33 @@ test('Should propagate traces from server to client in pages router', async ({ p
'Next.js version does not support clientside instrumentation',
);

const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent?.transaction === 'GET /[locale]/pages-router-client-trace-propagation';
const serverSpanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === 'GET /[locale]/pages-router-client-trace-propagation' && span.is_segment;
});

const pageloadTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent?.transaction === '/[locale]/pages-router-client-trace-propagation';
const pageloadSpanPromise = waitForStreamedSpan('nextjs-15', span => {
return (
span.name === '/[locale]/pages-router-client-trace-propagation' &&
getSpanOp(span) === 'pageload' &&
span.is_segment
);
});

await page.goto(`/123/pages-router-client-trace-propagation`);

const serverTransaction = await serverTransactionPromise;
const pageloadTransaction = await pageloadTransactionPromise;
const serverSpan = await serverSpanPromise;
const pageloadSpan = await pageloadSpanPromise;

expect(serverTransaction.contexts?.trace?.trace_id).toBeDefined();
expect(pageloadTransaction.contexts?.trace?.trace_id).toBe(serverTransaction.contexts?.trace?.trace_id);
expect(serverSpan.trace_id).toBeDefined();
expect(pageloadSpan.trace_id).toBe(serverSpan.trace_id);

await test.step('release was successfully injected on the serverside', () => {
// Release as defined in next.config.js
expect(serverTransaction.release).toBe('foobar123');
expect(serverSpan.attributes['sentry.release']?.value).toBe('foobar123');
});

await test.step('release was successfully injected on the clientside', () => {
// Release as defined in next.config.js
expect(pageloadTransaction.release).toBe('foobar123');
expect(pageloadSpan.attributes['sentry.release']?.value).toBe('foobar123');
});
});
Original file line number Diff line number Diff line change
@@ -1,56 +1,21 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test('should create consistent parameterized transaction for i18n routes - locale: en', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/en/i18n-test`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.segment.name.source': 'route',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
},
},
transaction: '/:locale/i18n-test',
transaction_info: { source: 'route' },
type: 'transaction',
});
});

test('should create consistent parameterized transaction for i18n routes - locale: ar', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
});
for (const locale of ['en', 'ar']) {
test(`should create consistent parameterized span for i18n routes - locale: ${locale}`, async ({ page }) => {
const spanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === '/:locale/i18n-test' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/ar/i18n-test`);
await page.goto(`/${locale}/i18n-test`);

const transaction = await transactionPromise;
const span = await spanPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.segment.name.source': 'route',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
},
},
transaction: '/:locale/i18n-test',
transaction_info: { source: 'route' },
type: 'transaction',
expect(span.name).toBe('/:locale/i18n-test');
expect(span.attributes).toMatchObject({
'sentry.op': { value: 'pageload', type: 'string' },
'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' },
'sentry.segment.name.source': { value: 'route', type: 'string' },
});
});
});
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

test('should remove sentry-trace and baggage meta tags on ISR dynamic route page load', async ({ page }) => {
// Navigate to ISR page
Expand Down Expand Up @@ -41,15 +41,13 @@ test('should remove meta tags for different ISR dynamic route values', async ({
await expect(page.locator('meta[name="baggage"]')).toHaveCount(0);
});

test('should create unique transactions for ISR pages on each visit', async ({ page }) => {
test('should create unique traces for ISR pages on each visit', async ({ page }) => {
const traceIds: string[] = [];

// Load the same ISR page 5 times to ensure cached HTML meta tags are consistently removed
for (let i = 0; i < 5; i++) {
const transactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return !!(
transactionEvent.transaction === '/isr-test/:product' && transactionEvent.contexts?.trace?.op === 'pageload'
);
const spanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment;
});

if (i === 0) {
Expand All @@ -58,8 +56,8 @@ test('should create unique transactions for ISR pages on each visit', async ({ p
await page.reload();
}

const transaction = await transactionPromise;
const traceId = transaction.contexts?.trace?.trace_id;
const span = await spanPromise;
const traceId = span.trace_id;

expect(traceId).toBeDefined();
expect(traceId).toMatch(/[a-f0-9]{32}/);
Expand All @@ -72,23 +70,14 @@ test('should create unique transactions for ISR pages on each visit', async ({ p
});

test('ISR route should be identified correctly in the route manifest', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent.transaction === '/isr-test/:product' && transactionEvent.contexts?.trace?.op === 'pageload';
const spanPromise = waitForStreamedSpan('nextjs-15', span => {
return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto('/isr-test/laptop');
const transaction = await transactionPromise;

// Verify the transaction is properly parameterized
expect(transaction).toMatchObject({
transaction: '/isr-test/:product',
transaction_info: { source: 'route' },
contexts: {
trace: {
data: {
'sentry.segment.name.source': 'route',
},
},
},
});
const span = await spanPromise;

// Verify the span is properly parameterized
expect(span.name).toBe('/isr-test/:product');
expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
});
Loading
Loading