I'm writing tests in Xamarin UI Test for a tab-based Xamarin Forms app. I'd like to set the automation Ids on each tab item so that my UI Test can click a specific tab, without referring to the tab's Text label, which is localized.
I imagine you need to use a custom renderer and set ContentDescription (Android) and AccessibilityIdentifier (iOS), and I've been trying to do that, with mixed results. What is the correct way to do this? If I'm on the right track with custom renderer, which renderer method(s) should I override in IOS/Android to achieve this?
UPDATE:
iOS: Answer was provided by @apineda. See his solution below the question.
Android: Seems to required a custom renderer. It's a little yucky but it works. We have to recursively search the view hierarchy for the tab bar items and set "ContentDescription" for each. Since we are using a bottom-navigation bar, we search backwards for better performance. For topside navigation bar, you'll need to search for "TabLayout" instead of "BottomNavigationItemView".
[assembly: ExportRenderer(typeof(MainPage), typeof(CustomTabbedPageRenderer))]
namespace Company.Project.Droid.CustomRenderers
{
public class CustomTabbedPageRenderer : TabbedRenderer
{
private bool tabsSet = false;
public CustomTabbedPageRenderer(Context context)
: base(context)
{
}
protected override void DispatchDraw(Canvas canvas)
{
if (!tabsSet)
{
SetTabsContentDescription(this);
}
base.DispatchDraw(canvas);
}
private void SetTabsContentDescription(Android.Views.ViewGroup viewGroup)
{
if (tabsSet)
{
return;
}
// loop through the view hierarchy backwards. this will work faster since the tab bar
// is at the bottom of the page
for (int i = viewGroup.ChildCount -1; i >= 0; i--)
{
var menuItem = viewGroup.GetChildAt(i) as BottomNavigationItemView;
if (menuItem != null)
{
menuItem.ContentDescription = "TabBarItem" + i.ToString();
// mark the tabs as set, so we don't do this loop again
tabsSet = true;
}
else
{
var viewGroupChild = viewGroup.GetChildAt(i) as Android.Views.ViewGroup;
if (viewGroupChild != null && viewGroupChild.ChildCount > 0)
{
SetTabsContentDescription(viewGroupChild);
}
}
}
}
}
}
You don't need CustomRenderer
for this. You just need to set the AutomationId
to the children Pages
of the TabPage and this is assigned to the bar Item.
Let's say you have this TabPage
as below
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyGreatNamespace"
x:Class="MyGreatNamespace.MyTabPage">
<TabbedPage.Children>
<local:MainPage AutomationId="MainTab" Title="Main Page" />
<local:PageOne AutomationId="TabOne" Title="Page One" />
<local:PageTwo AutomationId="TabTwo" Title="Page Two" />
</TabbedPage.Children>
</TabbedPage>
With this configuration you will be able to do:
app.Tap("TabTwo");
And you won't need to use the Text
property.
Hope this helps.-
UPDATE:
Just confirmed the above does not work with Android (noticed your original question is for Android) but only with iOS. For some reason the behavior is different.
You can still use the Localized version of the Text to "Tap it" as explained below.
A trick you can use when dealing with Localized Text is that you set the right Culture then use the same resource set in the XAML as part of the Test.
i.e
app.Tap(AppResources.MyMainTabText);