Friday, June 3, 2016

Proper Page Modeling (aka Page Objects)

It is making me crazy to see how many people are talking about Page Objects in what seems to be the least robust way possible.  I will try to keep it civil, but here are several examples of what I consider a terrible way to model your UI for testing:

http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern
http://fluentbytes.com/maintainable-test-automation-for-winforms-using-codedui/ (this one makes me particularly sad as it's Marcel and he's kinda the name for testing in this space ~_~)
https://www.youtube.com/watch?list=PL6tu16kXT9PoUbSYNcLrMG8ox6UBbbsCv&v=UUxSUsUVg-U

In all of these cases, the page objects have methods like .SetUserName("MyUserName"), .ClickLoginLink(), .LoginUser(string username, string password).

How terribly inconsistent and un-maintainable.  What if the user name input is not shown by default and there is a button that shows the user name box.  I'll have to add methods for .IsUserNameBoxVisible(), .ClickButtonToShowUserNameBox().  Wowzer.  As a tester who did not write the page object, I have no consistent expectation of what the page object can do or what the controls on the page can do or what I can assert about them.

Instead, the page object, which I'll now refer to as a Page Model, should expose the UserName input as a property which has methods for .IsVisible(), .Click(), .IsEnabled(), .SetText(string text).  Now, instead of .SetUserName("MyUserName"), it would be .UserName.SetText("MyUserName").  At a glance you may say, so what?  However, having the .UserName property gives me (for free) all of the common actions I may take.  For instance,

interface ILoginPage : IPageModel
{
    IReadWriteValuePageModel<string, ILoginPage> UserName {get;}
    IValuablePageModel<string, ILoginPage> Password {get;}
    IClickablePageModel<ILoginPage> ShowLogin {get;}
    IClickablePageModel<IAccountPage> Login{get;}
}

// page starts with username hidden
Assert.IsTrue(UserName.IsHidden());

// click show login button
ShowLogin.Click();

// assert that the user name is now visible
Assert.IsTrue(UserName.IsVisible());

// assert that the login button is disabled (no username / password entered)
Assert.IsTrue(Login.IsNotActionable());

// enter user name
UserName.SetText("MyUserName");

// assert login button is still disabled (no password)
Assert.IsTrue(Login.IsNotActionable());

// enter a password
Password.SetText("MyPassword");

// the login button should be enabled
Assert.IsTrue(Login.IsActionable());

Login.Click();

compared to

interface ILoginPage
{
    ILoginPage SetUserName(string userName);
    bool IsUserNameVisible();
    ILoginPage SetPassword(string password);
    ILoginPage ClickShowUsernameButton();
    bool IsLoginButtonEnabled();
    IAccountPage ClickLoginButton();
}

If I want to check if the password is visible, then I have to add it as a method.  If I want to check that the show username button disappears after clicking, I have to add a method.  This violates the Open / Close principal and in general is annoying.

It is the responsibility of the tests themselves to create these higher level concepts like

public void LoginUser(string username, string password)
{
    if(ShowLogin.IsVisible())
    {
         ShowLogin.Click();
    }

    UserName.SetText(username).Password.SetText(password).Login.Click();
}

Then, this can be reused in a test:

[TestMethod]
public void GivenLogin_WhenSaveFirstNameInProfile_ThenNameIsLoadedBack()
{
    var accountPage = LoginUser("someUser", "somePass");
    Assert.IsTrue(accountPage.IsVisible());

    string myName = "Myname";
    account.FirstName.SetText(myName).Save.Click();
    Logoff();

    accountPage = LoginUser("someUser", "somePass");
    Assert.IsTrue(myName.Equals(accountPage.FirstName.Value));
}

No comments:

Post a Comment