UIAutomation testing with Cocos2d, possible?

1.7k views Asked by At

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.

2

There are 2 answers

1
Jonny On

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
1
vaskas On

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})