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!