DEV Community

VINIT RAJ
VINIT RAJ

Posted on

Maestro + CircleCI for React Native: The Practical Setup

Alright, if you’re building a React Native app without Expo EAS, or if you prefer the flexibility of your own custom setup rather than relying on managed services like Maestro Cloud for test orchestration, setting up proper end-to-end (E2E) testing on CircleCI can be challenging. While many apps are moving towards the full Expo ecosystem. Given that it’s the officially recommended framework from the React Native team, there are still plenty of projects using custom build systems and standalone frameworks, where having fine-grained control over your CI/CD pipeline is crucial.

I’ve often found myself building custom setups for E2E testing because I like keeping things under my control – without relying too much on managed services. This approach keeps things flexible, scalable, and cost-effective, but it also means you need to get the details right. If you’re in the same boat, this guide is for you.

The Gist of It

If you’re ready to jump right into the code, check out the Gist on GitHub – a complete, ready-to-run config.yml for running Maestro tests on CircleCI. It includes:

  • Reusable test command (run-maestro-tests)
  • iOS and Android job configurations
  • Best practices for stable test runs on virtualized CI environments

Fork it, tweak it, and drop a star if it saves you some headaches.


Breaking Down the config.yml

Here’s a quick breakdown of the key sections:

1. Orbs

I have used the CircleCI Android orb to simplify emulator setup and build:

orbs:
  android: circleci/[email protected]
Enter fullscreen mode Exit fullscreen mode

2. Execution Environment

Using the macOS execution environment for iOS, which you can read more about here:

macos:
  xcode: 16.0.0
Enter fullscreen mode Exit fullscreen mode

3. The run-maestro-tests Command

I kept this command reusable so you don’t have to rewrite it for iOS and Android:

commands:
  run-maestro-tests:
    description: 'Run Maestro Tests'
    parameters:
      platform:
        type: enum
        enum: [android, ios]
        description: 'Platform to run the tests for'
      maestro_file:
        type: string
        description: 'Path to the Maestro test file'
    steps:
      - run:
          name: Run Maestro Tests for << parameters.platform >>
          command: |
              maestro test << parameters.maestro_file >>
              if [ $? -eq 0 ]; then
                  echo "✅ Tests Passed"
              else
                  echo "❌ Tests Failed"
              fi
Enter fullscreen mode Exit fullscreen mode

4. Android Job

Choosing the right resource_class is critical. You don’t want your builds timing out or failing because you ran out of memory. Check out the CircleCI resource classes for a good overview.

run_maestro_android:
  executor:
    name: android/android_machine
    resource_class: xlarge # 8GB / 16GB for better stability
  steps:
    - checkout
    - install_node_modules
    - install_gems
    - install_maestro_cli
    - android/create_avd:
        avd_name: test
        system_image: 'system-images;android-34;google_apis;x86_64'
        install: true
        additional_args: '--device pixel_4'
    - run:
        name: Build Android App
        command: |
            cd android
            ./gradlew assembleRelease --no-daemon --console=plain
    - store_artifacts:
        path: ./android/app/build/outputs/apk/release/app-release.apk
    - run-maestro-tests:
        platform: android
        maestro_file: 'e2e/main.yml'
Enter fullscreen mode Exit fullscreen mode

Commands like install_gems, install_maestro_cli, and install_node_modules are implicit in the full config.yml – mostly boilerplate, so I’ve kept the focus here on the more interesting bits.

5. iOS Job

Setting up the iOS build can be a bit more finicky, but here’s a working approach:

run_maestro_ios:
  macos:
    xcode: 16.0.0
  resource_class: m2pro.medium
  steps:
    - node/install:
        node-version: '20.10.0'
    - checkout
    - install_node_modules
    - install_gems
    - run:
        name: Install Pods
        command: |
            cd ios
            bundle install
            bundle exec pod install
    - install_maestro_cli
    - run:
        name: Boot iPhone Simulator
        command: |
            xcrun simctl boot "iPhone 15"
            xcrun simctl list devices booted
    - run:
        name: Build iOS App for Simulator
        command: |
            cd ios
            xcodebuild clean build \
                -workspace MyApp.xcworkspace \
                -scheme MyApp-Dev \
                -configuration Release \
                -sdk iphonesimulator \
                -derivedDataPath ./build
    - store_artifacts:
        path: ./ios/build/Build/Products/Release-iphonesimulator/MyApp-Dev.app
    - run-maestro-tests:
        platform: ios
        maestro_file: 'e2e/main.yml'
Enter fullscreen mode Exit fullscreen mode

Debugging Like a Pro (with Caution)

E2E tests can be tricky – they work fine locally, but suddenly fail on CircleCI. This is often due to environment mismatches, network issues, or emulator quirks. Here’s a reliable way to debug these problems in real-time, but be mindful of the security implications.

  • SSH into the CI Machine

First, gain access to your CircleCI machine using SSH. This lets you poke around the live environment:

ssh -p <ci_machine_ip_address>
Enter fullscreen mode Exit fullscreen mode
  • Start Maestro Studio

Run Maestro Studio to get a live view of what the emulator is doing:

maestro studio
Enter fullscreen mode Exit fullscreen mode
  • Expose the Maestro Studio Network with ngrok

Use ngrok to create a secure tunnel to your CI machine so you can access Maestro Studio remotely:

ngrok authtoken <YOUR_NGROK_AUTH_TOKEN>
ngrok http 9999
Enter fullscreen mode Exit fullscreen mode

⚠️ Security Disclaimer:
Be careful with this approach. Exposing your CI machine to the public internet, even through a secure tunnel like ngrok, can introduce security risks. Make sure you’re not leaking sensitive environment variables or allowing unauthorized access. Use ngrok’s IP whitelisting or basic authentication for an extra layer of security if needed.

  • Inspect and Debug in Real-Time

Open the ngrok URL and you should be able to interact with the emulator live, inspect flows, and catch any UI issues or environment mismatches that might be causing your tests to fail.


Final Thoughts

With a bit of upfront setup, you can have reliable, automated E2E tests that keep your React Native app rock solid – without shelling out for expensive testing services. If this guide saved you some late-night headaches, drop a star on the Gist or share your setup in the comments.

Note: The current config.yml sets up a cron job to run these E2E tests every midnight. This was just my project choice to keep a consistent test cycle, but feel free to adjust or remove this schedule based on your team’s workflow and testing frequency.

Happy testing! 🚀

Top comments (0)