@@ -4,9 +4,30 @@ import { test } from 'vitest';
44
55const authToken = process . env . E2E_TEST_AUTH_TOKEN ;
66const sentryTestOrgSlug = process . env . E2E_TEST_SENTRY_ORG_SLUG ;
7- const sentryTestProject = process . env . E2E_TEST_SENTRY_PROJECT ;
87const EVENT_POLLING_TIMEOUT = 90_000 ;
98
9+ /**
10+ * The event serializer emits source context as `[lineNo, line]` pairs spanning the frame's line
11+ * and its surroundings, rather than the separate pre/context/post fields of the raw event.
12+ */
13+ interface SerializedFrame {
14+ lineNo : number | null ;
15+ colNo : number | null ;
16+ context : [ number , string ] [ ] | null ;
17+ }
18+
19+ function splitFrameContext ( frame : SerializedFrame ) : Record < string , unknown > {
20+ const context = frame . context ?? [ ] ;
21+
22+ return {
23+ preContext : context . filter ( ( [ lineNo ] ) => lineNo < ( frame . lineNo ?? 0 ) ) . map ( ( [ , line ] ) => line ) ,
24+ contextLine : context . find ( ( [ lineNo ] ) => lineNo === frame . lineNo ) ?. [ 1 ] ,
25+ postContext : context . filter ( ( [ lineNo ] ) => lineNo > ( frame . lineNo ?? 0 ) ) . map ( ( [ , line ] ) => line ) ,
26+ lineno : frame . lineNo ,
27+ colno : frame . colNo ,
28+ } ;
29+ }
30+
1031test (
1132 'Find symbolicated event on sentry' ,
1233 async ( { expect } ) => {
@@ -23,27 +44,35 @@ test(
2344
2445 while ( ! timedOut ) {
2546 await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ; // poll every two seconds
26- const response = await fetch (
27- `https://sentry.io/api/0/projects/${ sentryTestOrgSlug } /${ sentryTestProject } /events/${ eventId } /json/` ,
28- { headers : { Authorization : `Bearer ${ authToken } ` } } ,
29- ) ;
47+ const response = await fetch ( `https://sentry.io/api/0/organizations/${ sentryTestOrgSlug } /eventids/${ eventId } /` , {
48+ headers : { Authorization : `Bearer ${ authToken } ` } ,
49+ } ) ;
50+
51+ // This is org scoped, so the auth token needs `org:read` on top of the project scopes.
52+ // That never resolves by waiting, so fail loudly rather than timing out.
53+ if ( response . status === 401 || response . status === 403 ) {
54+ throw new Error (
55+ `Event lookup was rejected with ${ response . status } : ${ await response . text ( ) } . ` +
56+ 'E2E_TEST_AUTH_TOKEN needs the `org:read` scope.' ,
57+ ) ;
58+ }
3059
3160 // Only allow ok responses or 404
3261 if ( ! response . ok ) {
3362 expect ( response . status ) . toBe ( 404 ) ;
3463 continue ;
3564 }
3665
37- const eventPayload = await response . json ( ) ;
38- const frames = eventPayload . exception ?. values ?. [ 0 ] ?. stacktrace ?. frames ;
66+ const { event } = await response . json ( ) ;
67+ const exception = event . entries . find ( ( entry : { type : string } ) => entry . type === 'exception' ) ;
68+ const frames : SerializedFrame [ ] = exception . data . values [ 0 ] . stacktrace . frames ;
3969 const topFrame = frames [ frames . length - 1 ] ;
40- expect ( {
41- preContext : topFrame ?. pre_context ,
42- contextLine : topFrame ?. context_line ,
43- postContext : topFrame ?. post_context ,
44- lineno : topFrame ?. lineno ,
45- colno : topFrame ?. colno ,
46- } ) . toMatchSnapshot ( ) ;
70+
71+ if ( topFrame === undefined ) {
72+ throw new Error ( 'Symbolicated event has no stack frames.' ) ;
73+ }
74+
75+ expect ( splitFrameContext ( topFrame ) ) . toMatchSnapshot ( ) ;
4776 return ;
4877 }
4978
0 commit comments