Xcode 7 UITests with localized UI
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.
Can't get access to string localizations in UI Test (Xcode 7)
In the UI tests, the main bundle seems to be a random launcher app. That's why the .strings file doesn't appear: even though you added it to your test bundle, NSLocalizedString is checking the wrong bundle. To get around this, you need an invocation like this:
NSLocalizedString("LOCALIZATION_KEY", bundle: NSBundle(forClass: AClassInYourUITests.self), comment: "")
Which you might want to pull into a helper method.
Xcode 7 UI Testing target locale and region settings
I have figured it out. I set the locale settings in the launchArguments for testing temporary in Xcode.
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = [
"-inUITest",
"-AppleLanguages",
"(de)",
"-AppleLocale",
"de_DE"
]
For CI I use fastlane from Felix Krause and get localized screenshots with snapshot.
UITests-How to get current language in UITests
Storing the user's language in your app's UserDefaults
and then accessing that from your UITest won't work. Your app and the UITest are running as separate processes, which means that your UITest cannot access your app's UserDefaults
There is a simple solution: To become independent from the users language you can set the accessibilityIdentifier
on your UIButton
and then access the button via the accessibilityIdentifier
:
In your app:
button.accessibilityIdentifier = @"login";
In your UITest:
[app.buttons[@"login"] tap];
The accessibilityIdentifier is never displayed and VoiceOver
also does not read it, so it does not have to be localized. Just make sure you are using accessibilityIdentifier
and not accessibilityLabel
. Because accessibilityLabel
will be read by VoiceOver
for handicapped users and should be localized.
ALTERNATIVE
If you cannot use accessibilityIdentifier
to query your UI elements you could use your app's LocalizableStrings
file in your UITests to get the localized button title (in this case):
First add your Localizable.strings
file to your UITest target, then access the file via the UITest bundle to get the localized strings (I use a little helper method for that):
func localized(_ key: String) -> String {
let uiTestBundle = Bundle(for: AClassFromYourUITests.self)
return NSLocalizedString(key, bundle: uiTestBundle, comment: "")
}
I wrote a little blog post about this a while back with some more details.
Xcode UITest for different localizations strings
I found a way to fail the test if a text is not localized for a language.
I added this in my UITest:
XCTAssertEqual(lblSecond.label, returnLocalizedText(textKey: "Bye"))
What this does is you pass in the key for your localized text, and check it whether it does match with the label's text. You can get the label's text like so:
//you can set this identifier in storyboard or in your VC like
lblTitle.accessibilityIdentifier = "lblSecondIdentifier"
//Then in XCTest
let lblSecond = app.staticTexts["lblSecondIdentifier"]
XCTAssert(lblSecond.isHittable) //check whether it exist first
Then this is the function. It will check whether the path exist and look for text, and finally return the localized string if it exists:
func returnLocalizedText(textKey: String) -> String{
guard let languageCode = Locale(identifier: Locale.preferredLanguages.first!).languageCode else {
XCTFail("languageCode not found")
fatalError()
}
guard let path = Bundle(for: type(of: self)).path(forResource: "Localizable", ofType: "strings", inDirectory: nil, forLocalization: languageCode) else{
XCTFail("path not found")
fatalError()
}
guard let dict = NSDictionary(contentsOf: URL(fileURLWithPath: path)) else {
XCTFail("dict not found")
fatalError()
}
guard let text = dict.value(forKey: textKey) as? String else {
XCTFail("text not found")
fatalError()
}
return text
}
How to tap on a specific point using Xcode UITests
You can tap a specific point with the XCUICoordinate
API. Unfortunately you can't just say "tap 10,10" referencing a pixel coordinate. You will need to create the coordinate with a relative offset to an actual view.
We can use the mentioned web view to interact with the relative coordinate.
let app = XCUIApplication()
let webView = app.webViews.element
let coordinate = webView.coordinateWithNormalizedOffset(CGVector(dx: 10, dy: 10))
coordinate.tap()
Side note, but have you tried interacting with the web view directly? I've had a lot of success using app.links["Link title"].tap()
or app.staticTexts["A different link title"].tap()
. Here's a demo app I put together demonstrating interacting with a web view.
Update: As Michal W. pointed out in the comments, you can now tap a coordinate directly, without worrying about normalizing the offset.
let normalized = webView.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let coordinate = normalized.withOffset(CGVector(dx: 10, dy: 10))
coordinate.tap()
Notice that I pass 0,0 to the normalized vector and then the actual point, 10,10, to the second call.
Related Topics
How to Integrate Linphone into an Existing Project (Sip in iOS)
iOS - Detect Blow into Mic and Convert the Results! (Swift)
Uitableview:Viewforheaderinsection: Not Called During Reloaddata:
Could Not Instantiate Class Named Ibnslayoutconstraint
Cellforitematindexpath Not Called But Numberofitemsinsection Does
How to Remove All Gesture Recognizers from a Uiview in Swift
Avaudiosession Setcategory Swift 4.2 iOS 12 - Play Sound on Silent
How to Add Done Button on Keyboard on Top of Keyboard in iOS
Xcode 9.2 Upload to App Store Fails with Description Length and Invalid Toolchain Errors
"File Not Found", "Linker Command Failed with Exit Code 1" in Xcode 4.5.1
Present a View Controller, Dismiss It and Present a Different One in Swift
How to Only Show Bottom Border of Uitextfield in Swift
Show Datepicker on Textfield Tap
(Null) Libc++Abi.Dylib: Terminate Called Throwing an Exception
Tableview Rounded Corners and Shadow
How to Make Cmake Use Specific Compiler and Flags When Final Compilation Stage Instead of Detection