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 @@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
import * as Sentry from '@sentry/react';

Sentry.init({
traceLifecycle: 'static',
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
environment: import.meta.env.MODE || 'development',
integrations: [Sentry.browserTracingIntegration()],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';
import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';

function hasUrlPath(span: { attributes: Record<string, { value: unknown }> }, path: string): boolean {
return `${span.attributes['url.full']?.value}`.includes(path);
}

test('attributes fetch spans to their originating microfrontend', async ({ page }) => {
const transactionPromise = waitForTransaction('browser-mfe-vite', transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
const spansPromise = collectStreamedSpans('browser-mfe-vite', spans => {
const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client');

return ['/api/header-data', '/api/mfe-one-data', '/api/shell-config'].every(path =>
httpSpans.some(span => hasUrlPath(span, path)),
);
});

await page.goto('/');

const transactionEvent = await transactionPromise;
const httpSpans = transactionEvent.spans?.filter(span => span.op === 'http.client') || [];
const spans = await spansPromise;
const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client');

// MFE spans carry the mfe.name attribute set via withScope + spanStart hook
const headerSpan = httpSpans.find(s => s.description?.includes('/api/header-data'));
const mfeOneSpan = httpSpans.find(s => s.description?.includes('/api/mfe-one-data'));
const shellSpan = httpSpans.find(s => s.description?.includes('/api/shell-config'));
const headerSpan = httpSpans.find(span => hasUrlPath(span, '/api/header-data'));
const mfeOneSpan = httpSpans.find(span => hasUrlPath(span, '/api/mfe-one-data'));
const shellSpan = httpSpans.find(span => hasUrlPath(span, '/api/shell-config'));

expect(headerSpan).toBeDefined();
expect(mfeOneSpan).toBeDefined();
expect(shellSpan).toBeDefined();

expect(headerSpan?.data?.['mfe.name']).toBe('mfe-header');
expect(mfeOneSpan?.data?.['mfe.name']).toBe('mfe-one');
expect(headerSpan?.attributes['mfe.name']).toEqual({ value: 'mfe-header', type: 'string' });
expect(mfeOneSpan?.attributes['mfe.name']).toEqual({ value: 'mfe-one', type: 'string' });

// Shell span has no MFE tag
expect(shellSpan?.data?.['mfe.name']).toBeUndefined();
expect(shellSpan?.attributes['mfe.name']).toBeUndefined();
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import MyWorker2 from './worker2.ts?worker';
import * as Sentry from '@sentry/browser';

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

function waitForPageloadSpan() {
return waitForStreamedSpan('browser-webworker-vite', span => {
return getSpanOp(span) === 'pageload' && span.is_segment;
});
}

test('captures an error with debug ids and pageload trace context', async ({ page }) => {
const errorEventPromise = waitForError('browser-webworker-vite', async event => {
return !event.type && !!event.exception?.values?.[0];
});

const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});
const pageloadSpanPromise = waitForPageloadSpan();

await page.goto('/');

Expand All @@ -17,27 +21,24 @@ test('captures an error with debug ids and pageload trace context', async ({ pag
await page.waitForTimeout(1000);

const errorEvent = await errorEventPromise;
const transactionEvent = await transactionPromise;

const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
const pageloadSpan = await pageloadSpanPromise;

expect(errorEvent.exception?.values).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker');
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker-.+\.js$/);

expect(errorEvent.transaction).toBe('/');
expect(transactionEvent.transaction).toBe('/');
expect(pageloadSpan.name).toBe('Pageload');

expect(errorEvent.request).toEqual({
url: 'http://localhost:3030/',
headers: expect.any(Object),
});

expect(errorEvent.contexts?.trace).toEqual({
trace_id: pageloadTraceId,
span_id: pageloadSpanId,
trace_id: pageloadSpan.trace_id,
span_id: pageloadSpan.span_id,
});

expect(errorEvent.debug_meta).toEqual({
Expand Down Expand Up @@ -77,9 +78,7 @@ test('captures an error from the second eagerly added worker', async ({ page })
return !event.type && !!event.exception?.values?.[0];
});

const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});
const pageloadSpanPromise = waitForPageloadSpan();

await page.goto('/');

Expand All @@ -88,27 +87,24 @@ test('captures an error from the second eagerly added worker', async ({ page })
await page.waitForTimeout(1000);

const errorEvent = await errorEventPromise;
const transactionEvent = await transactionPromise;

const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
const pageloadSpan = await pageloadSpanPromise;

expect(errorEvent.exception?.values).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker 2');
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker2-.+\.js$/);

expect(errorEvent.transaction).toBe('/');
expect(transactionEvent.transaction).toBe('/');
expect(pageloadSpan.name).toBe('Pageload');

expect(errorEvent.request).toEqual({
url: 'http://localhost:3030/',
headers: expect.any(Object),
});

expect(errorEvent.contexts?.trace).toEqual({
trace_id: pageloadTraceId,
span_id: pageloadSpanId,
trace_id: pageloadSpan.trace_id,
span_id: pageloadSpan.span_id,
});

expect(errorEvent.debug_meta).toEqual({
Expand All @@ -127,9 +123,7 @@ test('captures an error from the third lazily added worker', async ({ page }) =>
return !event.type && !!event.exception?.values?.[0];
});

const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => {
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
});
const pageloadSpanPromise = waitForPageloadSpan();

await page.goto('/');

Expand All @@ -138,27 +132,24 @@ test('captures an error from the third lazily added worker', async ({ page }) =>
await page.waitForTimeout(1000);

const errorEvent = await errorEventPromise;
const transactionEvent = await transactionPromise;

const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
const pageloadSpan = await pageloadSpanPromise;

expect(errorEvent.exception?.values).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker 3');
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames).toHaveLength(1);
expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker3-.+\.js$/);

expect(errorEvent.transaction).toBe('/');
expect(transactionEvent.transaction).toBe('/');
expect(pageloadSpan.name).toBe('Pageload');

expect(errorEvent.request).toEqual({
url: 'http://localhost:3030/',
headers: expect.any(Object),
});

expect(errorEvent.contexts?.trace).toEqual({
trace_id: pageloadTraceId,
span_id: pageloadSpanId,
trace_id: pageloadSpan.trace_id,
span_id: pageloadSpan.span_id,
});

expect(errorEvent.debug_meta).toEqual({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/node';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ exports[`Find symbolicated event on sentry 1`] = `
{
"colno": 41,
"contextLine": "const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));",
"lineno": 9,
"lineno": 8,
"postContext": [
"",
"process.stdout.write(eventId);",
],
"preContext": [
" traceLifecycle: 'static',",
"Sentry.init({",
" environment: 'qa', // dynamic sampling bias to keep transactions",
" dsn: process.env.E2E_TEST_DSN,",
"});",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

/test-results/
/playwright-report/
/playwright/.cache/

!*.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as path from 'path';
import * as url from 'url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';

const __dirname = path.dirname(url.fileURLToPath(import.meta.url));

webpack(
{
entry: path.join(__dirname, 'src/index.js'),
output: {
path: path.join(__dirname, 'build'),
filename: 'app.js',
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
plugins: [
new webpack.EnvironmentPlugin(['E2E_TEST_DSN']),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'public/index.html'),
}),
],
mode: 'production',
},
(err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
return;
}

const info = stats.toJson();

if (stats.hasErrors()) {
console.error(info.errors);
process.exit(1);
}

if (stats.hasWarnings()) {
console.warn(info.warnings);
process.exit(1);
}
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "default-browser-static-test-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@sentry/browser": "file:../../packed/sentry-browser-packed.tgz",
"@types/node": "^18.19.1",
"typescript": "~5.0.0"
},
"scripts": {
"start": "serve -s build",
"build": "node build.mjs",
"test": "playwright test",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"webpack": "^5.91.0",
"serve": "14.0.1",
"terser-webpack-plugin": "^5.3.10",
"html-webpack-plugin": "^5.6.0"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

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

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Browser App</title>
</head>
<body>
<div id="app"></div>

<input type="button" value="Capture Exception" id="exception-button" />

<div id="navigation">
<a id="navigation-link" href="#navigation-target">Navigation Link</a>

<div id="navigation-target">Navigated Element</div>
</div>

<!-- The script tags for the bundled JavaScript files will be injected here by HtmlWebpackPlugin in build.mjs-->
</body>
</html>
Loading
Loading