Learning Cypress.io

While researching alternatives to Jest, I stumbled upon the testing tool Cypress.io[1]. It promised to make testing pleasant at the cost of only supporting Chrome. It seemed like an even trade, so I decided to give it a go.

In my very limited experience working with other testing tools, such as Selenium and Codeception, I’ve found that writing tests is incredibly painful because you would ran into timing issues that would be hard to reproduce on other machines, such as waiting for DOM elements to appear within the timeout limit. Apparently, Cypress doesn’t get these kind of issues because it executes directly in the browser.

So feeling inspired and hopeful about the future of web development, I decided that I would try and build a relatively simple vanilla JavaScript component like an accordion. I’d also want to try and unit test it with Cypress, even though there’s no first class support yet.

But first, let’s setup Webpack

It was Friday and I know that the biggest demotivator to doing frontend work in my spare-time is getting Webpack setup. With this in mind, I decided that I’d try and get a basic config setup in about an hour, just so that I could hit the ground running with coding and hacking at the problem the following day.

Needless to say, I underestimated Webpack and I’ll need to continue just getting my tools setup tomorrow. Dang.

Fresh Start

I got up nice and early, feeling motivated to fix my Webpack setup and excited to experiment with Cypress. The first issue I was greeted with was a nice error message from Cypress, telling me that it had trouble parsing my code and that my Webpack configs may be incorrect. Thanks Cypress!

Cypress Error Message

After resolving my transpiling issues by following the some of the Cypress documentation and looking at the Github examples, I then tried to run Cypress again.

It crashed.

I got an error with Cypress attempting to require “babel-loader”. My webpack config contained nothing to do with “babel-loader” and it felt… incorrect to me that it was a requirement.

Rather than install babel-loader and keep moving forward, I wanted to understand why this was happening. So. I dug into the package where the issue was occuring and discovered that the default Webpack config that Cypress tries to utilize will call “require(‘babel-loader’)”, regardless of whether you have your own config or not.

I raised a Github issue describing how I commented out the default config and how Cypress was working without babel-loader. To resolve this problem, I proposed that babel-loader is only require()’d if there is no override. Shortly after, I raised a pull request to fix the problem. Most impressively, someone from the Cypress team responded within 3 days, accepted the PR and had it tagged in NPM as a patch version, awesome. Just awesome.

Babbys First Test

Once I had resolved the babel-loader issues, I got a very basic TypeScript test working. It checks to make sure that the number 42 is actually the number 42! (Hint: It checks out)

describe('TypeScript', () => {
  it('works', () => {
    // note TypeScript definition
    let x: number = 42
    if (x) {
      expect(x).to.equal(42)
    }
  })
})

Smile

Cypress has been incredibly pleasant to use so far, it treats me like a human being. Nice readable error messages and built-in guides, telling me what I should do next. It even told me within the application to start a seperate local web server and run “cy.visit()”, nice one! I love a built-in tutorial!

Whilst reading up on how this would all work and tie together, it felt a bit too heavy to me. I don’t want to have to think about spawning two processes on something like TravisCI, I want things to be as simple as possible. So, because this is my spare time, I figured why not ignore the warnings on their documentation page completely and just try and experiment? What could go wrong? How much time could I really waste going down this path?

Have a break

I decided to stop there for the time being and play a bit of Overwatch with my partner. Between matches I tweaked Webpack config files and installed SASS so I could also test styling.

Node SASS no likely

I tried running my code only within Cypress.io and… no_good_emoji. node-sass started getting errors that seemed to be related to my installed Node version, it seemed to be expecting Node 8.10.0 instead of 10.6.0. I thought that perhaps NVM for Windows was being a bit janky with Cypress, so I decided to change my Node version from 10.6.0 to 8.10.0 . After hitting more errors related to the node-sass DLL being incompatible, I deleted my node_modules folder and re-ran yarn.

Argh. More errors!

Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 8.x
./src/Collapo.scss (./node_modules/css-loader??ref--4-1!./node_modules/postcss-loader/src??postcss!./node_modules/sass-loader/lib/loader.js??ref--4-3!./src/Collapo.scss)
Module build failed (from ./node_modules/sass-loader/lib/loader.js):
Error: A dynamic link library (DLL) initialization routine failed.
\\?\D:\wamp64\www\collapo\node_modules\node-sass\vendor\win32-x64-57\binding.node
 @ ./src/Collapo.scss 2:14-192
 @ ./cypress/integration/spec.ts

Troubleshooting

I started trying to fix the errors by loosely following a Github issue, I tried running the command:

yarn add --force node-sass

But that wasn’t working. I next tried running Cypress directly, instead of through Yarn. By that I mean I executed:

node node_modules/cypress/bin/cypress open

instead of:

yarn cypress:open

I tried this because I thought that Yarn could potentially be causing an issue with how environment variables were being passed in. Unfortunately this didn’t work either and so I tried to re-install node-sass at the latest version with the following command:

yarn remove node-sass
yarn add --dev node-sass@latest

But I still can’t get it working! I’m honestly tempted to just circumvent this issue by trying an alternative CSS preprocessor, like Stylus, as all the issues seem to be related to node-sass loading a DLL. However… since SASS is generally the agreed upon preprocessor and that my last 2 places of work were heavily invested in it, I’d rather my code be compatible with a toolset I use day-to-day.

I gotta tell ya, I’m feeling pretty frustrated by now, and really, these problems wouldn’t have happened if I just did what the Cypress documentation said to do, and ran the code on a seperete local webserver.

Time for a break.

Every day, once a day, give yourself a present

I decided to go for a walk down the street and got a hot chocolate with my partner. It’s a nice sunny day and stopping to have a chat and a treat is a good morale boost!

Back to business

I decided to have another crack at trying to get SASS working. So I decided to take a look at the sass-loader Github page to see if there was anything I could do to just straight-up avoid this problem rather than solve it. My intuition and past research[1] was telling me that node-sass might lead to further issues when running on a CI server, so if I can drop it, it’ll probably save me future headaches as well.

After reading about the sass-loader on the Github page, I noticed that the node-sass implementation is being dropped in favour of dart-sass (“sass” package), after reading about it, I decided to give it a go.

Oh yeahhh! Now when I run Cypress, I get no node-sass related errors and I don’t need to run a seperate local webserver, at least not yet! My stubbornness has paid off! I’m pleased that I found a drop-in replacement for node-sass that I can perhaps apply in my day-to-day!

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: require.resolve('css-loader'),
            options: {
              // todo: Jake: 2018-11-11
              // Change this loader to "2" if we use postcss below
              // https://github.com/webpack-contrib/css-loader#options
              importLoaders: 1,
              sourceMap: true,
              modules: true,
              localIdentName: '[name]__[local]',
            },
          },
          // todo: Jake: 2018-11-11
          // Install this so -webkit-* prefixes get generated for IE/old iOS
          /*{
            loader: require.resolve('postcss-loader'),
            options: {
            // Necessary for external CSS imports to work
            // https://github.com/facebookincubator/create-react-app/issues/2677
            ident: 'postcss',
            },
          },*/
          { 
           loader: require.resolve('sass-loader'),
            options: {
              implementation: require('sass'),
              includePaths: [
                path.resolve(__dirname, 'src')
              ]
            }
          }
        ],
      }
    ]
  }
};

CSS Modules and me

declare module '*.scss' {
  const content: {[className: string]: string};
  export = content;
}

I wanted to import SCSS files in my TypeScript files, so I added the declaration.d.ts file you see above to my project.

But… it it doesn’t seem to be working. When I try and import a stylesheet with my current Webpack config, the imported object is an empty JS object. Ergh. At this point, I’m heavily considering biting the bullet and running this in a seperate server. But before I do so, I want to attempt to build the output JavaScript and CSS files without Cypress, just to determine whether this is a Cypress issue, or something else entirely.

After googling a little, I discovered a React Github issue where someone had the same issue as me! Unfortunately, it didn’t relate to how I had my Webpack config setup. I’m not using React. Bummer! It did instead make me consider that maybe this issue was due to my Webpack configuration.

I looked at my loaders and did a bit of googling on each of them. Eventually, I discovered that the MiniCssExtractPlugin is not meant to be used for development builds and I tried commenting out the plugin… and…

Success! I can see the classnames in the stylesheet object I’m importing! But… we’re not quite there yet. The stylesheet I’m importing isn’t applying to the window. The button still looks ugly!

Dive into the DOM

I inspected the DOM with the Google Chrome DevTools and dug around. I discovered that the Cypress test script outputs in it’s own sandboxed iframe and that the stylesheet was outputting inside this iframe and not in the iframe that contained the rendered button. After discovering this, I decided to try and hoist the stylesheets out of the test iframe and into the buttons iframe. It worked! Now everything is running in Cypress! Also, by the way… iframe. iframe. iframe.

The final stretch

Hooray! Things work in Cypress! All is well and good right? Nope!

My production build no longer outputs a file. Up until this point, I was trying to do everything with a single Webpack config file but it was at this point I decided to just split it into 3 files. common, dev and prod.

I struggled for a little while trying to get my configs to be as concise as I wanted because I didn’t read the documentation to see how merging worked. So, I decided to look at the webpack-merge documentation. Lo’ and behold! It can do smart merging for me, which allows me to prepend loaders in my dev and prod configs, this helped reduce config duplication across dev and prod.

Phew! With all this done, I’m pretty happy! I think I’ll leave it here for the day!

If you want to see where I left this project at, you can see it on Github here. I’ve also attached my raw notes that I took as I went through the dev process, I used these to recall my process and feelings so I could write this blog post.

7 years ago