What are Page Objects?

Seleno supports the idea of Page Objects to model your web pages. The idea is to model each page (or part of a page) as a class, and then to use instances of those classes in your tests. To continue, with the google example, we're all familiar with this page.

597

In Seleno, you would model this page by inheriting from the Page class and then encapsulating the interaction with the page within methods. Seleno provides methods to find elements on the page (Find) and enter data into the text box (SendKeys). The Navigate method allows you to navigate to the results page by clicking on the Search button (btnG).

public class SearchPage : Page
{
    public SearchPage InputSearchTerm(string term)
    {
        Find.Element(By.Name("q"))
            .SendKeys(term);
        return this;
    }

    public ResultsPage Search()
    {
        return Navigate.To<ResultsPage>(By.Name("btnG"));
    }
}

public class ResultsPage : Page
{
    ...
}

Now your tests can just call methods on the Page Objects, without having to know anything about the interaction with the page or the use of Selenium.

public static class Host
{
    public static readonly SelenoHost Instance = new SelenoHost();

    static Host()
    {
        Instance.Run(configure => configure
            .WithWebServer(new InternetWebServer("http://www.google.co.uk")));
    }
}

[TestFixture]
public class GoogleSearchTests
{
    [Test]
    public void should_be_able_to_search()
    {
        var searchPage = Host.Instance.NavigateToInitialPage<SearchPage>();
        var resultsPage = searchPage
            .InputSearchTerm("BDDfy")
            .Search();
        resultsPage.Title.Should().Be("Google");
    }
}

Strongly Typed Page Objects

That's quite nice, but things start to get really interesting if you use view models. Seleno also provides strongly typed Page Objects, which allow you to read from and write to the page. Here is an example, from the Seleno MvcMusicStore sample. Because Seleno is aware of the RegisterModel view model, it is able to take a populated instance of that model and enter the information into the web page using the Input().Model method.

public class RegisterModel
{
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
}

public class RegisterPage : Page<RegisterModel>
{
    public HomePage CreateValidUser(RegisterModel model)
    {
        Input.Model(model);
        return Navigate.To<HomePage>(By.CssSelector("input[type='submit']"));
    }
}

This can then be used in a test, with a populated model generated by the CreateRegisterModel of the ObjectMother.

public class StronglyTypedPageObjectWithComponent
{
    [Test]
    public void Can_buy_an_Album_when_registered()
    {
        var orderedPage = Host.Instance.NavigateToInitialPage<HomeController, HomePage>(x => x.Index())
            .Menu
            .GoToAdminForAnonymousUser()
            .GoToRegisterPage()
            .CreateValidUser(ObjectMother.CreateRegisterModel())
            .GenreMenu
            .SelectGenreByName("Disco")
            .SelectAlbumByName("Le Freak")
            .AddAlbumToCart()
            .Checkout()
            .SubmitShippingInfo(ObjectMother.CreateShippingInfo(), "Free");

        orderedPage.Title.Should().Be("Checkout Complete");
    }
}

Notice here the Host.Instance.NavigateToInitialPage<HomeController, HomePage>(x => x.Index()) allows us to specify navigation by selecting a controller and action (rather than a Url string). Before using this method be sure to register the application routes when you are initializing Seleno, e.g.:

<AnInstanceOfSelenoHost>
    .Run("MvcMusicStore", 12345, 
        c => c.WithRouteConfig(RouteConfig.RegisterRoutes));