iPhone Dev Note #24: Using Frank
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
-
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.
-
Assuming you already have a working “frankified” project running, check by doing http://localhost:37265/
- Now do:
frank skeleton |
- This will give you a directory structure below.
- 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.
So all the builds goes to this folder:
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" |
-
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.
-
I expect to
- Build and Run the Frankified app.
- Run cucumber and see my tests fail or pass.
- 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.
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
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'