Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9d9663d
feat(spotlight): Add SENTRY_SPOTLIGHT environment variable support fo…
BYK Dec 30, 2025
f67c8a9
fix: Format and lint fixes
BYK Dec 30, 2025
b0a08af
fix: Only inject spotlight env var when set
BYK Dec 30, 2025
c71233e
test: Add bundler tests for __VITE_SPOTLIGHT_ENV__ replacement
BYK Dec 30, 2025
1edca18
test: Add E2E tests for SENTRY_SPOTLIGHT env var support
BYK Dec 30, 2025
039e873
chore: Fix prettier formatting
BYK Dec 30, 2025
0041135
fix: Bump size limits and fix E2E test webpack config
BYK Dec 30, 2025
744e6e9
fix: Resolve merge conflicts with develop
BYK Dec 30, 2025
f4bdbd3
test: Remove browser-spotlight E2E test temporarily
BYK Dec 30, 2025
0b17113
fix: Bump @sentry/nextjs client size limit to 47 KB
BYK Dec 30, 2025
f02d986
test: Add E2E tests for SENTRY_SPOTLIGHT env var support
BYK Dec 30, 2025
4526dcb
fix(e2e): Configure webpack to use development condition for Spotligh…
BYK Dec 30, 2025
82d4378
fix(e2e): Fix webpack DefinePlugin config for process object
BYK Dec 30, 2025
30d7374
fix: Format build.mjs
BYK Dec 30, 2025
44b918a
fix(e2e): Pass spotlight option directly as fallback for CI
BYK Dec 30, 2025
e25170e
fix(e2e): Explicitly add spotlightBrowserIntegration to test
BYK Dec 30, 2025
c68890b
fix(e2e): Use @sentry/browser directly for Spotlight test
BYK Dec 30, 2025
1f9d438
refactor(e2e): Convert browser-spotlight to use Vite
BYK Dec 31, 2025
ba2aec7
fix(e2e): Add development condition to Vite resolve config
BYK Dec 31, 2025
8d0301d
fix(e2e): Exclude Sentry packages from Vite pre-bundling
BYK Dec 31, 2025
5a19725
debug(e2e): Add debug logging to browser-spotlight test
BYK Dec 31, 2025
076655f
fix(e2e): Explicitly add Spotlight integration in test app
BYK Dec 31, 2025
865a338
fix(e2e): Test automatic Spotlight enablement via VITE_SENTRY_SPOTLIGHT
BYK Dec 31, 2025
58bcf67
fix(e2e): Use explicit resolve.conditions for development builds
BYK Dec 31, 2025
1f15ad5
fix(e2e): Use --mode development for vite build
BYK Dec 31, 2025
fc0982d
fix(e2e): Explicitly add development to resolve.conditions
BYK Dec 31, 2025
521634c
feat(browser): Include Spotlight integration in all builds
BYK Dec 31, 2025
4b94aa7
fix(e2e): Set env vars in vite config for import.meta.env access
BYK Dec 31, 2025
4208b02
fix(react): Fix Vite env var replacement for Spotlight
BYK Dec 31, 2025
47ac2a0
fix(e2e): Use Vite define for import.meta.env replacement
BYK Dec 31, 2025
2972fb6
debug(e2e): Add more logging to browser-spotlight test
BYK Dec 31, 2025
163e02b
fix(e2e): Exclude Sentry packages from optimizeDeps for proper define…
BYK Dec 31, 2025
229ccce
fix(e2e): Use @rollup/plugin-replace for env var replacement in deps
BYK Dec 31, 2025
1168a99
fix(e2e): Move replace plugin to rollupOptions for proper dep processing
BYK Dec 31, 2025
b0507a0
fix(e2e): Set VITE_SENTRY_SPOTLIGHT env var at build time
BYK Dec 31, 2025
2cbca20
fix: Update Spotlight env var handling and tests
BYK Dec 31, 2025
55c79eb
chore: Increase bundle size limits for Spotlight inclusion
BYK Dec 31, 2025
da4bc7a
debug: Add console output to diagnose CI failure
BYK Dec 31, 2025
4486908
debug: Forward browser console to test output
BYK Dec 31, 2025
e93fc83
debug: Add more detailed logging to pinpoint spotlight issue
BYK Dec 31, 2025
a27ef9b
debug: Check if import.meta.env is available at runtime
BYK Dec 31, 2025
421ab1f
fix(e2e): Add .npmrc for browser-spotlight to use Verdaccio registry
BYK Dec 31, 2025
109a374
feat(spotlight): Add Next.js Spotlight E2E test and fix import.meta.e…
BYK Dec 31, 2025
b4b0fb0
chore: Fix formatting
BYK Dec 31, 2025
1527c92
chore: Increase svelte size limit to accommodate try-catch
BYK Dec 31, 2025
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
22 changes: 11 additions & 11 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init'),
gzip: true,
limit: '25 KB',
limit: '25.5 KB',
},
{
name: '@sentry/browser - with treeshaking flags',
Expand Down Expand Up @@ -38,7 +38,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
limit: '42 KB',
limit: '42.5 KB',
},
{
name: '@sentry/browser (incl. Tracing, Profiling)',
Expand Down Expand Up @@ -82,7 +82,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
gzip: true,
limit: '85 KB',
limit: '86 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',
Expand Down Expand Up @@ -140,15 +140,15 @@ module.exports = [
import: createImport('init', 'ErrorBoundary'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '27 KB',
limit: '27.5 KB',
},
{
name: '@sentry/react (incl. Tracing)',
path: 'packages/react/build/esm/index.js',
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
ignore: ['react/jsx-runtime'],
gzip: true,
limit: '44 KB',
limit: '45 KB',
},
// Vue SDK (ESM)
{
Expand All @@ -163,28 +163,28 @@ module.exports = [
path: 'packages/vue/build/esm/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
limit: '44 KB',
limit: '44.5 KB',
},
// Svelte SDK (ESM)
{
name: '@sentry/svelte',
path: 'packages/svelte/build/esm/index.js',
import: createImport('init'),
gzip: true,
limit: '25 KB',
limit: '25.6 KB',
},
// Browser CDN bundles
{
name: 'CDN Bundle',
path: createCDNPath('bundle.min.js'),
gzip: true,
limit: '27.5 KB',
limit: '28 KB',
},
{
name: 'CDN Bundle (incl. Tracing)',
path: createCDNPath('bundle.tracing.min.js'),
gzip: true,
limit: '42.5 KB',
limit: '43 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay)',
Expand Down Expand Up @@ -234,7 +234,7 @@ module.exports = [
import: createImport('init'),
ignore: ['next/router', 'next/constants'],
gzip: true,
limit: '46.5 KB',
limit: '47 KB',
},
// SvelteKit SDK (ESM)
{
Expand All @@ -243,7 +243,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '42 KB',
limit: '43 KB',
},
// Node-Core SDK (ESM)
{
Expand Down
47 changes: 45 additions & 2 deletions dev-packages/bundler-tests/tests/bundling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,52 @@ describe('spotlight', () => {
expect(code).toContain(SPOTLIGHT_URL);
});

test(`${name} production bundle does not contain spotlight`, async () => {
// Spotlight is now included in production builds too (not dev-only)
// The integration is gated by the spotlight option, so there's no need
// to strip it from production builds
test(`${name} production bundle contains spotlight`, async () => {
const code = await bundler('production');
expect(code).not.toContain(SPOTLIGHT_URL);
expect(code).toContain(SPOTLIGHT_URL);
});
}
});

describe('__VITE_SPOTLIGHT_ENV__ rollup replacement', () => {
// Test that our rollup build correctly replaces __VITE_SPOTLIGHT_ENV__
// ESM bundles should have import.meta.env access, CJS should have undefined

function readSdkFile(packageName: string, format: 'esm' | 'cjs'): string {
const sdkPath = path.join(rootDir(), 'packages', packageName, 'build', format, 'sdk.js');
if (!fs.existsSync(sdkPath)) {
throw new Error(`SDK file not found: ${sdkPath}. Make sure to run yarn build:dev first.`);
}
return fs.readFileSync(sdkPath, 'utf8');
}

// Remove comments from code to test only actual code
function stripComments(code: string): string {
// Remove single-line comments
return code.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
}

test.each(['react', 'vue', 'svelte', 'solid'] as const)(
'%s ESM bundle contains import.meta.env.VITE_SENTRY_SPOTLIGHT access',
packageName => {
const code = stripComments(readSdkFile(packageName, 'esm'));
// ESM bundles should have import.meta.env access for Vite support
// The replacement is: import.meta.env.VITE_SENTRY_SPOTLIGHT (without optional chaining)
// so that Vite can properly do static replacement at build time
expect(code).toMatch(/import\.meta\.env\.[A-Z_]+SPOTLIGHT/);
},
);

test.each(['react', 'vue', 'svelte', 'solid'] as const)(
'%s CJS bundle does not contain import.meta.env (CJS incompatible)',
packageName => {
const code = stripComments(readSdkFile(packageName, 'cjs'));
// CJS bundles should NOT have import.meta.env as it's ESM-only syntax
// The __VITE_SPOTLIGHT_ENV__ placeholder should be replaced with 'undefined'
expect(code).not.toMatch(/import\.meta\.env/);
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spotlight E2E Test</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "browser-spotlight-test-app",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "VITE_SENTRY_SPOTLIGHT=http://localhost:3032/stream VITE_E2E_TEST_DSN=${E2E_TEST_DSN:-https://public@dsn.ingest.sentry.io/1234567} vite build",
"preview": "vite preview --port 3030",
"test": "playwright test",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test"
},
"dependencies": {
"@sentry/react": "latest || *",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@vitejs/plugin-react": "^4.2.1",
"vite": "~5.4.0"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const config = getPlaywrightConfig({
startCommand: `pnpm preview`,
});

// Add the Spotlight proxy server as an additional webServer
// This runs alongside the main event proxy and app server
config.webServer.push({
command: 'node start-spotlight-proxy.mjs',
port: 3032,
stdout: 'pipe',
stderr: 'pipe',
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as Sentry from '@sentry/react';

// Debug: Log env vars
console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT);
console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN);
console.log('[E2E Debug] MODE:', import.meta.env.MODE);

// Debug: Check if import.meta.env is available at runtime
console.log('[E2E Debug] typeof import.meta:', typeof import.meta);
console.log('[E2E Debug] typeof import.meta.env:', typeof import.meta.env);
console.log('[E2E Debug] import.meta.env object:', JSON.stringify(import.meta.env));

// Initialize Sentry - the @sentry/react SDK automatically parses
// VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!)
const initOptions = {
dsn: import.meta.env.VITE_E2E_TEST_DSN,
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
release: 'e2e-test',
environment: 'qa',
// Use tunnel to capture events at our proxy server
tunnel: 'http://localhost:3031',
debug: true,
};

console.log(
'[E2E Debug] Init options BEFORE Sentry.init:',
JSON.stringify({
dsn: initOptions.dsn,
spotlight: initOptions.spotlight,
debug: initOptions.debug,
}),
);

const client = Sentry.init(initOptions);

// Debug: Check what the client received
const clientOptions = client?.getOptions();
console.log(
'[E2E Debug] Client options AFTER Sentry.init:',
JSON.stringify({
dsn: clientOptions?.dsn,
spotlight: clientOptions?.spotlight,
debug: clientOptions?.debug,
}),
);

// Debug: Check if Spotlight integration was added
const integrations = clientOptions?.integrations || [];
const integrationNames = integrations.map(i => i.name);
console.log('[E2E Debug] Integrations:', integrationNames.join(', '));
console.log('[E2E Debug] Has SpotlightBrowser:', integrationNames.includes('SpotlightBrowser'));

// Simple render
document.getElementById('root').innerHTML = `
<div>
<h1>Spotlight E2E Test</h1>
<p>This page tests that VITE_SENTRY_SPOTLIGHT env var enables Spotlight integration.</p>
<button id="exception-button">Trigger Error</button>
</div>
`;

document.getElementById('exception-button').addEventListener('click', () => {
throw new Error('Spotlight test error!');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

// Start the main event proxy server that captures events via tunnel
startEventProxyServer({
port: 3031,
proxyServerName: 'browser-spotlight',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { startSpotlightProxyServer } from '@sentry-internal/test-utils';

console.log('[Spotlight Proxy] Starting spotlight proxy server on port 3032...');

// Start a Spotlight proxy server that captures events sent to /stream
// This simulates the Spotlight sidecar and allows us to verify events arrive
startSpotlightProxyServer({
port: 3032,
proxyServerName: 'browser-spotlight-sidecar',
})
.then(() => {
console.log('[Spotlight Proxy] Server started successfully on port 3032');
})
.catch(err => {
console.error('[Spotlight Proxy] Failed to start server:', err);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils';

// Forward browser console messages to the test output for debugging
test.beforeEach(async ({ page }) => {
page.on('console', msg => {
console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`);
});
page.on('pageerror', error => {
console.log(`[Browser Error] ${error.message}`);
});
});

/**
* Test that VITE_SENTRY_SPOTLIGHT environment variable automatically enables Spotlight.
*
* This test verifies that:
* 1. The SDK automatically parses VITE_SENTRY_SPOTLIGHT env var via import.meta.env
* 2. The SDK enables Spotlight WITHOUT explicit configuration (zero-config for Vite!)
* 3. Events are sent to both the tunnel AND the Spotlight sidecar URL
*
* IMPORTANT: The test app does NOT explicitly add spotlightBrowserIntegration.
* The SDK should automatically enable Spotlight when VITE_SENTRY_SPOTLIGHT is set.
*
* Test setup:
* - VITE_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time
* - tunnel is set to 'http://localhost:3031' for regular event capture
* - A Spotlight proxy server runs on port 3032 to capture Spotlight events
* - A regular event proxy server runs on port 3031 to capture tunnel events
*/
test('VITE_SENTRY_SPOTLIGHT env var automatically enables Spotlight', async ({ page }) => {
// Wait for the error to arrive at the regular tunnel (port 3031)
const tunnelErrorPromise = waitForError('browser-spotlight', event => {
return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!';
});

// Wait for the error event to arrive at the Spotlight sidecar (port 3032)
const spotlightErrorPromise = waitForSpotlightError('browser-spotlight-sidecar', event => {
return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!';
});

await page.goto('/');

const exceptionButton = page.locator('id=exception-button');
await exceptionButton.click();

// Both promises should resolve - the error should be sent to BOTH destinations
const [tunnelError, spotlightError] = await Promise.all([tunnelErrorPromise, spotlightErrorPromise]);

// Verify the Spotlight sidecar received the error
expect(spotlightError.exception?.values).toHaveLength(1);
expect(spotlightError.exception?.values?.[0]?.value).toBe('Spotlight test error!');
expect(spotlightError.exception?.values?.[0]?.type).toBe('Error');

// Verify the tunnel also received the error (normal Sentry flow still works)
expect(tunnelError.exception?.values).toHaveLength(1);
expect(tunnelError.exception?.values?.[0]?.value).toBe('Spotlight test error!');

// Both events should have the same trace context
expect(spotlightError.contexts?.trace?.trace_id).toBe(tunnelError.contexts?.trace?.trace_id);
});

/**
* Test that Spotlight automatically receives transaction events.
*/
test('VITE_SENTRY_SPOTLIGHT automatically sends transactions to sidecar', async ({ page }) => {
// Wait for a pageload transaction to arrive at the Spotlight sidecar
const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => {
return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload';
});

await page.goto('/');

const spotlightTransaction = await spotlightTransactionPromise;

// Verify the Spotlight sidecar received the transaction
expect(spotlightTransaction.type).toBe('transaction');
expect(spotlightTransaction.contexts?.trace?.op).toBe('pageload');
expect(spotlightTransaction.transaction).toBe('/');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"target": "ESNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["tests/**/*.ts"]
}
Loading
Loading