The Goal
In this series of posts I'm going to show how to leverage clean code principles when writing functional tests.
Fluent Interface
Navigation between flow steps is implemented via fluent interface, which is kind of syntactic sugar. At least the reader of the code has a feeling that the next step is naturally flowing from the previous one.
Our code under test, which I have mentioned many times in this series
[TestFixture]
public class BookForwarderScenario : BaseIntegrationTest
{
[Test]
[ImpersonateRandomSystemPortalUser]
public void BookSingleForwarderFlow()
{
CustomerApp
.NavigateToRegisterAndBook()
.CreateBookForwarderContext()
.ChooseFirstOrder()
.LaunchBookingWizard()
.SelectFirstForwarder()
.GoToTheNextWizardStep()
.SelectFirstContact()
.GoToTheNextWizardStep()
.SkipAgreedFreight()
.Book()
.EnsureNotification()
.EnsureOrderHasGoneFromGrid()
.GoToFollowUp()
.EnsureBookedOrderExists();
}
}
Non-fluent implementation example would look like:
...
CustomerApp.NavigateToRegisterAndBook();
CustomerApp.CreateBookForwarderContext();
CustomerApp.ChooseFirstOrder();
CustomerApp.LaunchBookingWizard();
...
Implementing fluent interface is not a big deal. The secret is that each method in the pipe line needs to return an instance of it's own class. Here are couple of methods, which make fluent pipe line possible:
public class BookForwarderFlowContext : BaseFlowContext
{
...
public BookForwarderFlowContext ChooseFirstOrder()
{
OrderNumber = WebDriver
.FindRegisterAndBookTable()
.FindLink()
.Text;
WebDriver
.FindRegisterAndBookTable()
.FindFirstRow()
.FindFirstVisibleCheckBox()
.Click();
return this;
}
public BookForwarderFlowContext BookTheOrder()
{
WebDriver
.FindBookForwarderButton()
.Click();
return this;
}
...
}
By the way, the context itself does not contain low-level WebDriver acrobatics. It is all hidden in extension methods. For instance, FindBookForwarderButton method looks like this:
public static partial class ISearchContextExtensions
{
public static IWebElement FindBookForwarderButton(this ISearchContext element)
{
return element.FindButtonByText("Book Forwarder");
}
That's all Folks
Posts in this series:
Part 3: High Level of Abstraction