Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -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),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't parent span be undefined for INP spans?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it? 😅 well today it is not! if there is an ongoing transaction, it will be a child of that. is that not correct? for me this seems like it makes sense, but 🤷 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that INP spans were always root spans, but maybe I'm mistaken.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @Lms24 when you're eventually back, you checked this out at the time. I'll merge this PR (as it shows the current behavior) but we can double check if this is fine.

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),
});
});
30 changes: 22 additions & 8 deletions dev-packages/test-utils/src/event-proxy-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added this to clean up logs here a bit, replay events will always fail to parse because they are compressed and not JSON.

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,

Check warning

Code scanning / CodeQL

Log injection

Log entry depends on a [user-provided value](1).
);
}
}
});
proxyResponse.end();
});
Expand Down