Challenges of Switching From Selenium to Cypress
Here at Simulmedia, we recently decided to take on a new project using a different front-end framework. So when I was asked to set up the tests for this new project, I started thinking about the current testing infrastructure and whether it would make sense in this new environment. Our current automated testing repo is built on Selenium WebDriver written in Python that utilizes Pytest. Since the front-end team was branching out and trying new technologies, I decided to take some time to see what else is out there, and if it would make sense for me and the rest of the developers.
I had never had the need or interest to abandon Selenium WebDriver. Why would I want to move away from something that is tried and true? Why would I want to switch programming languages after writing in Python for the past few years? One of the more likable parts of writing automated tests using Selenium WebDriver is that you have the freedom to build a repo from scratch and truly customize it to your needs. You get to choose your language, your test runner and how and where your tests are run. So why would I want to start writing tests using an out of the box, all-in-one, single-language testing tool called Cypress? Well, the answer to these questions can be boiled down to: curiosity and the desire to find alternatives that will better fit our overall development strategy.
On my journey there were the obvious challenges of brushing off the old JS knowledge that I had and learning the nuances of the Cypress syntax, along with the do’s and don'ts and best practices. But as a whole, Cypress and Selenium share the same dialect, which means the core concepts of testing are still the same. But here are some of the speed bumps that I hit during this transition:
First off and maybe one of the most frustrating parts of automated testing is waiting. WebDriver gives the user a few different ways that you can accomplish waiting: Fluent, Implicit and Explicit. Each having to be declared somewhere either in your set up or directly injected into a step of your test. With Cypress, you do not have to worry about declaring any kind of waits. Most of Cypress’ custom commands have built-in waits that will logically wait for that element to reach a usable state. For example, I have this custom click command using WebDriver:
It now becomes a single line call in Cypress:
This was a refreshing take on the sometimes-tedious task of finding creative ways to wait in WebDriver.The next challenge was figuring out a way to write tests when you are not allowed to assign an element to a variable. Cypress works asynchronously, meaning that each Cypress command returns immediately and then appends to a queue of commands that execute at a later time. So if you want to use an element and its value, you’ll need to use Cypress’ closures (native promises), which are pretty foreign coming from Python and WebDriver. This makes you rethink the way you would write a test. Instead of having access to everything in your test no matter where you're calling it from, you now need to think of the test as dominoes falling in line.
Lastly and maybe mostly importantly is working at a different pace. Running tests in parallel no matter where you are running them can add a bit of latency to your test run. WebDriver is built to send your test commands through HTTP to your test browser. Whereas Cypress executes in the browser and in the same run loop as the device under test. This means there are no network connection issues allowing the tests to run at a faster speed then they’re otherwise used to.
After writing a few tests and running them in headless mode, I kept getting failures. At first I could not figure out what was going on. I stepped through the test in debug mode and the test would pass every time. I then turned to watching the video of the test failure, which is where I noticed in the Cypress console that the PATCH request to the API was not executing fast enough. Saving a change, reloading the page and asserting the change was executed before the API returned a 200 on the PATCH. That a page refresh would finish before an API request was pretty new to me.
But since Cypress is working within the browser, it has access to all the network traffic -- as you can see in this example of their test logs:
This is a very versatile advantage to have when running any automated test. So to combat the milliseconds of time I needed to wait for the API, I was able to easily intercept the API request. Once the API request is intercepted, Cypress allows you to assign that request to an alias. Then you can set a wait on that alias (which you can see next to the API calls in the logs):
The next step will not execute before the API request to return a status code.
The ability to intercept requests and wait on them to execute was a learning experience for sure. But those two lines of code saved me from adding one of the dreaded static waits that slow down your test and ultimately shows that you have waved the white flag on your problem. But now my tests will execute on time and with less flakiness since I am able to target exactly what I need for my test to pass.
So has Cypress converted me from Selenium WebDriver? That is still to be determined... stay tuned.