Skip to content

Getting Started

John Ferguson Smart edited this page Dec 17, 2013 · 11 revisions

Web Testing with Thucydides - a quick start guide

Introduction

In this guide, we will go through the steps involved in setting up a simple set of web tests using WebDriver/Selenium 2 and Thucydides, and open source library designed to facilitate acceptance testing with WebDriver. To keep it simple, we will only be implementing the web tests using JUnit at this stage. To follow these instructions, you will need Maven and at least Java 5. To keep things simple, these tests will run against a publicly-available web site. Once you have a few sample tests running, and some reports being generated, you should be in a position to start writing tests for your own app.

Set up Maven

Thucydides works best with Maven, so you will need to have Maven 3.0.x installed. You should also edit your settings.xml file to add a new pluginGroup entry, so that you can run the Thucydides report generation from the command line more easily: net.thucydides.maven.plugins

A minimalist settings.xml file with all of this configuration would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<settings>
   <pluginGroups>
       <pluginGroup>net.thucydides.maven.plugins</pluginGroup>
  </pluginGroups>
</settings>

Creating a test project

Next you need to create a new Maven project for your web tests. You can also integrate the Thucydides webtests as part of the integration tests of an existing project by running your web application with Jetty, for example, but I prefer to maintain the tests as a separate project so that they can be easily run against any server.

If you're not sure how to create a new Maven project using your IDE, you can just create one from the command line using mvn archetype:generate command. Just choose the thucydides-simple-archetyepe and whatever group and package names suit you, as illustrated here:

$ mvn archetype:generate -Dfilter=thucydides
1: remote -> net.thucydides:thucydides-easyb-archetype (Thucydides automated acceptance testing project using Selenium 2, JUnit and easyb)
2: remote -> net.thucydides:thucydides-jbehave-archetype (Thucydides automated acceptance testing project using Selenium 2, JUnit and JBehave)
3: remote -> net.thucydides:thucydides-simple-archetype (Thucydides automated acceptance testing project using Selenium 2 and JUnit)
4: local -> com.wakaleo.webtests.wikipedia:sample-thucydides-project-archetype (sample-thucydides-project-archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 3
...  
Define value for property 'groupId': : com.wakaleo.webtests.wikipedia
Define value for property 'artifactId': : wikipediatests
Define value for property 'version': 1.0-SNAPSHOT: 
Define value for property 'package': com.wakaleo.webtests.wikipedia: 
Confirm properties configuration:
groupId: com.wakaleo.webtests.wikipedia
artifactId: wikipediatests
version: 1.0-SNAPSHOT
package: com.wakaleo.webtests.wikipedia
Y: 
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.wakaleo.webtests.wikipedia
[INFO] Parameter: packageName, Value: com.wakaleo.webtests.wikipedia
[INFO] Parameter: package, Value: com.wakaleo.webtests.wikipedia
[INFO] Parameter: artifactId, Value: wikipediatests
[INFO] Parameter: basedir, Value: /Users/johnsmart
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] project created from Old (1.x) Archetype in dir: /Users/johnsmart/wikipediatests
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 minutes 11 seconds
[INFO] Finished at: Tue Jun 28 10:20:12 EST 2011
[INFO] Final Memory: 12M/81M
[INFO] ------------------------------------------------------------------------  

Adding Thucydides dependencies to the Maven project

If you want to add Thucydides to an existing project, you just need to add the Thucydides dependencies to your project. In the pom.xml file, update the default JUnit dependency to at least 4.11, and add a dependency to hamcrest-all and thucydides-junit. Thucydides uses SLF4J for its logging, so add an SLF4J implementation (e.g. slf4j-simple) as well. If you are not using Maven 3, make sure you configure the Maven compiler plugin to use Java 5. Finally, add the thucydides-maven-plugin, which provides the Thucydides reporting services. The resulting pom.xml file should look something like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.wakaleo.webtests.wikipedia</groupId>
  <artifactId>wikipediawebtests</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>wikipediawebtests</name>
  <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <thucydides.version>0.9.228</thucydides.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>net.thucydides</groupId>
            <artifactId>thucydides-junit</artifactId>
            <version>${thucydides.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.1</version>
            <type>pom</type>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>net.thucydides.maven.plugins</groupId>
                <artifactId>maven-thucydides-plugin</artifactId>
                <version>${thucydides.version}</version>
            </plugin>
        </plugins>
    </build>
</project>                      

Once you have set this up, run mvn package to ensure that the pom.xml and settings.xml configurations are correct.

Writing your first acceptance test

Let's start off by writing a JUnit acceptance test. However, before we do this, we need to describe the application a little, indicating how the functionalities we will be testing are organized into high level capabilities or features, and user stories. We need to do this so that Thucydides can give an accurate picture of how much testing is required for each user story and functionality. We can do this by modelling the features and user stories as very light-weight Java classes - this makes it easier to refactor. Another way is to place the stories in a directory structure that represents the functional hierarchy. In this example, we will use the first approach.

Open up this project in your favorite IDE (if you haven't already done so) and create a new class called Application. I usually place this class in a special package called requirements. This class contains the list of "features" ("epics", in agile terms) that make up the application, and stories related to each feature. These stories are not the tests themselves - rather they are used to model the application requirements. Several tests may refer to the same story.

For these tests, create an Application class like this:

import net.thucydides.core.annotations.Feature;

public class Application {
    @Feature
    public class Search {
        public class SearchByKeyword {}
        public class SearchForQuote{}
    }

    @Feature
    public class Contribute {
        public class AddNewArticle {}
        public class EditExistingArticle {}
    }
}

Now create a new test class in a package of your choice called SearchByKeywordStoryTest.java. At this stage we will include two "pending" tests. Pending tests are tests that have been specified and automated (i.e. written as an executable test), but not yet implemented.

@RunWith(ThucydidesRunner.class)
@Story(Application.Search.SearchByKeyword.class)
public class SearchByKeywordStoryTest {

    @Managed
    public WebDriver webdriver;

    @ManagedPages(defaultUrl = "http://www.wikipedia.com")
    public Pages pages;

    @Pending @Test
    public void searching_by_unambiguious_keyword_should_display_the_corresponding_article() {
    }

    @Pending @Test
    public void searching_by_ambiguious_keyword_should_display_the_disambiguation_page() {
    }
}

The ThucydidesRunner class indicates that this is a Thucydides tests. The @Managed and @ManagedPages annotations and fields are required to take care of our page objects - more about this later.

Before we continue, let's run these tests and see what happens. You can run them directly within your IDE - you should get two skipped tests. But it gets better. On the command line, or from within your IDE, invoke the thucydides:aggregate goal, e.g.:

$ mvn verify thucydides:aggregate

Congratulations! You have just generated your first Thucydides report! Go to the target/thucydides directory and open the home.html file. This will open a dashboard view with a large yellow square. This yellow square represents your application: only one feature has been specified so far (the 'Search' feature), but you can drill down into this feature to see what user stories and acceptance criteria have been specified. It's a little boring for now, but as we add more features and more tests, it will become more interesting.

Next, we will implement one of these tests. In Thucydides, tests are broken down into several "steps". During the reporting, each step appears in the test report as part of the overall test. Steps are just annotated methods, stored in special classes called "step libraries". A step library is a class that extends the ScenarioSteps class.

The best way to discover what steps and step libraries you need is to start expressing them in your tests, and implementing what you need. Let's take the first test: searching_by_unambiguious_keyword_should_display_the_corresponding_article. We could implement this as shown here (notice that we have removed the @Pending annotation, as this will no longer be pending):

@Test
public void searching_by_unambiguious_keyword_should_display_the_corresponding_article() {
    endUser.enters_keyword("cats");
    endUser.should_see_article_with_title("Cat");
}

So where does endUser come from? This will be our step library! It is often convenient to organize steps by actor, as it makes the tests read more fluently. It also encourages step names ("enters_keyword" and "should_see_article_with_title") that are more readable.

Steps are designed to maintain a healthy layer of abstraction/isolation between what your users what to do, and how they will go about doing it. So these high-level steps often don't have much in the way of technical detail, which is intentional. The details come further down.

This won't compile yet, so we need to flesh things out. Let's create a step library called EndUserSteps:

import net.thucydides.core.annotations.Step;
import net.thucydides.core.pages.Pages;
import net.thucydides.core.steps.ScenarioSteps;

public class EndUserSteps extends ScenarioSteps {

    @Step
    public void enters_keyword(String keyword) {
    }
         
    @Step
    public void should_see_article_with_title(String title) {
    }
}              

Note that you need to implement a constructor of the form shown here (the Pages object is a factory class that Thucydides uses to provide page objects to the tests and test steps). The step methods also need to be annotated with the @Step annotation.

Next, we just need to add an instance of the EndUserSteps library, a public field annotated with the @Steps annotation:

@RunWith(ThucydidesRunner.class)
@Story(Application.Search.SearchByKeyword.class)
public class SearchByKeywordStoryTest {

    @Managed
    public WebDriver webdriver;

    @ManagedPages(defaultUrl = "http://www.wikipedia.com")
    public Pages pages;

    @Steps
    public EndUserSteps endUser;

    @Test
    public void searching_by_unambiguious_keyword_should_display_the_corresponding_article() {
        endUser.searches_for("cats");
        endUser.should_see_article_with_title("Cat - Wikipedia, the free encyclopedia");                                            
    }

    @Pending @Test
    public void searching_by_ambiguious_keyword_should_display_the_disambiguation_page() {
    }
}

Now that we have defined our steps, we need to implement them. This is where the Page Objects come into play. Page Objects are a pattern that involves modelling your user interface in a way that hides the more technical details (such as the HTML structure of the page) behind a class, only presenting the functionality of the class in relatively high-level terms. But before we add a page object, let's see what we need it to do by implementing the steps we defined earlier on.

First, let's implement the searches_for() method. This is what the code I would like to have looks like:

import net.thucydides.core.pages.Pages;
import net.thucydides.core.steps.ScenarioSteps;

public class EndUserSteps extends ScenarioSteps {

     HomePage homePage;

    @Step
    public void searches_for(String keyword) {
        homePage.enter_keywords(keyword);
    }
} 

The first line of the searches_for method fetches a page object from the Thucydides page object factory. To do this, you use the currentPageAt method and provide the class of the Page Object you want to obtain. Thucydides will instantiate this page for you. (It actually does a little more than this, including optionally checking that you are one the right page, but we don't need to worry about that just now). Homepage is the name I have (fairly arbitrarily) chosen to represent the Wikipedia home page. And enter_keywords seems as good a name as any for typing keywords into the search field on the home page.

Of course this won't compile, so now we need to add a Page Object. Now that I've defined what I want my page object to do, though, I can get Eclipse to generate most of the class for me simply by using the "quick-fix" feature.

Once we have the class generated, we make it extend the Thucydides PageObject class. This class adds a lot of useful functions that you would otherwise have to write yourself, so it is generally a good idea. Then, once we have added a little WebDriver magic, we obtain a class along the following lines:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import net.thucydides.core.pages.PageObject;

public class HomePage extends PageObject {

    private WebElement searchInput;

    @FindBy(name="go")
    private WebElement searchButton;

    public HomePage(WebDriver driver) {
        super(driver);
    }

    public void enter_keywords(String keyword) {
        searchInput.sendKeys(keyword);
        searchButton.click();
    }
} 

Now try implementing the second test step. All this requires is to check the title of the page, a functionality that the PageObject class provides out of the box. So we just use a standard Hamcrest assert to check the value, with no extra work needed to the page object:

import com.wakaleo.webtests.wikipedia.pages.HomePage;
import net.thucydides.core.annotations.Step;
import net.thucydides.core.pages.Pages;
import net.thucydides.core.steps.ScenarioSteps;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

public class EndUserSteps extends ScenarioSteps {

    HomePage homePage;
    
    @Step
    public void searches_for(String keyword) {
        homePage.enter_keywords(keyword);
    }

    @Step
    public void should_see_article_with_title(String title) {
        assertThat(homePage.getTitle(), is(title));
    }
}

When this done, remove the @Pending annotation on the first test and rerun your tests from the command line:

$ mvn verify thucydides:aggregate

Once the tests have been executed, open up the home.html file in target/thucydides directory. You should now see one green test (passing) and one yellow test (pending). If you drill down into the green test, you should get to the executed steps and the corresponding screenshots.

Congratulations! You have now executed your first working Thucydides web tests! As an exercise, try implementing the second test. The source code for this example is available on Github at https://github.com/thucydides-webtests/thucydides-demos.

Although it requires a bit more thought upfront, there are several advantages to this multi-tiered approach. Steps are expressed in business terms, and can be safely shown to product owners, QA folk, end users and so forth without fear of drowning them in technical details.

High-level requirements, expressed as examples, also tend to change much less then the lower-level implementation details. Changes made to the pages within the Page Objects do not have to affect the test steps and tests that use those page objects, which makes maintenance easier.

Reuse is another advantage. You can reuse steps, give them parameters, reuse them for data-driven testing and so forth. Again, this makes for easier long-term maintenance.