Tuesday, February 14, 2017

Using an AutomationId with a Cell in Xamarin.Forms

In Xamarin Forms version 2.2 the AutomationId was introduced for iOS and Android. The AutomationIds are tied to renderers and elements that are derived from VisualElements. CellRenderers in their various flavors implement the IRegisterable interface but do not derive from VisualElementRenderer nor does the TextCell class derive from VisualElement. Instead it inherits directly from the Element class.

The upshot of the deal is that while there is a AutomationId property on the TextCell from parent Element class (or any of the other classes that derive from Cell) setting it does nothing. If you do have a design that uses the TextCell and want to use the AutomationId what can you do? I came up with this solution.

First I created a class that derives from the TextCell. It does nothing, it's only purpose is to create a type that we can create our own renderers for. An alternate approach to this one would be to use the default TextView with an Effect.


 public class AutomationTextCell : TextCell  
 {  
   public AutomationTextCell()  
   {  
   }  
  }  

Now we can create some custom renderers from it. Before that, I do want to talk a little about the AutomationId property. If we examine Xamarin Forms Element source code we see the property defined as:

 public string AutomationId  
 {  
   get { return _automationId; }  
   set  
   {  
     if (_automationId != null)  
       throw new InvalidOperationException("AutomationId may only be set one time");  
     _automationId = value;  
   }  
 }  

There is no BindableProperty backing store for this. This means that it will be unresponsive to changes in the custom renderer. The AutomationId that exists when the native control is created is the one that will be used, changes after that will not be honored.


Android

We want to set the ContentDescription property. So what we will do is create a custom renderer in the Android project for our AutomationTextCell and override the GetCellCore method to set the view's ContentDescription property with the current AutomationId value.


 [assembly: ExportRenderer(typeof(AutomationTextCell), typeof(AutomationTextCellRenderer))]  
 namespace YourNamespace  
 {  
   public class AutomationTextCellRenderer : TextCellRenderer  
   {  
     protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)  
     {  
       var view = base.GetCellCore(item, convertView, parent, context);  
       view.ContentDescription = Cell.AutomationId;  
       return view;  
     }  
   }  
 }  


iOS

We can create another custom renderer in an iOS project to set the AccessibilityIdentifier property of the native view to the AutomationId.


 [assembly: ExportRenderer(typeof(AutomationTextCell), typeof(AutomationTextCellRenderer))]  
 namespace YourNamespace  
 {  
   public class AutomationTextCellRenderer : TextCellRenderer  
   {  
     public AutomationTextCellRenderer()  
     {  
     }  
     public override UIKit.UITableViewCell GetCell(Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)  
     {  
       var tableViewCell = base.GetCell(item, reusableCell, tv);  
       tableViewCell.AccessibilityIdentifier = item.AutomationId;  
       return tableViewCell;  
     }  
   }  
 }  

Now the Marked method in UI test will be able to find the AutomationId's for cells. It is important to note that the TextCell for both platforms contain multiple underlying views so once you get a reference to it in your UI tests you may be doing some further querying to get the exact value you are looking for.

In your UI Test you can use app.Repl(); with the tree command to verify the automation Ids are being set and see the native structure of the cells.

Good luck!