I came from the RoR web development and – as we all know – Rails community loves tests. Personally, I am a fan of acceptance testing and using mostly capybara for it. So, when I shifted to the iOS development, it was essential for me to find some decent acceptance testing tools like Apple’s UI Automation, Calabash and Appium. The last one appeared to be a test automation framework of iOS, Android and hybrid apps that supports Ruby among other languages. I’ve chosen it to be the first one to explore and started writing tests for my current real project. Let’s see now whether Appium stack is a good choice for the our purposes.
To start with, we need 3 things to run integration tests with Appium:
- Appium server
- application package
- test script
Appium Installation
There are two ways to install Appium: download packaged app or install it via npm. I tried to install it via npm, but it didn’t work, so I opted for a bundled app and successfully ran couple examples. Then, however, I decided to fix my Appium npm install and managed to run it from console by completely reinstalling node and manually cleaning /usr/local/share/npm.
$: sudo npm install -g appium $: appium
Make app build
Below is a simple script to create a build and place it inside the desired folder to easily access it from test whenever required.
xcodebuild -sdk iphonesimulator6.0 \ -workspace /Users/mishyn/workspace/rw/project-ios/Project.xcworkspace \ -scheme Project \ ONLY_ACTIVE_ARCH=NO \ TARGETED_DEVICE_FAMILY=1 \ DEPLOYMENT_LOCATION=YES \ DSTROOT=`pwd`/app
Don’t forget to make scheme Project shared, otherwise you might get the following error:
ld: library not found for -lPods clang: error: linker command failed with exit pre 1 (use -v to see invocation)
Write spec
Here is the template, so we now need to fill in the flows.
def absolute_app_path file = File.join(File.dirname(__FILE__), 'app/Applications/Project.app') raise "App doesn't exist #{file}" unless File.exist? file file end capabilities = { 'browserName' => 'iOS', 'platform' => 'Mac', 'version' => '6.0', 'app' => absolute_app_path } server_url = "http://127.0.0.1:4723/wd/hub" describe "Computation" do before :all do @driver = Selenium::WebDriver.for(:remote, :desired_capabilities => capabilities, :url => server_url) end after :all do @driver.quit end it "should open search" do @driver.find_elements(:tag_name, '*') end end
Finding elements
Selenium operates with views similar to HTML DOM elements’ and it looks like this:
@driver.find_elements(:tag_name, 'collectionCell')
Guess how to find tableCells? Right:
@driver.find_elements(:tag_name, 'tableCell')
To get all elements use
@driver.find_elements(:tag_name, '*').map(&:tag_name) # =>
In the end, you will need only visible buttons
@driver.find_elements(:tag_name, 'button').select(&:displayed?).map(&:text)
Looks good so far.
To click on a button named “Done”, you can use:
@driver.find_element(:name, 'Done').click
In the real world, however, there are too many buttons with custom images; so, in this case we have to use accessibilityIdentifier.
Set accessibilityIdentifier to ‘Done’ in the code, and in the tests use
find_element(:name, 'Done')
More docs:
Examples:
Tips and tricks
Debugger
I use byebug for Ruby 2.0 as it allows me easily play with UI elements.
Networking
Finally, almost every real application communicates to remote server and this is also something that we want to test. So, as we test our app from outside, we mock webserver outside as well. To run server with mocked api, I use sinatra rb.
Final flow.
#run appium #run sinatra mock #build app #run spec
I’ve personally found Appium very useful, especially taking into account that it doesn’t require any code inside the application.
On the other hand, it requires a lot of prep work to run tests. So, In order to make start with Appium easier, I’ve created a simple gem :
appium-rspec-bootstrap
Hope you’ll find it handy too. Any comments and suggestions are very welcome.