Reduce cost and improve quality through approachable, frictionless automation
7 min read1 hour ago
–
End-to-end (E2E) testing is critical for delivering high-quality mobile applications, but it’s arguably the most expensive and time-consuming part of the testing strategy. When shipping both Android and iOS versions, this effort often doubles.
In this article, we’ll explore Maestro, a cross-platform UI automation framework that allows you to write tests once and run them on both platforms. This approach helps ensure quality while significantly reducing cost and maintenance overhead.
Maestro is particularly valuable for teams using code-sharing solutions like Kotlin Multiplatform to share business logic while maintaining platform-specific UIs with SwiftU…
Reduce cost and improve quality through approachable, frictionless automation
7 min read1 hour ago
–
End-to-end (E2E) testing is critical for delivering high-quality mobile applications, but it’s arguably the most expensive and time-consuming part of the testing strategy. When shipping both Android and iOS versions, this effort often doubles.
In this article, we’ll explore Maestro, a cross-platform UI automation framework that allows you to write tests once and run them on both platforms. This approach helps ensure quality while significantly reducing cost and maintenance overhead.
Maestro is particularly valuable for teams using code-sharing solutions like Kotlin Multiplatform to share business logic while maintaining platform-specific UIs with SwiftUI and Compose, as we demonstrated in one of our previous articles. Beyond the modern native frameworks, it also supports cross-platform solutions with Flutter and React Native as well as legacy Android Views and iOS UIKit. Maestro applies the same efficiency principle to testing and allows you to extend your cross-platform strategy to E2E testing, consolidating tests alongside your shared production code.
Testing
Different types of automated tests serve different purposes. A common way to visualize this is the test pyramid, which illustrates the ideal distribution of test types in your test suite.
Press enter or click to view image in full size
Unit tests form the base of the pyramid. They’re inexpensive to create, quick to run, and focus on validating individual components in isolation. They should make up the majority of your tests: fast, dependable, and providing immediate feedback during development.
As you move up the pyramid, tests become more expensive in both implementation effort and execution time, so the number of tests typically decreases.
Integration tests verify that multiple components work together correctly. They’re still relatively cheap compared to E2E tests, though they require more setup than unit tests. These are typically “grey box” tests where test code is bundled with the app, giving you advantages like access to the app lifecycle and the ability to wait for idle states. However, you cannot test the actual release build this way.
UI tests (Espresso for Android, XCUITest for iOS) sit near the top of the pyramid. They’re significantly more complicated to implement and much slower to execute than lower-level tests. This is where traditional mobile testing becomes particularly costly. When developing native Android and iOS apps in a traditional way, without code sharing, the pyramid effectively doubles. In practice, you’re maintaining two separate pyramids, likely in isolated repositories, built on entirely different technologies.
Looking back at the KMP concept, we introduced in our previous post: Kotlin Multiplatform changes this equation favorably. When you share business logic in KMP while keeping UI platform-specific (SwiftUI, Compose), you can also share unit and integration tests across platforms. This makes these tests even cheaper: write once, run on both Android and iOS.
Press enter or click to view image in full size
The expense only returns when you reach UI tests, where you’d traditionally need separate Espresso and XCUITest implementations. This is exactly where Maestro provides value.
End-to-end testing
End-to-end (E2E) testing is “black box” testing that runs outside the app and simulates actual user behavior. Unlike integration or UI tests, E2E tests operate outside the app sandbox. They can interact with the whole system as a user would. This means they can handle permission dialogs, switch between apps, and access the full device environment, rather than being boxed into a single app’s isolated context.
E2E testing evaluates the complete functional and data flow of an application, encompassing all interconnected subsystems from start to finish. For mobile apps, this means testing the actual production app against real backends and services: no mocks, no development shortcuts. All components are integrated and tested together as they would be in production. This typically represents the final validation step in the release cycle before the app reaches distribution channels or real users.
E2E tests are often expensive because they’re performed manually by humans. This happens when teams lack proper tooling or avoid automation due to the significant upfront effort required. Because of that, automated E2E tests are frequently deprioritized in favor of shipping new features, pushing the testing burden onto manual processes.
This manual testing mostly occurs in one of two ways: controlled QA processes with dedicated testers or during alpha testing phases where developers and stakeholders manually verify functionality. Both approaches have significant downsides: It is unreliable due to a human factor. You’re relying on documentation and trusting that testers execute the exact same steps every time. In practice, steps get skipped, edge cases are forgotten, and consistency deteriorates over time. Especially as the app grows more complex and test scenarios multiply.
The better approach is to standardize and automate using UI automation tooling. This is where frameworks like Maestro come in, allowing you to codify your E2E tests and execute them consistently, reliably, and repeatedly.
What is Maestro?
Maestro is a cross-platform UI automation framework designed specifically for mobile applications. Unlike traditional platform-specific testing tools, Maestro allows you to write tests once in a platform-agnostic way and run them on both Android and iOS.
Maestro is not only platform-agnostic but also technology-agnostic. It works with Compose and SwiftUI, but equally well with Android Views, iOS UIKit, Flutter, React Native, and more. Regardless of your tech stack, you use the same testing framework and syntax. Check the Meastro documentation for the complete list of supported technologies.
Similar to how unit and integration tests can be shared in KMP, Maestro tests are written once and executed across platforms. This makes them significantly cheaper to maintain than writing separate Espresso tests for Android and XCUITest tests for iOS.
Info: Maestro is free to run locally, open source and released under Apache 2.0 licence. They also offer running the tests on their paid cloud plan “Maestro Cloud”. This article only focusses on the local test execution.
The beauty of Maestro is its built-in tolerance for the inherent flakiness of mobile testing. Mobile apps and devices are inherently unstable. Animations have varying durations, network requests take unpredictable amounts of time, and UI rendering can be inconsistent across devices. Maestro embraces this instability rather than fighting it. It has built-in tolerance for animation timings and inconsistent network loading times, automatically retrying interactions and waiting for elements to become available. This significantly reduces test flakiness without requiring you to manually add arbitrary wait times or complex synchronization logic. The result is more reliable tests that accurately reflect real-world app behavior, without the maintenance burden of constantly tweaking timeouts and waits.
# flow.yamlappId: your.app.id---- launchApp clearState: true- assertVisible: text: "Welcome back, .*"- tapOn: "Name"- inputText: "Hello World"- tapOn: "Done"- killApp
Tests are defined in human-readable, declarative YAML (https://yaml.org) flows. This makes them accessible to non-developers and easy to understand at a glance. Because tests are interpreted rather than compiled, you can iterate quickly. Make a change; run the test immediately. No compilation step, no waiting. Installation is equally straightforward: just one executable. There’s no sophisticated setup required, no dependency chains to manage, and no complex configuration. Download the CLI, and you’re ready to start writing tests.
For complete examples and screencaptures demonstrating Maestro’s capabilities, see their fantastic documentation, or try their interactive playground directly in your browser.
Press enter or click to view image in full size
From here, you can go further and explore more advanced features, e.g., run more complex code (such as fetching test user credentials from your backend) with JavaScript integration, structure tests using a Page Object Model, or record the screen during testing, and so on…
To make test flow creation and maintenance even more accessible, Maestro includes Maestro Studio, which allows interactive flow creation by visually selecting UI elements and actions. Launch it by running Maestro Studio.
Info: There is a new version of Maestro Studio in Beta currently, called Maestro Studio Desktop. Which is a fully self-contained IDE to create and maintain your test flows. No need to even have the CLI installed, making it even more accessible for users with no developer background.
While Maestro can achieve much of what traditional platform-native test solutions like XCUITest or Espresso offer, some limitations remain, e.g.:
- Real device testing is limited to Android. iOS can currently only use simulators, preventing testing of an exact release build (only simulator builds can be tested, not the actual artifact pushed to the App Store or TestFlight). This means the well-known limitations of iOS simulators apply and restrict the testing capabilities. For example, you cannot use real Bluetooth and cannot easily modify date/time or time zone settings.
- While branching and conditional logic are supported, logic-heavy tests become harder to manage in YAML.
- As of this writing, the community seems to be relatively small.
Conclusion
With an easy-to-use cross-platform E2E testing solution like Maestro, we can “cheat” the test pyramid and make proper E2E tests cheaper than traditional UI tests per platform.
Press enter or click to view image in full size
This gets even more extreme when using KMP to share business logic. We can reduce the number of expensive UI tests per platform and replace them with cheaper and more extensive E2E tests, which run platform-agnostic.
This shifts the question from “can we afford comprehensive E2E tests?” to “can we afford not to?” (by Jonas Rottmann)