4

Is it possible to use UIAutomation with cocos2d or any opengl application for that matter?

Specifically I want to use the zucchini framework to test my cocos2d game but that just uses UIAutomation anyway.

RobEarl
  • 7,862
  • 6
  • 35
  • 50
Christian Schlensker
  • 21,708
  • 19
  • 73
  • 121
  • I'm researching this too. Have you found _any_ way to write automated tests for a Cocos2D/OpenGL app? – Danyal Aytekin Feb 07 '12 at 18:02
  • Small amount of progress... with UIAutomation you can apparently say, "tap at these coordinates". Not much but it's a start. See the first reply here. http://answers.oreilly.com/topic/1646-how-to-use-uiautomation-to-create-iphone-ui-tests/ – Danyal Aytekin Feb 07 '12 at 18:14
  • We might have better luck with the KIF framework. Its tests are written in Objective-C and run as part of the application. So it could query the app directly to get the coords for certain elements. I really like the look of zucchini though. – Christian Schlensker Feb 07 '12 at 18:57
  • Will have a look at KIF... I had a further idea for a UIAutomation approach. In Debug mode we could have every Cocos2D node generate an associated dummy UIKit element, positioned and named usefully, then we could assert on properties of the UIKit element. – Danyal Aytekin Feb 08 '12 at 11:17
  • KIF looks good but think I'm going to try to knock something up using UIAutomation or Zucchini. Will put it on Github. – Danyal Aytekin Feb 08 '12 at 17:10
  • It's been a year...Did anything happen on the UI automated testing front with cocos2d ? – Gaylord Zach Mar 13 '13 at 20:13
  • I'm trying calabash-iOS as of now, having some progress, still haven't found a way to find cocos2d elements yet. http://stackoverflow.com/questions/16955365/testing-cocos2d-iphone-using-calabash-ios – Jonny Jun 07 '13 at 06:21
  • calabash-iOS has a backdoor, I am just guessing you could use it to hack into the runtime objective c classes and dig out the cocos2d objects etc in the end. I have yet to find time to try that. – Jonny Jun 13 '13 at 04:56
  • Basically what we need is to identify the accessibility labels/values of CCNode and its descendants like CCSprite. This way we could write scripts that could play our games by for example waiting until the "start" menu button appears on the menu screen, hopefully get it as a element in the script, have it pushed, and proceed with similar approach to complete one game round, etc... Objective-C should make this possible but might need to use the ObjC runtime which is a bit lower level. http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html – Jonny Jun 13 '13 at 06:37

2 Answers2

1

You can create custom steps in Zucchini and specify the coordinates to tap, e.g.

'Choose the red bird' : ->
   target.tap({x:278, y:36})

'Press Fire' : ->
   target.tap({x:170, y:260}) 
vaskas
  • 21
  • 1
0

So, I got started with calabash-iOS and expanding on its backdoor. This is just for starters but with this you can get the accessibility label of the current CCScene, so you can check what screen is currently on and thus use for scripting actions. I'm not used to working with objc runtime, but as you can see it's possible to get properties, methods etc. A bit more digging and it should be possible to wrap more functionality, and hopefully something to wrap the cocos2d CCNode structure as well. This is a work in progress.

To use this you need to install https://github.com/calabash/calabash-ios and then implement the below function in the app delegate. Don't forget to set .accessibilityLabel to something like @"menu", @"game" or similar in your code. Optimally, only for the *-cal target, you don't want this code in production builds.

-(NSString*)calabashBackdoor:(NSString*)aIgnorable {
    DLog(@"calabashBackdoor: %@", aIgnorable);

//  UIApplication* app = [UIApplication sharedApplication];
    if (aIgnorable != nil) {
        NSArray* p = [aIgnorable componentsSeparatedByString:@" "];
        NSString* command = [p objectAtIndex:0];

        if ([command isEqualToString:@"getCurrentSceneLabel"]) {
            CCDirector* director = [CCDirector sharedDirector];
            DLog(@"director.runningScene.accessibilityLabel: %@", director.runningScene.accessibilityLabel);
            return director.runningScene.accessibilityLabel;
        }
        else if ([command isEqualToString:@"class_copyMethodList"]) {
            CCDirector* director = [CCDirector sharedDirector];
            id inspectThisObject = director.runningScene;
            DLog(@"inspectThisObject: %@, %@", [inspectThisObject class], inspectThisObject);
            unsigned int count;

            // To get the class methods of a class, use class_copyMethodList(object_getClass(cls), &count).
            Method* methods = class_copyMethodList(object_getClass(inspectThisObject), &count);
            //NSMutableString* returnstring = [NSMutableString string];
            NSMutableArray* arrayOfMethodnames = [NSMutableArray array];
            if (methods != NULL) {
                for (int i = 0; i < count; i++) {
                    Method method = methods[i];
                    NSString* stringMethod = NSStringFromSelector(method_getName(method)); //NSStringFromSelector(method->method_name);
                    [arrayOfMethodnames addObject:stringMethod];
                }
                // An array of pointers of type Method describing the instance methods implemented by the class—any instance methods implemented by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
                free(methods);
            }
            DLog(@"arrayOfMethodnames: %@", arrayOfMethodnames);
            return [arrayOfMethodnames componentsJoinedByString:@","];
        }
        else if ([command isEqualToString:@"class_copyPropertyList"]) {
            CCDirector* director = [CCDirector sharedDirector];
            id inspectThisObject = director.runningScene;
            DLog(@"inspectThisObject: %@, %@", [inspectThisObject class], inspectThisObject);
            unsigned int count;

//          An array of pointers of type objc_property_t describing the properties declared by the class. Any properties declared by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
//
//          If cls declares no properties, or cls is Nil, returns NULL and *outCount is 0.
//          
            objc_property_t* properties = class_copyPropertyList(object_getClass(inspectThisObject), &count);

            NSMutableArray* arrayOfProperties = [NSMutableArray array];
            if (properties != NULL) {
                for (int i = 0; i < count; i++) {
                    objc_property_t property = properties[i];
                    const char* CCS = property_getName(property);
                    NSString* str = [NSString stringWithUTF8String:CCS];
                    [arrayOfProperties addObject:str];
                }
                free(properties);
            }
            DLog(@"arrayOfProperties: %@", arrayOfProperties);
            return [arrayOfProperties componentsJoinedByString:@","];           
        }
        else {
            DLog(@"Unhandled command: %@", command);
        }
    }

    return @"calabashBackdoor nil!";
}

In Prefix.pch put this

#ifdef DEBUG
#   define DLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#   define DLog(...) /* */
#endif
#define ALog(...) NSLog(__VA_ARGS__)

When you get calabash-ios and running, add this in step_definitions/somesteps.rb:

Then(/^I backdoor (.+)$/) do |x|
  backdoor("calabashBackdoor:", x)
end
Jonny
  • 15,955
  • 18
  • 111
  • 232
  • Actually... not sure what I was thinking but traversing the CCNode structure... I don't really need the objc runtime at all. This is very much possible and I have already implemented a few scripts that play my games. – Jonny Jul 19 '13 at 02:13