August 21, 2019

Cypress, Stripe, <iframe>s, Oh, My!

Cypress is a wonderful tool in the front-end arsenal.

I use it all the time for integration and end-to-end tests. A significant proportion of my testing effort goes into driving the app UI with Cypress, as a user would, and mocking network requests with json fixtures to rapidly iterate through app test states.

This allows me to fully test almost every part of the app functionality from the UI components through to the data requests, without needing a real API to run the tests against. You can use the same method to build UI in parallel with an incomplete API.

Today, I often use simple scripts to drive the UI while developing, even if that test code doesn't make it into the final test suite.

The fast interation cycle where you change code, refresh the page, and get instant feedback can become tedious when you need to cick buttons or fill out forms to get the app in to a state where the code you're working on presents itself.

This feedback loop is valuable. Once you get the hang of the basic Cypress commands it turns out to be efficient to write scripts to save you from those repetitive actions, and speed up the process. You change the code, hit save, and watch the tests execute the code you changed while sipping your coffee. It's magic.

We know testing is useful and good and necessary and that there is benefit to the process. We see this most in the confidence we get from the tests. A project with good test coverage of critical features is much safer to deploy than one without. This allows us to move faster and more confidently.

Sometimes, a test led development process can throw a frustrating spanner in the works. It can feel like it's slowing you down. Getting in the way.

This is probably not true.

What it probably means is that you've found a gnarly or brittle part of the application that needs the spotlight of automation shined on it – either to discover ways to simplify or improve the code, or at least to have some confidence that it continues to work as expected.

Cypress.Commands.add('iframe', { prevSubject: 'element' }, $iframe => {
  Cypress.log({
    name: 'iframe',
    consoleProps() {
      return {
        iframe: $iframe,
      }
    },
  })

  return new Cypress.Promise(resolve => {
    onIframeReady(
      $iframe,
      () => {
        resolve($iframe.contents().find('body'))
      },
      () => {
        $iframe.on('load', () => {
          resolve($iframe.contents().find('body'))
        })
      }
    )
  })
})