●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more
Before You Burn Out Hand-Testing Every Release: Automate Your Rork App's Critical Flows with Maestro
With Rork, a prompt that fixes one screen can quietly break another. Here is how I automate the revenue-critical path from launch to paywall using Maestro and run it in CI, including the spots where I got stuck as an indie developer.
I was verifying the same steps by finger, every single submission
I run several wallpaper and utility apps as an indie developer, and once I started shipping small Rork-built apps to the App Store and Google Play, I picked up an awkward habit. Every time I cut a submission build, I would launch the simulator and trace the same path by hand: finish onboarding, make the first save, confirm the paywall appears, tap restore.
The first few times it took a couple of minutes. But as screens multiplied and I kept making prompt edits like "change this button's color" or "add error handling to the save," the path I needed to verify kept branching. One time I thought I had only fixed some paywall copy, yet the restore button's onPress had come unwired, and I did not notice until right before submission. The wider the surface I checked by hand, the more I missed.
There is a structural reason regressions like this happen in an AI code-generation workflow. A prompt points at "the thing to fix" but guarantees nothing about the blast radius. That is exactly why the few flows tied directly to revenue are worth having a machine trace every time. Here is how I automate the critical flows of a Rork-generated app with Maestro, along with the places I actually got stuck.
Why I chose Maestro — the real difference from Detox
Detox has long been the default for React Native E2E, but in an indie workflow that iterates on generated code quickly, my conclusion is that Maestro gets you moving far faster. Here is the contrast, framed by the feel of getting started.
Aspect
Maestro
Detox
Test authoring
YAML (declarative, a few lines)
JavaScript (jest integration, more code)
Setup
Install the CLI, drop one flow in
Build config, config files, runner integration
Waiting
Waits for elements automatically
Often need explicit synchronization APIs
Flake resistance
Retries and auto-wait by default
Strong once tuned, shaky early on
Fit with Expo
Touches a built app from outside
Assumes integration into a native build
This is not a claim that Detox is worse. For a large app where you want to verify fine-grained native state, Detox's expressiveness pays off. But for the goal of "protect my generated app's revenue path starting tomorrow," Maestro — writable in a few lines of YAML and resistant to breakage — fit better.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Compare Maestro and Detox for Rork (Expo) generated apps, and write your first flow in YAML
✦Learn how to add testID, how to prompt Rork to emit testIDs, and how to handle permission dialogs and the purchase sandbox
✦Set up a GitHub Actions .yml that runs your critical flows on every PR and catches regressions in under five minutes
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Maestro finds elements by on-screen text or testID. Visible text alone works, but copy changes with translation and A/B tests, so for stability it is safer to put a testID on your key interaction targets.
Rork-generated components often lack testID, so either add them by hand to the exported code or have Rork emit them via prompt. I prefer to instruct it on the prompt side.
(Example Rork prompt)Add testID="save-button", testID="restore-button", andtestID="paywall-buy-button" to the save button, restore button,and the paywall purchase button respectively.Do not change the displayed text.
If you add them by hand, it is just one attribute on the target Pressable or TouchableOpacity.
// Example: add testID to the restore button<Pressable testID="restore-button" onPress={handleRestore} accessibilityRole="button"> <Text>Restore Purchases</Text></Pressable>
Pairing it with accessibilityRole also helps screen-reader support and makes the element easier for Maestro to locate later. Once you see it as more than a test-only attribute, the effort is easier to justify.
Prep 2: Install Maestro and drop in your first flow
Maestro installs as a host-side CLI and drives a built app from the outside. It does not touch your app's source.
# Install the Maestro CLI (macOS / Linux)curl -fsSL "https://get.maestro.mobile.dev" | bash# Check the versionmaestro --version# Create a flow folder at the project rootmkdir -p .maestro
The ideal target is an Expo dev client or an internal-distribution build close to production. Expo Go cannot run native modules (purchases, notifications), so use a dev client for flow testing.
Make your first flow a smoke test that just confirms you can reach the first value screen. If this fails, nothing else you measure matters, so put it down first as the foundation.
# .maestro/smoke.yamlappId: net.rorklab.sample # replace with your bundle identifier---- launchApp: clearState: true # wipe prior state, start fresh every run- assertVisible: "Get Started" # onboarding's first screen copy- tapOn: "Get Started"- assertVisible: id: "home-screen" # the home testID
# Run locallymaestro test .maestro/smoke.yaml
The clearState: true is the key. If a prior run's login state or onboarding-complete flag lingers, you slip past the first-launch path and fail to verify what real users see.
One critical flow: onboarding through paywall
Once smoke passes, turn the revenue-critical path into a single flow. For me, the most painful thing to break was: clear onboarding, make the first save, hit the paywall when the free tier is exceeded, and confirm restore works.
# .maestro/paywall-flow.yamlappId: net.rorklab.sample---- launchApp: clearState: true- tapOn: "Get Started"# Allow the notification permission dialog; you can touch OS system dialogs too- runFlow: when: visible: "Allow notifications" commands: - tapOn: "Allow"# Perform the first save- tapOn: id: "add-button"- inputText: "Test note"- tapOn: id: "save-button"- assertVisible: id: "home-screen"# Repeat to exhaust the free tier (as a subflow)- repeat: times: 3 commands: - tapOn: id: "add-button" - tapOn: id: "save-button"# Confirm the paywall appears- assertVisible: id: "paywall-buy-button"# Confirm the restore button exists and is tappable- tapOn: id: "restore-button"- assertVisible: "Restore"
What pays off here is the conditional runFlow. System dialogs for notifications or ATT (tracking permission) appear or not depending on OS and test environment. Writing when.visible so you "only tap allow when it shows" stops the dialog's presence from failing the test. This was my first big stumble: back when I tapped the dialog unconditionally, later runs went red because the element was not found.
A pitfall in the purchase sandbox
It is tempting to drive all the way through the paywall's "Buy" button, but the real StoreKit / Google Play sandbox purchase dialog can ask for a password or Face ID, and pushing through it reliably in E2E is not realistic.
In my setup, I let Maestro guard up to "just before" the purchase button and leave actual payment to RevenueCat's sandbox or manual testing. What the E2E guarantees is that the paywall appears under the right conditions and the button is in a tappable state. If that breaks, free users cannot even reach the purchase screen, which makes it the boundary most worth protecting.
Keep test account values like email out of the flow and pass them via environment variables.
# using env- inputText: ${MAESTRO_TEST_EMAIL}
# inject at run timeMAESTRO_TEST_EMAIL="tester@example.com" maestro test .maestro/paywall-flow.yaml
Run it on every PR in CI
Passing locally means nothing if you forget to run it. Wire it into GitHub Actions so it runs automatically on every pull request — spinning up an emulator in the workflow and running the flows against a built APK.
Passing the .maestro/ directory runs every flow in order. Put smoke.yaml first so a failure stops things early and keeps CI wait time short. For the APK artifact, fetch it from Expo / EAS in a separate job and hand it over as an artifact, or use an internal-distribution build.
As a rough time budget, my small apps finish smoke plus the critical flow in three to five minutes. At that length it does not become a PR-blocking annoyance; instead it becomes a reassurance that "if it goes red, I broke something."
How much to test, and where to stop
E2E grows brittle the more you write, turning into maintenance debt. The line I draw in my own indie work is to cover only flows where breakage immediately damages revenue or trust: that it launches, that you can clear onboarding, that the paywall appears under the right conditions, and that restore is tappable. Narrowed to those four, the flows stay to a handful and maintenance stays light.
Conversely, I do not recommend chasing minor visual details or rarely-traveled settings branches in E2E. Those are better served by component-level tests or manual checks. Precisely because an app you rebuild quickly with AI is fluid, keeping the machine-guarded surface deliberately small and sharp is, I believe, the trick to running it for the long haul.
As a first step, drop just the one flow from launch to the first value screen into .maestro/smoke.yaml and run maestro test once. A single green line is enough to feel the relief of being freed from tracing the path by finger before every submission. Thank you for reading.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.