Testing Web Vitals With Cypress
Tags: javascript, webdev, testing, tutorial
Published: 2022-04-03 10:48:34

It is well understood that [performance is a critical consideration](https://web.dev/why-speed-matters/) for any website which can have far reaching impacts on everything from customer satisfaction, SEO, and ultimately your bottom line. You cannot determine the success of performance work without the ability measure the results and compare to [performance budgets](https://web.dev/performance-budgets-101/) - this calls for testing infrastructure to make sure you have the necessary visibility on metrics... introducing [`cypress-web-vitals`](https://www.npmjs.com/package/cypress-web-vitals).

`cypress-web-vitals` allows you to test against the Google Web Vital signals within your Cypress workflows through a new `cy.vitals()` custom command.

> Web Vitals is an initiative by Google to provide unified guidance for quality signals that are essential to delivering a great user experience on the web.
>
> Reference: https://web.dev/vitals/

## Getting started

In your project, install the dependencies:

```console
npm install --save-dev cypress-web-vitals cypress-real-events
```

> Note: `cypress-web-vitals` currently makes use of `cypress-real-events` to click the page to calculate first input delay. Hence it is needed as a peer-dependency.

Within you support commands file (normally `cypress/support/commands.js`), add this one liner to get setup:

```js
import "cypress-web-vitals";
```

And now you're set to get going with Web Vital performance budget tests in your Cypress workflow! :tada:

Add you first test like so:

```js
describe("Web Vitals", () => {
  it("should pass the meet Google's 'Good' thresholds", () => {
    cy.vitals({ url: "https://www.google.com/" });
  });
});
```

You are now set up to test against all of the Web Vitals using Google's "Good" threshold values:

- [Largest contentful paint (LCP)](https://web.dev/lcp/) - `2500`.
- [First input delay (FID)](https://web.dev/fid/) - `100`.
- [Cumulative layout shift (CLS)](https://web.dev/cls/) - `0.1`.
- [First contentful paint (FCP)](https://web.dev/fcp/) - `1800`.
- [Time to first byte (TTFB)](https://web.dev/time-to-first-byte/) - `600`.

## Customise your tests

You can further customise your tests through additional optional configuration which is passed to the `cy.vitals(webVitalsConfig)` call:

- Optional `url: string` - The url to audit. If not provided you will need to have called `cy.visit(url)` prior to the command.
- Optional `firstInputSelector: string` - The element to click for capturing FID. The first matching element is used. Default: `"body"`.
- Optional `thresholds: object` - The thresholds to audit the Web Vitals against. If not provided Google's "Good" thresholds will be used. If provided, any missing Web Vitals signals will not be audited.
- Optional `vitalsReportedTimeout: number` - Time in ms to wait for Web Vitals to be reported before failing. Default: `10000`.

For example:

```js
// Use the `main` element for clicking to capture the FID.
cy.vitals({ firstInputSelector: "main" });

// Test the page against against a CLS threshold of 0.2.
cy.vitals({ thresholds: { cls: 0.2 } });
```

For more details on usage refer to the [API docs](https://www.npmjs.com/package/cypress-web-vitals#api).

## How does it work?

1. The url is visited with the HTML response intercepted and modified by Cypress to include the [web-vitals](https://github.com/GoogleChrome/web-vitals) script and some code to record the Web Vitals values.
2. Several elements (if exist) starting with the provided element (based on `firstInputSelector`) are then clicked in quick succession to simulate a user clicking on the page. Note: if choosing a custom element, don't pick something that will navigate away from the page otherwise the plugin will fail to capture the Web Vitals metrics.
3. The audit then waits for the page load event to allow for the values of LCP and CLS to settle; which are subject to change as different parts of the page load into view.
4. Next the audit simulates a page visibility state change [which is required for the CLS Web Vital to be reported](https://www.npmjs.com/package/web-vitals#basic-usage).
5. The audit then attempts to wait for any outstanding Web Vitals to be reported for which thresholds have been provided.
6. Finally the Web Vitals values are compared against the thresholds, logging successful results and throwing an error for any unsuccessful signals. _Note: if the audit was unable to record a Web Vital then it is logged, but the test will not fail._

## Testing sites in the wild

Here are some example test runs against FAANG homepages to see `cypress-web-vitals` in action:

### Facebook

```js
cy.vitals({ url: "https://www.facebook.com/" });
```

![Cypress Web Vitals test against the Facebook landing page. All metrics below Google's "Good" thresholds.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5lye3uqo9n219pr56li.png)

### Amazon

```js
cy.vitals({ url: "https://www.amazon.com/" });
```

![Cypress Web Vitals test against the Amazon home page. All metrics below Google's "Good" thresholds.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7h9sihfx9lg5lj4qzvv.png)

### Netflix

```js
cy.vitals({
  url: "https://www.netflix.com/gb/",
  firstInputSelector: ".our-story-card-title",
});
```

![Cypress Web Vitals test against the Netflix landing page. All metrics below Google's "Good" thresholds.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qc1jb8wzy8hemrz5uxun.png)

> For Netflix we have had to introduce a custom element choice for the simulated "first click". Even though [first input delay can be measured when in cases where there is no event listener registered to the element](https://web.dev/fid/#what-if-an-interaction-doesn't-have-an-event-listener), there are scenarios where clicking the `body` doesn't cut it. Some good examples of elements that will reliably trigger an FID metric are:
> 
> - Text fields, checkboxes, and radio buttons (`<input>`, `<textarea>`)
> - Select dropdowns (`<select>`)
> - Links (`<a>`)
> 
> In order to ensure the Web Vitals can be tested against, it is best to try find an element that reliably triggers an FID, but won't navigate away from the page (perhaps avoid `<a>`!).

### Google

```js
cy.vitals({ url: "https://www.google.com/" });
```

![Cypress Web Vitals test against the Google home page. All metrics below Google's "Good" thresholds.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrdg1bd9y6q9umq93avl.png)

## Wrap-up

Using any awesome performance testing tooling lately? Tried out [cypress-web-vitals](https://www.npmjs.com/package/cypress-web-vitals) on your site and have results to share? Got any comments, queries, or questions? Leave a comment below!

That's all folks! :rocket: