Skip to content

Commit

Permalink
Add basic test for INP
Browse files Browse the repository at this point in the history
  • Loading branch information
philipwalton committed Dec 23, 2024
1 parent 6b33dfb commit 43ff243
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 24 deletions.
81 changes: 80 additions & 1 deletion test/e2e/onINP-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,70 @@ describe('onINP()', async function () {
assert.strictEqual(inp.navigationType, 'restore');
});

it('works when calling the function twice with different options', async function () {
if (!browserSupportsINP) this.skip();

await navigateTo(
'/test/inp?click=100&keydown=200&doubleCall=1&reportAllChanges2=1',
{readyState: 'interactive'},
);

const textarea = await $('#textarea');
simulateUserLikeClick(textarea);

await beaconCountIs(1, {instance: 2});

const [inp2_1] = await getBeacons({instance: 2});

assert(inp2_1.value > 100 - 8);
assert(inp2_1.id.match(/^v5-\d+-\d+$/));
assert.strictEqual(inp2_1.name, 'INP');
assert.strictEqual(inp2_1.value, inp2_1.delta);
assert.strictEqual(inp2_1.rating, 'good');
assert(
containsEntry(inp2_1.entries, 'click', '[object HTMLTextAreaElement]'),
);
assert(allEntriesValid(inp2_1.entries));
assert.match(inp2_1.navigationType, /navigate|reload/);

// Assert no beacons for instance 1 were received.
assert.strictEqual((await getBeacons({instance: 1})).length, 0);

await browser.keys(['a']);

await beaconCountIs(2, {instance: 2});

const [, inp2_2] = await getBeacons({instance: 2});

assert.strictEqual(inp2_2.id, inp2_1.id);
assert.strictEqual(inp2_2.name, 'INP');
assert.strictEqual(inp2_2.value, inp2_2.delta + inp2_1.delta);
assert.strictEqual(inp2_2.delta, inp2_2.value - inp2_1.delta);
assert.strictEqual(inp2_2.rating, 'needs-improvement');
assert(
containsEntry(inp2_2.entries, 'keydown', '[object HTMLTextAreaElement]'),
);
assert(allEntriesValid(inp2_2.entries));
assert.match(inp2_2.navigationType, /navigate|reload/);

await stubVisibilityChange('hidden');
await beaconCountIs(1, {instance: 1});

const [inp1] = await getBeacons({instance: 1});
assert(inp1.id.match(/^v5-\d+-\d+$/));
assert(inp1.id !== inp2_1.id);

assert.strictEqual(inp1.value, inp2_2.value);
assert.strictEqual(inp1.name, 'INP');
assert.strictEqual(inp1.value, inp1.delta);
assert.strictEqual(inp1.rating, 'needs-improvement');
assert(
containsEntry(inp1.entries, 'keydown', '[object HTMLTextAreaElement]'),
);
assert(allEntriesValid(inp1.entries));
assert.match(inp1.navigationType, /navigate|reload/);
});

describe('attribution', function () {
it('includes attribution data on the metric object', async function () {
if (!browserSupportsINP) this.skip();
Expand Down Expand Up @@ -722,9 +786,24 @@ const containsEntry = (entries, name, target) => {
return entries.findIndex((e) => e.name === name && e.target === target) > -1;
};

const allEntriesValid = (entries) => {
const renderTimes = entries
.map((e) => e.startTime + e.duration)
.sort((a, b) => a - b);

const allEntriesHaveSameRenderTimes =
renderTimes.at(-1) - renderTimes.at(0) === 0;

const entryData = entries.map((e) => JSON.stringify(e));

const allEntriesAreUnique = entryData.length === new Set(entryData).size;

return allEntriesHaveSameRenderTimes && allEntriesAreUnique;
};

const allEntriesPresentTogether = (entries) => {
const renderTimes = entries
.map((e) => Math.max(e.startTime + e.duration, e.processingEnd))
.map((e) => e.startTime + e.duration)
.sort((a, b) => a - b);

return renderTimes.at(-1) - renderTimes.at(0) <= 8;
Expand Down
38 changes: 27 additions & 11 deletions test/utils/beacons.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,51 @@ import fs from 'fs-extra';
const BEACON_FILE = './test/beacons.log';

/**
* @param {Object} count
* @return {Promise<boolean>} True if the beacon count matches.
* Runs a webdriverio waitUntil command, ending once the specified number of
* beacons haven been received (optionally matching the passed `opts` object).
*/
export async function beaconCountIs(count) {
export async function beaconCountIs(count, opts = {}) {
await browser.waitUntil(async () => {
const beacons = await getBeacons();
const beacons = await getBeacons(opts);

return beacons.length === count;
});
}

/**
* Gets the array of beacons sent for the current page load (i.e. the
* most recently sent beacons with the same metric ID).
* @return {Promise<Array>}
* Returns an array of beacons matching the passed `opts` object. If no
* `opts` are specified, the default is to return all beacon matching
* the most recently-received metric ID.
*/
export async function getBeacons(id = undefined) {
export async function getBeacons(opts = {}) {
const json = await fs.readFile(BEACON_FILE, 'utf-8');
const allBeacons = json.trim().split('\n').filter(Boolean).map(JSON.parse);

if (allBeacons.length) {
const lastBeaconID = allBeacons[allBeacons.length - 1].id;
return allBeacons.filter((beacon) => beacon.id === lastBeaconID);
const lastBeacon = allBeacons.findLast((beacon) => {
if (opts.instance) {
return opts.instance === beacon.instance;
}
return true;
});

if (lastBeacon) {
return allBeacons.filter((beacon) => {
if (beacon.id === lastBeacon.id) {
if (opts.instance) {
return opts.instance === beacon.instance;
}
return true;
}
return false;
});
}
}
return [];
}

/**
* Clears the array of beacons on the page.
* @return {Promise<void>}
*/
export async function clearBeacons() {
await fs.truncate(BEACON_FILE);
Expand Down
17 changes: 17 additions & 0 deletions test/views/inp.njk
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@
const {onINP} = await __testImport('{{ modulePath }}');
onINP((inp) => {
inp.instance = 1;
// Log for easier manual testing.
console.log(inp);
Expand All @@ -124,5 +126,20 @@
reportAllChanges: self.__reportAllChanges,
durationThreshold: self.__durationThreshold,
});
if (self.__doubleCall) {
onINP((inp) => {
inp.instance = 2;
// Log for easier manual testing.
console.log(inp);
// Test sending the metric to an analytics endpoint.
navigator.sendBeacon(`/collect`, JSON.stringify(__toSafeObject(inp)));
}, {
reportAllChanges: self.__reportAllChanges2,
durationThreshold: self.__durationThreshold2,
});
}
</script>
{% endblock %}
32 changes: 20 additions & 12 deletions test/views/layout.njk
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,29 @@
const params = new URL(location.href).searchParams;
if (params.has('reportAllChanges')) {
self.__reportAllChanges = Boolean(params.get('reportAllChanges'));
}
function infer(param) {
const val = params.get(param);
if (params.has('durationThreshold')) {
self.__durationThreshold = Number(params.get('durationThreshold'));
if (val) {
if (val.match(/^\d+$/)) {
return Number(val);
} else if (val.match(/^(true|false)$/)) {
return val === 'false' ? false : true;
}
return val;
}
}
if (params.has('lazyLoad')) {
self.__lazyLoad = Boolean(params.get('lazyLoad'));
}
// Default the first call to not reporting all changes.
self.__reportAllChanges = Boolean(infer('reportAllChanges'));
self.__durationThreshold = infer('durationThreshold');
if (params.has('loadAfterInput')) {
self.__loadAfterInput = Boolean(params.get('loadAfterInput'));
}
self.__doubleCall = Boolean(infer('doubleCall'));
self.__reportAllChanges2 = Boolean(infer('reportAllChanges2'));
self.__durationThreshold2 = infer('durationThreshold2');
self.__lazyLoad = Boolean(infer('lazyLoad'));
self.__loadAfterInput = Boolean(infer('loadAfterInput'));
if (params.has('hidden')) {
// Stub the page being loaded in the hidden state, but defer to the
Expand Down Expand Up @@ -232,7 +240,7 @@
};
self.__toSafeObject = (oldObj) => {
if (typeof oldObj !== 'object') {
if (oldObj === null || typeof oldObj !== 'object') {
return oldObj;
} else if (oldObj instanceof EventTarget) {
return oldObj.toString();
Expand Down

0 comments on commit 43ff243

Please sign in to comment.