Skip to content
Open
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
Expand Up @@ -4,6 +4,7 @@ import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';
import type {
CLSMetricWithAttribution,
FCPMetricWithAttribution,
MetricWithAttribution,
} from 'web-vitals/attribution';
import {
Expand Down Expand Up @@ -178,27 +179,15 @@ describe('WebVitalsInstrumentation', () => {
expect(clsEvent.time).to.deep.equal([3, 0]);
});

it('should not report FCP metrics by default', () => {
it('should report FCP metrics', () => {
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
urlDocument,
listeners: mockWebVitalListeners,
});

void expect(fcpStub.called).to.be.false;
});

it('should report FCP metrics when tracking is set to all', () => {
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
urlDocument,
trackingLevel: 'all',
listeners: mockWebVitalListeners,
});

void expect(fcpStub.calledOnce).to.be.true;
void expect(fcpStub.calledTwice).to.be.true;
const { args } = fcpStub.callsArg(0);
const metricReportFunc = args[0][0] as WebVitalOnReport;

Expand Down Expand Up @@ -367,27 +356,15 @@ describe('WebVitalsInstrumentation', () => {
expect(inpEvent.time).to.deep.equal([19, 0]);
});

it('should not report TTFB metrics by default', () => {
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
urlDocument,
listeners: mockWebVitalListeners,
});

void expect(ttfbStub.called).to.be.false;
});

it('should report TTFB metrics when tracking is set to all', () => {
it('should report TTFB metrics', () => {
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
urlDocument,
trackingLevel: 'all',
listeners: mockWebVitalListeners,
});

void expect(ttfbStub.calledOnce).to.be.true;
void expect(ttfbStub.calledTwice).to.be.true;
const { args } = ttfbStub.callsArg(0);
const metricReportFunc = args[0][0] as WebVitalOnReport;

Expand Down Expand Up @@ -735,6 +712,148 @@ describe('WebVitalsInstrumentation', () => {
});
});

it('should attribute the correct URL for FCP metrics', () => {
const testDocument: URLDocument = {
URL: 'https://first.com',
};
const pageManager = new EmbracePageManager();
pageManager.setCurrentRoute({
path: '/first/:id',
url: '/first/123',
});
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
pageManager,
urlDocument: testDocument,
listeners: mockWebVitalListeners,
urlAttribution: true,
});

void expect(fcpStub.callCount).to.equal(2);
const fcpFinalReportFunc = fcpStub.getCall(0).args[0] as WebVitalOnReport;
const fcpChangeReportFunc = fcpStub.getCall(1).args[0] as WebVitalOnReport;

const fcpMetric = {
name: 'FCP',
value: 22,
rating: 'poor',
delta: 0,
id: 'm1',
entries: [],
navigationType: 'navigate',
attribution: {
timeToFirstByte: 0,
firstByteToFCP: 0,
loadState: 'complete',
},
} as FCPMetricWithAttribution;

fcpChangeReportFunc(fcpMetric);

testDocument.URL = 'https://second.com';
pageManager.setCurrentRoute({
path: '/second/:id',
url: '/second/123',
});
const attributedPageID = pageManager.getCurrentPageId();
fcpChangeReportFunc(fcpMetric);
// should NOT be attributed to this URL since the metric hasn't changed
testDocument.URL = 'https://third.com';
pageManager.setCurrentRoute({
path: '/third/:id',
url: '/third/123',
});
fcpFinalReportFunc(fcpMetric);

spanSessionManager.endSessionSpan();
const finishedSpans = memoryExporter.getFinishedSpans();
expect(finishedSpans).to.have.lengthOf(1);
const sessionSpan = finishedSpans[0];
expect(sessionSpan.events).to.have.lengthOf(1);

const fcpEvent = sessionSpan.events[0];

expect(fcpEvent.name).to.be.equal('emb-web-vitals-report-FCP');
expect(fcpEvent.attributes).to.containSubset({
'url.full': 'https://second.com',
'app.surface.name': '/second/:id',
'app.surface.id': attributedPageID,
});
});

it('should attribute the correct URL for TTFB metrics', () => {
const testDocument: URLDocument = {
URL: 'https://first.com',
};
const pageManager = new EmbracePageManager();
pageManager.setCurrentRoute({
path: '/first/:id',
url: '/first/123',
});
instrumentation = new WebVitalsInstrumentation({
diag,
perf,
pageManager,
urlDocument: testDocument,
listeners: mockWebVitalListeners,
urlAttribution: true,
});

void expect(ttfbStub.callCount).to.equal(2);
const ttfbFinalReportFunc = ttfbStub.getCall(0).args[0] as WebVitalOnReport;
const ttfbChangeReportFunc = ttfbStub.getCall(1)
.args[0] as WebVitalOnReport;

const ttfbMetric = {
name: 'TTFB',
value: 33,
rating: 'poor',
delta: 99,
id: 'm1',
entries: [],
navigationType: 'navigate',
attribution: {
waitingDuration: 20,
cacheDuration: 40,
dnsDuration: 60,
connectionDuration: 80,
requestDuration: 100,
},
} as MetricWithAttribution;

ttfbChangeReportFunc(ttfbMetric);
// should be attributed to this URL since that is when the last change to the metric occurred
testDocument.URL = 'https://second.com';
pageManager.setCurrentRoute({
path: '/second/:id',
url: '/second/123',
});
const attributedPageID = pageManager.getCurrentPageId();
ttfbChangeReportFunc(ttfbMetric);
testDocument.URL = 'https://third.com';
pageManager.setCurrentRoute({
path: '/third/:id',
url: '/third/123',
});
ttfbFinalReportFunc(ttfbMetric);

spanSessionManager.endSessionSpan();
const finishedSpans = memoryExporter.getFinishedSpans();
expect(finishedSpans).to.have.lengthOf(1);
const sessionSpan = finishedSpans[0];
expect(sessionSpan.events).to.have.lengthOf(1);

const ttfbEvent = sessionSpan.events[0];

expect(ttfbEvent.name).to.be.equal('emb-web-vitals-report-TTFB');
expect(ttfbEvent.attributes).to.containSubset({
'url.full': 'https://second.com',
'app.surface.name': '/second/:id',
'app.surface.id': attributedPageID,
});
});

it('should attach page attributes when route is set', () => {
const pageManager = new EmbracePageManager();
pageManager.setCurrentRoute({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
import { EmbraceInstrumentationBase } from '../../EmbraceInstrumentationBase/index.ts';
import {
ALL_WEB_VITALS,
CORE_WEB_VITALS,
EMB_WEB_VITALS_PREFIX,
WEB_VITALS_ID_TO_LISTENER,
} from './constants.ts';
Expand Down Expand Up @@ -134,7 +133,6 @@ export class WebVitalsInstrumentation extends EmbraceInstrumentationBase {
public constructor({
diag,
perf,
trackingLevel = 'core',
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like we briefly use this as an example in the README that we should probably switch to something else:

trackingLevel: 'all'

listeners = WEB_VITALS_ID_TO_LISTENER,
urlDocument = window.document,
urlAttribution = true,
Expand All @@ -149,8 +147,7 @@ export class WebVitalsInstrumentation extends EmbraceInstrumentationBase {
});
this._listeners = listeners;
this._urlDocument = urlDocument;
this._metricsToTrack =
trackingLevel === 'all' ? [...ALL_WEB_VITALS] : [...CORE_WEB_VITALS];
this._metricsToTrack = [...ALL_WEB_VITALS];
this._urlAttribution = urlAttribution;
this._pageManager = pageManager ?? page.getPageManager();

Expand Down Expand Up @@ -214,6 +211,22 @@ export class WebVitalsInstrumentation extends EmbraceInstrumentationBase {
// When these web vitals make their final report (e.g. when the listeners w/ reportAllChanges=false trigger) the
// document's URL at that time may not match what it was at the time the scores were last updated. Instead, listen
// for updates to the scores and keep track of the Page information to attribute for each
this._listeners.TTFB?.(
() => {
this._attributedPage.TTFB = this._currentAttributedPage();
},
{
reportAllChanges: true,
},
);
this._listeners.FCP?.(
() => {
this._attributedPage.FCP = this._currentAttributedPage();
},
{
reportAllChanges: true,
},
);
this._listeners.INP?.(
() => {
this._attributedPage.INP = this._currentAttributedPage();
Expand Down Expand Up @@ -286,6 +299,14 @@ export class WebVitalsInstrumentation extends EmbraceInstrumentationBase {
private _getAttributedPageForMetric(
metric: MetricWithAttribution,
): AttributedPage {
if (metric.name === 'FCP' && this._attributedPage.FCP) {
return this._attributedPage.FCP;
}

if (metric.name === 'TTFB' && this._attributedPage.TTFB) {
return this._attributedPage.TTFB;
}

if (metric.name === 'INP' && this._attributedPage.INP) {
return this._attributedPage.INP;
}
Expand Down
Loading