Getting Started with Calabash: Cross-Platform Mobile UI Testing

Getting Started with Calabash: Cross-Platform Mobile UI Testing

Calabash is a cross-platform mobile UI testing framework built on top of Cucumber. It lets you write tests in Gherkin (Given/When/Then) and execute them against real iOS and Android apps. The framework works by embedding a small HTTP server into your app at test time, giving your test code a way to query and interact with the running app over a local socket.

Before going further: Calabash is no longer actively maintained. Microsoft retired it in 2021 when they shut down Xamarin Test Cloud (later App Center Test). The GitHub repositories are archived. If you are starting a new project, evaluate Appium or Maestro first. This guide is useful if you are maintaining an existing Calabash suite or evaluating the framework historically.

How Calabash Works

Calabash has two gems:

  • calabash-ios — communicates with a Calabash server compiled into your iOS app
  • calabash-android — instruments your APK with an HTTP test server using Robotium under the hood

Both expose a Ruby DSL that maps Gherkin steps to UI actions: tap, swipe, type text, assert element visibility. Cucumber drives the scenario runner and produces HTML or JSON reports.

The key architectural constraint: tests run on the same machine as the device or simulator. There is no remote WebDriver protocol. The test process communicates directly with the in-app server over http://localhost:37265 (iOS) or http://localhost:34777 (Android).

Prerequisites

  • Ruby 2.5 or higher (2.7 recommended for Calabash 0.x)
  • Xcode and iOS simulators (for iOS)
  • Android SDK with adb in your PATH (for Android)
  • Bundler

Check your Ruby version:

ruby --version
# ruby 2.7.6p219 (2022-04-12 revision c9c2245dc0) [x86_64-darwin21]

Installation

Create a Gemfile in your test project root:

source 'https://rubygems.org'

gem 'calabash-android', '~> 0.9'
gem 'calabash-ios',     '~> 0.21'
gem 'cucumber',         '~> 7.1'
gem 'rspec',            '~> 3.11'

Install:

bundle install

If you only need one platform, omit the other gem. For a cross-platform project, both can coexist in the same Gemfile.

Project Structure

Calabash follows the standard Cucumber directory layout:

my-app-tests/
├── Gemfile
├── Gemfile.lock
├── features/
│   ├── support/
│   │   ├── app_installation_hooks.rb
│   │   ├── app_life_cycle_hooks.rb
│   │   └── env.rb
│   ├── step_definitions/
│   │   ├── login_steps.rb
│   │   └── navigation_steps.rb
│   └── login.feature
└── config/
    └── cucumber.yml

The features/support/env.rb file bootstraps the Calabash runtime. For iOS:

# features/support/env.rb
require 'calabash-cucumber/management/sim_manager'
require 'calabash-cucumber/cucumber'

For Android:

# features/support/env.rb
require 'calabash-android/calabash_steps'

Writing Your First Feature File

Create features/login.feature:

Feature: User login

  Background:
    Given I am on the login screen

  Scenario: Successful login with valid credentials
    When I enter "alice@example.com" into the email field
    And I enter "s3cr3t!" into the password field
    And I tap the login button
    Then I should see the home screen

  Scenario: Login fails with wrong password
    When I enter "alice@example.com" into the email field
    And I enter "wrongpass" into the password field
    And I tap the login button
    Then I should see an error message "Invalid credentials"

Step Definitions

Create features/step_definitions/login_steps.rb:

Given('I am on the login screen') do
  wait_for_element_exists("* marked:'Login'", timeout: 10)
end

When('I enter {string} into the email field') do |email|
  clear_text_in("* marked:'email_input'")
  tap("* marked:'email_input'")
  keyboard_enter_text(email)
end

When('I enter {string} into the password field') do |password|
  clear_text_in("* marked:'password_input'")
  tap("* marked:'password_input'")
  keyboard_enter_text(password)
end

When('I tap the login button') do
  tap("* marked:'login_button'")
end

Then('I should see the home screen') do
  wait_for_element_exists("* marked:'home_screen'", timeout: 15)
end

Then('I should see an error message {string}') do |message|
  wait_for_element_exists("* marked:'error_label'", timeout: 5)
  element_text = query("* marked:'error_label'", :text).first
  expect(element_text).to eq(message)
end

The marked: selector matches accessibility labels on iOS and content descriptions on Android, which makes many steps portable between platforms.

calabash-ios Setup

Before running iOS tests, you need a Calabash-instrumented build of your app. The simplest approach is to add the calabash target to your Xcode project.

Generate a -cal scheme:

bundle exec calabash-ios setup

This adds a new Xcode target named YourApp-cal with the Calabash server framework linked. Build it:

xcodebuild -workspace YourApp.xcworkspace \
           -scheme "YourApp-cal" \
           -sdk iphonesimulator \
           -derivedDataPath build/

Point Calabash at the built .app:

export APP_BUNDLE_PATH="build/Build/Products/Debug-iphonesimulator/YourApp-cal.app"

Running Tests

iOS:

bundle exec cucumber features/login.feature

Android (after instrumenting the APK — covered in the Android post):

bundle exec calabash-android run path/to/YourApp.apk

Run a specific scenario by tag:

bundle exec cucumber features/ --tags @smoke

Run with a specific profile defined in config/cucumber.yml:

# config/cucumber.yml
default:   --format pretty --tags 'not @wip'
smoke:     --format pretty --tags @smoke
ci:        --format json --out reports/cucumber.json --tags 'not @wip'
bundle exec cucumber -p ci

Hooks for App Lifecycle

features/support/app_life_cycle_hooks.rb is where you restart the app between scenarios to get a clean state:

Before do |scenario|
  start_test_server_in_background
end

After do |scenario|
  if scenario.failed?
    screenshot_embed
  end
  calabash_exit
end

screenshot_embed captures the current screen and embeds it in the Cucumber HTML report — useful for debugging failures in CI.

Console for Exploration

Calabash ships with an interactive console that lets you query a live app without writing test files:

bundle exec calabash-ios console
# or
bundle exec calabash-android console

Inside the console:

# Find all visible text elements
query("*", :text)

# Find a button by accessibility label
query("* marked:'Submit'")

# Tap it
tap("* marked:'Submit'")

# Check what's on screen
query("UILabel")

The console is the fastest way to discover the correct selectors before writing step definitions.

Next Steps

  • For Android-specific setup (APK instrumentation, emulator configuration), see the Calabash Android Testing post.
  • For iOS-specific setup (UIQuery, simulator vs device), see the Calabash iOS Testing post.
  • For BDD patterns with Gherkin and shared step libraries, see the Calabash with Gherkin post.
  • For a framework comparison, see Calabash vs Appium.

Read more

Start now free