Xcode 7 UITests with localized UI

13,106

Solution 1

I wanted to actually test the content of UI features and not just their existence, so setting a default language or using the accessibility identifiers wouldn't suit.

This builds on Volodymyr's and matsoftware's answers. However their answers rely on deviceLanguage which needs to be explicitly set in SnapshotHelper. This solution dynamically gets the actual supported language the device is using.

  1. Add the Localizable.strings files to your UITest target.
  2. Add the following code to your UITest target:

    var currentLanguage: (langCode: String, localeCode: String)? {
        let currentLocale = Locale(identifier: Locale.preferredLanguages.first!)
        guard let langCode = currentLocale.languageCode else {
            return nil
        }
        var localeCode = langCode
        if let scriptCode = currentLocale.scriptCode {
            localeCode = "\(langCode)-\(scriptCode)"
        } else if let regionCode = currentLocale.regionCode {
            localeCode = "\(langCode)-\(regionCode)"
        }
        return (langCode, localeCode)
    }
    
    func localizedString(_ key: String) -> String {
        let testBundle = Bundle(for: /* a class in your test bundle */.self)
        if let currentLanguage = currentLanguage,
            let testBundlePath = testBundle.path(forResource: currentLanguage.localeCode, ofType: "lproj") ?? testBundle.path(forResource: currentLanguage.langCode, ofType: "lproj"),
            let localizedBundle = Bundle(path: testBundlePath)
        {
            return NSLocalizedString(key, bundle: localizedBundle, comment: "")
        }
        return "?"
    }
    
  3. Access the method by localizedString(key)

For those languages with a script code, the localeCode will be langCode-scriptCode (for example, zh-Hans). Otherwise the localeCode will be langCode-regionCode (for example, pt-BR). The testBundle first tries to resolve the lproj by localeCode, then falls back to just langCode.

If it still can't get the bundle, it returns "?" for the string, so it will fail any UI tests that look for specific strings.

Solution 2

Option 1: Set a Default Language

Create a new scheme for UI Testing and set the default Application Language. This will lock the app into one localized file so you can write all of your tests for that language.

Set the option from Product -> Scheme -> Manage Schemes or ⌘⇧,. Then select the Options tab and set the language.

Xcode - Set the Default Application Language

Pros: Simple, one-time change.

Cons: Cannot be used to create localized screenshots with snapshot (a tool that runs your app via UI Testing and generates App Store screenshots along the way).

Option 2: Use -accessibilityIdentifier for Localized Strings

Instead of accessing items via their displayed text or value, use accessibilityIdentifier. This is read by the UI Testing framework but never shown or read to users (even with accessibility turned on). In the old UIAutomation docs Apple mentions using this for developer functionality, which this seams like a good use case.

You can then continue to set accessibilityLabel and accessibilityValue like normal, with the localized versions.

Pros: Can be used for more generic solutions, such as taking automated screenshots.

Cons: Might require more work changing each label you need "unlocalized" for testing.

Solution 3

YOU CAN RE-USE YOUR PROJECT LOCALIZATION BUNDLES!

When you test message boxes behaviour you need to know exactly what message box just appeared. You need to copy your localization from another scheme during build phase.

In your UI Tests target -> Build Phases -> Copy Bundle Resources, add the localization files needed (e.g. Localizable.strings).

Add a function similar to the following:

func localizedString(key:String) -> String {
/*1*/ let localizationBundle = NSBundle(path: NSBundle(forClass: /*2 UITestsClass*/.self).pathForResource(deviceLanguage, ofType: "lproj")!) 
/*3*/ let result = NSLocalizedString(key, bundle:localizationBundle!, comment: "") // 
    return result
}

/*1 Gets correct bundle for the localization file, see here: http://stackoverflow.com/questions/33086266/cant-get-access-to-string-localizations-in-ui-test-xcode-7 */
/*2 Replace this with a class from your UI Tests 
/*3 Gets the localized string from the bundle */

Then in your code you can use app.buttons[localizedString("localized.string.key")]

Full article is here: https://github.com/fastlane-old/snapshot/issues/321#issuecomment-159660882

Solution 4

The simplest and reliable way for me so far is to reference elements with elementBoundByIndex() Like this:

    let app = XCUIApplication()
    let tabBar = app.tabBars
    tabBar.buttons.elementBoundByIndex(2).tap()
    app.navigationBars.buttons.elementBoundByIndex(0).tap()
    app.tables.cells.elementBoundByIndex(2).tap()
    app.tables.elementBoundByIndex(1).cells.elementBoundByIndex(0).tap()

You can guess/experiment with this values and find elements you need.

Solution 5

The answer of Volodymyr helped me a lot, but it can fail if the localization bundle folder name differs from the deviceLanguage set in Snapshot. This snippet works fine for me in Swift 3.0 and with languages like italian (where current locale is "it" but device language is "it-IT").

    func localizedString(key:String) -> String {
      let languageBundlePath = Bundle(for: PlinthUITests.self).path(forResource: deviceLanguage, ofType: "lproj") ?? Bundle(for: PlinthUITests.self).path(forResource: NSLocale.current.languageCode!, ofType: "lproj")
      let localizationBundle = Bundle(path: languageBundlePath!)
      let result = NSLocalizedString(key, bundle:localizationBundle!, comment: "")
    return result
}
Share:
13,106
netshark1000
Author by

netshark1000

Updated on June 06, 2022

Comments

  • netshark1000
    netshark1000 almost 2 years

    In my App I'm using NSLocalizedString to localize my app. Now I want to switch to UITests and have Testcode like this:

    [tabBarsQuery.buttons["particiants"] tap];
    

    This works for English but fails for other languages.

    [tabBarsQuery.buttons[NSLocalizedString("PARTICIPANTS",comment:nil)] tap];
    

    Fails - probably because Localizable.strings is in another bundle. How can I test a localized app?

  • netshark1000
    netshark1000 over 8 years
    I don't want to lock my app to one language because I would like to create localized screenshots with github.com/fastlane/snapshot
  • Joe Masilotti
    Joe Masilotti over 8 years
    Please see my edit for a second option to use accessibilityIdentifier.
  • Gerrit Post
    Gerrit Post over 8 years
    Thanks this is the answer i've been searching for! works great! multi language UITesting and taking screenshots with fastlane
  • keeshux
    keeshux about 8 years
    rearrange layout later for whatever reason and you're screwed. tests will fail and go figure all the affected indexes each time. sorry but no, this is not even close to how reliable accessibilityIdentifier is. in order to make this feasible you should at the very least assign those elements to meaningful variable names
  • Vladimir Shutyuk
    Vladimir Shutyuk about 8 years
    @keeshux I agree :) That's why accepted answer recommends using accessibilityIdentifier. I needed this tests for fastlane/snapshot. And you make new screenshots when your "layout changes". And it is by far the fastest way for me.
  • Oleksandr
    Oleksandr over 7 years
    Thanks. It worked for me. It's worth to mention that the variable deviceLanguage is static and defined in SnapshotHelper##setupSnapshot(_).
  • Thermometer
    Thermometer over 6 years
    This works great for my localizable.strings! However, I cannot seem to get it to work with my Storyboard string files. Do you have any idea how I can fix that? Doesn't the storyboard strings go to the eventual lproj folder when building?
  • Bruno Muniz
    Bruno Muniz over 6 years
    @JoeMasilotti I'm using KIF so i'm not using XCUI and therefore i can't use snapshot but i'm trying to find a way to set multiple languages because i want to start my simulator in each one of this languages to force my app run in each language - test in each language. Do you have any idea about a workaround? Thanks.
  • Ash
    Ash about 6 years
    How to address UIAlertsAction and UIAlertControllers as they dont respond to accessibility Ids. I tried with Accessibility Labels, they work fine in English but with other languages, its not working.
  • Yitzchak
    Yitzchak about 6 years
    This gets a real problem when working with system buttons such as UIBarButtonItem - You cannot set it's accessibilityIdentifier =[ APPLE - DO SOMETHING
  • Kirow
    Kirow almost 5 years
    tried this solution, but not worked because deviceLanguage is en-US, but resource is en. Changed to Locale(identifier: deviceLanguage).languageCode
  • randomcontrol
    randomcontrol almost 5 years
    Unfortunately NSLocale.current.languageCode in my case was "en" while deviceLanguage was "de-DE". But in a comment to Volodymyrs answer Kirow gave the missing hint: Locale(identifier: deviceLanguage).languageCode
  • Nadzeya
    Nadzeya over 3 years
    If someone knows why this is not helpful, please let me know in comment. Would be happy to check it again and correct.