Base Model

When coding SW Models of applications you will soon notice that you are often reusing the same functionalities in order to interact with the application. A good example for this is selecting a value in a dropdown. First you need to click in the dropdown to expand it; then you need to find the required value in the dropdown list, for this you might even need to scroll the list; when you found the value you need to click it to close the list and save the selection. Another type of UI element that you often find in applications are tables, where you need some notion of columns and row to verify or interact with a specific cell.

In addition to these more complex interactions with the controls on the screen, it is a good practice to verify every interaction that is done with the UI was completed succesfully. We found that sometimes even a simple click on a button is not applied correctly; and sometimes it just takes a few seconds to open a dialog for example. Therefore we often want to automatically retry an interaction, if it didn't have the expected effect after a certain time.

The Base Model solves these two challenges by providing standard implementations for the most common UI control elements and contains some additional helper methods and extensions that help you to interact with your application. By using the Base Model it is also ensured that all interactions with the UI are logged, which will help during debugging of the automated test cases but also facilitates analyzing issues with the tested application.

Structure

If you use the Base Model in your software model it is recommended that you follow this structure. It is inspired by the page object pattern that was made popular by the Selenium community, but is adjusted to the specialties of our visual test engine.

Any UI should be separated in screens which contain the Primitives (UI elements such as buttons, textboxes etc) and offer methods that combine several interactions with these low-level elements. These abstraction methods will not only make your test case code more readable, but also help you to maintain your test automation solution. In many cases it will probably be enough to adjust the method that is called by the test case, but the test case itself will not have to be adjusted as it will still call the same method.

Example of a simple LoginScreen with primitives and LoginUser method

LoginScreen.cs
App.cs
TestCase.cs
public class LoginScreen : BaseScreen
{
public LoginScreen(IAppBasics appBasics) : base(appBasics, "Login Screen", Images.Login.Opened)
{ }
public TextBox UserNameTextBox { get; private set; }
public PwTextBox PasswordTextBox { get; private set; }
public Button SignInButton { get; private set; }
protected override void Initialize()
{
UserNameTextBox = new TextBox(t, "User name", Images.Login.UserName, filters: ScreenSelect);
PasswordTextBox = new PwTextBox(t, "Password", Images.Login.Password, Images.Common.PwChar, filters: ScreenSelect);
SignInButton = new Button(t, "Sign in", Images.Login.SignIn, filters: ScreenSelect);
}
public void LoginUser(string userName, string pw)
{
UserNameTextBox.Enter(userName);
PasswordTextBox.EnterPassword(pw);
SignInButton.Click(WaitForDisappear);
}
}
public class ExampleApp : IAppBasics
{
public ExampleApp(ITester t)
{
LoginScreen = new LoginScreen(this);
// ...
}
public LoginScreen { get; }
// ...
}
private ExampleApp App { get; set; }
[SetupTest]
public bool Setup(ITester t)
{
App = new ExampleApp(t);
return true;
}
[PreconditionStep()]
public virtual void PreconditionStep(ITester t)
{
App.LoginScreen.WaitFor();
App.LoginScreen.LoginUser("userName", "password");
}

In the example LoginScreen.cs above you can see how a simple screen using the base model can be implemented. It is using the BaseScreen base class which allows to initialize the primitives (TextBoxes and Button) with the ScreenSelect of the LoginScreen. This will improve the reliability and performance of the image and text searches. The other reason we don't instantiate the primitives in the constructor of the LoginScreen is to make sure that every time we navigate to the LoginScreen, the primitives do not keep their state from the last time the LoginScreen was interacted with, as this information might be incorrect now.

The App.cs that should be present in this or a similar form in your software model is the entry point for how the test case can interact with the Screen objects. The screens are instantiated in your App class and also accessible through a property. Since the App class implements the IAppBasics interface, we can directly pass this to the constructor of the Screen, which gives it access to the IAppBasics interface.

In the TestCase.cs above you can also see how we call LoginScreen.WaitFor(); before the actual login, to ensure we are on the correct screen and it is fully loaded before attempting to interact with the screen. It is important that this WaitFor method is called on every screen before interacting with it, as otherwise the primitives will not be instantiated yet.

What is a Screen?

Especially in the context of web testing, people often use the page object pattern incorrectly. Don't think of a screen as everything you see on the UI, after you navigate to a certain URL. Usually a UI consists of several sections that should also be separated in individual Screen objects. Often you can even reuse a screen object (e.g. a navigation menu or a sidebar) on different web pages. For this reason it often makes sense that a Screen object will itself contain other Screen Objects.