How to test React Native and Flutter applications on Android
React Native and Flutter allow a large proportion of code to be shared between platforms, but they do not eliminate differences in the operating system, permissions or hardware. Automated testing therefore needs to understand the framework used while also verifying the behaviour experienced by a user on an Android device. One universal suite is not the objective at any cost.
Where typical problems arise
Shared code can create the impression that testing one platform is enough. System dialogs, notifications, the keyboard, returning from the background and permission management are, however, governed by Android. A fault in this integration may not appear in a test of the component itself.
Another problem is the availability of elements to the automation tool. React Native renders components into the native interface, but a test needs stable identifiers and correctly configured accessibility properties. Flutter works with its own widget tree and provides information to system tools through the semantics tree. If important elements do not have meaningful semantic data, an external end-to-end test may not be able to find them reliably.
Locators based on text, fixed waits and dependence on production data then cause instability. The application’s framework will not solve these problems by itself.
It is equally important to choose situations that matter to the specific product. The overview of mobile scenarios suitable for automation helps distinguish critical journeys from edge combinations.
How to divide tests into three layers
Unit and component tests verify business logic and individual parts of the interface quickly and without an entire device. React Native uses tools from the JavaScript ecosystem for this; Flutter includes unit and widget tests directly in its SDK.
Framework integration tests check how larger parts of the application work together. Flutter provides the integration_test package, which can run on both an emulator and a physical device. Its official documentation, however, points out that it cannot interact directly with native platform interfaces, such as permission dialogs or notifications. Such scenarios need a complementary approach.
External end-to-end tests launch the built application and control it from the user’s perspective. Depending on the requirements, Appium or Maestro is suitable for Android. We use them to cover a smaller number of critical journeys and interactions with the system, not every detail of the logic.
How to prepare the application for stable end-to-end tests
First, we inspect the element tree on representative screens. We add stable test identifiers, names and semantic roles to important controls. These help both automation and accessibility, although an automated test is not in itself an accessibility audit.
We then create a pilot that includes:
- the main scenario in the application;
- one native dialog or permission;
- the application returning from the background;
- verification of the outcome through the interface or API;
- a run on both an emulator and a selected physical device.
For Flutter, we first verify whether the system semantics tree provides enough information for a standard Android driver. If not, we consider a framework-specific approach; community Appium drivers also need to be assessed according to their maintenance and compatibility. For Maestro, we verify the specific screens and commands in the same pilot.
The article Emulator or physical device explains the difference between a fast virtual run and physical hardware.
What you gain
Each layer verifies what it is suited to. Most of the logic gains fast tests, while a smaller end-to-end suite confirms that important journeys and system interactions work in the built Android application.
Stable identifiers and semantics reduce the tests’ sensitivity to visual changes. The pilot also reveals early whether the chosen tool understands the specific React Native or Flutter implementation and what modifications the application needs.
Next step
Choose one critical screen and inspect its test identifiers, semantic roles and behaviour with a system dialog. Then create a pilot across all three layers and keep every detail in the fastest layer that can verify it reliably.