Technology

Invoking Methods From Xamarin Test Cloud UITests

By August 15, 2015 No Comments

test-cloud-logoAs a Xamarin Certified Premier Partner and a Xamarin Test Cloud Partner RedBit has been working with customers implementing iOS and Android apps and automating their test processes using Test Cloud. In that time we have come up with some issues but workarounds were always implemented to accomplish what needed to be done and IApp.Invoke is one of those great workarounds. We used this recently on a project where we needed to test the tapping of a map pin on Android but it just wasn’t available.

In this article, we’ll go through how to use IApp.Invoke to invoke some code in both Android and iOS Xamarin based app. If you are interested specifically in the mapping example leave a comment and we’ll do a write up on that, but for this article, it will go through adding items to a list on both Android and iOS and testing it using UITest.

The App Details


The app functionality is very simple and essentially adds an item to a list view on both iOS and Android and looks as follows

testCloudScreenGrab-AndroidtestCloudScreenGrab-ios

 

The only difference between the two apps are on Android, when an item is clicked it shows the item text at the bottom of the screen. On iOS, when an item is clicked, it shows a full screen of the page as follows

testCloudScreenGrab-ios-details

 

All source code is available on GitHub and you can follow along in the next sections from the code.

Setting Up The Methods – Android


Now that we know about the functionality we can get into the methods to invoke within our Android app. There are two methods we want to invoke

  1. Method to invoke adding a new item called InvokeAddNewItem
  2. Method to invoke tapping on an item in the ListView depending on the index passed as a parameter called InvokeTapItem

The method implementations are as follows

[Preserve]
[Java.Interop.Export]
public void InvokeAddNewItem(string value){
    AddNewItem();
}

[Preserve]
[Java.Interop.Export]
public void InvokeTapItem(string index){
    var val = default(Int32);
    if (Int32.TryParse(index, out val))
    {
        this.ListView.PerformItemClick(
            this.ListView.Adapter.GetView(val, null, null),
            val,
            this.ListView.Adapter.GetItemId(val)
        );
    }
    else
        throw new Exception(string.Format(Unable to parse index parameter of {0}, index)); 
}

The key items to note here is not the implementation but the attributes of Android.Runtime.Preserve and Java.Interop.Export.

These are the key attributes you need to add to the methods to be able to invoke them from a Xamarin UITest and essentially makes these invokable from external processes.

Setting Up The Methods – iOS


On iOS, it takes a bit more work to get the methods to run. Essentially, we can only ‘expose’ methods from AppDelegate and from there we have to call the specific functionality to run. In this example Actions are used to run the functionality required from AppDelegate .

Setting Up AppDelegate

In your AppDelegate.cs setup two methods as follows

[Export(InvokeAddNewItem:), ]
public void InvokeAddNewItem(string val){
    MasterViewController.InvokeAddNewItem ();
}

[Export(InvokeTapItem:)]
public void InvokeTapItem(string index){
    MasterViewController.InvokeTapItem (index);
}

Notice how these are calling static Actions within MasterViewController called InvokeAddNewItem and InvokeTapItem. Also notice to expose these methods to UITests we use the Foundation.ExportAttribute passing in the method name with a colon on the end.

Setting Up MasterViewController

MasterViewContoller has two static actions defined as follows

public static Action InvokeAddNewItem{ get; private set; }
public static Action<string> InvokeTapItem { get; private set; }

In the constructor, we setup these actions to actually add an item to the list and tap on an item implemented as follows

InvokeAddNewItem = () => {
    this.AddNewItem (null, null);
};

InvokeTapItem = (index) => {
    var val = default(UInt32);
    if (UInt32.TryParse (index, out val))
    {
        TableView.SelectRow (NSIndexPath.Create(0,val), true, UITableViewScrollPosition.Top);
        this.PerformSegue(showDetail, this);
    }
    else
        throw new Exception (string.Format (Unable to parse index parameter of {0}, index)); 
};

This is essentially the same logic the Android app does except it’s using iOS specific classes.

Invoking from UITest


Now that the methods are ready the UITest can be created to invoke these methods.

NOTE: this is a sample only to show how to accomplish invoking methods. The source code provided on GitHub does have how a real test for this example app should be done.

The UITests provided have been setup to work on both Android and iOS and will leave it up to you to explore that source code.  For one of the tests we would want to test the following user scenario

When a users adds a new item to the list, they should be able to tap on that item and the app should show the details of that item. On iOS it should navigate to a details page and on Android it should show a ‘widget’ at the bottom.

This is easily implementable by the following standard test using no IApp.Invoke as follows

[Test]
public void TapItemTest(){
    _app.WaitForElement(_screenQueries.MainControl);

    // tap the add button
    _app.Tap(_screenQueries.AddButton);

    // what for the item
    _app.WaitForElement(_screenQueries.ListTextItem);

    // select the item in tableview
    _app.Tap(_screenQueries.ListTextItem);

    if (_isIos)
    {
        // for ios we go back
        _app.Back();
    }
    else
    {
        // for android we need to make sure there is a message
        _app.WaitForElement(s => s.Marked(message));
    }

    // assert as we are good
    Assert.Pass();

}

Now imagine a scenario where you don’t have access to the ‘add’ button or you could not tap on a listItem, this is where IApp.Invoke comes in handy. Here is the same test as above, but using the ‘invoke’ methods instead

[Test]
public void TapItemTestWithInvoke()
{
    // wait for nav button so we know splash is gone
    _app.WaitForElement(_screenQueries.MainControl);

    // add an item
    _app.Invoke(InvokeAddNewItem, );

    // what for the item
    _app.WaitForElement(_screenQueries.ListTextItem);

    // tap the first item
    _app.Invoke(InvokeTapItem, 0);

    if (_isIos)
    {
        // for ios we go back
        _app.Back();
    }
    else
    {
        // for android we need to make sure there is a message
        _app.WaitForElement(s => s.Marked(message));
    }

    // assert as we are good
    Assert.Pass();
}

What’s Next?


As I said, it’s a sample to show how to invoke some hidden methods within your iOS and Android app so using in this scenario doesn’t make a lot of sense. But imagine a scenario where you need to simulate some location code, you don’t have access to some elements on the screen which is the case with Android Maps and pins or you want to run some internal ‘test harness’ code, IApp.Invoke will be helpful.

Before wrapping up, some key main things to remember are

  1. Foundation.ExportAttribute for iOS
  2. Android.Runtime.Preserve and Java.Interop.Export for Android
  3. on iOS make sure to add the colon 🙂

Next steps are to download the source code from GitHub and try it out yourself. And if you need help automating tests with Test Cloud, contact us and our team will be happy to help you out!

 

 

Leave a Reply