DEV Community

loading...

An end to the abuse of Accessibility IDs

nextlevelbeard profile image Ricardo Barbosa ・2 min read

As of React Native 0.64, we can and should change the standard way of specifying the attributes used for test automation purposes with Appium.

Since the dawn of time, the test automation community has been using and abusing the Accessibility ID for the purposes of tagging and finding the elements and components of React Native apps though Appium.

Why is this though? We've long had the ability to specify a testID in React Native components for quite a while now, the problem was that Appium had no way of retrieving this attribute on Android apps. Therefore we resorted to using the Accessibility ID for this purpose, something that IS retrievable.

A long standing problem with this method? It was sacrificing proper accessibility for your app by forcing you to maintain static accessibility labels. This can finally come to an end because the testID attribute will now be exposed on Android builds as the also very retrievable resource-id!

So what changes?

Declaring testing attributes

React Native <= 0.63

<Button
  accessibilityLabel="Login"
>
  t('login') // Translation for Login
</Button>
Enter fullscreen mode Exit fullscreen mode
  • We needed to set a static Accessibility ID to be able to find the element in our (Android) end-to-end tests
  • Android's TalkBack or iOS's Voice Over would read out a static text for all languages that your app supports, "Login" in this case

React Native >= 0.64

<Button
  accessibilityLabel={t('login')} // Can be dynamic, translated
  testID="landing-login"
>
  t('login') // Translation for Login
</Button>
Enter fullscreen mode Exit fullscreen mode
  • We no longer need to set a static Accessibility ID to be able to find the element in our (Android) end-to-end tests, we use the testID attribute
  • Android's TalkBack or iOS's Voice Over will read out the proper text out loud regardless of the language the user has set

Finding the element

This needs to change per platform under test now, which complicates things a tiny bit. On Android, we find the resource-id, on iOS on the other hand, we should search for the label attribute. Thankfully, we can simplify this.

Example (webdriverIO)

// Declare somewhere a cross platform way of finding testID:
const getTestID = (identifier, many = false) => {
  const fn = many ? $$ : $;
  const { capabilities: caps, isAndroid } = browser;
  const app = caps['appium:appPackage'] || caps.appPackage;

  return fn(isAndroid
    ? `android=resourceId("${app}:id/${identifier}")`
    : `-ios predicate string:label == '${identifier}'`);
};

// Using it 
const getTestID('landing-login');
Enter fullscreen mode Exit fullscreen mode

We can also define some global short-hands in a hook:

/**
* Gets executed before test execution begins.
*/
before: function (capabilities, specs, browser) {
  // ...
  global.$rn = (selector) => getTestID(selector, false);
  global.$$rn = (selector) => getTestID(selector, true);
  // ...
},
Enter fullscreen mode Exit fullscreen mode

And then in our tests use it like so:

const loginBtn = $rn('landing-login')   // First element
const loginBtns = $$rn('landing-login')  // All the matching elements
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

pic
Editor guide