@@ -1484,3 +1484,140 @@ test('Route manifest provides correct name when pageload span ends before lazy r
14841484 expect ( event . contexts ?. trace ?. op ) . toBe ( 'pageload' ) ;
14851485 expect ( event . contexts ?. trace ?. data ?. [ 'sentry.source' ] ) . toBe ( 'route' ) ;
14861486} ) ;
1487+
1488+ test ( 'GQL fetch span is attributed to the correct navigation transaction when navigating from index to lazy GQL page' , async ( {
1489+ page,
1490+ } ) => {
1491+ const pageloadPromise = waitForTransaction ( 'react-router-7-lazy-routes' , async transactionEvent => {
1492+ return (
1493+ ! ! transactionEvent ?. transaction &&
1494+ transactionEvent . contexts ?. trace ?. op === 'pageload' &&
1495+ transactionEvent . transaction === '/'
1496+ ) ;
1497+ } ) ;
1498+
1499+ const navigationPromise = waitForTransaction ( 'react-router-7-lazy-routes' , async transactionEvent => {
1500+ return (
1501+ ! ! transactionEvent ?. transaction &&
1502+ transactionEvent . contexts ?. trace ?. op === 'navigation' &&
1503+ transactionEvent . transaction === '/lazy-gql-a/fetch'
1504+ ) ;
1505+ } ) ;
1506+
1507+ await page . goto ( '/' ) ;
1508+ const pageloadEvent = await pageloadPromise ;
1509+
1510+ // Pageload should NOT contain any /api/graphql spans (neither UserAQuery nor UserBQuery)
1511+ const pageloadSpans = pageloadEvent . spans || [ ] ;
1512+ const pageloadGqlSpans = pageloadSpans . filter (
1513+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1514+ span . op === 'http.client' &&
1515+ ( span . description ?. includes ( '/api/graphql' ) || span . data ?. url ?. includes ( '/api/graphql' ) ) ,
1516+ ) ;
1517+ expect ( pageloadGqlSpans . length ) . toBe ( 0 ) ;
1518+
1519+ // Navigate to lazy GQL page A
1520+ const gqlLink = page . locator ( 'id=navigation-to-gql-a' ) ;
1521+ await expect ( gqlLink ) . toBeVisible ( ) ;
1522+ await gqlLink . click ( ) ;
1523+
1524+ const navigationEvent = await navigationPromise ;
1525+
1526+ // Verify the lazy GQL page rendered
1527+ await expect ( page . locator ( 'id=gql-page-a' ) ) . toBeVisible ( ) ;
1528+
1529+ // Verify the navigation transaction has the correct name
1530+ expect ( navigationEvent . transaction ) . toBe ( '/lazy-gql-a/fetch' ) ;
1531+ expect ( navigationEvent . contexts ?. trace ?. op ) . toBe ( 'navigation' ) ;
1532+
1533+ // Verify the UserAQuery GQL fetch span is inside this navigation transaction
1534+ const navSpans = navigationEvent . spans || [ ] ;
1535+ const userASpans = navSpans . filter (
1536+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1537+ span . op === 'http.client' && ( span . description ?. includes ( 'UserAQuery' ) || span . data ?. url ?. includes ( 'UserAQuery' ) ) ,
1538+ ) ;
1539+ expect ( userASpans . length ) . toBe ( 1 ) ;
1540+
1541+ // Verify NO UserBQuery spans leaked into this transaction
1542+ const userBSpans = navSpans . filter (
1543+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1544+ span . op === 'http.client' && ( span . description ?. includes ( 'UserBQuery' ) || span . data ?. url ?. includes ( 'UserBQuery' ) ) ,
1545+ ) ;
1546+ expect ( userBSpans . length ) . toBe ( 0 ) ;
1547+ } ) ;
1548+
1549+ test ( 'GQL fetch spans are attributed to correct navigation transactions when navigating between two lazy GQL pages' , async ( {
1550+ page,
1551+ } ) => {
1552+ await page . goto ( '/' ) ;
1553+ await page . waitForTimeout ( 500 ) ;
1554+
1555+ // Navigate to GQL page A
1556+ const firstNavPromise = waitForTransaction ( 'react-router-7-lazy-routes' , async transactionEvent => {
1557+ return (
1558+ ! ! transactionEvent ?. transaction &&
1559+ transactionEvent . contexts ?. trace ?. op === 'navigation' &&
1560+ transactionEvent . transaction === '/lazy-gql-a/fetch'
1561+ ) ;
1562+ } ) ;
1563+
1564+ const gqlALink = page . locator ( 'id=navigation-to-gql-a' ) ;
1565+ await expect ( gqlALink ) . toBeVisible ( ) ;
1566+ await gqlALink . click ( ) ;
1567+
1568+ const firstNavEvent = await firstNavPromise ;
1569+ await expect ( page . locator ( 'id=gql-page-a' ) ) . toBeVisible ( ) ;
1570+
1571+ // First navigation should have exactly the UserAQuery span
1572+ const firstNavSpans = firstNavEvent . spans || [ ] ;
1573+ const firstUserASpans = firstNavSpans . filter (
1574+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1575+ span . op === 'http.client' && ( span . description ?. includes ( 'UserAQuery' ) || span . data ?. url ?. includes ( 'UserAQuery' ) ) ,
1576+ ) ;
1577+ expect ( firstUserASpans . length ) . toBe ( 1 ) ;
1578+
1579+ // First navigation must NOT contain UserBQuery spans
1580+ const firstUserBSpans = firstNavSpans . filter (
1581+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1582+ span . op === 'http.client' && ( span . description ?. includes ( 'UserBQuery' ) || span . data ?. url ?. includes ( 'UserBQuery' ) ) ,
1583+ ) ;
1584+ expect ( firstUserBSpans . length ) . toBe ( 0 ) ;
1585+
1586+ // Now navigate from GQL page A to GQL page B
1587+ const secondNavPromise = waitForTransaction ( 'react-router-7-lazy-routes' , async transactionEvent => {
1588+ return (
1589+ ! ! transactionEvent ?. transaction &&
1590+ transactionEvent . contexts ?. trace ?. op === 'navigation' &&
1591+ transactionEvent . transaction === '/lazy-gql-b/fetch'
1592+ ) ;
1593+ } ) ;
1594+
1595+ const gqlBLink = page . locator ( 'id=navigate-to-gql-b' ) ;
1596+ await expect ( gqlBLink ) . toBeVisible ( ) ;
1597+ await gqlBLink . click ( ) ;
1598+
1599+ const secondNavEvent = await secondNavPromise ;
1600+ await expect ( page . locator ( 'id=gql-page-b' ) ) . toBeVisible ( ) ;
1601+
1602+ // Second navigation should have exactly the UserBQuery span
1603+ const secondNavSpans = secondNavEvent . spans || [ ] ;
1604+ const secondUserBSpans = secondNavSpans . filter (
1605+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1606+ span . op === 'http.client' && ( span . description ?. includes ( 'UserBQuery' ) || span . data ?. url ?. includes ( 'UserBQuery' ) ) ,
1607+ ) ;
1608+ expect ( secondUserBSpans . length ) . toBe ( 1 ) ;
1609+
1610+ // Second navigation must NOT contain UserAQuery spans (no leaking from first nav)
1611+ const secondUserASpans = secondNavSpans . filter (
1612+ ( span : { op ?: string ; description ?: string ; data ?: { url ?: string } } ) =>
1613+ span . op === 'http.client' && ( span . description ?. includes ( 'UserAQuery' ) || span . data ?. url ?. includes ( 'UserAQuery' ) ) ,
1614+ ) ;
1615+ expect ( secondUserASpans . length ) . toBe ( 0 ) ;
1616+
1617+ // Verify the two transactions have different trace IDs
1618+ const firstTraceId = firstNavEvent . contexts ?. trace ?. trace_id ;
1619+ const secondTraceId = secondNavEvent . contexts ?. trace ?. trace_id ;
1620+ expect ( firstTraceId ) . toBeDefined ( ) ;
1621+ expect ( secondTraceId ) . toBeDefined ( ) ;
1622+ expect ( firstTraceId ) . not . toBe ( secondTraceId ) ;
1623+ } ) ;
0 commit comments