7

I was wondering if there is a way to set dark mode in-code for the XCUIApplication within a swift UITests project.

I have a need to launch the app in light mode and dark mode in the same test. Setting this in the scheme as a hard coded value will not work, or hacking the simulator from the outside will not work either (for performance & maintainability reasons among others).

Currently I set launch arguments like so:

    let app = XCUIApplication()
    var launchArguments: [AnyHashable] = []
    launchArguments.append("-AppleLanguages")
    launchArguments.append(langCode)
    launchArguments.append("-AppleLocale")
    launchArguments.append(localeCode)
    app.launchArguments = launchArguments
    app.launch()

And it works great.

How do I set Dark Mode for a XCUIApplication instance?

What I've done:

  • Extensive search on Apple Development Docs.
  • StackOverflow only shows how to hard-code this in the scheme within Xcode, or how to hack the simulator from the outside by killing the simulator, erasing it, and hacking a plist value.

Thanks for any help!

TheJeff
  • 3,665
  • 34
  • 52
  • Maybe this helps: https://stackoverflow.com/questions/57988687/how-to-use-dark-mode-in-simulator-ios-13 – fphilipe Dec 22 '19 at 21:11
  • No not really. I updated the question to make it clear that hacking the simulator from the outside by shutting it down, erasing it and inserting a plist value isn't a solution for instructing XCUIApplicatuion to start in dark mode - which is the premise of the question. Also had already mentioned that setting this in the scheme within XCode UI isn't an option either. – TheJeff Dec 22 '19 at 21:32
  • Potential argument to try: `-AppleInterfaceStyle Dark`. I found that argument from `defaults read NSGlobalDomain AppleInterfaceStyle`. But it needs to be tested to verify. – RobLabs Jan 02 '20 at 18:47
  • 1
    @RobLabs you are the man! (or woman I dunno). Anyway this works. If you add this to an answer I'll accept it. Also I'm kind of confused when you say where you found it - is that a command? Or is it something in xcode? Thanks for any additional info :) – TheJeff Jan 05 '20 at 17:51
  • @TheJeff, I personally need more understanding of how to use launchArguments in XCUIApplication, but this should work: `launchArguments.append("-AppleInterfaceStyle Dark")`. – RobLabs Jan 05 '20 at 23:07
  • Yeah - do this launchArguments.append("-AppleInterfaceStyle") launchArguments.append("Dark") – TheJeff Jan 05 '20 at 23:52

3 Answers3

14

I'm also interested in this question because I'm using UI tests to take screenshots with Fastlane. The goal was to be able to switch between light and dark mode for different tests on the same target.

The solution provided by RobLabs doesn't seem to be working for me on Xcode 11.4 / iOS 13.4. I'm not sure this matches your requirements but I'm using a custom launch argument and then setting the interface style in SceneDelegate, for debug builds only:

In your test:

override func testDarkMode() { // use setUp() to affect all test cases
    app = XCUIApplication()  
    app.launchArguments.append("UITestingDarkModeEnabled")
    app.launch()
}

In SceneDelegate.swift:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    (...)

    #if DEBUG
    if CommandLine.arguments.contains("UITestingDarkModeEnabled") {
        window?.overrideUserInterfaceStyle = .dark
    }
    #endif

    (...)    
}

And now your test runs in dark mode.

lewis
  • 2,936
  • 2
  • 37
  • 72
Peter
  • 766
  • 7
  • 8
  • 3
    If you're using fastlane, an easier way is to just use the built-in dark mode flag in your config. Search for dark_mode here: https://docs.fastlane.tools/actions/capture_ios_screenshots/ – TheJeff Mar 27 '20 at 12:42
  • 3
    @TheJeff although fastlane is easier, as far as I can tell there isn't a way to make one screenshot use dark mode. If you want to have a "Supports Dark Mode" screenshot, Peter's solution works better because it can be configured for one test case. It also seems a lot less brittle than relying on arguments to the simulator which are undocumented and seem to flux quite often – lewis Apr 28 '20 at 11:50
  • 1
    Gotcha, yeah the question was around setting it for everything, not for one screen, however I'd agree with you that this seems like the only way to do this on one screen easily. – TheJeff Apr 28 '20 at 13:14
2

In macOS, from the Terminal.app, you can issue this command

defaults read NSGlobalDomain AppleInterfaceStyle

Which responds with

Dark


In your XCTestCase, this should work

    func testAppleInterfaceStyleDark() {
        let app = XCUIApplication()
        var launchArguments: [AnyHashable] = []

        launchArguments.append("-AppleInterfaceStyle")
        launchArguments.append("Dark")
        app.launchArguments = launchArguments as! [String]
        app.launch()
    }

Update as of Xcode 11.4 Beta

You can now toggle the appearance in the Simulator. This is a great way to test maps and other Dark mode features.

  • From the Simulator Menu Item > Features > Toggle Appearance, or shiftA

Simulator supports toggling appearance for iOS simulators (13.0 and later). From within the app select Debug > Toggle Appearance. From the command line use the simctl ui subcommand, e.g. to set dark appearance

xcrun simctl ui <device> appearance dark
shim
  • 9,289
  • 12
  • 69
  • 108
RobLabs
  • 2,257
  • 2
  • 18
  • 20
  • Shout out to @TheJeff for testing based on my original comment from a few days ago. – RobLabs Jan 08 '20 at 02:53
  • 1
    Thanks Rob, very helpful - and couldn't find this anywhere. – TheJeff Jan 08 '20 at 15:30
  • 3
    Unfortunately, in my test it didn't work. The app still launch in light mode in the simulator. Should this code works for XCUITest? – Legolas Wang Jan 12 '20 at 00:57
  • Yep. Are you using Xcode 11 (current 11.3.1) with updated/current simulators running iOS 13? Are you compiling with current swift 5 code? Have you tried resetting the simulator you're running on? Does the simulator have dark mode as a developer option under settings? – TheJeff Jan 13 '20 at 18:56
  • Doesn't the app writer need to implement functions to handle processing of the launchArguments to invoke Dark Mode? – ablarg Jan 13 '20 at 19:12
  • Nope, the launch arguments are for the XCUIApplication() which is part of the UI Test framework with Xcode. This launch argument sets dark mode on the device's operating system only - as this is a new iOS 13 feature. This iOS setting (dark mode) impacts apps built in XCode 11 with targets of iOS 13. If you're toggling your iOS simulator or device to dark mode and your app looks the same, then you most likely need to read some tutorials on updating your app for dark mode. Also make sure your app isn't configured to override the interface style - it should inherit from the OS to work. – TheJeff Jan 13 '20 at 19:36
  • 5
    This method of using launch arguments to set dark mode doesn't work for me - Xcode 11.3.1 testing on a iOS 13.3 simulator. – Gabriel Mar 01 '20 at 21:11
  • Cool, just curious if any command like `xcrun simctl ui appearance dark` would work in physical iPhone devices? – alwaysday1 Dec 07 '21 at 02:45
1

This is my approach, currently being used in the Patrol testing framework. Works only when the device's language is set to English (I tested only the US locale).

#if targetEnvironment(simulator)
  self.preferences.descendants(matching: .any)["Developer"].firstMatch.tap()
  
  let value = self.preferences.descendants(matching: .any)["Dark Appearance"].firstMatch.value! as! String
  if value == "0" {
    self.preferences.descendants(matching: .any)["Dark Appearance"].firstMatch.tap()
  }
#else
  self.preferences.descendants(matching: .any)["Display & Brightness"].firstMatch.tap()
  self.preferences.descendants(matching: .any)["Dark"].firstMatch.tap()
#endif
Bartek Pacia
  • 1,085
  • 3
  • 15
  • 37