diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/subject.js new file mode 100644 index 000000000000..d91cfe341de9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/subject.js @@ -0,0 +1,11 @@ +async function run() { + Sentry.startSpan({ name: 'parent_span' }, () => { + Sentry.startSpan({ name: 'child_span', attributes: { someAttribute: '' } }, () => { + // whatever a user would do here + }); + }); +} + +(async () => { + await run(); +})(); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts new file mode 100644 index 000000000000..a5e3e651467a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts @@ -0,0 +1,21 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest('sends an empty string attribute', async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + const childSpan = transaction.spans?.[0]; + expect(childSpan?.data?.someAttribute).toBe(''); +}); diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 3b98558f2728..5789bbe94513 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -513,6 +513,7 @@ export interface ResourceEntry extends Record { encodedBodySize?: number; decodedBodySize?: number; renderBlockingStatus?: string; + deliveryType?: string; } /** Create resource-related spans */ @@ -539,6 +540,10 @@ export function _addResourceSpans( setResourceEntrySizeData(attributes, entry, 'encodedBodySize', 'http.response_content_length'); setResourceEntrySizeData(attributes, entry, 'decodedBodySize', 'http.decoded_response_content_length'); + if (entry.deliveryType != null) { + attributes['http.response_delivery_type'] = entry.deliveryType; + } + if ('renderBlockingStatus' in entry) { attributes['resource.render_blocking_status'] = entry.renderBlockingStatus; } diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index 8e67df1645d0..be78df6920fc 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -338,6 +338,32 @@ describe('_addResourceSpans', () => { }), ); }); + + // resource delivery types: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/deliveryType + // i.e. better but not yet widely supported way to check for browser cache hit + it.each(['cache', 'navigational-prefetch', ''])( + 'attaches delivery type ("%s") to resource spans if available', + deliveryType => { + const spans: Span[] = []; + + getClient()?.on('spanEnd', span => { + spans.push(span); + }); + + const entry: ResourceEntry = { + initiatorType: 'css', + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + deliveryType, + }; + + _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); + + expect(spans).toHaveLength(1); + expect(spanToJSON(spans[0]!).data).toMatchObject({ 'http.response_delivery_type': deliveryType }); + }, + ); }); const setGlobalLocation = (location: Location) => {