11import { 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