Skip to content

Commit 9ac9641

Browse files
JPeer264claude
andauthored
test(test-utils): Add fetchSpanAttributes to read span attributes via the sentry CLI
`sentry trace view --json` drops all attributes of a span when the trace-items endpoint returns an `int` attribute, because the endpoint sends those values as strings and the CLI's schema rejects them. Read the attributes through `sentry api` instead, and share the CLI runner with fetchTrace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 06a2d11 commit 9ac9641

1 file changed

Lines changed: 40 additions & 8 deletions

File tree

  • dev-packages/test-utils/src

‎dev-packages/test-utils/src/cli.ts‎

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { spawnSync } from 'node:child_process';
2+
import type { SpawnSyncReturns } from 'node:child_process';
23

34
/**
45
* Spans only become queryable once they have made it through to EAP, which takes
@@ -30,13 +31,8 @@ export function traceTarget(traceId: string): string {
3031
return `${process.env['E2E_TEST_SENTRY_ORG_SLUG']}/${process.env['E2E_TEST_SENTRY_PROJECT']}/${traceId}`;
3132
}
3233

33-
/**
34-
* Fetch a trace of the E2E test project through the `sentry` CLI, which the calling test app has to
35-
* list as a dev dependency. Returns an empty list while the trace has not landed yet.
36-
*/
37-
export function fetchTrace(traceId: string): TraceItem[] {
38-
const target = traceTarget(traceId);
39-
const result = spawnSync('pnpm', ['exec', 'sentry', 'trace', 'view', target, '--json', '--fresh'], {
34+
function runSentryCli(args: string[]): SpawnSyncReturns<string> {
35+
const result = spawnSync('pnpm', ['exec', 'sentry', ...args], {
4036
encoding: 'utf8',
4137
maxBuffer: 64 * 1024 * 1024,
4238
env: {
@@ -50,11 +46,22 @@ export function fetchTrace(traceId: string): TraceItem[] {
5046

5147
if (result.error) {
5248
throw new Error(
53-
`Could not run \`pnpm exec sentry trace view\`: ${result.error.message}. ` +
49+
`Could not run \`pnpm exec sentry ${args[0]}\`: ${result.error.message}. ` +
5450
'The test app needs `sentry` as a dev dependency.',
5551
);
5652
}
5753

54+
return result;
55+
}
56+
57+
/**
58+
* Fetch a trace of the E2E test project through the `sentry` CLI, which the calling test app has to
59+
* list as a dev dependency. Returns an empty list while the trace has not landed yet.
60+
*/
61+
export function fetchTrace(traceId: string): TraceItem[] {
62+
const target = traceTarget(traceId);
63+
const result = runSentryCli(['trace', 'view', target, '--json', '--fresh']);
64+
5865
if (result.status === 0) {
5966
return (JSON.parse(result.stdout) as { spans?: TraceItem[] }).spans ?? [];
6067
}
@@ -80,6 +87,31 @@ export function fetchTrace(traceId: string): TraceItem[] {
8087
throw new Error(`sentry trace view ${target} exited with ${result.status}: ${result.stderr}`);
8188
}
8289

90+
/**
91+
* Fetch all attributes of a span in the E2E test project, keyed by attribute name. Returns
92+
* `undefined` while the span is not queryable yet.
93+
*
94+
* `sentry trace view --json` cannot be used for this: the trace-items endpoint sends `int` attribute
95+
* values as strings, the CLI's schema rejects that, and the CLI then drops all attributes of the span.
96+
*/
97+
export function fetchSpanAttributes(traceId: string, spanId: string): Record<string, unknown> | undefined {
98+
const path =
99+
`/projects/${process.env['E2E_TEST_SENTRY_ORG_SLUG']}/${process.env['E2E_TEST_SENTRY_PROJECT']}` +
100+
`/trace-items/${spanId}/?trace_id=${traceId}&item_type=spans`;
101+
const result = runSentryCli(['api', path]);
102+
103+
if (result.status === 0) {
104+
const { attributes } = JSON.parse(result.stdout) as { attributes: { name: string; value: unknown }[] };
105+
return Object.fromEntries(attributes.map(({ name, value }) => [name, value]));
106+
}
107+
108+
if (result.stdout.includes('"Not found."')) {
109+
return undefined;
110+
}
111+
112+
throw new Error(`sentry api ${path} exited with ${result.status}: ${result.stdout}${result.stderr}`);
113+
}
114+
83115
/**
84116
* Errors attach to whichever span was active when they were captured, and relocate from the
85117
* top level into that span once it lands, so a given event can surface at any depth.

0 commit comments

Comments
 (0)