diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts index c554c47d8dde..39e07b89c0ee 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { waitForEnvelopeItem, waitForTransaction } from '@sentry-internal/test-utils'; test('sends a pageload transaction with a parameterized URL', async ({ page }) => { const transactionPromise = waitForTransaction('react-router-6', async transactionEvent => { @@ -54,3 +54,45 @@ test('sends a navigation transaction with a parameterized URL', async ({ page }) }, }); }); + +test('sends an INP span', async ({ page }) => { + const inpSpanPromise = waitForEnvelopeItem('react-router-6', item => { + return item[0].type === 'span'; + }); + + await page.goto(`/`); + + await page.click('#exception-button'); + + await page.waitForTimeout(500); + + // Page hide to trigger INP + await page.evaluate(() => { + window.dispatchEvent(new Event('pagehide')); + }); + + const inpSpan = await inpSpanPromise; + + expect(inpSpan[1]).toEqual({ + data: { + 'sentry.origin': 'auto.http.browser.inp', + 'sentry.op': 'ui.interaction.click', + release: 'e2e-test', + environment: 'qa', + transaction: '/', + 'sentry.exclusive_time': expect.any(Number), + replay_id: expect.any(String), + }, + description: 'body > div#root > input#exception-button[type="button"]', + op: 'ui.interaction.click', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.browser.inp', + exclusive_time: expect.any(Number), + measurements: { inp: { unit: 'millisecond', value: expect.any(Number) } }, + segment_id: expect.any(String), + }); +}); diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 25ddbfe94966..82693bf4af89 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -115,14 +115,28 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P eventCallbackListeners.forEach(listener => { const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); - const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody), - rawProxyRequestBody: proxyRequestBody, - rawSentryResponseBody, - sentryResponseStatusCode: sentryResponse.statusCode, - }; - - listener(Buffer.from(JSON.stringify(data)).toString('base64')); + try { + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody), + rawProxyRequestBody: proxyRequestBody, + rawSentryResponseBody, + sentryResponseStatusCode: sentryResponse.statusCode, + }; + + listener(Buffer.from(JSON.stringify(data)).toString('base64')); + } catch (error) { + if (`${error}`.includes('Unexpected token') && proxyRequestBody.includes('{"type":"replay_event"}')) { + // eslint-disable-next-line no-console + console.log('[event-proxy-server] Info: Received replay event, skipping...'); + } else { + // eslint-disable-next-line no-console + console.error( + '[event-proxy-server] Error: Failed to parse Sentry request envelope', + error, + proxyRequestBody, + ); + } + } }); proxyResponse.end(); });