Skip to content

Getting Started

Pouria Almassi edited this page Jun 1, 2016 · 5 revisions

This guide gives an introduction to writing and running tests with Calabash iOS.

This guide expects that you are using a managed Ruby (e.g. rbenv, rvm) or the Calabash Sandbox.

Calabash iOS requires Ruby >= 2.0 and Xcode >= 6.0.1. The latest release of both are preferred.

Exploring the app (or the sample project).

From Xcode, start your app with the -cal scheme in iOS simulator.

Run it

In Xcode, select the -cal scheme (or similar) and do CMD-R to run. Look at the log output and verify that you see:

DEBUG CalabashServer:242 | Creating the server: <LPHTTPServer: 0xdeadbeef5480>
DEBUG CalabashServer:243 | Calabash iOS server version: CALABASH VERSION: 0.19.1
DEBUG CalabashServer:246 | App Base SDK: iphonesimulator9.3
DEBUG CalabashServer:278 | IPHONE_SIMULATOR_ROOT: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
DEBUG CalabashServer:264 | Calabash iOS server is listening on: 10.0.0.13 port 37265

If those messages are there, you're good to go.

Setup testing.

Test are run using Cucumber and written in a special and particularly readable language: Gherkin.

In your project directory you can run the following command to setup a features directory for your tests (if you didn't already do this in the installation phase).

$ calabash-ios gen

----------Question----------
I'm about to create a subdirectory called features.
features will contain all your calabash tests.
Please hit return to confirm that's what you want.
---------------------------

<RETURN>

----------Info----------
features subdirectory created. 
Try executing 

cucumber

---------------------------

This generated a features skeleton dir for your tests:

$ tree -L 1 features/
features/
├── my_first.feature
├── step_definitions
└── support

This step also copies the scripts for interactive exploration and test into your working directory (see below).

##Running your tests

Now try running the feature my_first.feature with cucumber.

$ cucumber 
Feature: Running a test
  As an iOS developer
  I want to have a sample feature file
  So I can begin testing quickly

  Scenario: Example steps 

... #different output here
Given I am on the Welcome Screen                 # features/step_definitions/my_first_steps.rb:1
Then I swipe left                                # calabash-cucumber-0.9.127.pre1/features/step_definitions/calabash_steps.rb:190
And I wait until I don't see "Please swipe left" # calabash-cucumber-0.9.127.pre1/features/step_definitions/calabash_steps.rb:128
And take picture                                 # calabash-cucumber-0.9.127.pre1/features/step_definitions/calabash_steps.rb:185

1 scenario (1 passed)
4 steps (4 passed)
0m15.097s

If everything went well it should have started the iOS simulator with your app, run the sample test and taken a screenshot (saved as screenshot_0.png). Obviously we'd want to do more than just screenshotting. We'll get to that next!

If something went wrong it is most likely because your XCode is building your app in a non-expected location. The default location is ~/Library/Developer/Xcode/DerivedData/??-xyz/Build/Products/Debug-iphonesimulator/??-cal.app. Where ?? is your app name and xyz is a random set of characters. If you are building to another location you need to specify the environment variable APP: set it the path where Xcode placed your built .app folder. (Alternatively open the file features/support/01_launch.rb and comment-in APP.)

To improve your workflow, we recommend that you use command-line build scripts to stage your .app and .ipa to a known location. For examples see:

Launching and Customizing launch

You can target a specific simulator using the DEVICE_TARGET environment variable.

$ DEVICE_TARGET="iPhone 6 (8.3 Simulator)" cucumber
$ DEVICE_TARGET="8AA57D7F-0805-42A1-ACAE-2E819CC5EAD2" cucumber

To see a list of available simulators, use the instruments command:

$ xcrun instruments -s devices

For a list of all the environment variables that Calabash iOS responds to see the official documentation.

Using the predefined steps

First a note of warning: Using pre-defined steps to build real test suites is bad practice. The predefined steps are not phrased in the business language of your app, and will be uninteresting to anyone but the tester responsible for automation. This is not what Cucumber is for. We recommend The Cucumber Book to understand the intention and strengths of Cucumber.

In addition, the predefined steps are limited in functionality and are slower than what you can otherwise build.

With all that said, we do provide a number of simple predefined steps that will give you a feeling of how calabash runs. Once you have that feeling and you understand calabash better, you should write your tests using the Ruby API

Take a look at the file features/my_first.feature:

$ cat features/my_first.feature 
Feature: Running a test
  As an iOS developer
  I want to have a sample feature file
  So I can begin testing quickly

Scenario: Example steps
  Given I am on the Welcome Screen
  Then I swipe left
  And I wait until I don't see "Please swipe left"
  And take picture

This feature has a single scenario (Example steps) which should correspond to a use case. It probably doesn't make any sense for your application or the sample app. But you can try one of the predefined steps to touch a view on screen. In the sample app, let's try and touch the tab bar button Second using the predefined step I touch "...":

Feature: Running a test
  As an iOS developer
  I want to have a sample feature file
  So I can begin testing quickly

Scenario: Example steps
  Given I am on the Welcome Screen
  And I touch "Second"

and run

$ cucumber
...
Given I am on the Welcome Screen # features/step_definitions/my_first_steps.rb:1
And I touch "Second"             # calabash-cucumber-0.9.74/features/step_definitions/calabash_steps.rb:15

1 scenario (1 passed)
2 steps (2 passed)
0m4.585s

You can continue in this fashion using the predefined steps defined in Calabash. They are documented here:

Predefined steps

Soon you'll want to define your own steps. This is documented in detail here:

Writing custom steps

But before you do that. Please read:

Explore interactively!

A nice way to use calabash is to explore it interactively. To start-up a console, you can run the command: calabash-ios console and then start a simulator with start_test_server_in_background for iOS7 to get touch events to work.

Try starting your app using the -cal scheme from Xcode and then run calabash-ios console. This will give you a Calabash console (it is just a Ruby irb with calabash loaded).

From this console you can explore your application interactively.

You can query, touch, scroll, etc from the irb session. You'll see how below. By the way. This information below also serves as an introduction for the APIs that are available when writing custom steps as described in 03-Writing-custom-steps.

Query

Notice that the sample app has two buttons. Now try this from the console:

irb > query("button")

You should see something like this:

=> [{"class"=>"UIRoundedRectButton", "frame"=>{"y"=>287, "width"=>72, "x"=>100, "height"=>37}, "UIType"=>"UIControl", "description"=>"<UIRoundedRectButton: 0x7d463d0; frame = (100 287; 72 37); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7d46ae0>>"}, {"class"=>"UIRoundedRectButton", "frame"=>{"y"=>215, "width"=>73, "x"=>109, "height"=>37}, "UIType"=>"UIControl", "description"=>"<UIRoundedRectButton: 0x7d3a760; frame = (109 215; 73 37); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7d3a8a0>>"}]

This is actually an array with two objects that are descriptions of the two buttons. For example, the class of the first button is "UIRoundedRectButton". We can dive into this information using Ruby programming:

irb > result = query("button")
...
irb > btn1 = result[0]
...
irb > class1 = btn1["class"]
=> "UIRoundedRectButton"

The query function takes a string query as an argument. The query argument is similar to a css selector, for example we can do:

irb > query("button index:0 label")
=> [{"class"=>"UIButtonLabel", "frame"=>{"y"=>9, "width"=>38, "x"=>17, "height"=>19}, "UIType"=>"UIView", "description"=>"<UIButtonLabel: 0x7d41120; frame = (17 9; 38 19); text = 'other'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7d40510>>"}]

This means "find the first button, and then inside of that find all labels".

Query may also take parameters that are mapped to Objective-C selectors on the found object.

irb > query("button index:0 label", :text)
=> ["other"]

Query is very powerful, but the full syntax and power of query is beyond the scope of the Getting started guide. It is documented in the wiki at Query syntax.

Touch

Anything that can be found using query can also be touched. Try this while you watch the iOS Simulator:

irb >  touch("button index:0")

Notice that the button is touched (turns blue), although this button doesn't do anything.

You can also touch the tab bars:

irb >  touch("tabBarButton index:1")

The filter: index:1 means that it is the second tab-bar button that should be touched.

Setting accessibilityIdentifiers and accessibilityLabels

In general UI views are found using accessibility ids or labels. Go to the "First" tab in simulator, and then in your console session try this:

# query for the switch on the "First" tab
irb > query("view marked:'switch'")
[
[0] {
            "class" => "UISwitch",
              "frame" => {
                 "y" => 148,
             "width" => 79,
                 "x" => 106,
            "height" => 27
        },
             "UIType" => "UIControl",
        "description" => "<UISwitch: 0x7d3ffb0; frame = (106 148; 79 27); <snip> >>"
    }
]

You can set the accessibility attributes of UIView from the Interface Builder or programmatically.

In some cases the UIKit framework will set the accessibilityLabel for you if you don't explicitly set the value.

For example, UITabBarButton will have an accessibility label that is the same as title of the tab bar button.

irb > query("tabBarButton marked:'Second'")
[
    [0] {
              "class" => "UITabBarButton",
                 "id" => nil,
               "rect" => {
            "center_x" => 120,
                   "y" => 520,
               "width" => 76,
                   "x" => 82,
            "center_y" => 544,
              "height" => 48
        },
              "frame" => {
                 "y" => 1,
             "width" => 76,
                 "x" => 82,
            "height" => 48
        },
              "label" => "Second",
        "description" => "<UITabBarButton: 0x857e010; frame = (82 1; 76 48); <snip> >"
    }
]

Whenever possible, you should use the accessibilityIdentifier of UIView and reserve the accessibilityLabel for providing localized Accessibility traits.

- (void) viewDidLoad {
  [super viewDidLoad];

  UISwitch *switch = self.wantsCoffeeSwitch;

  # ex. queries
  # query "view marked:'wants coffee'" 
  # query "switch {accessibilityIdentifier LIKE 'wants coffee'}"
  # query "switch marked:'wants coffee'"
  switch.accessibilityIdentifier = @"wants coffee";

  # accessibilityLabels should be localized and follow the conventions
  # described by Apple's Accessibility documentation
  if (switch.isOn == YES) {
    switch.accessibilityLabel =  NSLocalizedString(@"Wants coffee");
  } else {
    switch.accessibilityLabel =  NSLocalizedString(@"Does not want coffee");
  }
}

Next steps

We suggest that you can jump into writing Custom steps.

Clone this wiki locally