Extend The Framework to Fit Your Needs

To be sure that you will use BELLATRIX happily and frequently, we developed enormous ways to extend it. We have thus provided you with a wider test execution life cycle with the ability to plug-in everywhere.

Extended Test Execution Life cycle and Plugins

Have a full control over your test execution.

All test frameworks give you the possibility to execute logic at certain points before or after tests. Typically tests are placed inside test classes. The standard test workflow for MSTest and NUnit is:

1. Execute Assembly Initialize – once for the whole project
2. Execute Class Initialize – once for the test class
3. Execute Test Initialize – executed before each test
4. Execute Test
5. Execute Test Cleanup – executed after each test
6. Execute Class Cleanup – once for the test class
7. Execute Assembly Cleanup – once for the whole project

Why do you need to extend the standard workflow?

Usually, you want to use these primary methods to execute business test-related logic. Sample Use Cases:

  • Take screenshots of test failure
  • Take videos on test failure
  • Fail tests if not executed under a certain time
  • Log test results in a 3rd party system
  • Control the app life cycle

In order to meet these requirements and allow you to write custom plugins, BELLATRIX comes with an extended test execution life cycle. When you write a plugin, you can execute logic in various phases of the new life cycle- before TestInitialize, after TestInitialize, before TestCleanup, after TestCleanup and more. For each test, all registered plugins logic is executed. This is how some of the BELLATRIX features work such as video and screenshot on test failure, app life cycle, and more.

You can easily write your custom plugins. See how it is done. In BELLATRIX, we have a feature called “execution time under”. Here you specify for the test class the maximal allowed test execution time if some of the tests take longer to run- it fails automatically. This is done through a test workflow plugin.

This is how we specify the maximal allowed time.

 [ExecutionTimeUnder(2)]
 public class MeasuredResponseTimesTests : IOSTest

This is part of the plugin.

public class ExecutionTimeUnderTestWorkflowPlugin : TestWorkflowPlugin
{
    protected override void PostTestInit(object sender, TestWorkflowPluginEventArgs e)
    {
        // get the start time
    }

    protected override void PostTestCleanup(object sender, TestWorkflowPluginEventArgs e)
    {
        // total time = start time - current time
        // IF total time > specified time ===> FAIL TEST
    }
}

In PostTestInit we start a stopwatch. We get the number placed inside the attribute in the PostTestCleanup method. Then stop the stopwatch and compare the actual and specified times. In case first took longer we fail the test.

Learn more

Override Globally Element Actions

Change how specific element action is performed.

For example, instead of using our button Click method, use Enter to click the button. If you want to change this behavior for all tests, each BELLATRIX UI element gives you this possibility. You need to initialize the element static delegates- Override{MethodName}Globally.

[AssemblyInitialize]
public static void AssemblyInitialize(TestContext testContext)
{
    Button.OverrideClickGlobally = (e) =>
    {
        e.ToExists().ToBeClickable().WaitToBe();
        e.ScrollToVisible(ScrollDirection.Down);
        e.Click();
    };
}

Here we change the behavior of all BELLATRIX UI button elements. Instead of using clicking the button directly first wait to be clickable and scroll to be visible.

Learn more

Locally Override Element Actions

Change the UI element action behavior, but only for part of your tests.

In this scenario, you may use BELLATRIX “override element locally” feature. You need to initialize the static delegates- Override{MethodName}Locally. This may be useful to make a temporary fix only for certain page where the default behavior is not working as expected.

RadioButton.OverrideClickLocally = (e) =>
{
    e.ToExists().ToBeClickable().WaitToBe();
    e.ScrollToVisible(ScrollDirection.Down);
    e.Click();
};

We override the behavior of the radio button control. Instead of using clicking the button directly first wait to be clickable and scroll to be visible. After the test is over, the default behavior is restored.

Learn more

Element Action Hooks

Another way to extend BELLATRIX is to use the controls hooks.

This is how the BDD logging is implemented. For each control’s method, there are two hooks- one that is called “before the action” and one “after”.
For example, the available hooks for the button are:

  • Clicking – an event executed before button click
  • Clicked – an event executed after the button is clicked
  • Hovering – an event executed before button hover
  • Hovered – an event executed after the button is hovered

You need to implement the event handlers for these events and subscribe to them.

public void ClickingEventHandler(object sender, ElementActionEventArgs<IOSElement> arg)
{
    Logger.LogInformation($"Click {arg.Element.ElementName}");
}

public void HoveringEventHandler(object sender, ElementActionEventArgs<IOSElement> arg)
{
    Logger.LogInformation($"Hover {arg.Element.ElementName}");
}

These are two sample event handlers for the BELLATRIX button where we log messages before clicking and hovering a button.

Learn more

Common Services Action Hooks

Another way to extend BELLATRIX is to use hooks for its common services.

The base class for all web elements- Element provides a few special events as well:

  • ScrollingToVisible – called before scrolling
  • ScrolledToVisible – called after scrolling
  • CreatingElement – called before creating the element
  • CreatedElement – called after the creation of the element
  • CreatingElements – called before the creation of nested element
  • CreatedElements – called after the creation of nested element
  • ReturningWrappedElement – called before searching for a native WebDriver element

Learn more

Extend Existing UI Elements

You have the ability to add new methods to existing BELLATRIX UI elements. You can add new methods in two ways.

Extension Methods

One way to extend an existing element is to create an extension method for the additional action.

public static class ButtonExtensions
{
   public static void SubmitButtonWithScroll(this Button button)
   {
      button.ToExists().ToBeClickable().WaitToBe();
      button.ScrollToVisible(ScrollDirection.Down);
      button.Click();
   }
}

To use the new method, add a using statement to the extension methods’ namespace.

button.SubmitButtonWithScroll();

Click the button with first scrolling to it.

Learn more

Child Elements

The second way of extending an existing element is to create a child element. Inherit the UI control you want to extend.

public class ExtendedButton : Button
{
    public void SubmitButtonWithScroll()
    {
         this.ToExists().ToBeClickable().WaitToBe();
         ScrollToVisible(ScrollDirection.Down);
         Click();
    }
}

Next in your tests, use the ExtendedButton instead of the regular Button to have access to these methods.

ExtendedButton button = App.ElementCreateService.CreateByIdContaining<ExtendedButton>("button");

Learn more

Extend Common Services

Ability to add a new method to BELLATRIX common services.

For example, a new way of login into your application. To extend the BELLATRIX common services you can create an extension method for the additional action.

public static class AppServiceExtensions
{
    public static void LoginToApp(this IOSAppService appService, string userName, string password)
    {
        var elementCreateService = new ElementCreateService();
        var userNameField = elementCreateService.CreateById<TextField>("IntegerA");
        var passwordField = elementCreateService.CreateById<Password>("IntegerB");
        var loginButton = elementCreateService.CreateById<Button>("ComputeSumButton");

        userNameField.SetText(userName);
        passwordField.SetPassword(password);
        loginButton.Click();
    }
}

To use the new method, add a using statement to the extension methods’ namespace.

App.AppService.LoginToApp("bellatrix", "topSecret");

Learn more

Add New Find Locators

The ability to avoid code duplication and complex locate expressions by adding a custom find locators.

Here is a sample implementation of the locator for finding all elements starting with name. First, we need to create the By locator.

public class ByNameStartingWith : By<IOSDriver<IOSElement>, IOSElement>
{
    private readonly string _locatorValue;

    public ByNameStartingWith(string name)
        : base(name) => _locatorValue = $"//*[starts-with(@name, '{Value}')]";

    public override IOSElement FindElement(IOSDriver<IOSElement> searchContext) => searchContext.FindElementByXPath(_locatorValue);

    public override IEnumerable<IOSElement> FindAllElements(IOSDriver<IOSElement> searchContext) => searchContext.FindElementsByXPath(_locatorValue);

    public override AppiumWebElement FindElement(IOSElement element) => element.FindElementByXPath(_locatorValue);

    public override IEnumerable<AppiumWebElement> FindAllElements(IOSElement element) => element.FindElementsByXPath(_locatorValue);

    public override string ToString() => $"Name starting with = {Value}";
}

We override all available methods and use UIAutomator regular expression for finding an element with ID starting with. To ease the usage of the locator, we need to create an extension method of ElementCreateService.

public static TElement CreateByNameStartingWith<TElement>(this ElementCreateService repo, string id)
    where TElement : Element<IOSDriver<IOSElement>, IOSElement>
{
    return repo.Create<TElement, ByNameStartingWith, IOSDriver<IOSElement>, IOSElement>(new ByNameStartingWith(id));
}

This is everything after that you can use your new locator as it was originally part of BELLATRIX.

Learn more

Add New Element Wait Methods

Ability to add your custom element wait logic.

Imagine that you want to wait for an element to have a specific content. First, create a new class and inherit the BaseUntil class.

public class UntilHaveSpecificContent<TDriver, TDriverElement> : BaseUntil<TDriver, TDriverElement>
    where TDriver : IOSDriver<TDriverElement>
    where TDriverElement : AppiumWebElement
{
    private readonly string _elementContent;

    public UntilHaveSpecificContent(string elementContent, int? timeoutInterval = null, int? sleepInterval = null)
        : base(timeoutInterval, sleepInterval)
    {
        _elementContent = elementContent;
        TimeoutInterval = timeoutInterval ?? ConfigurationService.Instance.GetMobileSettings().ElementToHaveContentTimeout;
    }

    public override void WaitUntil<TBy>(TBy by) 
        => WaitUntil(ElementHasSpecificContent(WrappedWebDriver, by), TimeoutInterval, SleepInterval);

    private Func<TDriver, bool> ElementHasSpecificContent<TBy>(TDriver searchContext, TBy by)
        where TBy : By<TDriver, TDriverElement>
    {
        return driver =>
        {
            try
            {
                var element = by.FindElement(searchContext);
                return element.Text == _elementContent;
            }
            catch (NoSuchElementException)
            {
                return false;
            }
            catch (InvalidOperationException)
            {
                return false;
            }
        };
    }
}

The important part is located in the ElementHasSpecificContent function. There we find the element and check the current value in the Text attribute. The internal WaitUntil will wait until the value changes in the specified time. The next and final step is to create an extension method for all UI elements.

public static TElementType ToHaveSpecificContent<TElementType>(
	this TElementType element, 
	string content, 
	int? timeoutInterval = null, 
	int? sleepInterval = null)
where TElementType : Element
{
    var until = new UntilHaveSpecificContent<IOSDriver<IOSElement>, IOSElement>(content, timeoutInterval, sleepInterval);
    element.EnsureState(until);
    return element;
}

After UntilHaveSpecificContent is created, it is important to be passed on to the element’s EnsureState method. From now on, the method is available as it was originally part of the framework.

Learn more

Try Now Bellatrix

Build up to 100 tests using full product capabilities.

Download