Integrating Cypress.io into Fundbox’s core products

Itay Kotler
Fundbox Engineering
6 min readJul 15, 2019

About a week after I joined Fundbox as an automation engineer, I was approached by a senior front end developer with unprecedented enthusiasm. He said: “You should check out Cypress! It looks like an amazing alternative to Selenium!” After doing some research, I approached my manager and we started planning a small Proof of Concept (POC). This has turned into our current testing infrastructure at Fundbox. It wasn’t as smooth a ride as I had hoped it would be, but it was worth it!

Our former testing framework was written in Python and was using Selenium. We used SauceLabs as our testing platform to run tests in parallel and on different machines. We had hundreds of complex E2E tests.

It was my first week and I wasn’t familiar with the business yet. My JavaScript skills were very poor, since my experience was mostly with Python/Ruby, and I had only worked with Selenium.

A year later, we have achieved the following:

  • Working Cypress infrastructure with dozens of tests
  • CI/CD pipeline using TeamCity and Docker
  • Reduction in the run time of Sanity tests by 3 times

This post will shed light on Cypress. I will also share how we overcame the challenges we’ve encountered along the way.

The Cypress framework

Cypress is a new testing framework that recently came out of its beta phase. It is a JavaScript End-to-End testing framework, allowing you to write tests in a simple and convenient way. It also allows you to interact with your web application, debug like any other native web application, and have many capabilities that can help you write and test effortlessly.

Why Cypress

Cypress has a unique architecture, different from the one Selenium is using. Cypress has made its mark in a relatively short time, with new features and fixes released regularly. It was designed to overcome the old, slow, and flaky Selenium framework. In short, to quote the about page: Are you tired of tests behaving badly? We were.”

Cypress has great documentation, examples, and video tutorials on their website. They show how to install and write your first test in a couple of minutes.

Taking the leap

After several months of getting familiar with the business and doing some refactoring to the Selenium code, we decided: it’s time!

JavaScript became my new friend. Reading how Node.js and JavaScript are working was my main task. Reading up on Cypress became my obsession. Many meetings were held with the frontend developers to understand their needs and make it easier for them to write tests.

A small POC became our current Cypress infrastructure, with new features and tests being written every day.

Dealing with issues

During the PoC phase, the team and I encountered several issues that needed addressing. We also wanted to make the new framework developer-friendly by providing them with reusable commands and functionalities. Here are some examples of how we overcame the problems and how we enhanced the Fundbox Cypress framework.

Popup windows

One of Cypress’s main limitations is not being able to control new windows, tabs, and iframes. While Selenium offers control of them out of the box.

Using the cy.stub we managed to open a popup window in the same browser window. This piece of code saved us a lot of trouble. It allowed us to interact with elements inside the stubbed window, preventing us from “breaking” the tests into two parts.

export function stubOpenWindow({ path, url }) {
cy.url().should("contain", path)
.then((currentUrl) => {
url = (url === undefined) ? currentUrl : url;
cy.visit(url, {
onBeforeLoad(win) {
cy.stub(win, "open").as("windowOpen").returns(win);
},
});
});
}

The iframe issue

Targeting elements inside an iframe is a well-known problem with an ongoing thread. Here, we found many easy-to-implement solutions. We created a custom command that finds an element inside an iframe.

Cypress.Commands.add("getElementInIframe", ({ iframe, findBy }) => {
const maxRetries = 3;
const sleep = 5000;
let numOfRetries = 0;
let app;

function isModalLoaded() {
if (numOfRetries < maxRetries) {
numOfRetries++;
cy.get(iframe)
.then(($iFrame) => {
app = $iFrame.contents().find(findBy);
const resp = !!app.length;
if (resp === true) {
expect(resp).to.eq(true);
return cy.wrap(app);
} else {
cy.wait(sleep);
isModalLoaded();
}
});
} else {
assert.isNotOk(true, `Failed to get Iframe '${iframe}' after ${maxRetries} retries`);
}
}

isModalLoaded();
});

Using the support/index.js

This file runs before every single spec file. It serves as a place to run reusable behavior such as Custom Commands or global overrides, thus preventing you from repeating your code. You can define your behaviors in a before/beforeEach functions within the support file. The following examples are performed before each test:

  1. Setting the default Selector Priority that is returned per element.
  2. Creating unique testing email and assigning the baseURL according to the Fundbox product.
  3. Logging in to our back-office applications to simulate different user’s states.
  4. Using both cy.server() and cy.route() to validate every HTTP request.
  5. afterEach() function: failing the entire test suite and terminating Cypress, if a step has failed. Since our tests are E2E, each `it` step depends on the one before it. There is no reason to continue the test run and waste precious testing time.
afterEach(function() {
if (this.currentTest.state === "failed") {
Cypress.env("FAILED_PREV_TEST", true);
logoutAndSuspendUser.call(this);
cy.waitForAllAjaxResponder();
if (Cypress.env("FAIL_FAST")) {
Cypress.runner.stop();
}
}
});

6. after() function: clearing the environment by logging out. Deleting all cookies, clearing the Local Storage and suspending the user.

Using Custom Commands

Common functionalities such as login() and logout() are performed by using Cypress’s Commands.

Retry mechanism

It tries to perform an action with timeouts between each retry. This retry operation is a great technique to retry flaky steps before failing the test suite.

Logout command with retry mechanism:

Cypress.Commands.add("logout", () => {
const maxRetries = 3;
const sleep = 3000;
let numOfRetries = 0;

function logoutFromDashboard() {
if (numOfRetries < maxRetries) {
numOfRetries++;
cy.window()
.then((win) => win.location = "/logout")
.then(() => cy.url())
.then((url) => {
if (url.includes("/login")) {
expect(url).to.include("/login");
cy.clearCookie("csrftoken_local");
} else {
cy.wait(sleep);
logoutFromDashboard();
}
});
} else {
assert.isNotOk(true, `Failed to logout after ${maxRetries} retries`);
}
}

logoutFromDashboard();
});

CI/CD

Part of the migration from Selenium to Cypress included an in-house solution to run our tests and stop using SauceLabs. This move saves us time and money.

Docker containers were chosen for their easy integration and the fact that Cypress provides basic docker images. Parallelism was achieved using Python’s concurrent.futures package.

CI/CD configuration
  • To achieve this, we built a TeamCity job that runs Packer
  • The job builds the docker image and pushes it to AWS ECR. It is scheduled to run once a week
  • Once Cypress job is triggered, the code will first pull the latest image from the ECR repository, and then run docker build to update the container with the latest changes.
  • After the container is ready, we are using concurrent.futures Python package to open threads and run the container in each of them.
  • The number of threads to fork is a configuration in the TeamCity job.

Final thoughts

Cypress has many abilities that will make you fall in love with testing again. No more complex configurations that will make you abandon the testing phase.

Our venture into using Cypress has resulted in more than 30 new tests added by our front end developers, bugs being easily caught and handled. As well as complex flows being written in dedicated commands, which helps the front end developers write small, readable tests.

As Cypress is an advanced, original and robust framework that holds lots of features, it can help you test your product from A to Z. Try it!

--

--