Integration tests = Cucumber + Selenium + Spring Boot

Let’s say you had this web application with nice unit tests covering the backend calculations, where people keep calling wrong calculations from the frontend. Microservices or not, it happens, so you’ll need proper integration testing. Let’s say the integration testing is a well documented but tedious two days job of clicking around. Understandably, everybody does their best to avoid it…

My answer was: how about we rewrite the integration test scenarios in the Gherkin almost-human-readable language, so we can use Cucumber to run them automated, simulating with Selenium the user browser actions! The supporting stack will be Java and Spring, actually Spring Boot, because you know all I have is a hammer….


The only problem with this brilliant plan is that either my googling is getting rusty, or all existing blogs on combining Spring with Cucumber are severely outdated. While with the first there’s not much I can improve anymore, I thought to work on the second by writing a fresh blog entry on integrating Spring Boot 1.4.2, Selenium Server 3.0.1 and Cucumber 1.2.5 (Gherkin is still 2.x, namely 2.12.2). I also used the Hamcrest matchers for the friendly expressiveness they offer. And the best test site I could find online is the one of SuiteCRM (open source CRM solution built on top of SugarCRM) thank you SuiteCRM guys for putting up with my test requests (not too many I hope).

There’s no better (which translates, better readable) way to approach Selenium testing than the page objects. My post is no page object tutorial, suffice to say wou will write one class for each page of your test site. Each one describes the functionality needed to test that particular page – the actions which must be performed there, the elements which must be checked and so on… Please realize that each time the test site changes your integration tests will break, but that’s exactly the whole point of the page object model: your tests MUST break and you MUST adapt the objects to the reality. So if SuiteCRM comes out with a new version, my shiny project will fail miserably at runtime, but I hope it will be enough to help guiding somebody’s first steps in BDD (behaviour driven development) anyway.

Speaking of page objects, there’s also that AbstractPage I used to place stuff which will be reused between all page objects. No complicated hierarchy trees, just avoiding duplication. You will notice here that I used the DriverFactory solution from Krishna Kokkula’s blog in order to support multi-threaded tests. This demo project doesn’t actually need it but my main one will do, so it’s worth keeping in mind.

Some implementation notes:

  • although Selenium doesn’t recommend it, I prefer putting a few asserts (the nice Hamcrest ones) for more checks in the test steps. Don’t overdo it though, use the asserts only when they complete the real aim of the test step.
  • …because if a findElement() fails, that’s exactly your test failing! Bravo, no need to assert it as well.
  • nevertheless, if you want to check for an element existence, use findElements() (notice the extra s?) to get a list and check if the list is empty. Or whatever size it should have.
    final List<WebElement> listTitles = driver.findElements(By.id("an_id"));
    assertThat(listTitles, hasSize(1));
  • I didn’t use the Selenium’s PageFactory for page objects because I remembered it not working with Spring beans pages. I didn’t even check it now… so potential for improvement here. But remember, the PageFactory will work only if your web application uses consistently element IDs and no, not everybody is like that.
  • returning from the test functions always the expected page object is very useful for chaining calls:
    basicPage.createContact().editContact(name);
  • if your web page uses javascript alerts, you should handle them properly at the moment you know they should appear:
        final Alert alt = driver.switchTo().alert();
        alt.accept();
  • for any tasks beyond entering text or clicking, you’ll need to use Selenium’s Actions. I didn’t need them in the end in this project, so the following sample code is taken from StackOverflow only to give you an idea:
    Actions action = new Actions(driver);
    WebElement element=driver.findElement(By.linkText("TEST"));
    action.doubleClick(element).perform();

Why do I have both application.properties and .template ? Well, it’s just a habit: if I need some environment specific and sometimes also sensitive information in this file, I only save the template in Git and keep the sensitive settings from application.properties then local. So you should never see both of them committed, here they are only serving to make a point.

Speaking about properties: you will also notice the bean-specific settings specified by their annotation

    @ConfigurationProperties(prefix = "landingpage")

These will be injected by Spring directly from the properties file, like landingpage.title for example. However if you need general settings like the app.baseurl which could be used in more page objects (aka regular Spring beans) you will have to map these by hand:

    @Value("${app.baseurl}")
    private String baseurl;

A few more notes on Selenium:

  • it may be tempty, but don’t force the WebDriver to search the whole page source for texts. driver.getPageSource().contains(“thing”) is noticeably slower.
  • people tend to shun the xpath matchers By.xpath(“//a[contains…”). Try to consistently use IDs like By.id(“my_id”) and your tests will still succeed even if you slightly change the page structure. Less fragile tests are much better.
  • the explicit wait() is nice and useful, but for some Javascript generated pages it will still never find your element. Never. Get used to that and work around it somehow.
    final WebDriverWait wait = new WebDriverWait(driver, 10);
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("id_name")));

All kind of things will hide your clickable elements, from smart responsive pages to dumb cookie warnings. Selenium IS the user so it won’t be able to click invisible things until you either scroll them into view (eww, very much eww and yuck), or you just use the JavascriptExecutor to click things even if not visible. Don’t abuse it though! Better try to understand why your page is so misbehaving and maybe click first the enabling elements (like, click to open first the collapsed menu…).

    final WebElement button = driver.findElement(By.id("logout_link"));
    final JavascriptExecutor executor = (JavascriptExecutor)driver;
    executor.executeScript("arguments[0].click();", button);

And at the end, some points about Cucumber:

  • the Background section from the feature file will be executed at the beginning of each scenario from a feature, thus useful to reduce boilerplate. Unfortunately there’s no similar Gherkin stuff to be done at the end of each scenario… But lo, there’s the Java @After annotation exactly for that! It’s just confusing, you do the ramp-up in one language/file and the close-up in another…
  • they plan a major Gherkin upgrade to v3 which will allow better describing the test steps. Worth checking whenever it comes as it should solve the above limitation as well.

Code:

The project code can be checked out from GitHub.

https://github.com/sorin-costea/bdd

You can run the project as mvn test, or directly in Eclipse as a JUnit test (by running or debugging the CucumberTest class). During development I prefer the latter one because I like the helpful JUnit view popping up automatically, but when running headless in your CI pipeline Maven is the way to go.

Advertisements

2 thoughts on “Integration tests = Cucumber + Selenium + Spring Boot

  1. Pingback: This Week in Spring – January 3rd, 2017 | Alexius DIAKOGIANNIS

  2. Pingback: Integration tests, part 2 (notes on Cucumber and Selenium) | Trying things

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s