Issuing a synchronous HTTP GET request or invoking shell script in JavaScript from iOS UIAutomation
Asked Answered
C

5

7

I am trying to use Apple's UIAutomation to write unit tests for an iOS Application that has a server-side component. In order to setup the test server in various states (as well as simulate two clients communicating through my server), I would like to issue HTTP get requests from within my javascript-based test.

Can anyone provide an example of how to either issue HTTP GET requests directly from within UIAutomation javascript tests, or how to invoke a shell script from within my UIAutomation javascript tests?

FWIW, most of the core objects made available by all browsers are missing within the UIAutomation runtime. Try to use XMLHTTPRequest for example and you will get an exception reporting that it cannot find the variable.

Thanks!

Crafty answered 31/5, 2011 at 18:23 Comment(0)
C
5

Folks,

I was able to work around this by sending HTTP requests to the iOS client to process and return the results in a UIAlertView. Note that all iOS code modifications are wrapped in #if DEBUG conditional compilation directives.

First, setup your client to send out notifications in the event of a device shake. Read this post for more information.

Next, in your iOS main app delegate add this code:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(deviceShakenShowDebug:)
                                             name:@"DeviceShaken" 
                                           object:nil];

Then add a method that looks something like this:

- (void) deviceShakenShowDebug:(id)sender
{
    if (!self.textFieldEnterDebugArgs)
    {
        self.textFieldEnterDebugArgs = [[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 260.0, 25.0)] autorelease];
        self.textFieldEnterDebugArgs.accessibilityLabel = @"AlertDebugArgsField";
        self.textFieldEnterDebugArgs.isAccessibilityElement = YES;
        [self.textFieldEnterDebugArgs setBackgroundColor:[UIColor whiteColor]];
        [self.tabBarController.selectedViewController.view addSubview:self.textFieldEnterDebugArgs];
        [self.tabBarController.selectedViewController.view bringSubviewToFront:self.textFieldEnterDebugArgs];
    }
    else
    {
        if ([self.textFieldEnterDebugArgs.text length] > 0)
        {
            if ([self.textFieldEnterDebugArgs.text hasPrefix:@"http://"])
            {
                [self doDebugHttpRequest:self.textFieldEnterDebugArgs.text];    
            }
        }
    }
}

- (void)requestDidFinishLoad:(TTURLRequest*)request
{
        NSString *response = [[[NSString alloc] initWithData:((TTURLDataResponse*)request.response).data 
                                                    encoding:NSUTF8StringEncoding] autorelease];

        UIAlertView *resultAlert = 
            [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Request Loaded",@"")
                                       message:response
                                      delegate:nil
                             cancelButtonTitle:NSLocalizedString(@"OK",@"")
                             otherButtonTitles:nil] autorelease];
        resultAlert.accessibilityLabel = @"AlertDebugResult";
        [resultAlert show];
}

This code will add a UITextField to the very top view controller after a shake, slapped right above any navigation bar or other UI element. UIAutomation, or you the user, can manually enter a URL into this UITextField. When you shake the device again, if the text begins with "http" it will issue an HTTP request in code (exercise for the reader to implement doDebugHttpRequest).

Then, in my UIAutomation JavaScript file, I have defined the following two functions:

function httpGet(url, delayInSec) {
  if (!delayInSec) delay = 1;
  var alertDebugResultSeen = false;
  var httpResponseValue = null;

  UIATarget.onAlert = function onAlert(alert) {    
    httpResponseValue = alert.staticTexts().toArray()[1].name();
    alert.buttons()[0].tap();
    alertDebugResultSeen = true;
  }

  var target = UIATarget.localTarget();
  var application = target.frontMostApp();
  target.shake(); // bring up the input field
  application.mainWindow().textFields()["AlertDebugArgsField"].setValue(url);
  target.shake(); // send back to be processed
  target.delay(delayInSec);
  assertTrue(alertDebugResultSeen);
  return httpResponseValue;
}

function httpGetJSON(url, delayInSec) {
  var response = httpGet(url, delayInSec);
  return eval('(' + response + ')');
}

Now, in my javascript file, I can call

httpGet('http://localhost:3000/do_something')

and it will execute an HTTP request. If I want JSON data back from the server, I call

var jsonResponse = httpGetJSON('http://localhost:3000/do_something')

If I know it is going to be a long-running call, I call

var jsonResponse = httpGetJSON('http://localhost:3000/do_something', 10 /* timeout */)

I've been using this approach successfully now for several weeks.

Crafty answered 28/7, 2011 at 6:20 Comment(2)
looks like quite ugly hack. But it should work :). But I'm looking for tool that allows doing this without changing iOS code...Accord
If you find one, I'm all ears. There's also corner.squareup.com/2011/07/ios-integration-testing.html but it uses undocumented APIs evidently. It's too bad Apple's solution sucks so hard.Crafty
F
4

Try performTaskWithPathArgumentsTimeout

UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", "http://google.com", 30);
Frank answered 30/12, 2011 at 15:31 Comment(0)
E
3

Just a small correction. The answer that suggests using UIATarget.host().performTaskWithPathArgumentsTimeout is an easy way to make a request on a URL in iOS 5.0+, but the syntax of the example is incorrect. Here is the correct way to make this call:

UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", ["http://google.com"], 30);

The "[" around the "args" param is important, and the test will die with an exception similar to the following if you forget the brackets:

Error: -[__NSCFString count]: unrecognized selector sent to instance

Here is a fully working example that hits google.com and logs all the output:

var result = UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", ["http://www.google.com"], 30);

UIALogger.logDebug("exitCode: " + result.exitCode);

UIALogger.logDebug("stdout: " + result.stdout);

UIALogger.logDebug("stderr: " + result.stderr);
Embower answered 28/8, 2012 at 0:7 Comment(0)
P
2

+1 for creative use of "shake()". However, that's not an option for some projects, especially those that actually use the shake feature.

Think outside the box. Do the fetching with something else (Python, Ruby, node.js, bash+wget, etc). Then, you can use the pre-canned response and auto-generate the ui-test.js on the fly by including that dynamically generated json payload as the "sample data" into the test. Then you simply execute the test.

In my opinion, the test is the test, leave that alone. The test data you are using, if it's that dynamic, it ought to be separated from the test itself. By doing it this way of fetching / generating JSON, and referencing it from the test, you can update that JSON however often you like, either immediately right before every test, or on a set interval like when you know the server has been updated. I'm not sure you would want to generate it while the test is running, that seems like it would create problems. Taking it to the next level, you could get fancy and use functions that calculate what values ought to be based on other values, and expose them as "dynamic properties" of the data, rather than that math being inside the test, but at that point I think the discussion is more of an academic one rather than the practical one of how.

Physicality answered 1/8, 2011 at 20:11 Comment(0)
R
2

Apple has recently updated UIAutomation to include a new UIAHost element for performing a task on the Host that is running the instance of Instruments that is executing the tests.

Renfroe answered 8/12, 2011 at 22:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.