Xcode 7 Uitests with Localized Ui

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.

  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.

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



Leave a reply



Submit