Automated AMP Validation using Jest and Puppeteer

Puppets on strings

I’ve previously written about how I’ve implemented automated AMP validation using Jest and AMP Optimizer for the Web Stories WordPress plugin. The context there was that we’re writing unit tests using Jest and wanted a way to verify AMP validity of individual components. Recently, we were in a situation were we needed this AMP validation in a different context: Puppeteer.

Using Puppeteer and Jest

Puppeteer is a library which provides a high-level API to control a (headless) browser like Chromium or even Firefox. We use Puppeteer selectively to verify the Web Stories plugin’s behavior in end-to-end tests. One goal there was to ensure that the pages we generate in WordPress are 100% valid AMP. So, how can we do that in the browser?

Puppeteer controls your browser like a puppet. Depicted: Puppets on strings
Puppeteer controls your browser like a puppet. Photo by Sagar Dani on Unsplash

Thankfully, Puppeteer provides a wide variety of APIs, such as for running arbitrary JavaScript on pages or taking the page’s source and do something with it (e.g. take a snapshot).

A simple test/program using Puppeteer could do a task like this:

  1. Open example.com
  2. Click on link X
  3. Wait for navigation
  4. Do Y

Or in the context of Web Stories for WordPress:

  1. Create new web story in WordPress
  2. Publish story
  3. Preview story on the frontend
  4. Run AMP validation

In our case, we’re using Puppeteer together with Jest, which means we could implement the Puppeteer AMP validation in a way that was very similar to our pre-existing solution.

Puppeteer AMP Validation

And indeed the implementation was pretty straightforward. All that was needed is calling page.content() to get the full HTML contents of the page and then processing it further using our existing functions. The only caveat: we didn’t want to use the AMP Optimizer to accidentally skew results by transforming markup.

Custom Jest Matchers

Once again I leveraged Jest’s custom matchers API. Custom matchers allow writing tests like this:

it('should produce valid AMP output', async () => {
  await expect(foo).toBeValidAMP();
});Code language: JavaScript (javascript)

I previously created such matcher for the unit tests, now I just needed the same for the e2e tests. The result is simple and easy to understand:

async function toBeValidAMP(page) {
  const errors = await getAMPValidationErrors(await page.content(), false);
  const pass = errors.length === 0;

  return {
    pass,
    message: () =>
      pass
        ? `Expected page not to be valid AMP.`
        : `Expected page to be valid AMP. Errors:\n${errors.join('\n')}`,
  };
}Code language: JavaScript (javascript)

The only real difference to my previous article is the usage of page.content().

That’s it! We now have a new Jest matcher that validates our web page and warns us when invalid markup is encountered!

The Result

Now, when using an assertion like expect(page).toBeValidAMP() in your test suite, you would warned in case of AMP validation errors.

Curious to see the whole source code? Check out the Web Stories editor’s GitHub repository.

Use Cases

In this case, we’re using the Puppeteer AMP validation solution for end-to-end tests. But Puppeteer is much more powerful and can be used in a variety of ways.

Another use case I could think of is quickly validating a whole website simply by opening each page in a headless browser and call the above function for the AMP validation. You could even do this on a schedule to get instantly notified if somehow your AMP pages become invalid.