Skip to content

Commit 02151f5

Browse files
msonnbcodex
andauthored
test(e2e): Port plain browser and bundler E2E apps to span streaming (#24111)
Ports the plain browser and bundler E2E apps to the default span-streaming lifecycle, while retaining `default-browser-static` as representative coverage for the legacy static lifecycle. Streamed trace assertions collect spans across envelopes where needed and check low-cardinality segment names. Fixes #23806 --------- Co-authored-by: OpenAI Codex <codex@openai.com>
1 parent fab1ae8 commit 02151f5

27 files changed

Lines changed: 536 additions & 318 deletions

File tree

‎dev-packages/e2e-tests/test-applications/browser-mfe-vite/apps/shell/src/main.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
33
import * as Sentry from '@sentry/react';
44

55
Sentry.init({
6-
traceLifecycle: 'static',
76
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
87
environment: import.meta.env.MODE || 'development',
98
integrations: [Sentry.browserTracingIntegration()],
Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
3+
4+
function hasUrlPath(span: { attributes: Record<string, { value: unknown }> }, path: string): boolean {
5+
return `${span.attributes['url.full']?.value}`.includes(path);
6+
}
37

48
test('attributes fetch spans to their originating microfrontend', async ({ page }) => {
5-
const transactionPromise = waitForTransaction('browser-mfe-vite', transactionEvent => {
6-
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
9+
const spansPromise = collectStreamedSpans('browser-mfe-vite', spans => {
10+
const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client');
11+
12+
return ['/api/header-data', '/api/mfe-one-data', '/api/shell-config'].every(path =>
13+
httpSpans.some(span => hasUrlPath(span, path)),
14+
);
715
});
816

917
await page.goto('/');
1018

11-
const transactionEvent = await transactionPromise;
12-
const httpSpans = transactionEvent.spans?.filter(span => span.op === 'http.client') || [];
19+
const spans = await spansPromise;
20+
const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client');
1321

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

1927
expect(headerSpan).toBeDefined();
2028
expect(mfeOneSpan).toBeDefined();
2129
expect(shellSpan).toBeDefined();
2230

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

2634
// Shell span has no MFE tag
27-
expect(shellSpan?.data?.['mfe.name']).toBeUndefined();
35+
expect(shellSpan?.attributes['mfe.name']).toBeUndefined();
2836
});

‎dev-packages/e2e-tests/test-applications/browser-webworker-vite/src/main.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import MyWorker2 from './worker2.ts?worker';
33
import * as Sentry from '@sentry/browser';
44

55
Sentry.init({
6-
traceLifecycle: 'static',
76
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
87
environment: import.meta.env.MODE || 'development',
98
tracesSampleRate: 1.0,

‎dev-packages/e2e-tests/test-applications/browser-webworker-vite/tests/errors.test.ts‎

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
2+
import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
3+
4+
function waitForPageloadSpan() {
5+
return waitForStreamedSpan('browser-webworker-vite', span => {
6+
return getSpanOp(span) === 'pageload' && span.is_segment;
7+
});
8+
}
39

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

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

1317
await page.goto('/');
1418

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

1923
const errorEvent = await errorEventPromise;
20-
const transactionEvent = await transactionPromise;
21-
22-
const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
23-
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
24+
const pageloadSpan = await pageloadSpanPromise;
2425

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

3031
expect(errorEvent.transaction).toBe('/');
31-
expect(transactionEvent.transaction).toBe('/');
32+
expect(pageloadSpan.name).toBe('Pageload');
3233

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

3839
expect(errorEvent.contexts?.trace).toEqual({
39-
trace_id: pageloadTraceId,
40-
span_id: pageloadSpanId,
40+
trace_id: pageloadSpan.trace_id,
41+
span_id: pageloadSpan.span_id,
4142
});
4243

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

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

8483
await page.goto('/');
8584

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

9089
const errorEvent = await errorEventPromise;
91-
const transactionEvent = await transactionPromise;
92-
93-
const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
94-
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
90+
const pageloadSpan = await pageloadSpanPromise;
9591

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

10197
expect(errorEvent.transaction).toBe('/');
102-
expect(transactionEvent.transaction).toBe('/');
98+
expect(pageloadSpan.name).toBe('Pageload');
10399

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

109105
expect(errorEvent.contexts?.trace).toEqual({
110-
trace_id: pageloadTraceId,
111-
span_id: pageloadSpanId,
106+
trace_id: pageloadSpan.trace_id,
107+
span_id: pageloadSpan.span_id,
112108
});
113109

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

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

134128
await page.goto('/');
135129

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

140134
const errorEvent = await errorEventPromise;
141-
const transactionEvent = await transactionPromise;
142-
143-
const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id;
144-
const pageloadSpanId = transactionEvent.contexts?.trace?.span_id;
135+
const pageloadSpan = await pageloadSpanPromise;
145136

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

151142
expect(errorEvent.transaction).toBe('/');
152-
expect(transactionEvent.transaction).toBe('/');
143+
expect(pageloadSpan.name).toBe('Pageload');
153144

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

159150
expect(errorEvent.contexts?.trace).toEqual({
160-
trace_id: pageloadTraceId,
161-
span_id: pageloadSpanId,
151+
trace_id: pageloadSpan.trace_id,
152+
span_id: pageloadSpan.span_id,
162153
});
163154

164155
expect(errorEvent.debug_meta).toEqual({

‎dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as Sentry from '@sentry/node';
22

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa', // dynamic sampling bias to keep transactions
65
dsn: process.env.E2E_TEST_DSN,
76
});

‎dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ exports[`Find symbolicated event on sentry 1`] = `
44
{
55
"colno": 41,
66
"contextLine": "const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));",
7-
"lineno": 9,
7+
"lineno": 8,
88
"postContext": [
99
"",
1010
"process.stdout.write(eventId);",
1111
],
1212
"preContext": [
13-
" traceLifecycle: 'static',",
13+
"Sentry.init({",
1414
" environment: 'qa', // dynamic sampling bias to keep transactions",
1515
" dsn: process.env.E2E_TEST_DSN,",
1616
"});",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
25+
/test-results/
26+
/playwright-report/
27+
/playwright/.cache/
28+
29+
!*.d.ts
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as path from 'path';
2+
import * as url from 'url';
3+
import HtmlWebpackPlugin from 'html-webpack-plugin';
4+
import TerserPlugin from 'terser-webpack-plugin';
5+
import webpack from 'webpack';
6+
7+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
8+
9+
webpack(
10+
{
11+
entry: path.join(__dirname, 'src/index.js'),
12+
output: {
13+
path: path.join(__dirname, 'build'),
14+
filename: 'app.js',
15+
},
16+
optimization: {
17+
minimize: true,
18+
minimizer: [new TerserPlugin()],
19+
},
20+
plugins: [
21+
new webpack.EnvironmentPlugin(['E2E_TEST_DSN']),
22+
new HtmlWebpackPlugin({
23+
template: path.join(__dirname, 'public/index.html'),
24+
}),
25+
],
26+
mode: 'production',
27+
},
28+
(err, stats) => {
29+
if (err) {
30+
console.error(err.stack || err);
31+
if (err.details) {
32+
console.error(err.details);
33+
}
34+
return;
35+
}
36+
37+
const info = stats.toJson();
38+
39+
if (stats.hasErrors()) {
40+
console.error(info.errors);
41+
process.exit(1);
42+
}
43+
44+
if (stats.hasWarnings()) {
45+
console.warn(info.warnings);
46+
process.exit(1);
47+
}
48+
},
49+
);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "default-browser-static-test-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@sentry/browser": "file:../../packed/sentry-browser-packed.tgz",
7+
"@types/node": "^18.19.1",
8+
"typescript": "~5.0.0"
9+
},
10+
"scripts": {
11+
"start": "serve -s build",
12+
"build": "node build.mjs",
13+
"test": "playwright test",
14+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
15+
"test:build": "pnpm install && pnpm build",
16+
"test:assert": "pnpm test"
17+
},
18+
"browserslist": {
19+
"production": [
20+
">0.2%",
21+
"not dead",
22+
"not op_mini all"
23+
],
24+
"development": [
25+
"last 1 chrome version",
26+
"last 1 firefox version",
27+
"last 1 safari version"
28+
]
29+
},
30+
"devDependencies": {
31+
"@playwright/test": "~1.56.0",
32+
"@sentry-internal/test-utils": "link:../../../test-utils",
33+
"webpack": "^5.91.0",
34+
"serve": "14.0.1",
35+
"terser-webpack-plugin": "^5.3.10",
36+
"html-webpack-plugin": "^5.6.0"
37+
},
38+
"volta": {
39+
"extends": "../../package.json"
40+
}
41+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
3+
const config = getPlaywrightConfig({
4+
startCommand: `pnpm start`,
5+
});
6+
7+
export default config;

0 commit comments

Comments
 (0)