diff --git a/.gitignore b/.gitignore index 350639942..7262f8135 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ dist test-results build-test-results .turbo/ +.claude/settings.local.json +.vscode diff --git a/packages/web-sdk/src/api-page/api/PageAPI/PageAPI.test.ts b/packages/web-sdk/src/api-page/api/PageAPI/PageAPI.test.ts index b7327a6f2..f42d8ca96 100644 --- a/packages/web-sdk/src/api-page/api/PageAPI/PageAPI.test.ts +++ b/packages/web-sdk/src/api-page/api/PageAPI/PageAPI.test.ts @@ -40,6 +40,8 @@ describe('PageAPI', () => { getCurrentRoute: sinon.stub().returns(mockRoute), getCurrentPageId: sinon.stub().returns('test-page-id'), clearCurrentRoute: sinon.stub(), + setPageLabel: sinon.stub(), + getPageLabel: sinon.stub(), }; pageAPI.setGlobalPageManager(mockPageManager); const pageManager = pageAPI.getPageManager(); @@ -55,6 +57,8 @@ describe('PageAPI', () => { getCurrentRoute: sinon.stub().returns(mockRoute), getCurrentPageId: sinon.stub().returns('test-page-id'), clearCurrentRoute: sinon.stub(), + setPageLabel: sinon.stub(), + getPageLabel: sinon.stub(), }; pageAPI.setGlobalPageManager(mockPageManager); diff --git a/packages/web-sdk/src/api-page/manager/NoOpPageManager/NoOpPageManager.ts b/packages/web-sdk/src/api-page/manager/NoOpPageManager/NoOpPageManager.ts index b6c391228..77b19f5f5 100644 --- a/packages/web-sdk/src/api-page/manager/NoOpPageManager/NoOpPageManager.ts +++ b/packages/web-sdk/src/api-page/manager/NoOpPageManager/NoOpPageManager.ts @@ -11,5 +11,11 @@ export class NoOpPageManager implements PageManager { return ''; } + public setPageLabel(_label: string): void {} + + public getPageLabel(): string | null { + return null; + } + public clearCurrentRoute(): void {} } diff --git a/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.test.ts b/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.test.ts index bafb058b1..d7d90a498 100644 --- a/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.test.ts +++ b/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.test.ts @@ -21,6 +21,8 @@ describe('ProxyPageManager', () => { getCurrentRoute: sinon.stub().returns(mockRoute), getCurrentPageId: sinon.stub().returns('test-page-id'), clearCurrentRoute: sinon.stub(), + setPageLabel: sinon.stub(), + getPageLabel: sinon.stub(), }; }); diff --git a/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.ts b/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.ts index a91038b6b..3abf4a6bb 100644 --- a/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.ts +++ b/packages/web-sdk/src/api-page/manager/ProxyPageManager/ProxyPageManager.ts @@ -26,6 +26,14 @@ export class ProxyPageManager implements PageManager { return this.getDelegate().getCurrentPageId(); } + public setPageLabel(label: string): void { + this.getDelegate().setPageLabel(label); + } + + public getPageLabel(): string | null { + return this.getDelegate().getPageLabel(); + } + public clearCurrentRoute(): void { this.getDelegate().clearCurrentRoute(); } diff --git a/packages/web-sdk/src/api-page/manager/types.ts b/packages/web-sdk/src/api-page/manager/types.ts index b76b7ab25..c502636a4 100644 --- a/packages/web-sdk/src/api-page/manager/types.ts +++ b/packages/web-sdk/src/api-page/manager/types.ts @@ -3,6 +3,8 @@ export interface Route { path: string; // This is the URL of the route after replacing the URL params. i.e. /products/123 url: string; + // Optional label for the route, used as app.surface.label + label?: string; } export interface PageManager { @@ -12,5 +14,9 @@ export interface PageManager { getCurrentPageId: () => string | null; + setPageLabel: (label: string) => void; + + getPageLabel: () => string | null; + clearCurrentRoute: () => void; } diff --git a/packages/web-sdk/src/common/attributes.ts b/packages/web-sdk/src/common/attributes.ts new file mode 100644 index 000000000..f6583d85e --- /dev/null +++ b/packages/web-sdk/src/common/attributes.ts @@ -0,0 +1,6 @@ +import { KEY_APP_SURFACE_LABEL } from '../constants/index.ts'; + +// These attributes are exposed in the public API for users to use in the attributes scrubber +export const attributes = { + pageLabel: KEY_APP_SURFACE_LABEL, +}; diff --git a/packages/web-sdk/src/common/index.ts b/packages/web-sdk/src/common/index.ts index 6283b0289..7a13cd04f 100644 --- a/packages/web-sdk/src/common/index.ts +++ b/packages/web-sdk/src/common/index.ts @@ -1,6 +1,8 @@ +export { attributes } from './attributes.ts'; export type { AttributeScrubber, PathnameDocument, + TitleDocument, URLDocument, VisibilityStateDocument, } from './types.ts'; diff --git a/packages/web-sdk/src/common/types.ts b/packages/web-sdk/src/common/types.ts index 03f1c501a..f89b5a763 100644 --- a/packages/web-sdk/src/common/types.ts +++ b/packages/web-sdk/src/common/types.ts @@ -13,6 +13,11 @@ export interface PathnameDocument { pathname: string; } +// Useful for testing so that we can pass in a document-like object and change its title +export interface TitleDocument { + title: string; +} + export interface AttributeScrubber { key: string; scrub: (value: string) => string; diff --git a/packages/web-sdk/src/constants/attributes.ts b/packages/web-sdk/src/constants/attributes.ts index 955ccba45..035a76b41 100644 --- a/packages/web-sdk/src/constants/attributes.ts +++ b/packages/web-sdk/src/constants/attributes.ts @@ -30,6 +30,7 @@ export const KEY_EMB_MAX_PENDING_SPANS_REACHED = // to be consistent with mobile where we use 'app.surface.*' for screen names and ids export const KEY_EMB_PAGE_PATH = 'app.surface.name'; export const KEY_EMB_PAGE_ID = 'app.surface.id'; +export const KEY_APP_SURFACE_LABEL = 'app.surface.label'; export enum EMB_TYPES { Session = 'ux.session', diff --git a/packages/web-sdk/src/constants/index.ts b/packages/web-sdk/src/constants/index.ts index 9f802548d..1a75c5092 100644 --- a/packages/web-sdk/src/constants/index.ts +++ b/packages/web-sdk/src/constants/index.ts @@ -4,6 +4,7 @@ export { EMB_NAVIGATION_INSTRUMENTATIONS, EMB_STATES, EMB_TYPES, + KEY_APP_SURFACE_LABEL, KEY_EMB_APP_INSTANCE_ID, KEY_EMB_COLD_START, KEY_EMB_ERROR_CODE, diff --git a/packages/web-sdk/src/index.ts b/packages/web-sdk/src/index.ts index 0babada25..c08ee953d 100644 --- a/packages/web-sdk/src/index.ts +++ b/packages/web-sdk/src/index.ts @@ -6,6 +6,7 @@ export { session } from './api-sessions/index.ts'; export type { ExtendedSpan } from './api-traces/index.ts'; export { trace } from './api-traces/index.ts'; export { user } from './api-users/index.ts'; +export { attributes } from './common/index.ts'; export { getNavigationInstrumentation } from './instrumentations/index.ts'; export type { DynamicConfigManager, DynamicSDKConfig } from './sdk/index.ts'; export { initSDK } from './sdk/index.ts'; diff --git a/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.test.ts b/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.test.ts index 75290a28a..2088c42e7 100644 --- a/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.test.ts +++ b/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.test.ts @@ -2,6 +2,7 @@ import * as chai from 'chai'; import sinonChai from 'sinon-chai'; import { UUID_PATTERN } from '../../../tests/utils/constants.ts'; import type { Route } from '../../api-page/index.ts'; +import type { TitleDocument } from '../../common/index.ts'; import { EmbracePageManager } from './EmbracePageManager.ts'; chai.use(sinonChai); @@ -9,9 +10,11 @@ const { expect } = chai; describe('EmbracePageManager', () => { let pageManager: EmbracePageManager; + let mockDocument: TitleDocument; beforeEach(() => { - pageManager = new EmbracePageManager(); + mockDocument = { title: '' }; + pageManager = new EmbracePageManager({ titleDocument: mockDocument }); }); it('should initialize with null values', () => { @@ -72,4 +75,46 @@ describe('EmbracePageManager', () => { expect(initialPageId).to.equal(secondPageId); }); + + it('should set and get custom route label', () => { + pageManager.setPageLabel('my-custom-label'); + expect(pageManager.getPageLabel()).to.equal('my-custom-label'); + }); + + it('should fallback to document.title when custom label is not set', () => { + mockDocument.title = 'My Page Title'; + expect(pageManager.getPageLabel()).to.equal('My Page Title'); + }); + + it('should not fallback to document.title when fallback is disabled', () => { + const customPageManager = new EmbracePageManager({ + useDocumentTitleAsPageLabel: false, + titleDocument: mockDocument, + }); + mockDocument.title = 'My Page Title'; + void expect(customPageManager.getPageLabel()).to.be.null; + }); + + it('should prefer custom label over document.title fallback', () => { + mockDocument.title = 'My Page Title'; + pageManager.setPageLabel('custom-label'); + expect(pageManager.getPageLabel()).to.equal('custom-label'); + }); + + it('should set route label from route.label when setting route', () => { + const routeWithLabel: Route = { + path: '/products/:id', + url: '/products/123', + label: 'Products Page', + }; + pageManager.setCurrentRoute(routeWithLabel); + expect(pageManager.getPageLabel()).to.equal('Products Page'); + }); + + it('should clear custom label on routing', () => { + mockDocument.title = 'My Page Title'; + pageManager.setPageLabel('custom-label'); + pageManager.clearCurrentRoute(); + expect(pageManager.getPageLabel()).to.equal('My Page Title'); + }); }); diff --git a/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.ts b/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.ts index f28737bf9..4bc98821c 100644 --- a/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.ts +++ b/packages/web-sdk/src/managers/EmbracePageManager/EmbracePageManager.ts @@ -1,24 +1,57 @@ import type { PageManager, Route } from '../../api-page/index.ts'; +import type { TitleDocument } from '../../common/index.ts'; import { generateUUID } from '../../utils/index.ts'; +import type { EmbracePageManagerArgs } from './types.ts'; export class EmbracePageManager implements PageManager { private _currentRoute: Route | null = null; private _currentPageId: string | null = null; + private _pageLabel: string | null = null; + private readonly _titleDocument: TitleDocument | undefined; + private readonly _useDocumentTitleAsPageLabel: boolean; + + public constructor({ + useDocumentTitleAsPageLabel = true, + titleDocument = window.document, + }: EmbracePageManagerArgs = {}) { + this._useDocumentTitleAsPageLabel = useDocumentTitleAsPageLabel; + this._titleDocument = titleDocument; + } public getCurrentPageId = (): string | null => this._currentPageId; public getCurrentRoute = () => this._currentRoute; + public setPageLabel = (label: string): void => { + this._pageLabel = label; + }; + + public getPageLabel = (): string | null => { + return ( + this._pageLabel || + (!this._useDocumentTitleAsPageLabel || !this._titleDocument + ? null + : this._titleDocument.title) + ); + }; + public setCurrentRoute = (route: Route) => { if (!this._currentRoute || this._currentRoute.url !== route.url) { this._currentPageId = generateUUID(); } this._currentRoute = route; + + if (route.label) { + this._pageLabel = route.label; + } else { + this._pageLabel = null; + } }; public clearCurrentRoute = () => { this._currentRoute = null; this._currentPageId = null; + this._pageLabel = null; }; } diff --git a/packages/web-sdk/src/managers/EmbracePageManager/types.ts b/packages/web-sdk/src/managers/EmbracePageManager/types.ts index 11093398c..83829eaaa 100644 --- a/packages/web-sdk/src/managers/EmbracePageManager/types.ts +++ b/packages/web-sdk/src/managers/EmbracePageManager/types.ts @@ -1,9 +1,12 @@ import type { DiagLogger } from '@opentelemetry/api'; +import type { TitleDocument } from '../../common/index.ts'; import type { EMB_NAVIGATION_INSTRUMENTATIONS } from '../../constants/index.ts'; export interface EmbracePageManagerArgs { diag?: DiagLogger; shouldCleanupPathOptionsFromRouteName?: boolean; + useDocumentTitleAsPageLabel?: boolean; + titleDocument?: TitleDocument; } export interface SetCurrentRouteSpanOptions { diff --git a/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.test.ts b/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.test.ts index cbf369d6b..6164e3c71 100644 --- a/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.test.ts +++ b/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.test.ts @@ -4,7 +4,11 @@ import type { InMemoryLogRecordExporter } from '@opentelemetry/sdk-logs'; import * as chai from 'chai'; import { setupTestLogExporter } from '../../../tests/utils/index.ts'; import type { PageManager, Route } from '../../api-page/index.ts'; -import { KEY_EMB_PAGE_ID, KEY_EMB_PAGE_PATH } from '../../constants/index.ts'; +import { + KEY_APP_SURFACE_LABEL, + KEY_EMB_PAGE_ID, + KEY_EMB_PAGE_PATH, +} from '../../constants/index.ts'; import { EmbracePageManager } from '../../managers/index.ts'; import { PageLogRecordProcessor } from './PageLogRecordProcessor.ts'; @@ -49,6 +53,21 @@ describe('PageLogRecordProcessor', () => { pageManager.getCurrentPageId(), ); expect(log.attributes[KEY_EMB_PAGE_PATH]).to.equal('/products/:id'); + void expect(log.attributes[KEY_APP_SURFACE_LABEL]).to.be.undefined; + }); + + it('should attach custom label when available', () => { + pageManager.setCurrentRoute(mockRoute); + pageManager.setPageLabel('CustomLabel'); + + logger.emit({ + body: 'some log', + }); + + const finishedLogs = memoryExporter.getFinishedLogRecords(); + const log = finishedLogs[finishedLogs.length - 1]; + + expect(log.attributes[KEY_APP_SURFACE_LABEL]).to.equal('CustomLabel'); }); it('should not override page attributes', () => { @@ -70,6 +89,23 @@ describe('PageLogRecordProcessor', () => { expect(log.attributes[KEY_EMB_PAGE_PATH]).to.equal('/custom/path'); }); + it('should not override surface label attribute', () => { + pageManager.setCurrentRoute(mockRoute); + pageManager.setPageLabel('DefaultLabel'); + + logger.emit({ + body: 'some log', + attributes: { + [KEY_APP_SURFACE_LABEL]: 'ExistingLabel', + }, + }); + + const finishedLogs = memoryExporter.getFinishedLogRecords(); + const log = finishedLogs[finishedLogs.length - 1]; + + expect(log.attributes[KEY_APP_SURFACE_LABEL]).to.equal('ExistingLabel'); + }); + it('should not attach surface name and id when route is null', () => { pageManager.clearCurrentRoute(); diff --git a/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.ts b/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.ts index c8c937bcf..5d3ffbdf5 100644 --- a/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.ts +++ b/packages/web-sdk/src/processors/PageLogRecordProcessor/PageLogRecordProcessor.ts @@ -1,6 +1,10 @@ import type { LogRecordProcessor, SdkLogRecord } from '@opentelemetry/sdk-logs'; import type { PageManager } from '../../api-page/index.ts'; -import { KEY_EMB_PAGE_ID, KEY_EMB_PAGE_PATH } from '../../constants/index.ts'; +import { + KEY_APP_SURFACE_LABEL, + KEY_EMB_PAGE_ID, + KEY_EMB_PAGE_PATH, +} from '../../constants/index.ts'; import type { PageLogRecordProcessorArgs } from './types.ts'; export class PageLogRecordProcessor implements LogRecordProcessor { @@ -16,22 +20,25 @@ export class PageLogRecordProcessor implements LogRecordProcessor { } public onEmit(logRecord: SdkLogRecord): void { + // If the log already has page attributes, do not override them if ( - logRecord.attributes[KEY_EMB_PAGE_PATH] || - logRecord.attributes[KEY_EMB_PAGE_ID] + !logRecord.attributes[KEY_EMB_PAGE_PATH] || + !logRecord.attributes[KEY_EMB_PAGE_ID] ) { - // If the log already has page attributes, do not override them - return; - } + const currentRoute = this._pageManager.getCurrentRoute(); - const currentRoute = this._pageManager.getCurrentRoute(); + if (currentRoute) { + logRecord.setAttribute(KEY_EMB_PAGE_PATH, currentRoute.path); + logRecord.setAttribute( + KEY_EMB_PAGE_ID, + this._pageManager.getCurrentPageId(), + ); + } + } - if (currentRoute) { - logRecord.setAttribute(KEY_EMB_PAGE_PATH, currentRoute.path); - logRecord.setAttribute( - KEY_EMB_PAGE_ID, - this._pageManager.getCurrentPageId(), - ); + const appSurfaceLabel = this._pageManager.getPageLabel(); + if (appSurfaceLabel && !logRecord.attributes[KEY_APP_SURFACE_LABEL]) { + logRecord.setAttribute(KEY_APP_SURFACE_LABEL, appSurfaceLabel); } } diff --git a/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.test.ts b/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.test.ts index 7a3d30002..561510895 100644 --- a/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.test.ts +++ b/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.test.ts @@ -4,7 +4,11 @@ import type { InMemorySpanExporter } from '@opentelemetry/sdk-trace-web'; import * as chai from 'chai'; import { setupTestTraceExporter } from '../../../tests/utils/index.ts'; import type { PageManager, Route } from '../../api-page/index.ts'; -import { KEY_EMB_PAGE_ID, KEY_EMB_PAGE_PATH } from '../../constants/index.ts'; +import { + KEY_APP_SURFACE_LABEL, + KEY_EMB_PAGE_ID, + KEY_EMB_PAGE_PATH, +} from '../../constants/index.ts'; import { EmbracePageManager } from '../../managers/index.ts'; import { PageSpanProcessor } from './PageSpanProcessor.ts'; @@ -50,6 +54,22 @@ describe('PageSpanProcessor', () => { expect(readableSpan.attributes[KEY_EMB_PAGE_PATH]).to.equal( '/products/:id', ); + void expect(readableSpan.attributes[KEY_APP_SURFACE_LABEL]).to.be.undefined; + }); + + it('should attach custom label when available', () => { + pageManager.setCurrentRoute(mockRoute); + pageManager.setPageLabel('SpanLabel'); + + const span = tracer.startSpan('test-span-label'); + span.end(); + + const finishedSpans = memoryExporter.getFinishedSpans(); + const readableSpan = finishedSpans[finishedSpans.length - 1]; + + expect(readableSpan.attributes[KEY_APP_SURFACE_LABEL]).to.equal( + 'SpanLabel', + ); }); it('should not attach page attributes when route is null', () => { @@ -88,6 +108,25 @@ describe('PageSpanProcessor', () => { ); }); + it('should not override surface label attribute', () => { + pageManager.setCurrentRoute(mockRoute); + pageManager.setPageLabel('DefaultLabel'); + + const span = tracer.startSpan('span-with-label', { + attributes: { + [KEY_APP_SURFACE_LABEL]: 'ExistingLabel', + }, + }); + span.end(); + + const finishedSpans = memoryExporter.getFinishedSpans(); + const readableSpan = finishedSpans[finishedSpans.length - 1]; + + expect(readableSpan.attributes[KEY_APP_SURFACE_LABEL]).to.be.equal( + 'ExistingLabel', + ); + }); + it('should make sure forceFlush no-op does not fail', () => { const processor = new PageSpanProcessor({ pageManager: new EmbracePageManager(), diff --git a/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.ts b/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.ts index 0f32c82c9..525e308cc 100644 --- a/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.ts +++ b/packages/web-sdk/src/processors/PageSpanProcessor/PageSpanProcessor.ts @@ -1,6 +1,10 @@ import type { ReadableSpan, SpanProcessor } from '@opentelemetry/sdk-trace-web'; import type { PageManager } from '../../api-page/index.ts'; -import { KEY_EMB_PAGE_ID, KEY_EMB_PAGE_PATH } from '../../constants/index.ts'; +import { + KEY_APP_SURFACE_LABEL, + KEY_EMB_PAGE_ID, + KEY_EMB_PAGE_PATH, +} from '../../constants/index.ts'; import type { PageSpanProcessorArgs } from './types.ts'; export class PageSpanProcessor implements SpanProcessor { @@ -16,20 +20,23 @@ export class PageSpanProcessor implements SpanProcessor { // Attach page attributes at span end to capture the page where the span completed public onEnd(span: ReadableSpan): void { + // If the span already has page attributes, do not override them if ( - span.attributes[KEY_EMB_PAGE_PATH] || - span.attributes[KEY_EMB_PAGE_ID] + !span.attributes[KEY_EMB_PAGE_PATH] || + !span.attributes[KEY_EMB_PAGE_ID] ) { - // If the span already has page attributes, do not override them - return; - } + const currentRoute = this._pageManager.getCurrentRoute(); + const currentPageId = this._pageManager.getCurrentPageId(); - const currentRoute = this._pageManager.getCurrentRoute(); - const currentPageId = this._pageManager.getCurrentPageId(); + if (currentRoute && currentPageId) { + span.attributes[KEY_EMB_PAGE_PATH] = currentRoute.path; + span.attributes[KEY_EMB_PAGE_ID] = currentPageId; + } + } - if (currentRoute && currentPageId) { - span.attributes[KEY_EMB_PAGE_PATH] = currentRoute.path; - span.attributes[KEY_EMB_PAGE_ID] = currentPageId; + const appSurfaceLabel = this._pageManager.getPageLabel(); + if (appSurfaceLabel && !span.attributes[KEY_APP_SURFACE_LABEL]) { + span.attributes[KEY_APP_SURFACE_LABEL] = appSurfaceLabel; } } diff --git a/packages/web-sdk/src/sdk/initSDK.ts b/packages/web-sdk/src/sdk/initSDK.ts index 05f243efd..7081ce783 100644 --- a/packages/web-sdk/src/sdk/initSDK.ts +++ b/packages/web-sdk/src/sdk/initSDK.ts @@ -96,6 +96,7 @@ export const initSDK = ( registerGlobally = true, blockNetworkSpanForwarding = false, restrictedProtocols = new Set(['file:']), + useDocumentTitleAsPageLabel = true, }: SDKInitConfig = {} as SDKInitConfig, ): SDKControl | false => { try { @@ -240,7 +241,10 @@ export const initSDK = ( ); } - const pageManager = setupPage({ registerGlobally }); + const pageManager = setupPage({ + useDocumentTitleAsPageLabel, + registerGlobally, + }); const { tracerProvider, embraceTraceManager } = setupTraces({ resource: resourceWithWebSDKAttributes, @@ -485,8 +489,13 @@ const setupLogs = ({ return { loggerProvider, embraceLogManager }; }; -const setupPage = ({ registerGlobally }: SetupPageArgs) => { - const embracePageManager = new EmbracePageManager(); +const setupPage = ({ + useDocumentTitleAsPageLabel, + registerGlobally, +}: SetupPageArgs) => { + const embracePageManager = new EmbracePageManager({ + useDocumentTitleAsPageLabel, + }); if (registerGlobally) { page.setGlobalPageManager(embracePageManager); diff --git a/packages/web-sdk/src/sdk/types.ts b/packages/web-sdk/src/sdk/types.ts index 3fb50d424..6586d90f2 100644 --- a/packages/web-sdk/src/sdk/types.ts +++ b/packages/web-sdk/src/sdk/types.ts @@ -223,6 +223,13 @@ type BaseSDKInitConfig = { * **default**: new Set(['file:']) */ restrictedProtocols?: Set; + + /** + * useDocumentTitleAsPageLabel enables the fallback to document.title for page label when a custom label is not set. + * + * **default**: true + */ + useDocumentTitleAsPageLabel?: boolean; }; /* @@ -328,6 +335,7 @@ export interface SetupLogsArgs { } export interface SetupPageArgs { + useDocumentTitleAsPageLabel?: boolean; registerGlobally?: boolean; } diff --git a/tests/integration/platforms/next-15-turbopack-app/src/components/EmbraceWebSdk.tsx b/tests/integration/platforms/next-15-turbopack-app/src/components/EmbraceWebSdk.tsx index 26559cc47..3dff08c1a 100644 --- a/tests/integration/platforms/next-15-turbopack-app/src/components/EmbraceWebSdk.tsx +++ b/tests/integration/platforms/next-15-turbopack-app/src/components/EmbraceWebSdk.tsx @@ -12,6 +12,8 @@ export const embraceWebSdk = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/platforms/next-15-webpack-app/src/components/EmbraceWebSdk.tsx b/tests/integration/platforms/next-15-webpack-app/src/components/EmbraceWebSdk.tsx index 26559cc47..3dff08c1a 100644 --- a/tests/integration/platforms/next-15-webpack-app/src/components/EmbraceWebSdk.tsx +++ b/tests/integration/platforms/next-15-webpack-app/src/components/EmbraceWebSdk.tsx @@ -12,6 +12,8 @@ export const embraceWebSdk = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/platforms/next-16-app/src/components/EmbraceWebSdk.tsx b/tests/integration/platforms/next-16-app/src/components/EmbraceWebSdk.tsx index 26559cc47..3dff08c1a 100644 --- a/tests/integration/platforms/next-16-app/src/components/EmbraceWebSdk.tsx +++ b/tests/integration/platforms/next-16-app/src/components/EmbraceWebSdk.tsx @@ -12,6 +12,8 @@ export const embraceWebSdk = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/platforms/vite-6/src/otel.ts b/tests/integration/platforms/vite-6/src/otel.ts index 334f27b38..98aae5fe0 100644 --- a/tests/integration/platforms/vite-6/src/otel.ts +++ b/tests/integration/platforms/vite-6/src/otel.ts @@ -10,6 +10,8 @@ const sdkControl = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/platforms/vite-7/src/otel.ts b/tests/integration/platforms/vite-7/src/otel.ts index 334f27b38..98aae5fe0 100644 --- a/tests/integration/platforms/vite-7/src/otel.ts +++ b/tests/integration/platforms/vite-7/src/otel.ts @@ -10,6 +10,8 @@ const sdkControl = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/platforms/webpack-5/src/otel.ts b/tests/integration/platforms/webpack-5/src/otel.ts index 334f27b38..98aae5fe0 100644 --- a/tests/integration/platforms/webpack-5/src/otel.ts +++ b/tests/integration/platforms/webpack-5/src/otel.ts @@ -10,6 +10,8 @@ const sdkControl = initSDK({ logLevel: DiagLogLevel.ALL, embraceDataURL: 'http://localhost:3001', embraceConfigURL: 'http://localhost:3001', + // Disabled until integration testing golden file generation is made more consistent across environments + useDocumentTitleAsPageLabel: false, }); declare global { diff --git a/tests/integration/tests/e2e/cdn-tests.spec.ts b/tests/integration/tests/e2e/cdn-tests.spec.ts index 528c0dea4..af1dbe148 100644 --- a/tests/integration/tests/e2e/cdn-tests.spec.ts +++ b/tests/integration/tests/e2e/cdn-tests.spec.ts @@ -29,12 +29,14 @@ test.describe('CDN E2E Tests', () => { expect(hasInitSDK).toBe(true); }); - test('it should expose 8 functions', async ({ page }) => { + test('it should expose 8 functions and an attributes object', async ({ + page, + }) => { await page.goto(CDN_TEST_URL); const sdk = await page.evaluate(() => window.EmbraceWebSdk); - expect(Object.entries(sdk).length).toBe(8); + expect(Object.entries(sdk).length).toBe(9); }); test('it should not reinitialize when loaded twice', async ({ page }) => {