Moving React Native Detox from GitHub Actions to Local Machine

Edu Depetris

- Jun 25, 2024
  • Ci
  • React Native
  • Detox Testing
  • Github Actions
After a challenging journey to configure Detox with GitHub Actions to run our tests, we decided to stop and move the process locally.

We couldn’t get a stable test runner on GitHub Actions with Detox, at least for iOS. We faced various issues, such as tests taking too long (consuming many GitHub Action minutes) or the suite failing in unexpected ways that required significant engineering effort to debug and fix. This was a lot of unpleasant work that we didn’t want to continue doing.

On the other hand, working locally is quite easy for us. We don’t have major issues or unexpected errors, and it’s much faster than our current CI machine.

With that context in mind and inspired by this DHH post, we decided to stop running our tests on Github Actions and do it locally instead.

However, we still want to let everyone know that our pull request has passed the tests. So we decided to sign off our commits and push the result to GitHub.

Our new workflow looks like this:

 â€˘ Run tests locally.
 â€˘ If tests pass, invoke a GitHub workflow to mark the commit as successful.
 â€˘ Profit!

So in this article, let’s walk through how we sign off our work and mark our commits on GitHub.

We are inspired by the idea of having a bash script that runs the tests or anything and then invokes an action on GitHub.

To make it easier, we only run Detox tests, but you might want to run linters as well.

Let’s start with the script signoff.sh:

#!/bin/bash

# Ensure the script exits on any command failure
set -e

# Run Detox tests
yarn detox test --configuration ios.sim.debug --cleanup --headless

# Check if Detox tests passed
if [ $? -eq 0 ]; then
  TEST_STATUS="passed"
else
  TEST_STATUS="failed"
fi

Then let’s add execution permission and test our script:

chmod +x signoff.sh
./signoff.sh
# ...
Ran all test suites.
✨  Done in 35.38s.

The first part is done. Now let’s focus on a workflow on GitHub that we’re going to trigger from our script if the tests pass.

For this, we’re going to use the “workflow_dispatch” event, which will allow us to run a workflow via GitHub CLI, REST API, or from the GitHub UI. This is pretty handy because it allows us to do it from the GitHub interface in case we can’t run the script locally.

In this workflow, we want to mark a commit, via its SHA, as successful for the continuous integration (only the tests in our example).

Let’s create our workflow in .github/workflows/mark_tests_passed.yml:

name: Mark Detox Tests as Passed

on:
  workflow_dispatch:
    inputs:
      commit_sha:
        description: 'The commit SHA to mark tests as passed'
        required: true

jobs:
  mark-tests-passed:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set Status to Success
        run: |
          COMMIT_SHA="${{ github.event.inputs.commit_sha }}"
          gh api -X POST repos/${{ github.repository }}/statuses/$COMMIT_SHA \
            -f state=success \
            -f description='Detox tests passed' \
            -f context='detox-tests'
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}

Creating the commit status:
GitHub allows us to create a commit status using its REST API, and we can simplify it by using the GitHub CLI api. Luckily for us, GitHub CLI is pre-installed on workflows. The only parameter that we need is the COMMIT_SHA to identify it.

Now let’s focus on the last part of our script, which is to invoke this workflow with the COMMIT_SHA.

Using GitHub CLI it’s quite simple:

gh workflow run mark_tests_passed.yml -f commit_sha="$COMMIT_SHA"

Let’s put everything together in our signoff.sh script:

#!/bin/bash

# Ensure the script exits on any command failure
set -e

# Run Detox tests
yarn detox test --configuration ios.sim.debug --cleanup --headless

# Check if Detox tests passed
if [ $? -eq 0 ]; then
  TEST_STATUS="passed"
else
  TEST_STATUS="failed"
fi

# Get the current commit SHA
COMMIT_SHA=$(git rev-parse HEAD)

# Get the current branch name
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

# Try to get the pull request number (if any)
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number')

# Trigger the GitHub Action workflow
if [ "$TEST_STATUS" == "passed" ]; then
  if [ -n "$PR_NUMBER" ]; then
    gh workflow run mark_tests_passed.yml --ref "$BRANCH_NAME" -f commit_sha="$COMMIT_SHA"
  else
    echo "No open pull request found for branch $BRANCH_NAME. Triggering workflow without PR context."
    gh workflow run mark_tests_passed.yml -f commit_sha="$COMMIT_SHA"
  fi
else
  echo "Detox tests failed for commit $COMMIT_SHA. No workflow will be triggered."
fi

I added an extra step: using Git and GitHub CLI, I identify the branch name and check if there is an open pull request for this branch:

# Get the current branch name
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

# Try to get the pull request number (if any)
PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number')

If there’s an open PR, I want to run the workflow within that context so it’s associated with the PR. Otherwise, it runs without a context, which is useful for branches that don’t have an open pull request, like main or develop.

That is it. Let’s see the final results:

./signoff.sh

 PASS  e2e/login.test.js (3.987 s)
  Login
    âś“ should have English as default language (987 ms)
    âś“ should login with the default user (3000 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.98 s
Ran all test suites.

✨  Done in 4.57s.
âś“ Created workflow_dispatch event for mark_tests_passed.yml at dynamic-base-e2e

On the Pull Request:

✔️ status on commit

Additionally, we can run the workflow from the GitHub UI using the commit SHA:

run workflow from Github UI


Happy Coding.