Testing a Xamarin app

Xamarin provides a convenient way to write, build, and deploy “native” apps for multiple platforms from a single codebase. This comes in handy for data layer changes because it’s possible to make a small change in one file and have it propagate to all of your platforms. As engineers on one of WillowTree’s Xamarin projects, this development process has presented unique challenges and often provoked thought for new ways testing.

One unique component of Xamarin is Xamarin.UITest. This framework integrates seamlessly with the Xamarin development cycle and just as in development, allows you to write a single test that will work on all of your project’s supported platforms. Automated interaction with your app is facilitated through the UITest API, IApp, which contains methods for clicking, scrolling, and much more. However, if you are tasked with creating extensive automated UI tests for your application, it’s possible that you will reach some limitations of IApp. In our case, we needed a way to scroll left or right to an element that was within another element. There is a method available through the Android interface via AndroidApp.ScrollRightTo() but it isn’t supported on iOS. We wanted a method that would work for both platforms and search in either direction.

Creating a custom extension

At WillowTree, we follow the page-object model commonly used for structuring UI test suites. We created a class called TestExtension that will be accessible to extended pages and tests throughout our suite. This class is based on generic C# extension methods which allow you to "add methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.”

image1

In the TestExtension.cs class, we wrote a helper method called SwipeLeftOrRight() that takes the IApp interface and two string parameters; marked and within.

marked - Element you want to swipe to. within - Element that that your marked element is present in (but not necessarily visible). retryCount - Defaults to 3 but can be altered for different use case.

Imagine a side-scrolling carousel on a mobile device that has a collection of thumbnail images. A known thumbnail would be our marked element while the carousel would be our within element. The TryToFind() method handles the looping portion of our SwipeLeftOrRight() method.

public static class TestExtension
{
    /// <summary>
    /// Attempts to find the element by scrolling right to left initially. If no item is found then
    /// it will attempt to swipe left to right to find the item in the other direction.
    /// </summary>
    /// <param name="marked">Marked selector to select what element to bring on screen.</param>
    public static void SwipeLeftOrRight(this IApp app, string marked, string within, int retryCount = 3)
    {
        // Ensure the within view is shown
        app.WaitForElement(within);

        bool found = false;

        // Swipe right to left
        found = app.TryToFind(() => app.SwipeRightToLeft(within),
            () => app.Query(marked).Any(), retryCount);

        if (found)
        {
            return;
        }

        //Doubles back past initial object
        retryCount = retryCount * 2;

        // Swipe Left to right
        found = app.TryToFind(() => app.SwipeLeftToRight(within),
            () => app.Query(marked).Any(), retryCount);

        if (found)
        {
            return;
        }

        throw new AssertionException($"Couldn't find element marked, {marked}, in either direction");
    }

    /// <summary>
    /// Runs the specified command x amount of times or until the assertion is successful
    /// </summary>
    /// <param name="retryCount">The number of times to execute the action.</param>
    /// <param name="testAction">The action to perform on each loop
    /// <param name="assertion">The assertion to check after each loop</param>
    private static bool TryToFind(this IApp app, Action testAction, Func<bool> assertion, int retryCount)
    {
        // See if the assertion is satisfied before looping
        if (assertion.Invoke())
        {
            return true;
        }

        for (int i = 0; i < retryCount; i++)
        {
            testAction.Invoke();

            if (assertion.Invoke())
            {
                return true;
            }
        }

        // Nothing found
        return false;
    }
}

Using the extension

With this TestExtension class, you can now easily call app.SwipeLeftOrRight() on pages that extend from the base initialization of IApp. Example:

public void SelectSpecificImageFromCarousel()
{
    App.SwipeLeftOrRight(Image_ID, ImageCarousel_ID);
    App.Tap(Image_ID);
}

This example method will swipe 3 times in either direction within the ImageCarousel_ID element until it finds Image_ID. If it isn’t found, an AssertionException will be thrown letting us know that it could not find the marked element within ImageCarousel_ID.

Conclusion

This is an example of one possible implementation of an extension. Look for opportunities in your automated test suite to create your own extensions. Abstracting helpful test methods saves time and effort while keeping your page objects and tests clean, concise, and readable.