From 57bd25fdea9627bb0eaae7d1267e565347f2bed8 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Thu, 21 Jul 2022 11:49:16 -0700 Subject: [PATCH] Add a metric rating property --- README.md | 55 ++++++++++++++++++--------- src/lib/bindReporter.ts | 19 +++++++++- src/lib/initMetric.ts | 1 + src/onCLS.ts | 9 ++++- src/onFCP.ts | 9 ++++- src/onFID.ts | 13 +++++-- src/onINP.ts | 8 +++- src/onLCP.ts | 9 ++++- src/onTTFB.ts | 8 +++- src/types/base.ts | 6 +++ test/css/styles.css | 3 ++ test/e2e/onCLS-test.js | 30 +++++++++++++-- test/e2e/onFCP-test.js | 31 ++++++++++++++- test/e2e/onFID-test.js | 5 +++ test/e2e/onINP-test.js | 18 ++++++++- test/e2e/onLCP-test.js | 84 ++++++++++++++++++++++++++++------------- test/e2e/onTTFB-test.js | 27 +++++++++++++ test/views/layout.njk | 3 ++ 18 files changed, 274 insertions(+), 64 deletions(-) create mode 100644 test/css/styles.css diff --git a/README.md b/README.md index 13995241..f55b3411 100644 --- a/README.md +++ b/README.md @@ -421,7 +421,7 @@ function sendToGoogleAnalytics({name, delta, value, id}) { // OPTIONAL: any additional params or debug info here. // See: https://web.dev/debug-web-vitals-in-the-field/ - // metric_rating: 'good' | 'ni' | 'poor', + // metric_rating: 'good' | 'needs-improvement' | 'poor', // debug_info: '...', // ... }); @@ -657,32 +657,53 @@ The "standard" build of the `web-vitals` library includes some of the same logic ```ts interface Metric { - // The name of the metric (in acronym form). + /** + * The name of the metric (in acronym form). + */ name: 'CLS' | 'FCP' | 'FID' | 'INP' | 'LCP' | 'TTFB'; - // The current value of the metric. + /** + * The current value of the metric. + */ value: number; - // The delta between the current value and the last-reported value. - // On the first report, `delta` and `value` will always be the same. + /** + * The rating as to whether the metric value is within the "good", + * "needs improvement", or "poor" thresholds of the metric. + */ + rating: 'good' | 'needs-improvement' | 'poor'; + + /** + * The delta between the current value and the last-reported value. + * On the first report, `delta` and `value` will always be the same. + */ delta: number; - // A unique ID representing this particular metric that's specific to the - // current page. This ID can be used by an analytics tool to dedupe - // multiple values sent for the same metric, or to group multiple deltas - // together and calculate a total. + /** + * A unique ID representing this particular metric instance. This ID can + * be used by an analytics tool to dedupe multiple values sent for the same + * metric instance, or to group multiple deltas together and calculate a + * total. It can also be used to differentiate multiple different metric + * instances sent from the same page, which can happen if the page is + * restored from the back/forward cache (in that case new metrics object + * get created). + */ id: string; - // Any performance entries relevant to the metric value calculation. - // The array may also be empty if the metric value was not based on any - // entries (e.g. a CLS value of 0 given no layout shifts). + /** + * Any performance entries relevant to the metric value calculation. + * The array may also be empty if the metric value was not based on any + * entries (e.g. a CLS value of 0 given no layout shifts). + */ entries: (PerformanceEntry | LayoutShift | FirstInputPolyfillEntry | NavigationTimingPolyfillEntry)[]; - // For regular navigations, the type will be the same as the type indicated - // by the Navigation Timing API (or `undefined` if the browser doesn't - // support that API). For pages that are restored from the bfcache, this - // value will be 'back_forward_cache'. - navigationType: NavigationType | 'back_forward_cache' | 'prerender' | undefined; + /** + * For regular navigations, the type will be the same as the type indicated + * by the Navigation Timing API (or `undefined` if the browser doesn't + * support that API). For pages that are restored from the bfcache, this + * value will be 'back_forward_cache'. + */ + navigationType: NavigationTimingType | 'back_forward_cache' | 'prerender' | undefined; } ``` diff --git a/src/lib/bindReporter.ts b/src/lib/bindReporter.ts index cbc585d9..e930f10d 100644 --- a/src/lib/bindReporter.ts +++ b/src/lib/bindReporter.ts @@ -17,23 +17,38 @@ import {Metric, ReportCallback} from '../types.js'; +const getRating = (value: number, thresholds: number[]) => { + if (value > thresholds[1]) { + return 'poor'; + } + if (value > thresholds[0]) { + return 'needs-improvement'; + } + return 'good'; +}; + + export const bindReporter = ( callback: ReportCallback, metric: Metric, + thresholds: number[], reportAllChanges?: boolean, ) => { let prevValue: number; + let delta: number; return (forceReport?: boolean) => { if (metric.value >= 0) { if (forceReport || reportAllChanges) { - metric.delta = metric.value - (prevValue || 0); + delta = metric.value - (prevValue || 0); // Report the metric if there's a non-zero delta or if no previous // value exists (which can happen in the case of the document becoming // hidden when the metric value is 0). // See: https://github.com/GoogleChrome/web-vitals/issues/14 - if (metric.delta || prevValue === undefined) { + if (delta || prevValue === undefined) { prevValue = metric.value; + metric.delta = delta; + metric.rating = getRating(metric.value, thresholds); callback(metric); } } diff --git a/src/lib/initMetric.ts b/src/lib/initMetric.ts index 893188a3..ebd8d052 100644 --- a/src/lib/initMetric.ts +++ b/src/lib/initMetric.ts @@ -38,6 +38,7 @@ export const initMetric = (name: Metric['name'], value?: number): Metric => { return { name, value: typeof value === 'undefined' ? -1 : value, + rating: 'good', // Will be updated if the value changes. delta: 0, entries: [], id: generateUniqueID(), diff --git a/src/onCLS.ts b/src/onCLS.ts index c423f669..72fb7172 100644 --- a/src/onCLS.ts +++ b/src/onCLS.ts @@ -51,6 +51,9 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/cls/#what-is-a-good-cls-score + const thresholds = [0.1, 0.25]; + // Start monitoring FCP so we can only report CLS if FCP is also reported. // Note: this is done to match the current behavior of CrUX. if (!isMonitoringFCP) { @@ -106,7 +109,8 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => { const po = observe('layout-shift', handleEntries); if (po) { - report = bindReporter(onReportWrapped, metric, opts.reportAllChanges); + report = bindReporter( + onReportWrapped, metric, thresholds, opts.reportAllChanges); onHidden(() => { handleEntries(po.takeRecords() as CLSMetric['entries']); @@ -117,7 +121,8 @@ export const onCLS = (onReport: CLSReportCallback, opts?: ReportOpts) => { sessionValue = 0; fcpValue = -1; metric = initMetric('CLS', 0); - report = bindReporter(onReportWrapped, metric, opts!.reportAllChanges); + report = bindReporter( + onReportWrapped, metric, thresholds, opts!.reportAllChanges); }); } }; diff --git a/src/onFCP.ts b/src/onFCP.ts index 372b7672..f15efbd1 100644 --- a/src/onFCP.ts +++ b/src/onFCP.ts @@ -32,6 +32,9 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/fcp/#what-is-a-good-fcp-score + const thresholds = [1800, 3000]; + const visibilityWatcher = getVisibilityWatcher(); let metric = initMetric('FCP'); let report: ReturnType; @@ -68,7 +71,7 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => { const po = fcpEntry ? null : observe('paint', handleEntries); if (fcpEntry || po) { - report = bindReporter(onReport, metric, opts.reportAllChanges); + report = bindReporter(onReport, metric, thresholds, opts.reportAllChanges); if (fcpEntry) { handleEntries([fcpEntry]); @@ -76,7 +79,9 @@ export const onFCP = (onReport: FCPReportCallback, opts?: ReportOpts) => { onBFCacheRestore((event) => { metric = initMetric('FCP'); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter( + onReport, metric, thresholds, opts!.reportAllChanges); + requestAnimationFrame(() => { requestAnimationFrame(() => { metric.value = performance.now() - event.timeStamp; diff --git a/src/onFID.ts b/src/onFID.ts index fbc87781..ed6cb3a0 100644 --- a/src/onFID.ts +++ b/src/onFID.ts @@ -36,6 +36,9 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/fid/#what-is-a-good-fid-score + const thresholds = [100, 300]; + const visibilityWatcher = getVisibilityWatcher(); let metric = initMetric('FID'); let report: ReturnType; @@ -54,7 +57,7 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => { } const po = observe('first-input', handleEntries); - report = bindReporter(onReport, metric, opts.reportAllChanges); + report = bindReporter(onReport, metric, thresholds, opts.reportAllChanges); if (po) { onHidden(() => { @@ -72,7 +75,9 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => { } onBFCacheRestore(() => { metric = initMetric('FID'); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter( + onReport, metric, thresholds, opts!.reportAllChanges); + window.webVitals.resetFirstInputPolyfill(); window.webVitals.firstInputPolyfill(handleEntry as FirstInputPolyfillCallback); }); @@ -81,7 +86,9 @@ export const onFID = (onReport: ReportCallback, opts?: ReportOpts) => { if (po) { onBFCacheRestore(() => { metric = initMetric('FID'); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter( + onReport, metric, thresholds, opts!.reportAllChanges); + resetFirstInputPolyfill(); firstInputPolyfill(handleEntry as FirstInputPolyfillCallback); }); diff --git a/src/onINP.ts b/src/onINP.ts index c1f57fab..8cce2caf 100644 --- a/src/onINP.ts +++ b/src/onINP.ts @@ -135,6 +135,9 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/inp/#what's-a-%22good%22-inp-value + const thresholds = [200, 500]; + // TODO(philipwalton): remove once the polyfill is no longer needed. initInteractionCountPolyfill(); @@ -187,7 +190,7 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => { durationThreshold: opts.durationThreshold || 40, } as PerformanceObserverInit); - report = bindReporter(onReport, metric, opts.reportAllChanges); + report = bindReporter(onReport, metric, thresholds, opts.reportAllChanges); if (po) { // Also observe entries of type `first-input`. This is useful in cases @@ -214,7 +217,8 @@ export const onINP = (onReport: ReportCallback, opts?: ReportOpts) => { prevInteractionCount = getInteractionCount(); metric = initMetric('INP'); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter( + onReport, metric, thresholds, opts!.reportAllChanges); }); } }; diff --git a/src/onLCP.ts b/src/onLCP.ts index f174f4bc..255ecb61 100644 --- a/src/onLCP.ts +++ b/src/onLCP.ts @@ -41,6 +41,9 @@ export const onLCP = (onReport: ReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/lcp/#what-is-a-good-lcp-score + const thresholds = [2500, 4000]; + const visibilityWatcher = getVisibilityWatcher(); let metric = initMetric('LCP'); let report: ReturnType; @@ -66,7 +69,7 @@ export const onLCP = (onReport: ReportCallback, opts?: ReportOpts) => { const po = observe('largest-contentful-paint', handleEntries); if (po) { - report = bindReporter(onReport, metric, opts.reportAllChanges); + report = bindReporter(onReport, metric, thresholds, opts.reportAllChanges); const stopListening = () => { if (!reportedMetricIDs[metric.id]) { @@ -88,7 +91,9 @@ export const onLCP = (onReport: ReportCallback, opts?: ReportOpts) => { onBFCacheRestore((event) => { metric = initMetric('LCP'); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter( + onReport, metric, thresholds, opts!.reportAllChanges); + requestAnimationFrame(() => { requestAnimationFrame(() => { metric.value = performance.now() - event.timeStamp; diff --git a/src/onTTFB.ts b/src/onTTFB.ts index 3c6b4fcf..c89707b5 100644 --- a/src/onTTFB.ts +++ b/src/onTTFB.ts @@ -56,8 +56,12 @@ export const onTTFB = (onReport: ReportCallback, opts?: ReportOpts) => { // Set defaults opts = opts || {}; + // https://web.dev/ttfb/#what-is-a-good-ttfb-score + const thresholds = [800, 1800]; + let metric = initMetric('TTFB'); - let report = bindReporter(onReport, metric, opts.reportAllChanges); + let report = bindReporter( + onReport, metric, thresholds, opts.reportAllChanges); whenReady(() => { const navEntry = getNavigationEntry(); @@ -83,7 +87,7 @@ export const onTTFB = (onReport: ReportCallback, opts?: ReportOpts) => { onBFCacheRestore(() => { metric = initMetric('TTFB', 0); - report = bindReporter(onReport, metric, opts!.reportAllChanges); + report = bindReporter(onReport, metric, thresholds, opts!.reportAllChanges); report(true); }); }; diff --git a/src/types/base.ts b/src/types/base.ts index ef51add7..0ea78235 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -28,6 +28,12 @@ export interface Metric { */ value: number; + /** + * The rating as to whether the metric value is within the "good", + * "needs improvement", or "poor" thresholds of the metric. + */ + rating: 'good' | 'needs-improvement' | 'poor'; + /** * The delta between the current value and the last-reported value. * On the first report, `delta` and `value` will always be the same. diff --git a/test/css/styles.css b/test/css/styles.css new file mode 100644 index 00000000..87de0008 --- /dev/null +++ b/test/css/styles.css @@ -0,0 +1,3 @@ +body { + background-color: yellow; +} diff --git a/test/e2e/onCLS-test.js b/test/e2e/onCLS-test.js index 4cbde8e7..fb8ba9ad 100644 --- a/test/e2e/onCLS-test.js +++ b/test/e2e/onCLS-test.js @@ -19,6 +19,7 @@ import {beaconCountIs, clearBeacons, getBeacons} from '../utils/beacons.js'; import {browserSupportsEntry} from '../utils/browserSupportsEntry.js'; import {domReadyState} from '../utils/domReadyState.js'; import {imagesPainted} from '../utils/imagesPainted.js'; +import {nextFrame} from '../utils/nextFrame.js'; import {stubForwardBack} from '../utils/stubForwardBack.js'; import {stubVisibilityChange} from '../utils/stubVisibilityChange.js'; @@ -52,6 +53,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 2); assert.match(cls.navigationType, /navigate|reload/); }); @@ -72,6 +74,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 2); assert.match(cls.navigationType, /navigate|reload/); }); @@ -94,6 +97,7 @@ describe('onCLS()', async function() { assert(cls1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls1.name, 'CLS'); assert.strictEqual(cls1.value, cls1.delta); + assert.strictEqual(cls1.rating, 'good'); assert.strictEqual(cls1.entries.length, 2); assert.match(cls1.navigationType, /navigate|reload/); @@ -120,6 +124,7 @@ describe('onCLS()', async function() { 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.rating, 'poor'); assert.strictEqual(cls2.entries.length, 2); assert.match(cls2.navigationType, /navigate|reload/); assert.match(cls2.id, /^v2-\d+-\d+$/); @@ -152,6 +157,7 @@ describe('onCLS()', async function() { 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.rating, 'poor'); assert.strictEqual(cls3.entries.length, 4); assert.match(cls3.navigationType, /navigate|reload/); assert.match(cls3.id, /^v2-\d+-\d+$/); @@ -211,6 +217,7 @@ describe('onCLS()', async function() { assert(cls1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls1.name, 'CLS'); assert.strictEqual(cls1.value, cls1.delta); + assert.strictEqual(cls1.rating, 'good'); assert.strictEqual(cls1.entries.length, 1); assert.match(cls1.navigationType, /navigate|reload/); @@ -218,6 +225,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls2.name, 'CLS'); assert.strictEqual(cls2.id, cls1.id); assert.strictEqual(cls2.value, cls1.value + cls2.delta); + assert.strictEqual(cls2.rating, 'good'); assert.strictEqual(cls2.entries.length, 2); assert.match(cls2.navigationType, /navigate|reload/); @@ -244,6 +252,7 @@ describe('onCLS()', async function() { assert(cls1.value >= 0); assert(cls1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls1.value, cls1.delta); + assert.strictEqual(cls1.rating, 'good'); assert.strictEqual(cls1.entries.length, 1); assert.match(cls1.navigationType, /navigate|reload/); @@ -251,6 +260,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls2.name, 'CLS'); assert.strictEqual(cls2.id, cls1.id); assert.strictEqual(cls2.value, cls1.value + cls2.delta); + assert.strictEqual(cls2.rating, 'good'); assert.strictEqual(cls2.entries.length, 2); assert.match(cls2.navigationType, /navigate|reload/); @@ -283,6 +293,7 @@ describe('onCLS()', async function() { assert(cls1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls1.name, 'CLS'); assert.strictEqual(cls1.value, cls1.delta); + assert.strictEqual(cls1.rating, 'good'); assert.strictEqual(cls1.entries.length, 2); assert.match(cls1.navigationType, /navigate|reload/); @@ -326,6 +337,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls2.name, 'CLS'); assert.strictEqual(cls2.id, cls1.id); assert.strictEqual(cls2.value, cls1.value + cls2.delta); + assert.strictEqual(cls2.rating, 'good'); assert.strictEqual(cls2.entries.length, 2); assert.match(cls2.navigationType, /navigate|reload/); @@ -346,6 +358,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls3.name, 'CLS'); assert.strictEqual(cls3.id, cls2.id); assert.strictEqual(cls3.value, cls2.value + cls3.delta); + assert.strictEqual(cls3.rating, 'good'); assert.strictEqual(cls3.entries.length, 3); assert.match(cls3.navigationType, /navigate|reload/); }); @@ -368,6 +381,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls1.delta, cls1.value); assert.strictEqual(cls1.name, 'CLS'); assert.strictEqual(cls1.value, cls1.delta); + assert.strictEqual(cls1.rating, 'good'); assert.strictEqual(cls1.entries.length, 2); assert.match(cls1.navigationType, /navigate|reload/); @@ -383,9 +397,9 @@ describe('onCLS()', async function() { assert(cls2.id.match(/^v2-\d+-\d+$/)); assert(cls2.id !== cls1.id); - assert.strictEqual(cls2.delta, cls2.value); assert.strictEqual(cls2.name, 'CLS'); assert.strictEqual(cls2.value, cls2.delta); + assert.strictEqual(cls2.rating, 'good'); assert.strictEqual(cls2.entries.length, 1); assert.strictEqual(cls2.navigationType, 'back_forward_cache'); @@ -401,9 +415,9 @@ describe('onCLS()', async function() { assert(cls3.id.match(/^v2-\d+-\d+$/)); assert(cls3.id !== cls2.id); - assert.strictEqual(cls3.delta, cls3.value); assert.strictEqual(cls3.name, 'CLS'); assert.strictEqual(cls3.value, cls3.delta); + assert.strictEqual(cls3.rating, 'good'); assert.strictEqual(cls3.entries.length, 1); assert.strictEqual(cls3.navigationType, 'back_forward_cache'); }); @@ -427,6 +441,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls2.name, 'CLS'); assert.strictEqual(cls2.id, cls1.id); assert.strictEqual(cls2.value, cls1.value + cls2.delta); + assert.strictEqual(cls2.rating, 'good'); assert.strictEqual(cls2.entries.length, 2); assert.match(cls2.navigationType, /navigate|reload/); @@ -446,6 +461,7 @@ describe('onCLS()', async function() { assert(cls3.id !== cls2.id); assert.strictEqual(cls3.name, 'CLS'); assert.strictEqual(cls3.value, cls3.delta); + assert.strictEqual(cls3.rating, 'good'); assert.strictEqual(cls3.entries.length, 1); assert.strictEqual(cls3.navigationType, 'back_forward_cache'); }); @@ -466,6 +482,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, 0); assert.strictEqual(cls.delta, 0); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 0); assert.match(cls.navigationType, /navigate|reload/); }); @@ -486,6 +503,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, 0); assert.strictEqual(cls.delta, 0); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 0); assert.match(cls.navigationType, /navigate|reload/); }); @@ -506,6 +524,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, 0); assert.strictEqual(cls.delta, 0); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 0); assert.match(cls.navigationType, /navigate|reload/); }); @@ -526,6 +545,7 @@ describe('onCLS()', async function() { assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, 0); assert.strictEqual(cls.delta, 0); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 0); assert.match(cls.navigationType, /navigate|reload/); }); @@ -550,7 +570,7 @@ describe('onCLS()', async function() { await stubForwardBack(); // Wait for a frame to be painted. - await browser.executeAsync((done) => requestAnimationFrame(done)); + await nextFrame(); await triggerLayoutShift(); @@ -563,6 +583,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.delta, cls.value); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 1); assert.strictEqual(cls.navigationType, 'back_forward_cache'); }); @@ -584,6 +605,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 2); assert.match(cls.navigationType, /navigate|reload/); @@ -620,6 +642,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 1); assert.match(cls.navigationType, /navigate|reload/); @@ -656,6 +679,7 @@ describe('onCLS()', async function() { assert(cls.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(cls.name, 'CLS'); assert.strictEqual(cls.value, cls.delta); + assert.strictEqual(cls.rating, 'good'); assert.strictEqual(cls.entries.length, 0); assert.match(cls.navigationType, /navigate|reload/); diff --git a/test/e2e/onFCP-test.js b/test/e2e/onFCP-test.js index be83557f..7f4c1876 100644 --- a/test/e2e/onFCP-test.js +++ b/test/e2e/onFCP-test.js @@ -47,6 +47,7 @@ describe('onFCP()', async function() { assert(fcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp.name, 'FCP'); assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 1); assert.match(fcp.navigationType, /navigate|reload/); }); @@ -68,6 +69,7 @@ describe('onFCP()', async function() { assert(fcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp.name, 'FCP'); assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 1); assert.strictEqual(fcp.entries[0].startTime - activationStart, fcp.value); assert.strictEqual(fcp.navigationType, 'prerender'); @@ -112,7 +114,7 @@ describe('onFCP()', async function() { it('does not report if the document changes to hidden before the first entry', async function() { if (!browserSupportsFCP) this.skip(); - await browser.url('/test/fcp?invisible=1'); + await browser.url('/test/fcp?renderBlocking=1000'); await stubVisibilityChange('hidden'); await stubVisibilityChange('visible'); @@ -124,6 +126,25 @@ describe('onFCP()', async function() { assert.strictEqual(beacons.length, 0); }); + it('reports after a render delay before the page changes to hidden', async function() { + if (!browserSupportsFCP) this.skip(); + + await browser.url('/test/fcp?renderBlocking=2000'); + + // Change to hidden after the first render. + await browser.pause(2500); + await stubVisibilityChange('hidden'); + + const [fcp] = await getBeacons(); + assert(fcp.value >= 0); + assert(fcp.id.match(/^v2-\d+-\d+$/)); + assert.strictEqual(fcp.name, 'FCP'); + assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'needs-improvement'); + assert.strictEqual(fcp.entries.length, 1); + assert.match(fcp.navigationType, /navigate|reload/); + }); + it('reports if the page is restored from bfcache', async function() { if (!browserSupportsFCP) this.skip(); @@ -136,6 +157,7 @@ describe('onFCP()', async function() { assert(fcp1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp1.name, 'FCP'); assert.strictEqual(fcp1.value, fcp1.delta); + assert.strictEqual(fcp1.rating, 'good'); assert.strictEqual(fcp1.entries.length, 1); assert.match(fcp1.navigationType, /navigate|reload/); @@ -150,6 +172,7 @@ describe('onFCP()', async function() { assert(fcp2.id !== fcp1.id); assert.strictEqual(fcp2.name, 'FCP'); assert.strictEqual(fcp2.value, fcp2.delta); + assert.strictEqual(fcp2.rating, 'good'); assert.strictEqual(fcp2.entries.length, 0); assert.strictEqual(fcp2.navigationType, 'back_forward_cache'); @@ -164,6 +187,7 @@ describe('onFCP()', async function() { assert(fcp3.id !== fcp2.id); assert.strictEqual(fcp3.name, 'FCP'); assert.strictEqual(fcp3.value, fcp3.delta); + assert.strictEqual(fcp3.rating, 'good'); assert.strictEqual(fcp3.entries.length, 0); assert.strictEqual(fcp3.navigationType, 'back_forward_cache'); }); @@ -191,6 +215,7 @@ describe('onFCP()', async function() { assert(fcp1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp1.name, 'FCP'); assert.strictEqual(fcp1.value, fcp1.delta); + assert.strictEqual(fcp1.rating, 'good'); assert.strictEqual(fcp1.entries.length, 0); assert.strictEqual(fcp1.navigationType, 'back_forward_cache'); @@ -205,6 +230,7 @@ describe('onFCP()', async function() { assert(fcp2.id !== fcp1.id); assert.strictEqual(fcp2.name, 'FCP'); assert.strictEqual(fcp2.value, fcp2.delta); + assert.strictEqual(fcp2.rating, 'good'); assert.strictEqual(fcp2.entries.length, 0); assert.strictEqual(fcp2.navigationType, 'back_forward_cache'); }); @@ -232,6 +258,7 @@ describe('onFCP()', async function() { assert(fcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp.name, 'FCP'); assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 1); assert.match(fcp.navigationType, /navigate|reload/); @@ -278,6 +305,7 @@ describe('onFCP()', async function() { assert(fcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp.name, 'FCP'); assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 1); assert.strictEqual(fcp.navigationType, 'prerender'); @@ -316,6 +344,7 @@ describe('onFCP()', async function() { assert(fcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fcp.name, 'FCP'); assert.strictEqual(fcp.value, fcp.delta); + assert.strictEqual(fcp.rating, 'good'); assert.strictEqual(fcp.entries.length, 0); assert.strictEqual(fcp.navigationType, 'back_forward_cache'); diff --git a/test/e2e/onFID-test.js b/test/e2e/onFID-test.js index 629b1e39..21481f76 100644 --- a/test/e2e/onFID-test.js +++ b/test/e2e/onFID-test.js @@ -51,6 +51,7 @@ describe('onFID()', async function() { assert(fid.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fid.name, 'FID'); assert.strictEqual(fid.value, fid.delta); + assert.strictEqual(fid.rating, 'good'); assert.match(fid.navigationType, /navigate|reload/); assert.match(fid.entries[0].name, /(mouse|pointer)down/); }); @@ -102,6 +103,7 @@ describe('onFID()', async function() { assert(fid.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fid.name, 'FID'); assert.strictEqual(fid.value, fid.delta); + assert.strictEqual(fid.rating, 'good'); assert.match(fid.navigationType, /navigate|reload/); assert.match(fid.entries[0].name, /(mouse|pointer)down/); if (browserSupportsFID) { @@ -169,6 +171,7 @@ describe('onFID()', async function() { assert(fid1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fid1.name, 'FID'); assert.strictEqual(fid1.value, fid1.delta); + assert.strictEqual(fid1.rating, 'good'); assert.match(fid1.navigationType, /navigate|reload/); assert.match(fid1.entries[0].name, /(mouse|pointer)down/); @@ -185,6 +188,7 @@ describe('onFID()', async function() { assert(fid2.id.match(/^v2-\d+-\d+$/)); assert(fid1.id !== fid2.id); assert.strictEqual(fid2.name, 'FID'); + assert.strictEqual(fid2.rating, 'good'); assert.strictEqual(fid2.value, fid2.delta); assert.strictEqual(fid2.navigationType, 'back_forward_cache'); assert.match(fid2.entries[0].name, /(mouse|pointer)down/); @@ -207,6 +211,7 @@ describe('onFID()', async function() { assert(fid.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(fid.name, 'FID'); assert.strictEqual(fid.value, fid.delta); + assert.strictEqual(fid.rating, 'good'); assert.match(fid.navigationType, /navigate|reload/); assert.match(fid.entries[0].name, /(mouse|pointer)down/); diff --git a/test/e2e/onINP-test.js b/test/e2e/onINP-test.js index 53a30635..998d8e9b 100644 --- a/test/e2e/onINP-test.js +++ b/test/e2e/onINP-test.js @@ -55,6 +55,7 @@ describe('onINP()', async function() { assert(inp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp.name, 'INP'); assert.strictEqual(inp.value, inp.delta); + assert.strictEqual(inp.rating, 'good'); assert(containsEntry(inp.entries, 'click', 'h1')); assert(interactionIDsMatch(inp.entries)); assert(inp.entries[0].interactionId > 0); @@ -78,6 +79,7 @@ describe('onINP()', async function() { assert(inp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp.name, 'INP'); assert.strictEqual(inp.value, inp.delta); + assert.strictEqual(inp.rating, 'good'); assert(containsEntry(inp.entries, 'click', 'h1')); assert(interactionIDsMatch(inp.entries)); assert(inp.entries[0].interactionId > 0); @@ -101,6 +103,7 @@ describe('onINP()', async function() { assert(inp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp.name, 'INP'); assert.strictEqual(inp.value, inp.delta); + assert.strictEqual(inp.rating, 'good'); assert(containsEntry(inp.entries, 'click', 'h1')); assert(interactionIDsMatch(inp.entries)); assert(inp.entries[0].interactionId > 0); @@ -124,6 +127,7 @@ describe('onINP()', async function() { assert(inp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp.name, 'INP'); assert.strictEqual(inp.value, inp.delta); + assert.strictEqual(inp.rating, 'good'); assert(containsEntry(inp.entries, 'click', 'h1')); assert(interactionIDsMatch(inp.entries)); assert(inp.entries[0].interactionId > 0); @@ -151,6 +155,7 @@ describe('onINP()', async function() { const [inp1] = await getBeacons(); assert(inp1.value >= 600); // Initial pointerdown blocking time. + assert.strictEqual(inp1.rating, 'poor'); await clearBeacons(); await stubVisibilityChange('visible'); @@ -167,6 +172,7 @@ describe('onINP()', async function() { const [inp2] = await getBeacons(); assert(inp2.value >= 400); // Initial pointerdown blocking time. assert(inp2.value < inp1.value); // Should have gone down. + assert.strictEqual(inp2.rating, 'needs-improvement'); await clearBeacons(); await stubVisibilityChange('visible'); @@ -182,6 +188,7 @@ describe('onINP()', async function() { const [inp3] = await getBeacons(); assert(inp3.value >= 200); // 2nd-highest pointerdown blocking time. assert(inp3.value < inp2.value); // Should have gone down. + assert.strictEqual(inp3.rating, 'needs-improvement'); }); it('reports approx p98 interaction when 50+ interactions (reportAllChanges === true)', async function() { @@ -214,6 +221,10 @@ describe('onINP()', async function() { assert(inp2.value < inp1.value); // Should have gone down. assert(inp3.value >= 200); // 2nd-highest pointerdown blocking time. assert(inp3.value < inp2.value); // Should have gone down. + + assert.strictEqual(inp1.rating, 'poor'); + assert.strictEqual(inp2.rating, 'needs-improvement'); + assert.strictEqual(inp3.rating, 'needs-improvement'); }); it('reports a new interaction after bfcache restore', async function() { @@ -234,6 +245,7 @@ describe('onINP()', async function() { assert(inp1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp1.name, 'INP'); assert.strictEqual(inp1.value, inp1.delta); + assert.strictEqual(inp1.rating, 'good'); assert(containsEntry(inp1.entries, 'click', 'h1')); assert(interactionIDsMatch(inp1.entries)); assert.match(inp1.navigationType, /navigate|reload/); @@ -257,6 +269,7 @@ describe('onINP()', async function() { assert(inp1.id !== inp2.id); assert.strictEqual(inp2.name, 'INP'); assert.strictEqual(inp2.value, inp2.delta); + assert.strictEqual(inp2.rating, 'good'); assert(containsEntry(inp2.entries, 'keydown', '#textarea')); assert(interactionIDsMatch(inp2.entries)); assert(inp2.entries[0].interactionId > inp1.entries[0].interactionId); @@ -265,7 +278,7 @@ describe('onINP()', async function() { await stubForwardBack(); await setBlockingTime('keydown', 0); - await setBlockingTime('pointerdown', 200); + await setBlockingTime('pointerdown', 300); const button = await $('button'); await button.click(); @@ -282,6 +295,7 @@ describe('onINP()', async function() { assert(inp1.id !== inp3.id); assert.strictEqual(inp3.name, 'INP'); assert.strictEqual(inp3.value, inp3.delta); + assert.strictEqual(inp3.rating, 'needs-improvement'); assert(containsEntry(inp3.entries, 'pointerdown', '#reset')); assert(interactionIDsMatch(inp3.entries)); assert(inp3.entries[0].interactionId > inp2.entries[0].interactionId); @@ -324,6 +338,7 @@ describe('onINP()', async function() { assert(inp1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp1.name, 'INP'); assert.strictEqual(inp1.value, inp1.delta); + assert.strictEqual(inp1.rating, 'good'); assert(containsEntry(inp1.entries, 'click', 'h1')); assert(interactionIDsMatch(inp1.entries)); assert(inp1.entries[0].interactionId > 0); @@ -362,6 +377,7 @@ describe('onINP()', async function() { assert(inp2.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(inp2.name, 'INP'); assert.strictEqual(inp2.value, inp1.value + inp2.delta); + assert.strictEqual(inp2.rating, 'needs-improvement'); assert(containsEntry(inp2.entries, 'pointerup', '#reset')); assert(interactionIDsMatch(inp2.entries)); assert(inp2.entries[0].interactionId > 0); diff --git a/test/e2e/onLCP-test.js b/test/e2e/onLCP-test.js index 89e074b6..afc879ec 100644 --- a/test/e2e/onLCP-test.js +++ b/test/e2e/onLCP-test.js @@ -120,6 +120,7 @@ describe('onLCP()', async function() { await beaconCountIs(1); const [lcp] = await getBeacons(); + assert.strictEqual(lcp.rating, 'good'); assert.strictEqual(lcp.entries[0].startTime - activationStart, lcp.value); assert.strictEqual(lcp.navigationType, 'prerender'); }); @@ -181,7 +182,7 @@ describe('onLCP()', async function() { it('does not report if the document changes to hidden before the first render', async function() { if (!browserSupportsLCP) this.skip(); - await browser.url('/test/lcp?invisible=1'); + await browser.url('/test/lcp?renderBlocking=1000'); await stubVisibilityChange('hidden'); await stubVisibilityChange('visible'); @@ -197,6 +198,26 @@ describe('onLCP()', async function() { assert.strictEqual(beacons.length, 0); }); + it('reports after a render delay before the page changes to hidden', async function() { + if (!browserSupportsLCP) this.skip(); + + await browser.url('/test/lcp?renderBlocking=3000'); + + // Change to hidden after the first render. + await browser.pause(3500); + await stubVisibilityChange('hidden'); + + const [lcp1] = await getBeacons(); + + assert(lcp1.value > 3000); + assert.strictEqual(lcp1.name, 'LCP'); + assert.strictEqual(lcp1.value, lcp1.delta); + assert.strictEqual(lcp1.rating, 'needs-improvement'); + assert.strictEqual(lcp1.entries.length, 1); + assert.strictEqual(lcp1.entries[0].element, 'img'); + assert.match(lcp1.navigationType, /navigate|reload/); + }); + it('stops reporting after the document changes to hidden (reportAllChanges === false)', async function() { if (!browserSupportsLCP) this.skip(); @@ -226,6 +247,7 @@ describe('onLCP()', async function() { assert(lcp1.value > 0); 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.entries[0].element, 'h1'); assert.match(lcp1.navigationType, /navigate|reload/); @@ -242,6 +264,7 @@ describe('onLCP()', async function() { assert(lcp.value > 0); assert.strictEqual(lcp.name, 'LCP'); assert.strictEqual(lcp.value, lcp.delta); + assert.strictEqual(lcp.rating, 'good'); assert.strictEqual(lcp.entries.length, 1); assert.strictEqual(lcp.entries[0].element, 'h1'); assert.match(lcp.navigationType, /navigate|reload/); @@ -279,27 +302,29 @@ describe('onLCP()', async function() { await stubForwardBack(); await beaconCountIs(1); + const [lcp1] = await getBeacons(); + + assert(lcp1.value > 0); // Greater than the image load delay. + assert(lcp1.id.match(/^v2-\d+-\d+$/)); + assert.strictEqual(lcp1.name, 'LCP'); + assert.strictEqual(lcp1.value, lcp1.delta); + assert.strictEqual(lcp1.rating, 'good'); + assert.strictEqual(lcp1.entries.length, 0); + assert.strictEqual(lcp1.navigationType, 'back_forward_cache'); + + await clearBeacons(); + await stubForwardBack(); + await beaconCountIs(1); + const [lcp2] = await getBeacons(); assert(lcp2.value > 0); // Greater than the image load delay. assert(lcp2.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(lcp2.name, 'LCP'); assert.strictEqual(lcp2.value, lcp2.delta); + assert.strictEqual(lcp2.rating, 'good'); assert.strictEqual(lcp2.entries.length, 0); assert.strictEqual(lcp2.navigationType, 'back_forward_cache'); - - await clearBeacons(); - await stubForwardBack(); - await beaconCountIs(1); - - const [lcp3] = await getBeacons(); - - assert(lcp3.value > 0); // Greater than the image load delay. - assert(lcp3.id.match(/^v2-\d+-\d+$/)); - assert.strictEqual(lcp3.name, 'LCP'); - assert.strictEqual(lcp3.value, lcp3.delta); - assert.strictEqual(lcp3.entries.length, 0); - assert.strictEqual(lcp3.navigationType, 'back_forward_cache'); }); it('reports if the page is restored from bfcache even when the document was hidden at page load time', async function() { @@ -323,27 +348,29 @@ describe('onLCP()', async function() { await stubForwardBack(); await beaconCountIs(1); + const [lcp1] = await getBeacons(); + + assert(lcp1.value > 0); // Greater than the image load delay. + assert(lcp1.id.match(/^v2-\d+-\d+$/)); + assert.strictEqual(lcp1.name, 'LCP'); + assert.strictEqual(lcp1.value, lcp1.delta); + assert.strictEqual(lcp1.rating, 'good'); + assert.strictEqual(lcp1.entries.length, 0); + assert.strictEqual(lcp1.navigationType, 'back_forward_cache'); + + await clearBeacons(); + await stubForwardBack(); + await beaconCountIs(1); + const [lcp2] = await getBeacons(); assert(lcp2.value > 0); // Greater than the image load delay. assert(lcp2.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(lcp2.name, 'LCP'); assert.strictEqual(lcp2.value, lcp2.delta); + assert.strictEqual(lcp2.rating, 'good'); assert.strictEqual(lcp2.entries.length, 0); assert.strictEqual(lcp2.navigationType, 'back_forward_cache'); - - await clearBeacons(); - await stubForwardBack(); - await beaconCountIs(1); - - const [lcp3] = await getBeacons(); - - assert(lcp3.value > 0); // Greater than the image load delay. - assert(lcp3.id.match(/^v2-\d+-\d+$/)); - assert.strictEqual(lcp3.name, 'LCP'); - assert.strictEqual(lcp3.value, lcp3.delta); - assert.strictEqual(lcp3.entries.length, 0); - assert.strictEqual(lcp3.navigationType, 'back_forward_cache'); }); describe('attribution', function() { @@ -577,6 +604,7 @@ const assertStandardReportsAreCorrect = (beacons) => { assert(lcp.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(lcp.name, 'LCP'); assert.strictEqual(lcp.value, lcp.delta); + assert.strictEqual(lcp.rating, 'good'); assert.strictEqual(lcp.entries.length, 1); assert.match(lcp.navigationType, /navigate|reload/); }; @@ -588,6 +616,7 @@ const assertFullReportsAreCorrect = (beacons) => { assert(lcp1.id.match(/^v2-\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.match(lcp1.navigationType, /navigate|reload/); @@ -595,6 +624,7 @@ const assertFullReportsAreCorrect = (beacons) => { assert.strictEqual(lcp2.value, lcp1.value + lcp2.delta); assert.strictEqual(lcp2.name, 'LCP'); assert.strictEqual(lcp2.id, lcp1.id); + assert.strictEqual(lcp2.rating, 'good'); assert.strictEqual(lcp2.entries.length, 1); assert(lcp2.entries[0].startTime > lcp1.entries[0].startTime); assert.match(lcp2.navigationType, /navigate|reload/); diff --git a/test/e2e/onTTFB-test.js b/test/e2e/onTTFB-test.js index 0005af01..d5efa52d 100644 --- a/test/e2e/onTTFB-test.js +++ b/test/e2e/onTTFB-test.js @@ -73,6 +73,7 @@ describe('onTTFB()', async function() { assert(ttfb.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb.name, 'TTFB'); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.navigationType, 'navigate'); assert.strictEqual(ttfb.entries.length, 1); @@ -90,6 +91,25 @@ describe('onTTFB()', async function() { assert(ttfb.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb.name, 'TTFB'); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); + assert.strictEqual(ttfb.navigationType, 'navigate'); + assert.strictEqual(ttfb.entries.length, 1); + + assertValidEntry(ttfb.entries[0]); + }); + + it('reports the correct value when the response is delayed', async function() { + await browser.url('/test/ttfb?delay=1000'); + + const ttfb = await getTTFBBeacon(); + + assert(ttfb.value >= 1000); + assert(ttfb.value >= ttfb.entries[0].requestStart); + assert(ttfb.value <= ttfb.entries[0].loadEventEnd); + assert(ttfb.id.match(/^v2-\d+-\d+$/)); + assert.strictEqual(ttfb.name, 'TTFB'); + assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'needs-improvement'); assert.strictEqual(ttfb.navigationType, 'navigate'); assert.strictEqual(ttfb.entries.length, 1); @@ -103,6 +123,7 @@ describe('onTTFB()', async function() { assert(ttfb.value >= 0); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.entries.length, 1); assert.strictEqual(ttfb.navigationType, 'prerender'); assert.strictEqual(ttfb.value, Math.max( @@ -123,6 +144,7 @@ describe('onTTFB()', async function() { assert(ttfb.value >= 0); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.entries.length, 1); assert.strictEqual(ttfb.navigationType, 'prerender'); assert.strictEqual(ttfb.value, Math.max( @@ -141,6 +163,7 @@ describe('onTTFB()', async function() { assert(ttfb1.value <= ttfb1.entries[0].loadEventEnd); assert(ttfb1.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb1.name, 'TTFB'); + assert.strictEqual(ttfb1.rating, 'good'); assert.strictEqual(ttfb1.value, ttfb1.delta); assert.strictEqual(ttfb1.navigationType, 'navigate'); assert.strictEqual(ttfb1.entries.length, 1); @@ -156,6 +179,7 @@ describe('onTTFB()', async function() { assert(ttfb2.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb2.name, 'TTFB'); assert.strictEqual(ttfb2.value, ttfb2.delta); + assert.strictEqual(ttfb2.rating, 'good'); assert.strictEqual(ttfb2.navigationType, 'back_forward_cache'); assert.strictEqual(ttfb2.entries.length, 0); }); @@ -172,6 +196,7 @@ describe('onTTFB()', async function() { assert(ttfb.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb.name, 'TTFB'); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.navigationType, 'navigate'); assert.strictEqual(ttfb.entries.length, 1); @@ -202,6 +227,7 @@ describe('onTTFB()', async function() { assert(ttfb.value >= 0); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.entries.length, 1); assert.strictEqual(ttfb.navigationType, 'prerender'); assert.strictEqual(ttfb.value, @@ -242,6 +268,7 @@ describe('onTTFB()', async function() { assert(ttfb.id.match(/^v2-\d+-\d+$/)); assert.strictEqual(ttfb.name, 'TTFB'); assert.strictEqual(ttfb.value, ttfb.delta); + assert.strictEqual(ttfb.rating, 'good'); assert.strictEqual(ttfb.navigationType, 'back_forward_cache'); assert.strictEqual(ttfb.entries.length, 0); diff --git a/test/views/layout.njk b/test/views/layout.njk index 139fdcfb..369457e0 100644 --- a/test/views/layout.njk +++ b/test/views/layout.njk @@ -137,6 +137,9 @@ {% endif %} {% block head %}{% endblock %} + {% if renderBlocking %} + + {% endif %}