Skip to content

Commit 747a37a

Browse files
nicohrubecclaude
andcommitted
chore: Resolve merge conflict and add changelog entry for #20453
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2 parents 7bb98e7 + 9680821 commit 747a37a

100 files changed

Lines changed: 1049 additions & 304 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/write-tests/SKILL.md

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,17 @@ Follow these steps in order before writing any test code.
2222
1. **Decide the framework.** Testing a function's return value, side effects, or module interactions
2323
→ Vitest (lives under `packages/<name>/test/`). Testing that a real HTTP request to a running app
2424
produces the correct Sentry envelope → Playwright (lives under
25-
`dev-packages/e2e-tests/test-applications/<app>/tests/`).
25+
`dev-packages/e2e-tests/test-applications/<app>/tests/`). Testing Node SDK instrumentation
26+
against real envelope output → node-integration-tests (lives under
27+
`dev-packages/node-integration-tests/suites/`).
28+
29+
**Parameterization differs by framework — pick the right one:**
30+
31+
| Framework | How to parameterize |
32+
| ---------------------- | ------------------------------------------------------------- |
33+
| Vitest | `it.each` / `it.for` (runner-integrated, one test each) |
34+
| Playwright E2E | `.forEach()` outside `test()` (registers separate tests) |
35+
| Node integration tests | Loops **inside** a single `test()` body (one Node.js process) |
2636

2737
2. **Read 2–3 existing test files** in the target `test/` directory. Specifically note:
2838
- Which `vi.mock` style they use (string path or import form)
@@ -299,6 +309,64 @@ describe('patchRoute', () => {
299309

300310
---
301311

312+
## Writing node-integration-tests
313+
314+
Node integration tests (`dev-packages/node-integration-tests/`) use `createEsmAndCjsTests` to
315+
run a real Node scenario file and assert on captured Sentry envelopes.
316+
317+
### Minimize `test()` calls — each one spawns a separate Node process
318+
319+
**This is the opposite of the Playwright rule.** In Playwright, each `test()` is cheap — use
320+
`.forEach()` to register many tests. In node-integration-tests, each `test()` forks a fresh Node
321+
process with full startup cost. A `describe.each` matrix that looks reasonable in a unit test
322+
context balloons into dozens of cold starts and slows CI by a large factor.
323+
324+
**Rule: loop inside the test body, not around `test()` calls.**
325+
326+
```typescript
327+
// Bad: 2 routes × 5 methods = 10 separate Node processes
328+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
329+
describe.each(['/sync', '/async'])('when using %s route', route => {
330+
describe.each(['get', 'post', 'put', 'delete', 'patch'])('when using %s method', method => {
331+
test('handles transaction', async () => {
332+
// ...
333+
});
334+
});
335+
});
336+
});
337+
```
338+
339+
```typescript
340+
// Good: one Node process, all combinations asserted in a single test run
341+
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
342+
test('handles transactions for all route/method/path combinations', async () => {
343+
const runner = createRunner();
344+
const requests: Array<{ method: string; url: string }> = [];
345+
346+
for (const route of ['/sync', '/async']) {
347+
for (const method of ['get', 'post', 'put', 'delete', 'patch']) {
348+
const fullPath = `${route}${path}`;
349+
runner.expect({
350+
transaction: { transaction: `${method.toUpperCase()} ${fullPath}` },
351+
});
352+
requests.push({ method, url: fullPath });
353+
}
354+
}
355+
356+
const started = runner.start();
357+
for (const req of requests) {
358+
await started.makeRequest(req.method, req.url);
359+
}
360+
await started.completed();
361+
}, 60_000);
362+
});
363+
```
364+
365+
If a subset of cases has meaningfully different expectations (e.g., error vs. success), split
366+
into two tests — not thirty.
367+
368+
---
369+
302370
## Writing Playwright E2E tests
303371

304372
### When to write E2E tests
@@ -366,17 +434,35 @@ expect(mechanism?.type).toBe('auto.http.hono.context_error');
366434

367435
### Parameterized E2E tests
368436

369-
For Playwright tests (unlike Vitest), `for...of` loops are the established codebase convention.
370-
Use `for...of` (not `.forEach()`) so Playwright's test registration works correctly:
437+
For Playwright tests (unlike Vitest), use standard JS `.forEach()` as this is recommended by Playwright,
438+
**not** `it.each` or `it.for`, which are Vitest-only APIs. The `.forEach()` runs at discovery time, registering
439+
each case as its own independent test. All cases then run separately at execution time.
371440

372441
```typescript
373-
for (const { name, prefix } of SCENARIOS) {
374-
test.describe(name, () => {
375-
test('captures named middleware span', async ({ baseURL }) => {
376-
// ...
377-
});
442+
[
443+
{ a: 1, b: 1, expected: 2 },
444+
{ a: 1, b: 2, expected: 3 },
445+
{ a: 2, b: 1, expected: 3 },
446+
].forEach(({ a, b, expected }) => {
447+
test(`given ${a} and ${b} as arguments, returns ${expected}`, ({ page }) => {
448+
expect(a + b).toEqual(expected);
378449
});
379-
}
450+
});
451+
```
452+
453+
**Don't put the loop inside a single test.** That collapses all cases into one test body — a
454+
failure in one iteration aborts the rest, and the runner reports a single failure with no
455+
per-case visibility:
456+
457+
```typescript
458+
// Bad: all routes tested in one test — a failure on /users skips /posts entirely
459+
test('captures transactions for all routes', async ({ baseURL }) => {
460+
for (const route of ['/users', '/posts', '/comments']) {
461+
const txn = await waitForTransaction(APP_NAME, e => e.transaction === `GET ${route}`);
462+
await fetch(`${baseURL}${route}`);
463+
expect(txn.contexts?.trace?.op).toBe('http.server');
464+
}
465+
});
380466
```
381467

382468
### Common pitfalls

.github/workflows/pr-review-reminder.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
runs-on: ubuntu-latest
2626
steps:
2727
- name: Checkout repository
28-
uses: actions/checkout@v4
28+
uses: actions/checkout@v6
2929

3030
- name: Remind pending reviewers
3131
uses: actions/github-script@v7

.version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"_comment": "Auto-generated by scripts/bump-version.js. Used by the gitflow sync workflow to detect version bumps. Do not edit manually.",
3-
"version": "10.50.0"
3+
"version": "10.51.0"
44
}

CHANGELOG.md

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,126 @@
44

55
- **feat(browser): Add `ingest_settings` to v2 log envelope payload ([#20453](https://github.com/getsentry/sentry-javascript/pull/20453))**
66

7-
Inference of user data (e.g. browser name/version) on log events is now gated behind the `sendDefaultPii` option. Previously, this data was always inferred by default.
7+
Inference of user data (e.g. IP address, browser name/version) on log events is now gated behind the `sendDefaultPii` option. Previously, this data was always inferred by default.
88

9-
- **feat(nitro): Add `@sentry/nitro` SDK**
9+
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
10+
11+
## 10.51.0
12+
13+
### Important Changes
14+
15+
- **feat(cloudflare): Add trace propagation for RPC method calls ([#20343](https://github.com/getsentry/sentry-javascript/pull/20343))**
16+
17+
Trace context is now propagated across Cloudflare Workers RPC calls, connecting traces between Workers and Durable Objects.
18+
This feature is opt-in and requires setting `enableRpcTracePropagation: true` in your SDK configuration:
19+
20+
```ts
21+
// Worker
22+
export default Sentry.withSentry(
23+
env => ({
24+
dsn: env.SENTRY_DSN,
25+
enableRpcTracePropagation: true,
26+
}),
27+
handler,
28+
);
29+
30+
// Durable Object
31+
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
32+
env => ({
33+
dsn: env.SENTRY_DSN,
34+
enableRpcTracePropagation: true,
35+
}),
36+
MyDurableObjectBase,
37+
);
38+
```
39+
40+
- **feat(hono)!: Change setup for `@sentry/hono/node` (`init` in external file) ([#20497](https://github.com/getsentry/sentry-javascript/pull/20497))**
41+
42+
To improve Node.js instrumentation, the `sentry()` middleware exported from `@sentry/hono/node` no longer accepts configuration options.
43+
Instead, you must configure the SDK by calling `Sentry.init()` in a dedicated instrumentation file that runs before your application code (read more in the [Hono SDK readme](https://github.com/getsentry/sentry-javascript/blob/develop/packages/hono/README.md):
44+
45+
```ts
46+
// instrument.mjs (or instrument.ts)
47+
import * as Sentry from '@sentry/hono/node';
48+
49+
Sentry.init({
50+
dsn: '__DSN__',
51+
tracesSampleRate: 1.0,
52+
});
53+
```
54+
55+
- **feat(nitro): Add `@sentry/nitro` SDK ([#19224](https://github.com/getsentry/sentry-javascript/pull/19224))**
1056

1157
A new `@sentry/nitro` package provides first-class Sentry support for [Nitro](https://nitro.build/) applications, with HTTP handler and error instrumentation, middleware tracing, request isolation, and build-time source map uploading via `withSentryConfig`.
1258
Read more in the [Nitro SDK docs](https://docs.sentry.io/platforms/javascript/guides/nitro/) and the [Nitro SDK readme](https://github.com/getsentry/sentry-javascript/blob/develop/packages/nitro/README.md).
1359

60+
### Other Changes
61+
62+
- deps(minimatch): Upgrade patch version to use new `brace-expansion` peer-dep ([#20198](https://github.com/getsentry/sentry-javascript/pull/20198))
63+
- docs: Add deprecation notices to `bin` scripts ([#20570](https://github.com/getsentry/sentry-javascript/pull/20570))
64+
- feat(astro): Drop prerendered http.server filter via `ignoreSpans` ([#20513](https://github.com/getsentry/sentry-javascript/pull/20513))
65+
- feat(aws-serverless): Validate extension tunnel DSN against `SENTRY_DSN` ([#20528](https://github.com/getsentry/sentry-javascript/pull/20528))
66+
- feat(browser): Add `ingest_settings` to span v2 envelope payload ([#20411](https://github.com/getsentry/sentry-javascript/pull/20411))
67+
- feat(browser): Add support for streamed spans in `httpContextIntegration` ([#20464](https://github.com/getsentry/sentry-javascript/pull/20464))
68+
- feat(core): Backfill otel attributes on streamed spans ([#20439](https://github.com/getsentry/sentry-javascript/pull/20439))
69+
- feat(core): clear up integrations on dispose ([#20407](https://github.com/getsentry/sentry-javascript/pull/20407))
70+
- feat(core): Instrument langgraph createReactAgent ([#20344](https://github.com/getsentry/sentry-javascript/pull/20344))
71+
- feat(core): Support attribute matching in `ignoreSpans` ([#20512](https://github.com/getsentry/sentry-javascript/pull/20512))
72+
- feat(feedback): allow error messages to be customized ([#20474](https://github.com/getsentry/sentry-javascript/pull/20474))
73+
- feat(hono): Support middleware spans defined in app groups ([#20465](https://github.com/getsentry/sentry-javascript/pull/20465))
74+
- feat(nextjs): Filter unwanted segments when span streaming is enabled ([#20384](https://github.com/getsentry/sentry-javascript/pull/20384))
75+
- feat(nextjs): Migrate edge event processors to span-first APIs ([#20551](https://github.com/getsentry/sentry-javascript/pull/20551))
76+
- feat(nextjs): Migrate server event processors to span-first APIs ([#20527](https://github.com/getsentry/sentry-javascript/pull/20527))
77+
- feat(nextjs): Set global attribute for turbopack usage ([#20558](https://github.com/getsentry/sentry-javascript/pull/20558))
78+
- feat(nitro): Nitro SDK ([#19224](https://github.com/getsentry/sentry-javascript/pull/19224))
79+
- feat(react-router): Clean up bogus `*` http.route attribute on segment spans ([#20471](https://github.com/getsentry/sentry-javascript/pull/20471))
80+
- feat(react-router): Drop low-quality transactions via `ignoreSpans` ([#20514](https://github.com/getsentry/sentry-javascript/pull/20514))
81+
- feat(sveltekit): Support span streaming in `svelteKitSpansEnhancement` integration ([#20496](https://github.com/getsentry/sentry-javascript/pull/20496))
82+
- feat(tanstackstart-react): Add dynamic tunnel route helper and generator ([#20264](https://github.com/getsentry/sentry-javascript/pull/20264))
83+
- fix: update prisma v7 spans descriptions ([#20456](https://github.com/getsentry/sentry-javascript/pull/20456))
84+
- fix(core): Avoid parse-time SyntaxError on Safari <16.4 in postgresjs ([#20498](https://github.com/getsentry/sentry-javascript/pull/20498))
85+
- fix(core): Ensure `isSentryRequest` handles subdomains properly ([#20530](https://github.com/getsentry/sentry-javascript/pull/20530))
86+
- fix(core): Ensure ip address headers are stripped when lower case ([#20484](https://github.com/getsentry/sentry-javascript/pull/20484))
87+
- fix(core): Filter more cookie names for PII ([#20485](https://github.com/getsentry/sentry-javascript/pull/20485))
88+
- fix(core): Use symbol for normalization checks ([#20486](https://github.com/getsentry/sentry-javascript/pull/20486))
89+
- fix(hono): Distinguish `.use()` middleware in sub-apps from `.all()` handlers ([#20554](https://github.com/getsentry/sentry-javascript/pull/20554))
90+
- fix(nextjs): Ensure we do not match tunnel endpoints too broadly ([#20488](https://github.com/getsentry/sentry-javascript/pull/20488))
91+
- fix(opentelemetry): Add conditional browser export to avoid node deps ([#20556](https://github.com/getsentry/sentry-javascript/pull/20556))
92+
- fix(replay): Avoid main-thread blocking in WorkerHandler under event bursts ([#20548](https://github.com/getsentry/sentry-javascript/pull/20548))
93+
- fix(replay): Ensure `maskAttributes` works with `maskAllText=false` ([#20491](https://github.com/getsentry/sentry-javascript/pull/20491))
94+
- fix(supabase): Consider `sendDefaultPii` for supabase integration ([#20490](https://github.com/getsentry/sentry-javascript/pull/20490))
95+
96+
<details>
97+
<summary><strong>Internal Changes</strong></summary>
98+
99+
- chore: Add size limit reports on PRs for Cloudflare ([#20055](https://github.com/getsentry/sentry-javascript/pull/20055))
100+
- chore: Update CODEOWNERS ([#20559](https://github.com/getsentry/sentry-javascript/pull/20559))
101+
- chore(build): Opt-out of nx analytics ([#20487](https://github.com/getsentry/sentry-javascript/pull/20487))
102+
- chore(ci): Automatically bump size limit every week ([#20531](https://github.com/getsentry/sentry-javascript/pull/20531))
103+
- chore(ci): Bump pnpm/action-setup to v5 and pin to commit SHA ([#20462](https://github.com/getsentry/sentry-javascript/pull/20462))
104+
- chore(ci): Do not report flaky test issues if we cannot find a test name ([#20589](https://github.com/getsentry/sentry-javascript/pull/20589))
105+
- chore(ci): Streamline CI setup to split bundle, layer, tarball generation ([#20396](https://github.com/getsentry/sentry-javascript/pull/20396))
106+
- chore(ci): Vendor nx-affected-list action, drop dkhunt27 dependency ([#20463](https://github.com/getsentry/sentry-javascript/pull/20463))
107+
- chore(e2e): Add vue and vue-router to nuxt-4 canary build step to fix rollup resolution ([#20519](https://github.com/getsentry/sentry-javascript/pull/20519))
108+
- chore(e2e): Remove @tanstack/start-plugin-core override ([#20518](https://github.com/getsentry/sentry-javascript/pull/20518))
109+
- chore(size-limit): weekly auto-bump ([#20572](https://github.com/getsentry/sentry-javascript/pull/20572))
110+
- chore(skill): Add skill for writing unit and E2E tests ([#20561](https://github.com/getsentry/sentry-javascript/pull/20561))
111+
- chore(test): Reduce unneeded `idleTimeout` test config ([#20467](https://github.com/getsentry/sentry-javascript/pull/20467))
112+
- ci(size-bump): Fix path in size-limit auto-bump workflow ([#20566](https://github.com/getsentry/sentry-javascript/pull/20566))
113+
- fix(e2e/tanstackstart-react): pin @tanstack/start-plugin-core to unblock CI ([#20482](https://github.com/getsentry/sentry-javascript/pull/20482))
114+
- fix(tests): Remove nitro canary test job ([#20473](https://github.com/getsentry/sentry-javascript/pull/20473))
115+
- ref(browser): Use `safeSetSpanJSONAttributes` in cultureContext integration ([#20481](https://github.com/getsentry/sentry-javascript/pull/20481))
116+
- test(browser): Unflake some more tests ([#20591](https://github.com/getsentry/sentry-javascript/pull/20591))
117+
- test(nextjs): Pin `eslint-config-next` package to major ([#20552](https://github.com/getsentry/sentry-javascript/pull/20552))
118+
- test(node): Fix flaky ANR test ([#20592](https://github.com/getsentry/sentry-javascript/pull/20592))
119+
- test(node): Fix flaky worker thread integration test ([#20588](https://github.com/getsentry/sentry-javascript/pull/20588))
120+
- test(node): Unflake postgres tests ([#20593](https://github.com/getsentry/sentry-javascript/pull/20593))
121+
- test(node): Update timeout for cron integration tests ([#20586](https://github.com/getsentry/sentry-javascript/pull/20586))
122+
- test(supabase): Stop supabase before initializing ([#20563](https://github.com/getsentry/sentry-javascript/pull/20563))
123+
- test(tanstack): Prefix test labels ([#20569](https://github.com/getsentry/sentry-javascript/pull/20569))
124+
125+
</details>
126+
14127
## 10.50.0
15128

16129
### Important Changes

dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/subject.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
setTimeout(() => {
22
const cdnScript = document.createElement('script');
3-
cdnScript.src = '/cdn.bundle.js';
3+
// Distinct URL from the loader's `/cdn.bundle.js` so Chromium cannot satisfy this via memory-cache
4+
// (would skip `page.route` and make CDN load counts flaky).
5+
cdnScript.src = `/cdn.bundle.js?sentryInjected=1`;
46

57
cdnScript.addEventListener('load', () => {
68
Sentry.init({

dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile'
3030
const tmpDir = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true, skipDsnRouteHandler: true });
3131

3232
await page.route(`${TEST_HOST}/*.*`, route => {
33-
const file = route.request().url().split('/').pop();
33+
const pathname = new URL(route.request().url()).pathname;
34+
const file = pathname.split('/').pop() || '';
3435

36+
// Loader + subject both fetch the CDN bundle. Chromium may not hit `page.route` twice for the same URL
37+
// (memory cache); subject.js uses a cache-busted URL so we reliably observe two network loads.
3538
if (file === 'cdn.bundle.js') {
3639
cdnLoadedCount++;
3740
}
@@ -47,10 +50,8 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile'
4750

4851
const eventData = envelopeRequestParser(req);
4952

50-
await waitForFunction(() => cdnLoadedCount === 2);
51-
5253
// Still loaded the CDN bundle twice
53-
expect(cdnLoadedCount).toBe(2);
54+
await expect.poll(() => cdnLoadedCount, { timeout: 15_000 }).toBe(2);
5455

5556
// But only sent to Sentry once
5657
expect(sentryEventCount).toBe(1);
@@ -62,10 +63,3 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile'
6263
expect(eventData.exception?.values?.length).toBe(1);
6364
expect(eventData.exception?.values?.[0]?.value).toBe('window.doSomethingWrong is not a function');
6465
});
65-
66-
async function waitForFunction(cb: () => boolean, timeout = 2000, increment = 100) {
67-
while (timeout > 0 && !cb()) {
68-
await new Promise(resolve => setTimeout(resolve, increment));
69-
await waitForFunction(cb, timeout - increment, increment);
70-
}
71-
}

dev-packages/browser-integration-tests/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sentry-internal/browser-integration-tests",
3-
"version": "10.50.0",
3+
"version": "10.51.0",
44
"main": "index.js",
55
"license": "MIT",
66
"engines": {
@@ -60,9 +60,9 @@
6060
"@babel/preset-typescript": "^7.16.7",
6161
"@playwright/test": "~1.56.0",
6262
"@sentry-internal/rrweb": "2.34.0",
63-
"@sentry/browser": "10.50.0",
64-
"@sentry-internal/replay": "10.50.0",
65-
"@sentry/opentelemetry": "10.50.0",
63+
"@sentry/browser": "10.51.0",
64+
"@sentry-internal/replay": "10.51.0",
65+
"@sentry/opentelemetry": "10.51.0",
6666
"@supabase/supabase-js": "2.49.3",
6767
"axios": "1.15.0",
6868
"babel-loader": "^10.1.1",

dev-packages/browser-integration-tests/suites/profiling/test-utils.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ interface ValidateProfileOptions {
77
isChunkFormat?: boolean;
88
}
99

10+
/** Seconds — consecutive chunk timestamps can jitter slightly below float precision (see profiling flakes). */
11+
const CHUNK_SAMPLE_TIMESTAMP_EPSILON_SEC = 1e-5;
12+
1013
/**
1114
* Validates the metadata of a profile chunk envelope.
1215
* https://develop.sentry.dev/sdk/telemetry/profiles/sample-format-v2/
@@ -66,9 +69,9 @@ export function validateProfile(
6669
const ts = chunkProfileSample.timestamp;
6770
expect(Number.isFinite(ts)).toBe(true);
6871
expect(ts).toBeGreaterThan(0);
69-
// Monotonic non-decreasing timestamps
70-
expect(ts).toBeGreaterThanOrEqual(previousTimestamp);
71-
previousTimestamp = ts;
72+
// Monotonic non-decreasing timestamps (epsilon: jitter / IEEE754 around ~1e9 epoch seconds)
73+
expect(ts).toBeGreaterThanOrEqual(previousTimestamp - CHUNK_SAMPLE_TIMESTAMP_EPSILON_SEC);
74+
previousTimestamp = Math.max(previousTimestamp, ts);
7275
} else {
7376
// Legacy format uses elapsed_since_start_ns as a string
7477
const legacyProfileSample = sample as ThreadCpuProfile['samples'][number];

0 commit comments

Comments
 (0)