Xcode 7 UITests with localized UI
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.
- Add the
Localizable.strings
files to your UITest target. -
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 "?" }
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.
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
}
netshark1000
Updated on June 06, 2022Comments
-
netshark1000 almost 2 years
In my App I'm using
NSLocalizedString
to localize my app. Now I want to switch toUITests
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 over 8 yearsI 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 over 8 yearsPlease see my edit for a second option to use
accessibilityIdentifier
. -
Gerrit Post over 8 yearsThanks this is the answer i've been searching for! works great! multi language UITesting and taking screenshots with fastlane
-
keeshux about 8 yearsrearrange 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 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 over 7 yearsThanks. It worked for me. It's worth to mention that the variable deviceLanguage is static and defined in SnapshotHelper##setupSnapshot(_).
-
Thermometer over 6 yearsThis 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 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 about 6 yearsHow 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 about 6 yearsThis gets a real problem when working with system buttons such as UIBarButtonItem - You cannot set it's accessibilityIdentifier =[ APPLE - DO SOMETHING
-
Kirow almost 5 yearstried this solution, but not worked because deviceLanguage is
en-US
, but resource isen
. Changed toLocale(identifier: deviceLanguage).languageCode
-
randomcontrol almost 5 yearsUnfortunately 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 over 3 yearsIf someone knows why this is not helpful, please let me know in comment. Would be happy to check it again and correct.