|
Interacting with the UI
Simon would not be much good if it could not interact with your UI. This is by no means easy. Other BDD frameworks typically make use of Apples UI Automation framework which is based on Javascript. Apart from not being in the same language as your code, the documentation’s not great and the XPath queries can be very large. Simon takes a different approach. Being embedded in your applications executable, it has the ability to more directly interact with your application. Simon does this through a variety of classes and macro wrappers. Again the idea being to make your life writing test code as simple as possible. Like the UI automation framework, Simon takes the approach of viewing your UI as a tree of nodes. It then makes use of another of my libraries called dNodi to access this tree. dNodi also gives Simon the ability to query the tree of UIViews and run “XPath-like” queries against them. But dNodi’s syntax is a lot simpler than the UI Automation frameworks. Mostly though, Simon provides a suite of pre-processor macros to do the hard work for you. Some of these macros are covered in this document. You can find a complete list here Viewing the UI treeA good way to understand your UI is to include the SIPrintCurrentWindowTree() macro in a step implementation. It will print out the current tree structure showing each UIView’s index, class and other properties in tree hierarchy. This is useful when working out how to navigate to a particular UIView.
Will print something like the following in the console:
Tree view of current window
====================================================
[0] UIWindow
[0] UILayoutContainerView
[0] UITransitionView
[0] UIViewControllerWrapperView
[0] UIView
[0] UILabel, @text='Second View'
[1] UITextView
[0] UITextSelectionView
[1] UIImageView
[2] UIWebDocumentView
[1] UITabBar
[0] _UITabBarBackgroundView
[1] UITabBarButton
[0] UITabBarSwappableImageView
[1] UITabBarButtonLabel, @text='First'
[2] UITabBarButton [1]
[0] UITabBarSelectionIndicatorView
[1] UITabBarSwappableImageView
[2] UITabBarButtonLabel, @text='Second'
You can see that Simon attempts to give you the information you need for basic queries against the UI tree. The format is as follows Line format:
Getting a reference to a control on the UIThere are two base macros which are useful for accessing any UIView on your display. One which provides a reference to a single control using a dNodi path to locate it, and the other returning a list of zero or more controls from a path. A lot of SImon’s code for accessing the UI makes use of these two macros. SIFindView(query); Here’s some examples of using them:
This returns a button. but note that a generic query for a button like this on a UI that has more than one button will thrown an exception because Simon does not know which button you want. More on that later.
This will not throw an exception because we have asked for all the buttons. Accessing view properties using KVCWhen Simon encounters a request to select based on the attribute of a UIView, it passes the attribute value to iOS’s KVC API. This means that any property of a UIView which is KVC complient can be used in a query. For example, here’s our query from above augmented to look for a specific button:
This will find a button which has the text “hello” on it. Or throw an exception if there isn’t one or several. SIFindView is picky like that. Funny enough, Finding views using special attributesIn addition to being able to locate views using KVC accessable attributes, Simon also has two special attributes you can test against. They are protocol and isKindofClass. The names are pretty obvious – protocol looks for UIViews where the views class implements the protocol specified. Here’s an example:
IsKindOfClass tests the UIView’s class hierarchy to see if any of the classes match. This seems a it odd considering that the name of each UI node is also the class name, but this allows you to search for nodes that are derived from a specific class when you don’t know the implementation class. Here’s an example:
Doing stuffBasic actionsSimon provides a base set of macros for interacting with your UI. First lets take a look at tapping a control. The macro is UIView *view = SITapControl(query|view) As it’s name suggest this will tap a UIView. IF you pass a NSString containing a query, it will use dNodi to locate the UIView. Alternatively if you already have a reference to the UIView, you can simply pass that. Here’s an example:
it’s not hard to use and it will tap any UIView on the UI, which is to say, any button, image, slider, tableview, etc. The code automatically takes into account gesture recognisers as well. In addition it also returns the UIView that was tapped. So if you pass a dNodi query, you get back the UIView it down. This is used for further use in your test. The other base interaction is a swipe and here again Simon has a macro: UIView *view = SISwipeControl(query|view, direction, distance) This swipe macro is fairly simple at the moment. You can pass a UIView reference or NSString query just like the SITapView macro, to locate the control you want to swipe. direction is one of several pre-defined directions:
And distance is the distance in UI points you want to swipe. I’ve tested this against UITableViews and it’s worked quite well. The swipe will take about 0.5 of a second to execute. Again like the SITapView macro this will return the UIView that was swiped. Additional actionsIn addition to the basic actions above there are actions which are defined to simplify the amount of code you have to write. They represent the most commonly required actions:
Entering textSimon enters text in a field by activating it, and then using the built in keyboard to enter the text. The macro for this is SIEnterText(query|inputField, text) If you pass a query, Simon will locate the field using dNodi and then enter the text into it. If you pass a direct reference to the field Simon will just enter the text. Whether you use a query or pass a reference to the field, the UIView used must conform to the UIInputText protocol or Simon will throw a SIUINotAnInputFieldException. Pauses and waitsFinally there is a set of macros which halt the thread of your test code for a specific amount of time. These are defined because sometimes you want to give your UI a chance to do something before you continue with your test code. A common scenario being that you want to give the UI a chance to execute some animations. The first of these macros is SIPauseFor(seconds) Which simply sleeps the thread your test code is on for the specified number of seconds. As test code is run on background thread, this should not effect your application in any way. Note that this is a NSTimeInterval so you can sleep for partial seconds as well. The second macro is perhaps more useful when animations are in play: SIWaitFor(query, retryEvery, maxRetryAttempts) This macros examines the query to see if it returns a UIView, if it does then it finishes and your test continues executing. If it doesn’t find anything, then it sleeps the thread for the retryEvery NSTimeInterval and then tries again. maxRetryAttempts is the maximum number of times the macro will attempt to get a UIView from the query before it throws an exception. As an example, lets take a look at this step:
So here we are tapping a button which loads a second view. As this view is loaded using an animation we are waiting for the view to become visible by looking for a label on it. The code will retry 5 times a second for 2 seconds before aborting. The next macro is for when you are not sure of the duration of an animation: SIWaitForAnimationsToFinish(query, checkEvery) This macro checks the found UIView and the UIView hierarchy above it by searching up through the superviews. If it finds any animations processing on any UIView in the chain, it pauses for the specified amount (checkEvery) and then retries. Once there are no animations in play, it returns. Here is an example:
Which checks every 5th of a second to see if the label is still being animated. Note that this macro uses the SIWaitFor(query, retryEvery, maxRetryAttempts) when finding the view, so you can even run this against queries which are part of the next screen. When doing this it will use the same period for checking and a max retries of 20. So the above code will actually pause for up to 4 seconds, waiting for the label to appear in the UIView hierarchy before starting the animation checks. |
Index Simon What is BDD? Why Simon?Installation Quick Start Guide Simon's UI Writing Stories Mapping Stories Step Conversations Validating Results Accessing your app's UI Exceptions and Errors The Pieman Macro reference API reference Simon's CLI args Pieman's CLI args Change Log Thank you BSD License
Download latest |