Skip to content

Page Object

Mille Boström edited this page Nov 5, 2018 · 6 revisions

Page object pattern with Testura.Android.PageObject

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.

Install nuget package

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

Facebook example

Here we will show a quick example on how you can build your application and views class for a facebook application.

Views

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):

Login

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;
	}
}

Main

public class MainView : FacebookView
{
	public MainView(IFacebookApplicationViews facebookApplicationViews, IAndroidDevice device) : base(facebookApplicationViews, device)
	{
	}
}

Application

With all views done we can move over to the application classes. We first need an interface that contains all views in our application.

IFacebookApplicationViews

public interface IFacebookApplicationViews
{
	MainView MainView { get; }
	LoginView LoginView { get; }
}

Main application class

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

Crete a login test

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"));

	}
}

Addtional validation on view

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.