If you are into testing iphone apps. You might want to check out Frank (https://github.com/moredip/Frank/) and watch the videos, especially this one Testing Your Mobile Apps with Selenium 2 and Frank March 30th, 2011 by Pete Hodgsen.

Main Tutorial

  1. Follow this tutorial for steps 1-7 https://github.com/moredip/Frank/blob/master/tutorial/Tutorial.md Note: A bit outdated when you’re in steps 8.

  2. Assuming you already have a working “frankified” project running, check by doing http://localhost:37265/

2-symbiote.png

  1. Now do:
frank skeleton
  1. This will give you a directory structure below.

1-frank-skeleton.png

  1. If you try to run “cucumber”, it will complain that APP_BUNDLE_PATH is not set.

Note: Normally, this will be in a build directory within your project. However, I have my XCode build settings as shown below for the reason sometimes I forget to put the build folder on gitignore or svnignore (Sometimes my global git is a mess). Also, it becomes convenient for me to wipe out the whole directory after doing a “clean all”, makes me feel certain that I wiped it out myself. :) So, this is my personal preference only.

xcode-build-settings.png

So all the builds goes to this folder:
app-bundle-path.png

So, I append the APP_BUNDLE_PATH to my .bash_profile so I don’t have to do this everytime I run cucumber in terminal

  35 export vr=/Volumes/rupert
  36 export vrp=/Volumes/rupert/projects
  37 export vrpr=/Volumes/rupert/projects/rails3
  38 export vrpi=/Volumes/rupert/projects/iphone
  39 
  40 #For XCode Frank Testing
  41 export APP_BUNDLE_PATH=/Volumes/temp/iphone-builds/Debug-iphonesimulator/

Another alternative is to define the APP_BUNDLE_PATH in env.rb

require 'frank-cucumber'
#APP_BUNDLE_PATH = File.dirname(__FILE__) + "/../../build/Debug-iphonesimulator/EmployeeAdmin.app"
APP_BUNDLE_PATH = "/Volumes/temp/iphone-builds/Debug-iphonesimulator/Country-Frankified.app"
  1. While the project-fankified.app is running on the iOS simulator, I then ran “cucumber”. However, the app went to a background state. If you double-click the iOS Simulator Home Button, you will get all the apps running in the background. After selecting the app, cucumber ran the tests, since it is trying to connect/ping to the frank HTTP server, and the device rotated as expected. However, this is now what I expected.

  2. I expect to

  • Build and Run the Frankified app.
  • Run cucumber and see my tests fail or pass.
  1. So I edited “launch_steps.rb” as shown below by commenting lines 6-9. This will prevent “press_home_on_simulator”
   1 Given /^I launch the app$/ do
   2 
   3   # kill the app if it's already running, just in case this helps 
   4   # reduce simulator flakiness when relaunching the app. Use a timeout of 5 seconds to 
   5   # prevent us hanging around for ages waiting for the ping to fail if the app isn't running
   6   #begin
   7   #  Timeout::timeout(5) { press_home_on_simulator if frankly_ping }
   8   #rescue Timeout::Error 
   9   #end
  10 
  11   require 'sim_launcher'
  12 
  13   app_path = ENV['APP_BUNDLE_PATH'] || APP_BUNDLE_PATH
  14   raise "APP_BUNDLE_PATH was not set. \nPlease set a APP_BUNDLE_PATH ruby constant or environment variable to the path of your compiled Frankified iOS app      bundle" if app_path.nil?
  15 
  16   if( ENV['USE_SIM_LAUNCHER_SERVER'] )
  17     simulator = SimLauncher::Client.for_iphone_app( app_path, "4.2" )
  18   else
  19     simulator = SimLauncher::DirectClient.for_iphone_app( app_path, "4.2" )
  20   end
  21 
  22   num_timeouts = 
  23   loop do
  24     begin
  25       simulator.relaunch
  26       wait_for_frank_to_come_up
  27       break # if we make it this far without an exception then we're good to move on
  28 
  29     rescue Timeout::Error
  30       num_timeouts += 1
  31       puts "Encountered #{num_timeouts} timeouts while launching the app."
  32       if num_timeouts > 3
  33         raise "Encountered #{num_timeouts} timeouts in a row while trying to launch the app."
  34       end
  35     end
  36   end
  37 
  38   # TODO: do some kind of waiting check to see that your initial app UI is ready
  39   # e.g. Then "I wait to see the login screen"
  40 
  41 end

Notes and Issues

1. ld: duplicate symbol _OBJC_METACLASS_$_SBJsonParser
Frank comes with “json”, “uispec”, “cocoahttpserver”. Since my project also has json dependency, I just removed the json folder from frank/lib under “Group & Files”. Note: Only delete the references and don’t move to trash.
duplicate-json.png

2. I get “Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIImageView panoramaID]“
My project has MapKit as a dependency. I added this to main.m. See https://github.com/moredip/Frank/blob/d8a76223cad7df8143d8b6d3524c12494a65d069/main.m.sample

#ifdef FRANK
//UNCOMMENT THIS SECTION IF YOU'RE USING MapKit AND YOU ARE SEEING CRASHES IN iOS4
//Work around the issue in iOS 4 where exceptions thrown within a NSInvocation are not catchable. This was causing crashes in UIQuery::describeView when trying to dump the DOM w. Symbiote see http://groups.google.com/group/uispec/browse_thread/thread/1879741ebae978d/a90001a8956290af
@implementation NSObject (MapKitUISpecHack) 
- (id)_mapkit_hasPanoramaID { 
	return nil; 
} 
@end
#endif

3. Query a custom graphic ‘Home’ ToolbarButton
toolbar.png
In IB, you cannot specify an accessibilityLabel for a UIBarButtonItem inside a UIToolbar. So, to query this using

toolbarButton accessibilityLabel:'Home'

We need to specify this using code explicitly in viewDidLoad.

- (void)viewDidLoad {
	...
 
	#ifdef FRANK
	barButtonHome.accessibilityLabel = @"Home";
	barButtonSearch.accessibilityLabel = @"Search";
	barButtonMoreResults.accessibilityLabel = @"More";
	barButtonPageCurl.accessibilityLabel = @"Map Settings";
	#endif
 
	...
}

For the search button, the second one from the left and has a magnifying glass icon, it can be queried directly without specifying any code.

toolbarButton accessibilityLabel:'Search'