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

Update CLS to max session window 5s cap 1s gap #148

Merged
merged 1 commit into from May 11, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/getCLS.ts
Expand Up @@ -32,12 +32,32 @@ export const getCLS = (onReport: ReportHandler, reportAllChanges?: boolean) => {
let metric = initMetric('CLS', 0);
let report: ReturnType<typeof bindReporter>;

let sessionValue = 0;
let sessionEntries: PerformanceEntry[] = [];

const entryHandler = (entry: LayoutShift) => {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
(metric.value as number) += entry.value;
metric.entries.push(entry);
report();
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];

// If the entry is part of the current session, add it.
// Otherwise, start a new session.
if (sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}

if (sessionValue > metric.value) {
metric.value = sessionValue;
metric.entries = sessionEntries;
report();
}
}
};

Expand All @@ -51,6 +71,7 @@ export const getCLS = (onReport: ReportHandler, reportAllChanges?: boolean) => {
});

onBFCacheRestore(() => {
sessionValue = 0;
metric = initMetric('CLS', 0);
report = bindReporter(onReport, metric, reportAllChanges);
});
Expand Down
97 changes: 97 additions & 0 deletions test/e2e/getCLS-test.js
Expand Up @@ -72,6 +72,103 @@ describe('getCLS()', async function() {
assert.strictEqual(cls.entries.length, 2);
});

it('resets the session after timeout or gap elapses', async function() {
if (!browserSupportsCLS) this.skip();

await browser.url('/test/cls');

// Wait until all images are loaded and rendered.
await imagesPainted();
await browser.pause(1000);

await stubVisibilityChange('hidden');
await beaconCountIs(1);

const [cls1] = await getBeacons();

assert(cls1.value >= 0);
assert(cls1.id.match(/^v1-\d+-\d+$/));
assert.strictEqual(cls1.name, 'CLS');
assert.strictEqual(cls1.value, cls1.delta);
assert.strictEqual(cls1.entries.length, 2);

await browser.pause(1000);
await stubVisibilityChange('visible');
await clearBeacons();

// Force 2 layout shifts, totaling 0.5.
await browser.executeAsync((done) => {
document.body.style.overflow = 'hidden'; // Prevent scroll bars.
document.querySelector('main').style.left = '25vmax';
setTimeout(() => {
document.querySelector('main').style.left = '0px';
done();
}, 50);
});

await stubVisibilityChange('hidden');
await beaconCountIs(1);

const [cls2] = await getBeacons();

// The value should be exactly 0.5, but round just in case.
assert.strictEqual(Math.round(cls2.value * 100) / 100, 0.5);
assert.strictEqual(cls2.name, 'CLS');
assert.strictEqual(cls2.value, cls1.value + cls2.delta);
assert.strictEqual(cls2.entries.length, 2);
assert(cls2.id.match(/^v1-\d+-\d+$/));

await browser.pause(1000);
await stubVisibilityChange('visible');
await clearBeacons();

// Force 4 separate layout shifts, totaling 1.5.
await browser.executeAsync((done) => {
document.querySelector('main').style.left = '25vmax';
setTimeout(() => {
document.querySelector('main').style.left = '0px';
setTimeout(() => {
document.querySelector('main').style.left = '50vmax';
setTimeout(() => {
document.querySelector('main').style.left = '0px';
done();
}, 50);
}, 50);
}, 50);
});

await stubVisibilityChange('hidden');
await beaconCountIs(1);

const [cls3] = await getBeacons();

// The value should be exactly 1.5, but round just in case.
assert.strictEqual(Math.round(cls3.value * 100) / 100, 1.5);
assert.strictEqual(cls3.name, 'CLS');
assert.strictEqual(cls3.value, cls2.value + cls3.delta);
assert.strictEqual(cls3.entries.length, 4);
assert(cls3.id.match(/^v1-\d+-\d+$/));

await browser.pause(1000);
await stubVisibilityChange('visible');
await clearBeacons();

// Force 2 layout shifts, totalling 1.0 (less than the previous max).
await browser.executeAsync((done) => {
document.querySelector('main').style.left = '50vmax';
setTimeout(() => {
document.querySelector('main').style.left = '0px';
done();
}, 50);
});

// Wait a bit to ensure no beacons were sent.
await browser.pause(1000);

const beacons = await getBeacons();
assert.strictEqual(beacons.length, 0);
});

it('does not report if the browser does not support CLS', async function() {
if (browserSupportsCLS) this.skip();

Expand Down
15 changes: 9 additions & 6 deletions test/views/layout.njk
Expand Up @@ -44,19 +44,22 @@
{% endif %}
{% block head %}{% endblock %}
<style>
html {
height: 100%
* {
box-sizing: border-box;
}
*[hidden] {
visibility: hidden;
}
body {
font: 1em/1.5 sans-serif;
margin: 0;
min-height: 100%;
}
main {
border: 1px solid transparent; /* Prevent margin collapsing */
min-height: 100vh;
padding: 0 1em;
}
*[hidden] {
visibility: hidden;
position: relative;
width: 100%;
}
</style>
</head>
Expand Down