How to identify, create and choose when to use a PageObject, inner PageObject, PageObject method abstractions, and extension methods.
How two or more PageObjects interact with each other.
An example to create PageObjects from a web browser.
The completed sample solution described in this article can be downloaded here:
Before this guide
This guide assumes that you have read the Basic Concepts and Page Object articles. Also, we'll be using the following starter solution throughout this guide:
Identifying PageObjects
Open the starter solution QualityMatePageObjectExample.sln and the POExample.html file in a web browser. This will be our example web application.
We can represent the example web application with the following diagram. Doing this helps us keep in mind the different parts that make up the application under test; thus, creating each PageObject will be easier.
Creating a PageObject
As mentioned in the PageObject article, PageObjects can represent an entire web page. We can define our first PageObject using the example web application as RegisterPage and this will represent our page.
[Selector("body", SelectorScope.Root)]
public class RegisterPage : PageObject
{
}
Now we can populate our PageObject with the controls from the page (inputs, tabs, buttons, etc.). But beware: if we decide to populate RegisterPage with all the controls, we might end up with a class too big to maintain.
Creating an Inner PageObject
Instead of have a class that handles all controls within a page, we can abstract pages or group of controls into an Inner PageObject.
Inspecting POExample.html, we can create Inner PageObjects for each Tab (User Information and Contact information), this will reduce the RegisterPage control count and responsibilities.
User Information Tab to PageObject class
[Selector("#Tab1", SelectorScope.Root)]
public class UserInformationTab : PageObject
{
[Selector("#name")]
public ITextBox Name { get; set; }
[Selector("#email")]
public ITextBox Email { get; set; }
}
Contact Information Tab to PageObject class
[Selector("#Tab2", SelectorScope.Root)]
public class ContactInformationTab : PageObject
{
[Selector("#address")]
public ITextBox Address { get; set; }
[Selector("#city")]
public ITextBox City { get; set; }
[Selector("#state")]
public ITextBox State { get; set; }
[Selector("#phone")]
public ITextBox Phone { get; set; }
[Selector("#subscribe")]
public ICheckBox Subscribe { get; set; }
}
With both Inner PageObjects created, we'll add a reference to RegisterPage to these Inner PageObjects and any other controls that are responsibility of RegisterPage. The code should look like this:
[Selector("body", SelectorScope.Root)]
public class RegisterPage : PageObject
{
[Selector("#tabButton1")]
public IButton Tab1 { get; set; }
[Selector("#tabButton2")]
public IButton Tab2 { get; set; }
public UserInformationTab UserInformationTab { get; set; }
public ContactInformationTab ContactInformationTab { get; set; }
[Selector("#submit")]
public IButton Submit { get; set; }
[Selector("#results")]
public ITextBox Results { get; set; }
}
Now we have completed the proposed diagram with all the PageObjects.
PageObject Methods
Since PageObjects are C# classes, they may also contain methods that interact with their own controls or inner PageObjects. This reduces code duplication and separates responsibilities related to the PageObject in use.
To continue with our example application, each PageObject class (UserInformationTab and ContactInformationTab) could have their own method to fill the input fields they contain.
First, we'll declare the following structs that handle the data which will be passed to the PageObject.
public struct UserInformation
{
public UserInformation(string name, string email)
{
this.Name = name;
this.Email = email;
}
public string Name { get; }
public string Email { get; }
}
public struct UserContact
{
public UserContact(string address, string city, string state, string phone, bool subscribe)
{
this.Address = address;
this.City = city;
this.State = state;
this.Phone = phone;
this.Subscribe = subscribe;
}
public string Address { get; }
public string City { get; }
public string State { get; }
public string Phone { get; }
public bool Subscribe { get; }
}
We will also add the PageObjects methods that will use the struct data and write it to the specific control.
[Selector("#Tab1", SelectorScope.Root)]
public class UserInformationTab : PageObject
{
/// The controls already defined
public void FillUserInfo(UserInformation userInfo)
{
this.Name.Text = userInfo.Name;
this.Email.Text = userInfo.Email;
}
}
[Selector("#Tab2", SelectorScope.Root)]
public class ContactInformationTab : PageObject
{
/// The controls already defined
public void FillContactInfo(UserContact contact)
{
this.Address.Text = contact.Address;
this.City.Text = contact.City;
this.State.Text = contact.State;
this.Phone.Text = contact.Phone;
this.Subscribe.Checked = contact.Subscribe;
}
}
With both of these methods at hand, it only makes sense to extend the idea to RegisterPage, which can have a method that calls the Inner PageObject methods. From our example application, it only needs to pass the respective data (using a struct called UserData) and click the submit button.
public struct UserData
{
public UserData(UserInformation userInformation, UserContact userContact)
{
this.UserInformation = userInformation;
this.UserContact = userContact;
}
public UserInformation UserInformation { get; set; }
public UserContact UserContact { get; set; }
}
[Selector("body", SelectorScope.Root)]
public class RegisterPage : PageObject
{
/// The controls and Inner PageObjects already defined
public void AddUser(UserData info)
{
this.Tab1.Click();
this.UserInformationTab.FillUserInfo(info.UserInformation);
this.Tab2.Click();
this.ContactInformationTab.FillContactInfo(info.UserContact);
this.Submit.Click();
}
}
Create a test case
Finally, we should use these methods in a test case. For this example, we want to check if the submitted data is submitted as expected. Since the main topic of this article is PageObject, we can replace the code used for the test case script in MyTestClass.cs file for the following:
private void MyQualityMateScript(UiExecutionController controller)
{
// Get the PageObjet
RegisterPage myPageObject = controller.GetPageObject<RegisterPage>();
// Creation of UserInformation
UserInformation information = new UserInformation("QualityMate",
"qualitymate@mobilize.net");
// Creation of UserContact
UserContact contact = new UserContact("Costa Rica",
"San Jose",
"San Jose",
"512-243-5754",
true);
// Creation of UserData
UserData data = new UserData(information, contact);
// Use the method from RegisterPage PageObject
myPageObject.AddUser(data);
// Do validates of the UserData
myPageObject.Results.Validate(self
=> self.Text.Contains(information.Name)
&& self.Text.Contains(information.Email)
&& self.Text.Contains(contact.Address)
&& self.Text.Contains(contact.City)
&& self.Text.Contains(contact.State)
&& self.Text.Contains(contact.Phone)
&& self.Text.Contains(contact.Subscribe.ToString().ToLower())
);
}
Remember to load the QualityMateSettings.runsettings file on your IDE. If you're working on Visual Studio, information on how to do this can be found here.
What's next?
Compile the test project and run the test case to see the magic happen. Great isn't it?