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
@@ -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