End-to-end testing React Native with Maestro (and why we moved off Detox)
· 3 min read · Amrith Vengalath
- React Native
- Testing
- Maestro
- Detox
Mobile end-to-end testing has historically been where good intentions go to die. You set up a suite, it works for a month, then it gets flaky, people start ignoring red builds, and eventually nobody trusts it. I've been through that cycle with Detox more than once - it's powerful, but the setup and the flakiness wore the team down. Switching our E2E flows to Maestro is one of the few testing decisions I'd straightforwardly recommend.
Here's the comparison and the reality of using it.
What was painful before
Detox is capable and fast when it's working. Its "gray-box" approach syncs with the app internals, which makes it genuinely quick. But the costs were real for us:
- Native setup and configuration was involved, and keeping it working across RN upgrades was recurring effort.
- Flakiness around timing and synchronization, which ate trust.
- Tests written in JavaScript with a fair amount of ceremony.
None of this means Detox is bad - for some teams the speed is worth it. But the maintenance tax was high enough that the suite kept rotting.
What Maestro does differently
Maestro's bet is simplicity. Flows are written in plain YAML, it waits for elements automatically (which kills a huge category of flaky timing failures), and setup is dramatically lighter. A flow reads like a description of what a human would do:
appId: com.mycompany.myapp
---
- launchApp
- tapOn: "Sign in"
- tapOn:
id: "email-input"
- inputText: "[email protected]"
- tapOn:
id: "password-input"
- inputText: "hunter2"
- tapOn: "Continue"
- assertVisible: "Welcome back"Anyone on the team can read that, including people who don't write the app code. That readability is underrated - a test suite the whole team can understand is a test suite the whole team maintains.
The automatic waiting is the part that mattered most. So much E2E flakiness is "the element wasn't there yet." Maestro retries and waits built-in, so you're not sprinkling manual waits everywhere and still losing the timing lottery.
Using testID properly
The one habit that makes any of this work is putting stable testIDs on the elements you target. Tapping by visible text is fine for buttons with fixed labels, but for inputs and anything dynamic, target IDs:
<TextInput testID="email-input" />Text-based selectors break the moment copy changes or you localize. IDs are stable contracts between the app and the tests. We made adding testID to interactive elements a normal part of building a screen, not an afterthought when writing the test.
The honest limitations
Maestro isn't free of tradeoffs, and I'd be lying to say otherwise:
- It's generally slower per test than Detox's gray-box approach, because it interacts more like a black-box user. For a small-to-medium suite this is fine; for a massive suite the wall-clock time adds up.
- It's less suited to deep, complex programmatic scenarios where you need fine-grained control or to reach into app internals. For the 90% that's "log in, do the critical flow, assert the outcome," it's great. For exotic cases you may want more.
- Like all E2E, it's still slower and more involved than unit tests. E2E should be the small top of your testing pyramid - the critical user journeys - not where you test every edge case. Maestro doesn't change that math.
Where it fits
We use Maestro for the handful of flows that absolutely must work: onboarding, login, the core purchase/checkout path, the one or two journeys where a regression is a real incident. Those run in CI and we trust them now, which is the whole point - a flaky suite you ignore is worse than no suite. Unit and component tests cover the breadth; Maestro covers the critical depth.
If your team has an E2E suite that's quietly rotting from flakiness and maintenance fatigue, it's worth a serious look. The YAML readability and the built-in waiting addressed the two things that actually kill mobile E2E suites: nobody understanding them, and nobody trusting them.