5

I've been writing UI tests in Xcode 7.3 and recently wanted to add a launch argument to enable some test code inside the app. I initially tried setting XCUIApplication().launchArguments as several people have done in various posts, but they would not work.

Digging around it appears that both launchArguments and launchEnvironment cannot be setup within a UI test, even though the API documentation says they can.

Further when I attempted to set launch arguments and environment variables in the UI Testing scheme, they also where not passed through to the app, where as when unit testing or running the app, they are.

Here's a copy of a quick tests I did to prove this, all these tests fail.

import XCTest

class LaunchDebugUITests: XCTestCase {

    func testLaunchArgumentsSetting() {
        XCUIApplication().launchArguments = ["abc"]
        print("Arguments \(XCUIApplication().launchArguments)")
        XCTAssertTrue(XCUIApplication().launchArguments.contains("abc"))
    }

    func testLaunchArgumentsAppending() {
        XCUIApplication().launchArguments.append("abc")
        print("Arguments \(XCUIApplication().launchArguments)")
        XCTAssertTrue(XCUIApplication().launchArguments.contains("abc"))
    }

    func testLaunchEnvironmentSetting() {
        XCUIApplication().launchEnvironment = ["abc":"def"]
        print("Environment \(XCUIApplication().launchEnvironment)")
        XCTAssertEqual("def", XCUIApplication().launchEnvironment["abc"])
    }

    func testLaunchEnvironmentAppending() {
        XCUIApplication().launchEnvironment["abc"] = "def"
        print("Environment \(XCUIApplication().launchEnvironment)")
        XCTAssertEqual("def", XCUIApplication().launchEnvironment["abc"])
    }

} 

Has anyone else encountered this? Do you have a work around?

drekka
  • 20,957
  • 14
  • 79
  • 135

2 Answers2

17

Apple got back to me and told me I was using XCUIApplication() incorrectly.

You should not invoke XCUIApplication() multiple times.

Many of the blogs I read did this call multiple times and is most circumstances it doesn't matter. In fact many blog posts treat the function like it's accessing a singleton. I had a feeling this was incorrect as it looked wrong, but I figured other people would have got it right.

But it isn't. It's not accessing a singleton and actually creates a new XCUIApplication instance each time it's called. Hence my code was failing because I was setting launch arguments on one instance, then creating another one to launch.

So my tests should have actually looked like this:

func testLaunchArgumentsSetting() {
    let app = XCUIApplication()
    app.launchArguments = ["abc"]
    print("Arguments \(app.launchArguments)")
    XCTAssertTrue(app.launchArguments.contains("abc"))
    app.launch()
}
Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
drekka
  • 20,957
  • 14
  • 79
  • 135
  • Saviour! Was making the same mistake! Thanks! – Joris Timmerman May 23 '16 at 13:12
  • 1
    bang on! Key thing is to launch() the local variable that the launchArguments have been set on. This caused me hours of teeth gnashing. This blog saved the day: http://drekka.ghost.io/xcuiapplication-youre-probably-doing-it-wrong/ – Max MacLeod Jul 12 '16 at 16:05
  • oh my god thank you, spend the last hours with this problem – schirrmacher Jul 17 '16 at 18:19
  • This finally got my fastlane snapshot screenshots stuff working after like 4 hours of debugging. Finally figured it out by manually modifying the SnapshotHelper.swift code to print arguments, ensure they were being set properly, and then realizing the arguments weren't making it into my app. Ugh. Thank you! – Robbie Trencheny Jun 24 '19 at 05:47
2

You need to also, then, launch you app and check in the app for the argument. Here's how I do it...

func testFooBar() {
    // given
    app.launchArguments = ["shouldDoBar", "shouldDoFoo"]

    // when
    app.launch()

    // then
}   

Then in your app

int main(int argc, char *argv[]) {
    NSArray *arguments = [[NSProcessInfo processInfo] arguments];

    if ([arguments containsObject:@"shouldDoBar"]) {
       doBar();
    }

    if ([arguments containsObject:@"shouldDoFoo"]) {
       doFoo();
    }
    ...
}

You might want the arguments checks somewhere more appropriate to your use (and perhaps also wrapped in a #ifdef DEBUG ... #endif to avoid shipping it).

Michael
  • 1,213
  • 6
  • 9
  • Yep. Did all that, arguments where not passed. That's how I got into this bug in the first place. I wanted to set an arg in a test and have the app detect it and respond. In my case, to clear user defaults. – drekka Apr 13 '16 at 04:57
  • Apple got back to me and your answer is correct. I've given a more detailed explanation below as the first time I read it, it wasn't clear why it would work. – drekka Apr 14 '16 at 07:45