Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix reportAllChanges behavior for LCP when library is loaded late #468

Merged
merged 13 commits into from May 3, 2024
20 changes: 11 additions & 9 deletions src/onLCP.ts
Expand Up @@ -58,24 +58,26 @@ export const onLCP = (onReport: LCPReportCallback, opts?: ReportOpts) => {
let report: ReturnType<typeof bindReporter>;

const handleEntries = (entries: LCPMetric['entries']) => {
const lastEntry = entries[entries.length - 1] as LargestContentfulPaint;
if (lastEntry) {
// If reportAllChanges is set then call this function for each entry,
// otherwise only consider the last one.
if (!opts!.reportAllChanges) {
entries = entries.slice(-1);
}

entries.forEach((entry) => {
// Only report if the page wasn't hidden prior to LCP.
if (lastEntry.startTime < visibilityWatcher.firstHiddenTime) {
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
// The startTime attribute returns the value of the renderTime if it is
// not 0, and the value of the loadTime otherwise. The activationStart
// reference is used because LCP should be relative to page activation
// rather than navigation start if the page was prerendered. But in cases
// where `activationStart` occurs after the LCP, this time should be
// clamped at 0.
metric.value = Math.max(
lastEntry.startTime - getActivationStart(),
0,
);
metric.entries = [lastEntry];
metric.value = Math.max(entry.startTime - getActivationStart(), 0);
metric.entries = [entry];
report();
}
}
});
};

const po = observe('largest-contentful-paint', handleEntries);
Expand Down
36 changes: 23 additions & 13 deletions test/e2e/onLCP-test.js
Expand Up @@ -121,14 +121,24 @@ describe('onLCP()', async function () {
// Wait until all images are loaded and fully rendered.
await imagesPainted();

// Load a new page to trigger the hidden state.
await navigateTo('about:blank');
await beaconCountIs(2);
const [lcp1, lcp2] = await getBeacons();

// Even though the test sets `reportAllChanges` to true, since the library
// is lazy loaded after all elements have been rendered, only a single
// change will be reported.
await beaconCountIs(1);
assertStandardReportsAreCorrect(await getBeacons());
assert(lcp1.value > 0);
assert(lcp1.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp1.name, 'LCP');
assert.strictEqual(lcp1.value, lcp1.delta);
assert.strictEqual(lcp1.rating, 'good');
assert.strictEqual(lcp1.entries.length, 1);
assert.strictEqual(lcp1.navigationType, 'navigate');

assert(lcp2.value > 500); // Greater than the image load delay.
assert(lcp2.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp2.name, 'LCP');
assert(lcp2.value > lcp2.delta);
assert.strictEqual(lcp2.rating, 'good');
assert.strictEqual(lcp2.entries.length, 1);
assert.strictEqual(lcp2.navigationType, 'navigate');
});

it('accounts for time prerendering the page', async function () {
Expand Down Expand Up @@ -332,7 +342,7 @@ describe('onLCP()', async function () {

const [lcp1] = await getBeacons();

assert(lcp1.value > 0); // Greater than the image load delay.
assert(lcp1.value > 0);
assert(lcp1.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp1.name, 'LCP');
assert.strictEqual(lcp1.value, lcp1.delta);
Expand All @@ -346,7 +356,7 @@ describe('onLCP()', async function () {

const [lcp2] = await getBeacons();

assert(lcp2.value > 0); // Greater than the image load delay.
assert(lcp2.value > 0);
assert(lcp2.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp2.name, 'LCP');
assert.strictEqual(lcp2.value, lcp2.delta);
Expand Down Expand Up @@ -377,7 +387,7 @@ describe('onLCP()', async function () {

const [lcp1] = await getBeacons();

assert(lcp1.value > 0); // Greater than the image load delay.
assert(lcp1.value > 0);
assert(lcp1.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp1.name, 'LCP');
assert.strictEqual(lcp1.value, lcp1.delta);
Expand All @@ -391,7 +401,7 @@ describe('onLCP()', async function () {

const [lcp2] = await getBeacons();

assert(lcp2.value > 0); // Greater than the image load delay.
assert(lcp2.value > 0);
assert(lcp2.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp2.name, 'LCP');
assert.strictEqual(lcp2.value, lcp2.delta);
Expand All @@ -415,7 +425,7 @@ describe('onLCP()', async function () {

const [lcp] = await getBeacons();

assert(lcp.value > 0); // Greater than the image load delay.
assert(lcp.value > 0);
assert(lcp.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp.name, 'LCP');
assert.strictEqual(lcp.value, lcp.delta);
Expand Down Expand Up @@ -654,7 +664,7 @@ describe('onLCP()', async function () {

const [lcp2] = await getBeacons();

assert(lcp2.value > 0); // Greater than the image load delay.
assert(lcp2.value > 0);
assert(lcp2.id.match(/^v4-\d+-\d+$/));
assert.strictEqual(lcp2.name, 'LCP');
assert.strictEqual(lcp2.value, lcp2.delta);
Expand Down