From 1553fbfd6e5c0788cf48a22d80b244b6a0deba2b Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 11 May 2022 14:24:04 -0700 Subject: [PATCH] add method to measure Interaction to Next Paint (INP) `web-vitals` CHANGELOG for v3: - [BREAKING] Report TTFB after a bfcache restore - [BREAKING] Only include last LCP entry in metric entries - Add support for the new INP metric - Rename getXXX() functions to onXXX() - Add a navigationType property to the Metric object See https://github.com/GoogleChrome/web-vitals/blob/next/CHANGELOG.md --- .github/workflows/build_test_deploy.yml | 2 +- package.json | 2 +- packages/next/client/performance-relayer.ts | 28 +++--- .../compiled/web-vitals/web-vitals.umd.js | 2 +- packages/next/package.json | 2 +- packages/next/shared/lib/utils.ts | 2 +- pnpm-lock.yaml | 94 ++++++++++++------- .../relay-analytics/pages/index.js | 9 ++ .../relay-analytics/test/index.test.js | 14 +++ 9 files changed, 104 insertions(+), 51 deletions(-) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 3b7282863f2619..9cf37ee688f644 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -266,7 +266,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + - run: npm i -g playwright-chromium@1.17.2 && npx playwright install-deps if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: node run-tests.js --type development diff --git a/package.json b/package.json index 99de423ab14982..83851e3a685eb3 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "npm-run-all": "4.1.5", "nprogress": "0.2.0", "pixrem": "5.0.0", - "playwright-chromium": "1.14.1", + "playwright-chromium": "1.17.2", "plop": "3.0.5", "postcss-nested": "4.2.1", "postcss-pseudoelements": "5.0.0", diff --git a/packages/next/client/performance-relayer.ts b/packages/next/client/performance-relayer.ts index 3f386dbc12d3bd..c1d87fed8784bf 100644 --- a/packages/next/client/performance-relayer.ts +++ b/packages/next/client/performance-relayer.ts @@ -1,17 +1,18 @@ /* global location */ import { - getCLS, - getFCP, - getFID, - getLCP, - getTTFB, + onCLS, + onFCP, + onFID, + onINP, + onLCP, + onTTFB, Metric, - ReportHandler, + ReportCallback, } from 'next/dist/compiled/web-vitals' const initialHref = location.href let isRegistered = false -let userReportHandler: ReportHandler | undefined +let userReportHandler: ReportCallback | undefined function onReport(metric: Metric): void { if (userReportHandler) { @@ -71,7 +72,7 @@ function onReport(metric: Metric): void { } } -export default (onPerfEntry?: ReportHandler): void => { +export default (onPerfEntry?: ReportCallback): void => { // Update function if it changes: userReportHandler = onPerfEntry @@ -81,9 +82,10 @@ export default (onPerfEntry?: ReportHandler): void => { } isRegistered = true - getCLS(onReport) - getFID(onReport) - getFCP(onReport) - getLCP(onReport) - getTTFB(onReport) + onCLS(onReport) + onFID(onReport) + onFCP(onReport) + onLCP(onReport) + onTTFB(onReport) + onINP(onReport) } diff --git a/packages/next/compiled/web-vitals/web-vitals.umd.js b/packages/next/compiled/web-vitals/web-vitals.umd.js index 40ec0a8ad02e7a..59b6810a7313f2 100644 --- a/packages/next/compiled/web-vitals/web-vitals.umd.js +++ b/packages/next/compiled/web-vitals/web-vitals.umd.js @@ -1 +1 @@ -(function(){var e={733:function(e,t){!function(e,l){true?l(t):0}(this,(function(e){"use strict";var t,l,g,h,a=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},o=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var l=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return l.observe({type:e,buffered:!0}),l}}catch(e){}},u=function(e,t){var l=function n(l){"pagehide"!==l.type&&"hidden"!==document.visibilityState||(e(l),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",l,!0),addEventListener("pagehide",l,!0)},c=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},f=function(e,t,l){var g;return function(h){t.value>=0&&(h||l)&&(t.delta=t.value-(g||0),(t.delta||void 0===g)&&(g=t.value,e(t)))}},y=-1,d=function(){return"hidden"===document.visibilityState?0:1/0},m=function(){u((function(e){var t=e.timeStamp;y=t}),!0)},v=function(){return y<0&&(y=d(),m(),c((function(){setTimeout((function(){y=d(),m()}),0)}))),{get firstHiddenTime(){return y}}},p=function(e,t){var l,g=v(),h=a("FCP"),u=function(e){"first-contentful-paint"===e.name&&(w&&w.disconnect(),e.startTime=0&&l1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){T(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,_),removeEventListener("pointercancel",i,_)};addEventListener("pointerup",n,_),addEventListener("pointercancel",i,_)}(t,e):T(t,e)}},L=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,S,_)}))},P=new Set;e.getCLS=function(e,t){w||(p((function(e){b=e.value})),w=!0);var l,i=function(t){b>-1&&e(t)},g=a("CLS",0),h=0,y=[],m=function(e){if(!e.hadRecentInput){var t=y[0],w=y[y.length-1];h&&e.startTime-w.startTime<1e3&&e.startTime-t.startTime<5e3?(h+=e.value,y.push(e)):(h=e.value,y=[e]),h>g.value&&(g.value=h,g.entries=y,l())}},_=o("layout-shift",m);_&&(l=f(i,g,t),u((function(){_.takeRecords().map(m),l(!0)})),c((function(){h=0,b=-1,g=a("CLS",0),l=f(i,g,t)})))},e.getFCP=p,e.getFID=function(e,g){var y,w=v(),b=a("FID"),p=function(e){e.startTime=0&&(T||o)&&(n.delta=n.value-(y||0),(n.delta||void 0===y)&&(y=n.value,e(n)))}},I=-1,v=function(){return"hidden"===document.visibilityState?0:1/0},m=function(){l((function(e){var n=e.timeStamp;I=n}),!0)},h=function(){return I<0&&(I=v(),m(),c((function(){setTimeout((function(){I=v(),m()}),0)}))),{get firstHiddenTime(){return I}}},g=function(e,n){n=n||{};var o,y=h(),T=f("FCP"),a=function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(C&&C.disconnect(),e.startTime-1&&e(n)},y=f("CLS",0),T=0,w=[],u=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=w[0],C=w[w.length-1];T&&e.startTime-C.startTime<1e3&&e.startTime-n.startTime<5e3?(T+=e.value,w.push(e)):(T=e.value,w=[e]),T>y.value&&(y.value=T,y.entries=w,o())}}))},C=s("layout-shift",u);C&&(o=d(i,y,n.reportAllChanges),l((function(){u(C.takeRecords()),o(!0)})),c((function(){T=0,M=-1,y=f("CLS",0),o=d(i,y,n.reportAllChanges)})))},N={passive:!0,capture:!0},O=new Date,L=function(e,T){n||(n=T,o=e,y=new Date,A(removeEventListener),S())},S=function(){if(o>=0&&o1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){L(e,n),r()},i=function(){r()},r=function(){removeEventListener("pointerup",t,N),removeEventListener("pointercancel",i,N)};addEventListener("pointerup",t,N),addEventListener("pointercancel",i,N)}(n,e):L(n,e)}},A=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,b,N)}))},F=function(e,y){y=y||{};var w,C=h(),I=f("FID"),p=function(e){e.startTimen.latency){if(o)o.entries.push(e),o.latency=Math.max(o.latency,e.duration);else{var y={id:e.interactionId,latency:e.duration,entries:[e]};K[y.id]=y,G.push(y)}G.sort((function(e,n){return n.latency-e.latency})),G.splice(10).forEach((function(e){delete K[e.id]}))}}(e)}));var n,T=(n=Math.min(G.length-1,Math.floor(x()/50)),G[n]);T&&T.latency!==y.value&&(y.value=T.latency,y.entries=T.entries,o())},T=s("event",r,{durationThreshold:n.durationThreshold||40});o=d(e,y,n.reportAllChanges),T&&(l((function(){r(T.takeRecords()),y.value<0&&x()>0&&(y.value=0,y.entries=[]),o(!0)})),c((function(){G=[],z=B(),y=f("INP"),o=d(e,y,n.reportAllChanges)})))},Q={},_=function(e,n){n=n||{};var o,y=h(),T=f("LCP"),a=function(e){var n=e[e.length-1];if(n){var w=n.startTime;wperformance.now())return;y.entries=[e],T(!0)}},"complete"===document.readyState?setTimeout(o,0):addEventListener("load",(function(){return setTimeout(o,0)})),c((function(o){y=f("TTFB"),T=d(e,y,n.reportAllChanges),y.value=performance.now()-o.timeStamp,T(!0)}))};e.getCLS=E,e.getFCP=g,e.getFID=F,e.getINP=q,e.getLCP=_,e.getTTFB=j,e.onCLS=E,e.onFCP=g,e.onFID=F,e.onINP=q,e.onLCP=_,e.onTTFB=j,Object.defineProperty(e,"__esModule",{value:!0})}))}};if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var n={};e[61](0,n);module.exports=n})(); \ No newline at end of file diff --git a/packages/next/package.json b/packages/next/package.json index 0584f788e9b53c..925538b054ac61 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -269,7 +269,7 @@ "uuid": "8.3.2", "vm-browserify": "1.1.2", "watchpack": "2.3.1", - "web-vitals": "2.1.0", + "web-vitals": "3.0.0-beta.2", "webpack-sources1": "npm:webpack-sources@1.4.3", "webpack-sources3": "npm:webpack-sources@3.2.3", "webpack4": "npm:webpack@4.44.1", diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 49c9d5460f911d..1ec02c16788720 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -47,7 +47,7 @@ export type NextWebVitalsMetric = { } & ( | { label: 'web-vital' - name: 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB' + name: 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB' | 'INP' } | { label: 'custom' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29c21eca939730..72b2c27f9adb31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,7 +121,7 @@ importers: npm-run-all: 4.1.5 nprogress: 0.2.0 pixrem: 5.0.0 - playwright-chromium: 1.14.1 + playwright-chromium: 1.17.2 plop: 3.0.5 postcss-nested: 4.2.1 postcss-pseudoelements: 5.0.0 @@ -270,7 +270,7 @@ importers: npm-run-all: 4.1.5 nprogress: 0.2.0 pixrem: 5.0.0 - playwright-chromium: 1.14.1 + playwright-chromium: 1.17.2 plop: 3.0.5 postcss-nested: 4.2.1 postcss-pseudoelements: 5.0.0 @@ -564,7 +564,7 @@ importers: uuid: 8.3.2 vm-browserify: 1.1.2 watchpack: 2.3.1 - web-vitals: 2.1.0 + web-vitals: 3.0.0-beta.2 webpack-sources1: npm:webpack-sources@1.4.3 webpack-sources3: npm:webpack-sources@3.2.3 webpack4: npm:webpack@4.44.1 @@ -751,7 +751,7 @@ importers: uuid: 8.3.2 vm-browserify: 1.1.2 watchpack: 2.3.1 - web-vitals: 2.1.0 + web-vitals: 3.0.0-beta.2 webpack-sources1: /webpack-sources/1.4.3 webpack-sources3: /webpack-sources/3.2.3 webpack4: /webpack/4.44.1 @@ -7653,6 +7653,15 @@ packages: - supports-color dev: true + /agent-base/6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /agentkeepalive/3.4.1: resolution: {integrity: sha512-MPIwsZU9PP9kOrZpyu2042kYA8Fdt/AedQYkYXucHgF9QoD9dXVp0ypuGnHXSR0hTstBxdt85Xkh4JolYfK5wg==} engines: {node: '>= 4.0.0'} @@ -8818,7 +8827,7 @@ packages: mississippi: 3.0.0 mkdirp: 0.5.5 move-concurrently: 1.0.1 - promise-inflight: 1.0.1_bluebird@3.7.2 + promise-inflight: 1.0.1 rimraf: 2.7.1 ssri: 6.0.1 unique-filename: 1.1.1 @@ -9552,6 +9561,11 @@ packages: engines: {node: '>= 10'} dev: true + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: true + /comment-json/3.0.3: resolution: {integrity: sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==} engines: {node: '>= 6'} @@ -12053,7 +12067,7 @@ packages: dev: true /fd-slicer/1.1.0: - resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=} + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: pend: 1.2.0 dev: true @@ -12576,7 +12590,7 @@ packages: dev: true /get-stream/3.0.0: - resolution: {integrity: sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=} + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} engines: {node: '>=4'} dev: true @@ -13432,7 +13446,7 @@ packages: engines: {node: '>= 6'} dependencies: '@tootallnate/once': 1.1.2 - agent-base: 6.0.1 + agent-base: 6.0.2 debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -13776,7 +13790,7 @@ packages: dev: true /ip/1.1.5: - resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} + resolution: {integrity: sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==} dev: true /ipaddr.js/1.9.0: @@ -17956,7 +17970,7 @@ packages: dev: true /path-exists/3.0.0: - resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} /path-exists/4.0.0: @@ -18137,13 +18151,25 @@ packages: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} dev: false - /playwright-chromium/1.14.1: - resolution: {integrity: sha512-gAHDcrBKrl1Az6TuzC4T013Nl+qKVZeblc2VkElHqEuNQHPKW7840cQBZavFz38xJshC993iClVc6Y+bLgF8FA==} + /playwright-chromium/1.17.2: + resolution: {integrity: sha512-afht0lopmJJPOGsffqTthL2MsPDm4tTGeTJWRE7jcrMD7OX9v4EtzBDzvKvs45HHzct3lBKrtVneFJab1Uo6vA==} engines: {node: '>=12'} hasBin: true requiresBuild: true dependencies: - commander: 6.2.1 + playwright-core: 1.17.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /playwright-core/1.17.2: + resolution: {integrity: sha512-TCYIt2UNHvqGxvD79bBjBv9osDLAH1gn7AZD5kRpMNQJG6BAmJt8B4Ek8fzdKmCQOnHf9ASJmcYRszoIZxcdVA==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 8.3.0 debug: 4.3.4 extract-zip: 2.0.1 https-proxy-agent: 5.0.0 @@ -18154,8 +18180,10 @@ packages: proper-lockfile: 4.1.2 proxy-from-env: 1.1.0 rimraf: 3.0.2 + socks-proxy-agent: 6.2.1 stack-utils: 2.0.3 ws: 7.5.3 + yauzl: 2.10.0 yazl: 2.5.1 transitivePeerDependencies: - bufferutil @@ -19126,17 +19154,6 @@ packages: optional: true dev: true - /promise-inflight/1.0.1_bluebird@3.7.2: - resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - dependencies: - bluebird: 3.7.2 - dev: true - /promise-polyfill/6.1.0: resolution: {integrity: sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=} dev: true @@ -20871,8 +20888,8 @@ packages: resolution: {integrity: sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=} dev: true - /smart-buffer/4.1.0: - resolution: {integrity: sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==} + /smart-buffer/4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} dev: true @@ -20924,19 +20941,30 @@ packages: resolution: {integrity: sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==} engines: {node: '>= 6'} dependencies: - agent-base: 6.0.1 + agent-base: 6.0.2 + debug: 4.3.4 + socks: 2.6.2 + transitivePeerDependencies: + - supports-color + dev: true + + /socks-proxy-agent/6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 debug: 4.3.4 - socks: 2.5.1 + socks: 2.6.2 transitivePeerDependencies: - supports-color dev: true - /socks/2.5.1: - resolution: {integrity: sha512-oZCsJJxapULAYJaEYBSzMcz8m3jqgGrHaGhkmU/o/PQfFWYWxkAaA0UMGImb6s6tEXfKi959X6VJjMMQ3P6TTQ==} + /socks/2.6.2: + resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} dependencies: ip: 1.1.5 - smart-buffer: 4.1.0 + smart-buffer: 4.2.0 dev: true /sort-keys/2.0.0: @@ -23119,8 +23147,8 @@ packages: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} dev: true - /web-vitals/2.1.0: - resolution: {integrity: sha512-npEyJP8jHf3J71t1tRTEtz9FeKp8H2udWJUUq5ykfPhhstr//TUxiYhIEzLNwk4zv2ybAilMn7v7N6Mxmuitmg==} + /web-vitals/3.0.0-beta.2: + resolution: {integrity: sha512-W9OALsWK4RkA5GWvLhsfszy+Q29WJBB27Dnucc3eYP6/0kz1XsfMgm+4au9X/KjXMIo92ZRU1fWBaSdNsaVjJg==} dev: true /webidl-conversions/3.0.1: diff --git a/test/integration/relay-analytics/pages/index.js b/test/integration/relay-analytics/pages/index.js index cf1d4664d88a1c..f0ab68cb5836d5 100644 --- a/test/integration/relay-analytics/pages/index.js +++ b/test/integration/relay-analytics/pages/index.js @@ -16,12 +16,21 @@ if (typeof navigator !== 'undefined') { } } +function toggleText(e) { + const startTime = performance.now() + while (performance.now() < startTime + 50) { + // busy waiting + } + e.target.textContent = e.target.textContent === 'Click' ? 'Press' : 'Click' +} + export default () => { // Below comment will be used for replacing exported report method with hook based one. return (

Foo!

bar!

+
) } diff --git a/test/integration/relay-analytics/test/index.test.js b/test/integration/relay-analytics/test/index.test.js index d4140e36706d1d..796e7ffaa3ad92 100644 --- a/test/integration/relay-analytics/test/index.test.js +++ b/test/integration/relay-analytics/test/index.test.js @@ -108,4 +108,18 @@ function runTest() { expect(stdout).toMatch('Next.js Analytics') await browser.close() }) + + it('reports INP metric', async () => { + const browser = await webdriver(appPort, '/') + await browser.elementByCss('button').click() + await browser.waitForCondition( + 'document.querySelector("button").textContent === "Press"' + ) + // INP metric is only reported on pagehide or visibilitychange event, so refresh the page + await browser.refresh() + const INP = parseInt(await browser.eval('localStorage.getItem("INP")'), 10) + // We introduced a delay of 50ms, so INP duration should be >= 50 + expect(INP).toBeGreaterThanOrEqual(50) + await browser.close() + }) }