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 appcalabash-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
adbin 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 installIf 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.ymlThe 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)
endThe 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 setupThis 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.featureAndroid (after instrumenting the APK — covered in the Android post):
bundle exec calabash-android run path/to/YourApp.apkRun a specific scenario by tag:
bundle exec cucumber features/ --tags @smokeRun 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 ciHooks 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
endscreenshot_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 consoleInside 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.