fastlane-old / snapshot

Automate taking localized screenshots of your iOS app on every device
https://fastlane.tools
1.95k stars 140 forks source link

launch_arguments not added to NSUserDefaults.standardUserDefaults() and not added to NSProcessInfo.processInfo().arguments #381

Closed RainerSchwarze closed 8 years ago

RainerSchwarze commented 8 years ago

The snapshot README.md includes this:

You can provide additional arguments to your app on launch. These strings will be available in your code through NSProcessInfo.processInfo().arguments. Alternatively use user-default syntax (-key value) and they will be available as key-value pairs in NSUserDefaults.standardUserDefaults().

Part of my fastfile:

lane :mysnapshot2 do
  snapshot(
    devices: ["iPhone 5"],
    launch_arguments: ["-app_uitests websitescreenshots"],
    output_directory: "./screenshotsweb"
  )
end

In my UITest the setUp method looks like this:

- (void)setUp {
    [super setUp];
    XCUIApplication *app = [[XCUIApplication alloc] init];
    [SnapshotHelper setupSnapshot:app];
    NSLog(@"setUp: ud = %@", [NSUserDefaults standardUserDefaults].dictionaryRepresentation);
    NSLog(@"setUp: la = %@", app.launchArguments);
    NSLog(@"nspi.args = %@", [NSProcessInfo processInfo].arguments);
    ...
}

(The logging output is repeated in the test methods.)

However, the launch argument only exists in XCUIApplication.launchArguments. The same applies to -FASTLANE_SNAPSHOT - it only exists in the launchArguments array.

Did I do something wrong when accessing NSUserDefaults or NSProcessInfo? Is this a documentation bug or a software bug?

(I'm working in an Objective-C project and wrapped most of SnapshotHelper.swift with @objc class SnapshotHelper : NSObject { ... } in case this makes any difference.) I'm using snapshot 1.4.3.

Thanks

orgmir commented 8 years ago

I'm also trying to use this. I'm expecting to use the launch arguments in my app, are these only available on the UI test classes? Because the text gives all indication that it should be use on the standard app code.

orgmir commented 8 years ago

@RainerSchwarze Do you have any updates on this? Did you move on or used other strategy?

RainerSchwarze commented 8 years ago

@Orgmir I added a helper method to my screenshot test case:

- (NSString*) argValueForKey:(NSString*)key inApp:(XCUIApplication*)app {
    NSInteger index = [app.launchArguments indexOfObject:key];
    if (index==NSNotFound || index>=app.launchArguments.count-1) {
        return nil;
    }
    return [app.launchArguments objectAtIndex:index+1];
}

and I use it like this in the setUp method: (_run... are BOOL instance variables which are tested in the individual test methods)

NSString *testsel = [self argValueForKey:@"-app_uitests" inApp:app];
if ([testsel isEqualToString:@"appstorescreenshots"]) {
    _runAppStoreScreenshots = YES;
} else if ([testsel isEqualToString:@"websitescreenshots"]) {
    _runWebsiteScreenshots = YES;
}
Ashton-W commented 8 years ago

The launch_arguments option is for setting launch arguments on the app (app.launchArguments), and creating a screenshot matrix if multiple argument sets are specified.

You must call setupSnapshot before you launch the main app like so

    override func setUp() {
        super.setUp()

        let app = XCUIApplication()
        setupSnapshot(app)
        app.launch()
    }

I see how the documentation can be confusing, we should make it clearer.

I do like the idea of making the arguments available to the UI Tests, will need @KrauseFx's input on this.

Ashton-W commented 8 years ago

@RainerSchwarze from the snippet you posted, could I recommend creating separate Schemes or Targets for your different sets of UI Tests. You can edit a scheme and select a subset of tests for it to run, or create a new UI Testing target and keep the tests separate.

Ashton-W commented 8 years ago

Use this Snapfile in the Snapshot Example project.

RainerSchwarze commented 8 years ago

@Ashton-W Thanks for your information. I already call setupSnapshot in setUp in the right sequence. My UITests are separated from the screenshots using schemes and targets; so there will be no problem extending that to the different kinds of screenshot "runs".

owurman commented 8 years ago

I'm having a similar issue, trying to use the recommended approach up above. I can't even figure out how to log output for debugging purposes in the UITest. NSLog and print don't make it to the terminal. Any documentation on how to actually debug using logging?

RainerSchwarze commented 8 years ago

@owurman I believe that I found the log output in a file. When you run fastlane / snapshot there will be a console output indicating the current setup like --- iPhone 5 - en-US ---. Below that is a long line starting with $ set -o pipefail .... At the end of that line you will see | tee '/Some/File/name' | xcpretty. This shows your log file (it is huge - and i hope I remember correctly...).

owurman commented 8 years ago

@RainerSchwarze Thanks for the tip. It doesn't look like this log file is picking up "print" or "NSLog" output, however. At least, not from the UI Test...

owurman commented 8 years ago

Maybe you can help me compare what I'm doing to what you're doing? Although I'm using Swift...

Environment configuration: ENV["SNAPSHOT_PROTOCOL_INDEX"] ||= "0,1"

Snapfile: launch_arguments(["-snapshot_protocol_index #{ENV["SNAPSHOT_PROTOCOL_INDEX"]}"])

The test:

   func argValueForKey(key:String, app:XCUIApplication) -> String? {
      print("App launchArguments: %@", app.launchArguments.description)
      NSLog("App launchArguments: %@", app.launchArguments.description)
      if let index = app.launchArguments.indexOf(key) {
         return app.launchArguments[index+1]
      } else {
         return nil
      }
   }

Then later...

let snapshot_index = argValueForKey("-snapshot_protocol_index", app:app)

It seems to always be returning nil, however...

owurman commented 8 years ago

I'm frankly not even seeing where the launch arguments are being passed in by fastlane... ETA: Never mind, I see that's it's hacked via a file in /tmp that's read in by setLaunchArguments() in SnapshotHelper.swift

vpolouchkine commented 8 years ago

@owurman Are you trying to use launch arguments in your testing target? They should be used in the app. Something like https://gist.github.com/vpolouchkine/4fa22270fde1c882c56f

owurman commented 8 years ago

@vpolouchkine Yes, I need them in my testing target, too... is that not possible? Seems like it should be available in app.launchArguments.

owurman commented 8 years ago

OK, I see that the problem is that app.launchArguments is an empty array. I incorrect assumed that XCUIApplication() returned a singleton.

owurman commented 8 years ago

I managed to get it to work by calling setupSnapshot(app) again within the test itself. It's hacky, but so is "setupSnapshot" to begin with ;)

vpolouchkine commented 8 years ago

:+1:

owurman commented 8 years ago

Just some additional background for anyone who stumbles on this issue in the future:

launch_arguments are sent to the test from snapshot via a hack that puts them in /tmp/snapshot-launch_arguments.txt

This text file is read in by setLaunchArguments(app) in SnapshotHelper.swift, which is in turn called by setupSnapshot(app) in SnapshotHelper.swift.

When running the actual test you don't have access to the XCUIApplication (app) that was used to setup the launch arguments... or at least I don't know how to get to it. Therefore you have to call setupSnapshot(app) again in order to set up the launch arguments (or do your own direct read-in of the file). Here's how my code ended up:

   func argValueForKey(key:String, app:XCUIApplication) -> String? {
      if let index = app.launchArguments.indexOf(key) {
         return app.launchArguments[index+1]
      } else {
         return nil
      }
   }

   func testExample() {
      let app = XCUIApplication()
      /*
       * This will read in the launch arguments again so we can use
       * them in the UI test.
       */
      setupSnapshot(app)

      // ...
      let snapshot_index = argValueForKey("-snapshot_protocol_index", app:app)
      if let snapshot_index_unwrapped = snapshot_index {
      // ...
RainerSchwarze commented 8 years ago

@owurman Hmmm. I'm calling setupSnapshot in the setUp method of the test class - no need to call it in the test method itself.

owurman commented 8 years ago

@RainerSchwarze But then how do you access the launchArguments from within the test? I'd need the same XCUIApplication object. I guess I could save that to a global variable...

RainerSchwarze commented 8 years ago

@owurman Yes, I used properties and in the test methods I check the properties' values:

@interface MyAppUISnapshotTests : XCTestCase

@property (assign, nonatomic) BOOL runAppStoreScreenshots;
@property (assign, nonatomic) BOOL runWebsiteScreenshots;

@end

@implementation MyAppUISnapshotTests

- (void)setUp {
    [super setUp];

    XCUIApplication *app = [[XCUIApplication alloc] init];
    [SnapshotHelper setupSnapshot:app];

    NSString *testsel = [self argValueForKey:@"-myapp_uitests" inApp:app];
    NSLog(@"testsel = %@ / args = %@", testsel, app.launchArguments);
    if ([testsel isEqualToString:@"appstorescreenshots"]) {
        _runAppStoreScreenshots = YES;
    } else if ([testsel isEqualToString:@"websitescreenshots"]) {
        _runWebsiteScreenshots = YES;
    }
    [app launch];    
}

- (void)testAppStoreScreenshots {
    if (!_runAppStoreScreenshots) {
        NSLog(@"AppStore Screenshots are skipped.");
        return;
    }
...
owurman commented 8 years ago

Your way would seem to make a lot more sense ;)

fastlanebot commented 8 years ago

This issue was migrated to https://github.com/fastlane/fastlane/issues/2514. Please post all further comments there.

fastlane is now a mono repo, you can read more about the change in our blog post. All tools are now available in the fastlane main repo :rocket: