Loading...

Dealing with assertions in Selenium tests

 There are at least to approaches in locating assertions in the end to end tests written with use of Selenium. The firs and most popular solution is to use assertions methods directly in test case code but there are some problems with this solution: they looks strange when you use chaining, timing issues can't be solved in assertions. Other solution is to write assertions methods in your Page Object class. This solution looks great when you use chaining and can solve timing issues but from my point of view Page Object is not a best place for assertions. In this article I will describe the third solution which will be some kind of compromise that can handle timing issues, it looks nice with chaining and keep clear code structure in your framework.

To illustrate the problem lets assume that we want to create test case in which we are logging in to web page e-mail client, creating and sending new e-mail. In the test case we want to check weather after performing that action mail is stored in SEND folder and it has a correct content (title, recipients, message). When we create the assertions in the test case and use chaining then our test case can look similar to:

@Test
public void sentEmailShouldBeStoredInSentFolder() {
	Mail mailToSend = new Mail(RECIPIENT, TITLE, MESSAGE);

	MailsViewPage mailsView = logInToGmail(USER)
		.clickCreateMailButton()
		.createMail(mailToSend)
		.clickSendButton()
		.navigateToSendFolder();

	assertThat(mailsView.getMails(), contains(mailToSend))

	MailDetailsView mailDetailsVeiw = mailsView
		.openMailDetailsByTitle(title);

	assertThat(mailDetailsVeiw.getMail(), equals(mailToSend))
}


In the code above we can observe that we have to assign value from the last chaining method to mailsView and mailDetailsVeiw variable which looks strange. The next problem is that on the Page object side we still have to do some kind of validation because e.g. getMail() method executed in assertion can return list of mails before expected mail will appear in the folder (test can failed from time to time).


Better solution in such case is to use assertions on the Page object side. Please look at the code below:

@Test
public void sentEmailShouldBeStoredInSentFolder() {
	Mail mailToSend = new Mail(RECIPIENT, TITLE, MESSAGE);

	logInToGmail(USER)
		.clickCreateMailButton()
		.createMail(mailToSend)
		.clickSendButton()
		.navigateToSendFolder()
		.assertThatMailsListContains(mailToSend)
		.openMailDetailsByTitle(title)
		.assertThatMailDetailsEquals(mailToSend);
}


In this code chaining looks great because there are no assignment and we can use waits in assertions so timing issues can be avoided. The only problem I see in that approach is that Page object is not a correct place for assertions. In my opinion public methods from Page object should only give access to elements accessible on the page as a user.


Lets now focus on creating a third solution in our framework but before we create the test case lets create required classes. The first thing we need is the AbstractAssertion class in which we define the way of finishing the assertions execution in the test methods chaining (assertEnd method), getter and setter for the page object assertions will be performed against.

package com.testcraftsmanship.e2e.model.assertions;

import com.testcraftsmanship.e2e.model.base.BasePage;

public abstract class AbstractAssertion<T extends BasePage> {
    private T page;

    public T assertEnd() {
        return page;
    }

    public T getPage() {
        return page;
    }

    public void setPage(T page) {
        this.page = page;
    }
}


The next piece of puzzles is the class with assertions for concrete page object (e.g. MailsViewPage). In this class we define all assertions we need for the dedicated page object. I have just added one simple assertion method but of course can be more methods and they can be more complicated (e.g. use waits inside).

package com.testcraftsmanship.e2e.model.assertions;

import com.testcraftsmanship.e2e.model.pages.MailsViewPage;
import static org.fluentlenium.assertj.FluentLeniumAssertions.assertThat;

public class MailsViewPageAssertion extends AbstractAssertion<MailsViewPage> {

    public UsersPageAssertion mailListContains(Email email) {
        assertThat(getPage().getMailsList().contains(email));
        return this;
    }

}


We require an interface which define how all parts will be taken together. This interface should be implemented in every Page object class for which we have created (and want to use) assertions class. To make it easier we can implement it in some kind of abstract page from which all our page objects extends.

package com.testcraftsmanship.e2e.model.base;

import com.testcraftsmanship.e2e.model.assertions.AbstractAssertion;

public interface SupportAssertions {

    <G extends BasePage, T extends AbstractAssertion> T assertThat(Class<T> clazz);

}


The last part we need is implementation of assertThat method in the abstract page object. The method take as an argument class with assertions we want to perform in chaining.

package com.testcraftsmanship.e2e.model.base;

import com.testcraftsmanship.e2e.model.assertions.AbstractAssertion;
import org.fluentlenium.core.FluentPage;

public class BasePage extends FluentPage implements SupportAssertions {
    ...
    @Override
    @SuppressWarnings("unchecked")
    public <G extends BasePage, T extends AbstractAssertion> T assertThat(Class<T> clazz) {
        try {
            AbstractAssertion<G> assertion = clazz.getConstructor().newInstance();
            assertion.setPage((G) this);
            return (T) assertion;
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {
            throw new RuntimeException("Assertion creation exception", e);
        }
    }
    ...
}


When all classes are created then we can focus on the test case. Exactly the same scenario as those mentioned above created with use of newly created classes is shown below.

@Test
public void sentEmailShouldBeStoredInSentFolder() {
	Mail mailToSend = new Mail(RECIPIENT, TITLE, MESSAGE);

	logInToGmail(USER)
		.clickCreateMailButton()
		.createMail(mailToSend)
		.clickSendButton()
		.navigateToSendFolder()
		.assertThat(MailsViewPageAssertion.class)
			.mailListContains(mailToSend)
		.endAssert()
		.openMailDetailsByTitle(title)
		.assertThat(MailDetailsViewAssertion.class)
			.mailDetailsEquals(mailToSend)
		.endAssert();
}


Enjoy!