-
Notifications
You must be signed in to change notification settings - Fork 4
Page Object
One of the most popular design patterns for test automation frameworks are the page object pattern. Page object pattern is used to:
- Wrap functionallity of the page or view to simple functions
- Hide complexity
- Remove duplication of code
Testura provides an additional nuget package to help you design and set up your test automation framework for the page object pattern.
https://www.nuget.org/packages/Testura.Android.PageObject
PM> Install-Package Testura.Android.PageObject
The package contains two classes:
- Applicaton - Your main class that will contain all views
- View - All your views will inherit from this class
Here we will show a quick example on how you can build your application and views class for a facebook application.
Let's start with our own facebook view class:
public abstract class FacebookView : View
{
protected FacebookView(IFacebookApplicationViews facebookApplicationViews, IAndroidDevice device) : base(device)
{
FacebookApplicationViews = facebookApplicationViews;
}
protected IFacebookApplicationViews FacebookApplicationViews { get; }
}
All our views will inherit from this base class and as you can see it take two arguments in the constructor:
- facebookApplicationViews - A reference to all views in our application (will show the implementation later)
- device - A reference to our device
We can then use this view class to create two example views(Note: all ids are made up and only used as examples):
public class LoginView : FacebookView
{
[MapUiObject(ResourceId = "somethingUniqueOnView")]
private readonly UiObject _id;
[MapUiObject(ResourceId = "usernameId")]
private readonly UiObject _usernameTextbox;
[MapUiObject(ResourceId = "passwordId")]
private readonly UiObject _passwordTexbox;
[MapUiObject(ResourceId = "loginButtonId")]
private readonly UiObject _loginButton;
[MapUiObject(ResourceId = "errorLabelId")]
private readonly UiObject _errorLabel;
public LoginView(IFacebookApplicationViews facebookApplicationViews, IAndroidDevice device) : base(facebookApplicationViews, device)
{
}
public LoginView Exist()
{
if (!_id.IsVisible())
{
throw new ViewException("Can't find the login view.");
}
return this;
}
public MainView Login(string username, string password)
{
Exist();
_usernameTextbox.InputText(username);
_passwordTexbox.InputText(password);
_loginButton.Tap();
if (_errorLabel.IsVisible())
{
throw new LoginException($"Failed to login, error: {_errorLabel.Values().Text}");
}
return FacebookApplicationViews.MainView;
}
}
public class MainView : FacebookView
{
public MainView(IFacebookApplicationViews facebookApplicationViews, IAndroidDevice device) : base(facebookApplicationViews, device)
{
}
}
With all views done we can move over to the application classes. We first need an interface that contains all views in our application.
public interface IFacebookApplicationViews
{
MainView MainView { get; }
LoginView LoginView { get; }
}
Finally we can create the main application class where we set up the device and all views
public class FacebookApplication : Application, IFacebookApplicationViews
{
private IAndroidDevice _device;
/// <summary>
/// Set up our device and register and solve all dependencies
/// </summary>
public void SetUp()
{
_device = new AndroidDevice(new DeviceConfiguration { ShouldInstallDependencies = false });
Container.RegisterInstance<IAndroidDevice>(_device);
Container.RegisterInstance<IFacebookApplicationViews>(this);
RegisterViewDependencies();
SolveViewDependencies();
}
/// <summary>
/// Start the ui server and go to the main activity
/// </summary>
/// <returns></returns>
public LoginView Start()
{
_device.Ui.StartUiServer();
_device.Activity.Start("facebookPackage", "facebookActivity", forceStopActivity: true, clearTasks: false);
return LoginView;
}
public LoginView LoginView { get; protected set; }
public MainView MainView { get; protected set; }
}
The most important thing here is the SetUp method. When we call the SetUp method it will:
- Create and set up a new device
- Register the device to our unity container
- Register ourself as the IFacebookApplicationViews interface (so views can access other views)
- Go through all properties in our class that inherit from the View-class and register them in our unity container
- And finally solve all dependencies
If you haven't use dependency injection before I would recommend to read this: https://en.wikipedia.org/wiki/Dependency_injection
When everything done, let's create our first tests (in this example we're using NUnit)
[TestFixture]
class LoginTest
{
private FacebookApplication _facebookApplication;
[OneTimeSetUp]
public void SetUp()
{
_facebookApplication = new FacebookApplication();
_facebookApplication.SetUp();
}
[Test]
public void Login_WhenHavingCorrectLoginInformation_ShouldLogin()
{
_facebookApplication
.Start()
.Login("myUsername", "myPassword");
}
[Test]
public void Login_WhenHavingIncorrectLoginInformation_ShouldNotLogin()
{
Assert.Throws<LoginException>(() =>
_facebookApplication
.Start()
.Login("myUsername", "wrongPassword"));
}
}
Usually people validate the view by:
- Creating new validation methods on the view
- Adding properties or methods to grab values from the view
We recommend that you instead create separate validation classes that implement the IAndroidValidation<> interface. For example:
public class LoginViewErrorValidation : IAndroidViewValidation<LoginView>
{
private readonly string _expectedText;
public LoginViewControlTypeValidation(string expectedText)
{
_expectedText = expectedText;
}
public void Validate(IAndroidDevice device, LoginView view)
{
Assert.AreEqual(_expectedText, view.SomeControl.First().Text, "Wrong value");
}
}
And the create a new method on your view:
public void ValidateView(IAndroidViewValidation<LoginView> validations)
{
Validate(validations);
}
This helps you separate validation from the view.