Skip to content

Commit 5420b4c

Browse files
JPeer264claude
andcommitted
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 beca211 commit 5420b4c

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
@@ -28,13 +29,8 @@ export function traceTarget(traceId: string): string {
2829
return `${process.env['E2E_TEST_SENTRY_ORG_SLUG']}/${process.env['E2E_TEST_SENTRY_PROJECT']}/${traceId}`;
2930
}
3031

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

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

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

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

0 commit comments

Comments
 (0)