From ad63721bd2037fa456fa92b291870cf9cda15966 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Fri, 2 May 2025 15:04:37 -0700 Subject: [PATCH 1/4] [mcp] Add proper web-vitals metric collection --- .../packages/react-mcp-server/src/index.ts | 25 +-- .../react-mcp-server/src/tools/runtimePerf.ts | 163 +++++++++++------- 2 files changed, 118 insertions(+), 70 deletions(-) diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 138dc57dc14a9..2b2caf5d6e423 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -22,6 +22,10 @@ import assertExhaustive from './utils/assertExhaustive'; import {convert} from 'html-to-text'; import {measurePerformance} from './tools/runtimePerf'; +function calculateMean(values: number[]): string { + return values.length > 0 ? (values.reduce((acc, curr) => acc + curr, 0) / values.length) + 'ms' : 'could not collect'; +} + const server = new McpServer({ name: 'React', version: '0.0.0', @@ -286,7 +290,6 @@ server.tool( server.tool( 'review-react-runtime', `Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance. - This tool has some requirements on the code input: - The react code that is passed into this tool MUST contain an App functional component without arrow function. @@ -307,12 +310,12 @@ server.tool( (repeat until every metric is good or two consecutive cycles show no gain) - - Always run the tool once on the original code before any modification - - Run the tool again after making the modification, and apply one focused change based on the failing metric plus React-specific guidance: + - Apply one focused change based on the failing metric plus React-specific guidance: - LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components). - INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect. - CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders. - - Compare the results of your modified code compared to the original to verify that your changes have improved performance. + + Stop when every metric is classified as good. Return the final metric table and the list of applied changes. `, { @@ -326,17 +329,17 @@ server.tool( # React Component Performance Results ## Mean Render Time -${results.renderTime / iterations}ms +${calculateMean(results.renderTime)} +TEST: ${results.webVitals.inp} ## Mean Web Vitals -- Cumulative Layout Shift (CLS): ${results.webVitals.cls / iterations}ms -- Largest Contentful Paint (LCP): ${results.webVitals.lcp / iterations}ms -- Interaction to Next Paint (INP): ${results.webVitals.inp / iterations}ms -- First Input Delay (FID): ${results.webVitals.fid / iterations}ms +- Cumulative Layout Shift (CLS): ${calculateMean(results.webVitals.cls)} +- Largest Contentful Paint (LCP): ${calculateMean(results.webVitals.lcp)} +- Interaction to Next Paint (INP): ${calculateMean(results.webVitals.inp)} ## Mean React Profiler -- Actual Duration: ${results.reactProfiler.actualDuration / iterations}ms -- Base Duration: ${results.reactProfiler.baseDuration / iterations}ms +- Actual Duration: ${calculateMean(results.reactProfiler.actualDuration)} +- Base Duration: ${calculateMean(results.reactProfiler.baseDuration)} `; return { diff --git a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts index 7f4d0a1efecd8..491bcf091b5ac 100644 --- a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts +++ b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts @@ -8,25 +8,52 @@ import * as babelPresetEnv from '@babel/preset-env'; import * as babelPresetReact from '@babel/preset-react'; type PerformanceResults = { - renderTime: number; + renderTime: number[]; webVitals: { - cls: number; - lcp: number; - inp: number; - fid: number; - ttfb: number; + cls: number[]; + lcp: number[]; + inp: number[]; + fid: number[]; + ttfb: number[]; }; reactProfiler: { - id: number; - phase: number; - actualDuration: number; - baseDuration: number; - startTime: number; - commitTime: number; + id: number[]; + phase: number[]; + actualDuration: number[]; + baseDuration: number[]; + startTime: number[]; + commitTime: number[]; }; error: Error | null; }; + +type EvaluationResults = { + renderTime: number | null; + webVitals: { + cls: number | null; + lcp: number | null; + inp: number | null; + fid: number | null; + ttfb: number | null; + }; + reactProfiler: { + id: number | null; + phase: number | null; + actualDuration: number | null; + baseDuration: number | null; + startTime: number | null; + commitTime: number | null; + }; + error: Error | null; +}; + +function delay(time: number) { + return new Promise(function (resolve) { + setTimeout(resolve, time) + }); +} + export async function measurePerformance( code: string, iterations: number, @@ -68,66 +95,84 @@ export async function measurePerformance( const browser = await puppeteer.launch(); const page = await browser.newPage(); - await page.setViewport({width: 1280, height: 720}); + await page.setViewport({ width: 1280, height: 720 }); const html = buildHtml(transpiled); let performanceResults: PerformanceResults = { - renderTime: 0, + renderTime: [], webVitals: { - cls: 0, - lcp: 0, - inp: 0, - fid: 0, - ttfb: 0, + cls: [], + lcp: [], + inp: [], + fid: [], + ttfb: [], }, reactProfiler: { - id: 0, - phase: 0, - actualDuration: 0, - baseDuration: 0, - startTime: 0, - commitTime: 0, + id: [], + phase: [], + actualDuration: [], + baseDuration: [], + startTime: [], + commitTime: [], }, error: null, }; for (let ii = 0; ii < iterations; ii++) { - await page.setContent(html, {waitUntil: 'networkidle0'}); + await page.setContent(html, { waitUntil: 'networkidle0' }); await page.waitForFunction( 'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)', ); + // ui chaos monkey - await page.waitForFunction(`window.__RESULT__ !== undefined && (function() { - for (const el of [...document.querySelectorAll('a'), ...document.querySelectorAll('button')]) { - console.log(el); - el.click(); + const selectors = await page.evaluate(() => { + window.__INTERACTABLE_SELECTORS__ = []; + const elements = Array.from(document.querySelectorAll('a')).concat(Array.from(document.querySelectorAll('button'))); + for (const el of elements) { + window.__INTERACTABLE_SELECTORS__.push(el.tagName.toLowerCase()); } - return true; - })() `); - const evaluationResult: PerformanceResults = await page.evaluate(() => { + return window.__INTERACTABLE_SELECTORS__; + }); + + for (const selector of selectors) { + await page.click(selector); + await delay(500); + } + + // Visit a new page for 1s to background the current page so that WebVitals can finish being calculated + const tempPage = await browser.newPage(); + await tempPage.evaluate(() => { + return new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 1000); + }); + }); + await tempPage.close(); + + const evaluationResult: EvaluationResults = await page.evaluate(() => { return (window as any).__RESULT__; }); - // TODO: investigate why webvital metrics are not populating correctly - performanceResults.renderTime += evaluationResult.renderTime; - performanceResults.webVitals.cls += evaluationResult.webVitals.cls || 0; - performanceResults.webVitals.lcp += evaluationResult.webVitals.lcp || 0; - performanceResults.webVitals.inp += evaluationResult.webVitals.inp || 0; - performanceResults.webVitals.fid += evaluationResult.webVitals.fid || 0; - performanceResults.webVitals.ttfb += evaluationResult.webVitals.ttfb || 0; - - performanceResults.reactProfiler.id += - evaluationResult.reactProfiler.actualDuration || 0; - performanceResults.reactProfiler.phase += - evaluationResult.reactProfiler.phase || 0; - performanceResults.reactProfiler.actualDuration += - evaluationResult.reactProfiler.actualDuration || 0; - performanceResults.reactProfiler.baseDuration += - evaluationResult.reactProfiler.baseDuration || 0; - performanceResults.reactProfiler.startTime += - evaluationResult.reactProfiler.startTime || 0; - performanceResults.reactProfiler.commitTime += - evaluationResult.reactProfiler.commitTime || 0; + if (evaluationResult.renderTime !== null) { + performanceResults.renderTime.push(evaluationResult.renderTime); + } + + const webVitalMetrics = ['cls', 'lcp', 'inp', 'fid', 'ttfb'] as const; + for (const metric of webVitalMetrics) { + if (evaluationResult.webVitals[metric] !== null) { + performanceResults.webVitals[metric].push(evaluationResult.webVitals[metric]); + } + } + + const profilerMetrics = ['id', 'phase', 'actualDuration', 'baseDuration', 'startTime', 'commitTime'] as const; + for (const metric of profilerMetrics) { + if (evaluationResult.reactProfiler[metric] !== null) { + performanceResults.reactProfiler[metric].push( + evaluationResult.reactProfiler[metric] + ); + } + } performanceResults.error = evaluationResult.error; } @@ -159,14 +204,14 @@ function buildHtml(transpiled: string) { renderTime: null, webVitals: {}, reactProfiler: {}, - error: null + error: null, }; - webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; }); - webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; }); - webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; }); - webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; }); - webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; }); + webVitals.onCLS(({value}) => { window.__RESULT__.webVitals.cls = value; }); + webVitals.onLCP(({value}) => { window.__RESULT__.webVitals.lcp = value; }); + webVitals.onINP(({value}) => { window.__RESULT__.webVitals.inp = value; }); + webVitals.onFID(({value}) => { window.__RESULT__.webVitals.fid = value; }); + webVitals.onTTFB(({value}) => { window.__RESULT__.webVitals.ttfb = value; }); try { ${transpiled} From 7cce86202a3b02d234e4bf6f90bcea6bd443cd1f Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Fri, 2 May 2025 15:59:55 -0700 Subject: [PATCH 2/4] [mcp] Revert left over changes from merge hell --- .../packages/react-mcp-server/src/index.ts | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 2b2caf5d6e423..6e0f561ab2bd2 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; -import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'; -import {z} from 'zod'; -import {compile, type PrintedCompilerPipelineValue} from './compiler'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { compile, type PrintedCompilerPipelineValue } from './compiler'; import { CompilerPipelineValue, printReactiveFunctionWithOutlined, @@ -17,10 +17,10 @@ import { SourceLocation, } from 'babel-plugin-react-compiler/src'; import * as cheerio from 'cheerio'; -import {queryAlgolia} from './utils/algolia'; +import { queryAlgolia } from './utils/algolia'; import assertExhaustive from './utils/assertExhaustive'; -import {convert} from 'html-to-text'; -import {measurePerformance} from './tools/runtimePerf'; +import { convert } from 'html-to-text'; +import { measurePerformance } from './tools/runtimePerf'; function calculateMean(values: number[]): string { return values.length > 0 ? (values.reduce((acc, curr) => acc + curr, 0) / values.length) + 'ms' : 'could not collect'; @@ -37,12 +37,12 @@ server.tool( { query: z.string(), }, - async ({query}) => { + async ({ query }) => { try { const pages = await queryAlgolia(query); if (pages.length === 0) { return { - content: [{type: 'text' as const, text: `No results`}], + content: [{ type: 'text' as const, text: `No results` }], }; } const content = pages.map(html => { @@ -68,7 +68,7 @@ server.tool( } catch (err) { return { isError: true, - content: [{type: 'text' as const, text: `Error: ${err.stack}`}], + content: [{ type: 'text' as const, text: `Error: ${err.stack}` }], }; } }, @@ -89,7 +89,7 @@ server.tool( text: z.string(), passName: z.enum(['HIR', 'ReactiveFunction', 'All', '@DEBUG']).optional(), }, - async ({text, passName}) => { + async ({ text, passName }) => { const pipelinePasses = new Map< string, Array @@ -141,7 +141,7 @@ server.tool( } } }; - const errors: Array<{message: string; loc: SourceLocation | null}> = []; + const errors: Array<{ message: string; loc: SourceLocation | null }> = []; const compilerOptions: Partial = { panicThreshold: 'none', logger: { @@ -170,10 +170,10 @@ server.tool( if (result.code == null) { return { isError: true, - content: [{type: 'text' as const, text: 'Error: Could not compile'}], + content: [{ type: 'text' as const, text: 'Error: Could not compile' }], }; } - const requestedPasses: Array<{type: 'text'; text: string}> = []; + const requestedPasses: Array<{ type: 'text'; text: string }> = []; if (passName != null) { switch (passName) { case 'All': { @@ -274,14 +274,14 @@ server.tool( } return { content: [ - {type: 'text' as const, text: result.code}, + { type: 'text' as const, text: result.code }, ...requestedPasses, ], }; } catch (err) { return { isError: true, - content: [{type: 'text' as const, text: `Error: ${err.stack}`}], + content: [{ type: 'text' as const, text: `Error: ${err.stack}` }], }; } }, @@ -290,6 +290,7 @@ server.tool( server.tool( 'review-react-runtime', `Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance. + This tool has some requirements on the code input: - The react code that is passed into this tool MUST contain an App functional component without arrow function. @@ -310,19 +311,19 @@ server.tool( (repeat until every metric is good or two consecutive cycles show no gain) - - Apply one focused change based on the failing metric plus React-specific guidance: + - Always run the tool once on the original code before any modification + - Run the tool again after making the modification, and apply one focused change based on the failing metric plus React-specific guidance: - LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components). - INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect. - CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders. - - Stop when every metric is classified as good. Return the final metric table and the list of applied changes. + - Compare the results of your modified code compared to the original to verify that your changes have improved performance. `, { text: z.string(), iterations: z.number().optional().default(2), }, - async ({text, iterations}) => { + async ({ text, iterations }) => { try { const results = await measurePerformance(text, iterations); const formattedResults = ` From fa03b1920378ed82c04a0136278fcac68ffa15ec Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Fri, 2 May 2025 16:01:12 -0700 Subject: [PATCH 3/4] formatting --- .../packages/react-mcp-server/src/index.ts | 38 ++++++++++--------- .../react-mcp-server/src/tools/runtimePerf.ts | 26 +++++++++---- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 6e0f561ab2bd2..8ce2633d6e155 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { z } from 'zod'; -import { compile, type PrintedCompilerPipelineValue } from './compiler'; +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; +import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'; +import {z} from 'zod'; +import {compile, type PrintedCompilerPipelineValue} from './compiler'; import { CompilerPipelineValue, printReactiveFunctionWithOutlined, @@ -17,13 +17,15 @@ import { SourceLocation, } from 'babel-plugin-react-compiler/src'; import * as cheerio from 'cheerio'; -import { queryAlgolia } from './utils/algolia'; +import {queryAlgolia} from './utils/algolia'; import assertExhaustive from './utils/assertExhaustive'; -import { convert } from 'html-to-text'; -import { measurePerformance } from './tools/runtimePerf'; +import {convert} from 'html-to-text'; +import {measurePerformance} from './tools/runtimePerf'; function calculateMean(values: number[]): string { - return values.length > 0 ? (values.reduce((acc, curr) => acc + curr, 0) / values.length) + 'ms' : 'could not collect'; + return values.length > 0 + ? values.reduce((acc, curr) => acc + curr, 0) / values.length + 'ms' + : 'could not collect'; } const server = new McpServer({ @@ -37,12 +39,12 @@ server.tool( { query: z.string(), }, - async ({ query }) => { + async ({query}) => { try { const pages = await queryAlgolia(query); if (pages.length === 0) { return { - content: [{ type: 'text' as const, text: `No results` }], + content: [{type: 'text' as const, text: `No results`}], }; } const content = pages.map(html => { @@ -68,7 +70,7 @@ server.tool( } catch (err) { return { isError: true, - content: [{ type: 'text' as const, text: `Error: ${err.stack}` }], + content: [{type: 'text' as const, text: `Error: ${err.stack}`}], }; } }, @@ -89,7 +91,7 @@ server.tool( text: z.string(), passName: z.enum(['HIR', 'ReactiveFunction', 'All', '@DEBUG']).optional(), }, - async ({ text, passName }) => { + async ({text, passName}) => { const pipelinePasses = new Map< string, Array @@ -141,7 +143,7 @@ server.tool( } } }; - const errors: Array<{ message: string; loc: SourceLocation | null }> = []; + const errors: Array<{message: string; loc: SourceLocation | null}> = []; const compilerOptions: Partial = { panicThreshold: 'none', logger: { @@ -170,10 +172,10 @@ server.tool( if (result.code == null) { return { isError: true, - content: [{ type: 'text' as const, text: 'Error: Could not compile' }], + content: [{type: 'text' as const, text: 'Error: Could not compile'}], }; } - const requestedPasses: Array<{ type: 'text'; text: string }> = []; + const requestedPasses: Array<{type: 'text'; text: string}> = []; if (passName != null) { switch (passName) { case 'All': { @@ -274,14 +276,14 @@ server.tool( } return { content: [ - { type: 'text' as const, text: result.code }, + {type: 'text' as const, text: result.code}, ...requestedPasses, ], }; } catch (err) { return { isError: true, - content: [{ type: 'text' as const, text: `Error: ${err.stack}` }], + content: [{type: 'text' as const, text: `Error: ${err.stack}`}], }; } }, @@ -323,7 +325,7 @@ server.tool( text: z.string(), iterations: z.number().optional().default(2), }, - async ({ text, iterations }) => { + async ({text, iterations}) => { try { const results = await measurePerformance(text, iterations); const formattedResults = ` diff --git a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts index 491bcf091b5ac..242ab0aa757ad 100644 --- a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts +++ b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts @@ -27,7 +27,6 @@ type PerformanceResults = { error: Error | null; }; - type EvaluationResults = { renderTime: number | null; webVitals: { @@ -50,7 +49,7 @@ type EvaluationResults = { function delay(time: number) { return new Promise(function (resolve) { - setTimeout(resolve, time) + setTimeout(resolve, time); }); } @@ -95,7 +94,7 @@ export async function measurePerformance( const browser = await puppeteer.launch(); const page = await browser.newPage(); - await page.setViewport({ width: 1280, height: 720 }); + await page.setViewport({width: 1280, height: 720}); const html = buildHtml(transpiled); let performanceResults: PerformanceResults = { @@ -119,7 +118,7 @@ export async function measurePerformance( }; for (let ii = 0; ii < iterations; ii++) { - await page.setContent(html, { waitUntil: 'networkidle0' }); + await page.setContent(html, {waitUntil: 'networkidle0'}); await page.waitForFunction( 'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)', ); @@ -127,7 +126,9 @@ export async function measurePerformance( // ui chaos monkey const selectors = await page.evaluate(() => { window.__INTERACTABLE_SELECTORS__ = []; - const elements = Array.from(document.querySelectorAll('a')).concat(Array.from(document.querySelectorAll('button'))); + const elements = Array.from(document.querySelectorAll('a')).concat( + Array.from(document.querySelectorAll('button')), + ); for (const el of elements) { window.__INTERACTABLE_SELECTORS__.push(el.tagName.toLowerCase()); } @@ -161,15 +162,24 @@ export async function measurePerformance( const webVitalMetrics = ['cls', 'lcp', 'inp', 'fid', 'ttfb'] as const; for (const metric of webVitalMetrics) { if (evaluationResult.webVitals[metric] !== null) { - performanceResults.webVitals[metric].push(evaluationResult.webVitals[metric]); + performanceResults.webVitals[metric].push( + evaluationResult.webVitals[metric], + ); } } - const profilerMetrics = ['id', 'phase', 'actualDuration', 'baseDuration', 'startTime', 'commitTime'] as const; + const profilerMetrics = [ + 'id', + 'phase', + 'actualDuration', + 'baseDuration', + 'startTime', + 'commitTime', + ] as const; for (const metric of profilerMetrics) { if (evaluationResult.reactProfiler[metric] !== null) { performanceResults.reactProfiler[metric].push( - evaluationResult.reactProfiler[metric] + evaluationResult.reactProfiler[metric], ); } } From ece61036574284454476f3b4c3de17c581763d11 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes <57368278+jorge-cab@users.noreply.github.com> Date: Mon, 5 May 2025 10:20:06 -0700 Subject: [PATCH 4/4] Address comments --- compiler/packages/react-mcp-server/src/index.ts | 1 - compiler/packages/react-mcp-server/src/tools/runtimePerf.ts | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 8ce2633d6e155..2ec747eac4dfd 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -334,7 +334,6 @@ server.tool( ## Mean Render Time ${calculateMean(results.renderTime)} -TEST: ${results.webVitals.inp} ## Mean Web Vitals - Cumulative Layout Shift (CLS): ${calculateMean(results.webVitals.cls)} - Largest Contentful Paint (LCP): ${calculateMean(results.webVitals.lcp)} diff --git a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts index 242ab0aa757ad..91463046e547f 100644 --- a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts +++ b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts @@ -135,10 +135,8 @@ export async function measurePerformance( return window.__INTERACTABLE_SELECTORS__; }); - for (const selector of selectors) { - await page.click(selector); - await delay(500); - } + await Promise.all(selectors.map(selector => page.click(selector))); + await delay(500); // Visit a new page for 1s to background the current page so that WebVitals can finish being calculated const tempPage = await browser.newPage();