Feb 11, 2021

Skip the UI using API calls

As a QA engineer who would like to start with test automation, the first thing you will learn is how to interact with the page and write automated scripts to replay your actions. But following this path, you may fall into the trap of automating all the actions in your automated tests only through the user interface (UI).

Over time, this approach will lead to wrapping UI actions into separate commands and reusing them before or during each test, such as login into your app or preparing some of the data.

This attitude will most certainly cause the tests to be unstable, low-pace, and require constant care and maintenance.

In this article, I will explain and calculate the benefits of using the API calls in your automated UI tests.

I will also provide working examples to try them on your own, and there is a Cypress.io challenge prepared to test your skills at the end of the article.

Let’s start

For the sake of simplicity, we will implement a straightforward scenario:

???? Check that the total price is displayed on the first step of the booking process.

Our scenario consists of the following steps

1. Open Kiwi.com

2. Enter the start and end location

3. Hit the search button

4. Hit the Book button on the first flight found

5. Check that price is visible on 1st step of the booking process

Implementation of the automated UI test using Cypress.io looks as following:

???? Problems

At first sight, the automated test looks pretty straightforward. It takes around 9 seconds and doesn’t seem flaky at all. But let me uncover the issues and slice them into three main topics.


Imagine that we will end up with 80 scenarios testing the booking flow. Each one of them goes through the search and results page and relies on the respective UI. If anything goes wrong during this phase, we don’t get information on how the booking is going.

We don’t trust this test.


UI tests are always the bottleneck. They require constant maintenance and debugging. The UI level is also the most fragile since it displays the user’s information and actions in a smooth way. So smooth that the test automation framework needs to continually watch over the elements on the page to be displayed, clickable, and ready for action.

This test is slow in terms of execution and maintenance.


This test is designed for one goal only. It should an answer if the price is displayed on the first step of the booking page. It should not an answer if the search or result page works, nor that the flights are correctly displayed on the result page. We don’t care about that. (Well, actually, we do but in another test.)

This test takes too much responsibility and doesn’t let other tests take theirs.

This test is greedy.

Let’s help our test to be more reliable, faster, and single goal-oriented!


If we take a closer look at the result page, we can see that each displayed flight contains a Book button with a respective URL that navigates us to the booking page.

If we split the URL into separate query parameters using Postman and disable all of them besides the token, which plays a crucial part in our case, we can use this URL and navigate to the booking page directly.

Now we know that to get to the booking page directly, our URL should look like this.


How do we get the booking token?

In my previous article, I’ve described how to find a flight using the Search API by sending a request to flightsendpoint.

TL;DR: If you open the following URL in your browser, you should get a response JSONcontaining all data about the first found flight from Los Angeles to Tokyo, containing the booking token as well.


???? Intermezzo

As domain-specific as this example might be, keep in mind that every web page communicates with the backend. Try to explore what API calls you can use to skip some specific part of the application or prepare the test data. A good starting point could be the login screen. Sending a request to login endpoint might return a session-id that you can use to jump right into the app.

Updating the test

Based on this data, we can replace the UI phase as follows.

API part

  1. Send an HTTP request to theflights endpoint and look for a flight.
  2. Extract the booking_token from the response.

UI part

  1. Open the booking page using the booking_tokenas a query parameter.
  2. Check the price on the booking page.


Our test STARTS where it should. It opens the booking page directly, instead of crawling through the whole search process.

Our test FAILS where it should. I’ve added an assertion for the response to be sure that it contains the flight information.


Our test is shorter. We have reduced the lines of code from 36 to 25, but most importantly, we have eliminated the majority of UI actions that can cause flakiness, and replaced them with a single API call.


The execution time of the test dropped from 9.3 seconds to 6.22s (-32.98%)

Lines of code dropped from 36 to 25(-30.56%)

Comparison of the execution time of the pure UI and UI+API test

Four takeaways

???? Observe how you can use the API before hiding UI actions into functions and reusing them before each test.

???? Prepare test data or skip specific parts of the application, such as a login screen with an HTTP request instead of using UI.

???? Don’t make your tests greedy. Set a goal for each test, and get to this goal as fast as possible.

???? Don’t try to chain your tests and let them depend on each other.

????Cypress.io challenge

Your task: Implement the following scenario in Cypress.io


User can add baggage protection for checked baggage.

Main points you should focus on

  • reliability
  • adding the right assertions
  • final polishing and readability of the code

???? Send me the code (link to GitHub repository or code snippet) to [email protected] by 28.2.2021, and I’ll try to pick the best solution.

The winner will be contacted via email and get some neat Kiwi.com merch!

???? Happy testing!

Featured articles
Generating SwiftUI snapshot tests with Swift macros
Don’t Fix Bad Data, Do This Instead