简体   繁体   中英

UI Test (XCTest) with localized app.staticTexts

I am writing UI tests for my app that uses an UIWebView for its content. When buttons in the webview are pressed, it writes the test case as following:

func testExample() {
    let app = XCUIApplication()
    app.staticTexts["Log In"].tap()
}

The problem here is that if the app is in a different localization (eg "de-DE") then the button with text "Log In" doesn't exist. Instead its "Anmelden". I've tried the following, but neither of them work:

Localizable.strings version:

func testExample() {
    let name = NSLocalizedString("Log In", comment: "")

    let app = XCUIApplication()
    app.staticTexts[name].tap()
}

and

public class Titles {
    var homeScreenLogIn = "Log In"
}

public class TitlesDE: Titles {

    override init() {
        super.init()

        homeScreenLogIn = "Anmelden"
    }

}

...

func testExample() {
    titles = TitlesDE()

    let name = titles.homeScreenLogIn

    let app = XCUIApplication()
    app.staticTexts[name].tap()
}

UI Testing failure - Multiple matches found:

The problem is as well that I can't seem to figure out how to dump data to the debug/test output, since print() doesn't seem to work.

You should use the accessibilityIdentifier property that all UIKit objects have, as this string is not localized, it is used just for UI Automation.

That said, as you are using a UIWebView 's content there doesn't seem to be an easy solution.

Here is my solution where I extended the string object to include .localized()...You can probably ignore (or alter) all of the bundleName logic as it is specific to my project where a shell script moves/renames localization bundles in a very specific manner but basically, you need to get the bundle's directory name from the device's current set language. Also note that to make this work, I had to add to my UI test target under "Copy Bundle Resources" the app's Localizable.strings and Localizable.stringsdict

Use case: app.buttons["Tap Me!".localized()].tap() app.staticTexts["Localized String Value displayed Within App"].tap()

Just remember that the string you are calling .localized() on must be defined within the Localizable bundle from your app and if it isn't then it is not a string that will translate when the device language changes.

extension String {

func localized() -> String {

    // all this shit with bundleName is here only because we have a shell script that
    // renames/moves all of our localization folders
    var bundleName:String = deviceLanguage.stringByReplacingOccurrencesOfString("_", withString: "-")
    let secondaryBundleNameParts = deviceLanguage.componentsSeparatedByString("-")
    let secondaryBundleNamePartOne = secondaryBundleNameParts[0]
    let secondaryBundleNamePartTwo = secondaryBundleNameParts.count > 1 ? secondaryBundleNameParts[1] : ""
    let thirdBundleName = "\(secondaryBundleNamePartOne)-\(secondaryBundleNamePartTwo.uppercaseString)"
    if secondaryBundleNamePartOne == "es" {
        bundleName = secondaryBundleNamePartTwo == "ES" ? "es" : thirdBundleName
    }
    else if secondaryBundleNamePartOne == "pt" {
        bundleName = secondaryBundleNamePartTwo == "BR" ? "pt" : thirdBundleName
    }
    else if secondaryBundleNamePartOne == "zh" {
        bundleName = secondaryBundleNamePartTwo == "CN" ? "zh-Hans" : "zh-Hant"
    }
    else {
        bundleName = secondaryBundleNamePartOne
    }
    var bundlePath:String? = NSBundle(forClass: FGUITestCase.self).pathForResource(bundleName, ofType: "lproj")
    if bundlePath == nil {
        bundlePath = NSBundle(forClass: FGUITestCase.self).pathForResource(deviceLanguage, ofType: "lproj")
        if bundlePath == nil {
            bundlePath = NSBundle(forClass: FGUITestCase.self).pathForResource(bundleName, ofType:nil)
            if bundlePath == nil {
                bundlePath = NSBundle(forClass: FGUITestCase.self).pathForResource(deviceLanguage, ofType:nil)
                if bundlePath == nil {
                    for var i=0; i<100; i++ {
                        NSLog("OMG, WTF, Localization Bundle Not Found!: \(bundleName) || \(deviceLanguage)")
                        print("OMG, WTF, Localization Bundle Not Found!: \(bundleName) || \(deviceLanguage)")
                    }
                }
            }
        }
    }

    let bundle = NSBundle(path:bundlePath!)
    return NSLocalizedString(self, bundle:bundle!, comment: "")
}

subscript (i: Int) -> Character {
    return self[self.startIndex.advancedBy(i)]
}

subscript (i: Int) -> String {
    return String(self[i] as Character)
}

subscript (r: Range<Int>) -> String {
    let start = startIndex.advancedBy(r.startIndex)
    let end = start.advancedBy(r.endIndex - r.startIndex)
    return self[Range(start: start, end: end)]
}

}

The way I've been able to deal with this is to create a Localization class, which holds a dictionary of translations for different UI elements, which can be extracted using a String key, unique to the web view element in your app.

The localization to be used is set using the global variable, localization , which may be set at any point during the test, eg during setUp() .

Localizations are denoted using an enum.

Note: This is quite a crude implementation, as there's not much in the way of validation on a Dictionary - creating a localization set class and using a [String: LocalizedIdentifierSet] dictionary would be a more solid solution.

var localization = .Germany

class Localization {

    /// Dictionary containing localizations for each available localisation
    private static var localizations: [String: [Localization: String]] = [
        "signOutLink": [
            .UnitedKingdom: "SIGN OUT",
            .UnitedStates: "SIGN OUT",
            .France: "DÉCONNEXION",
            .Germany: "ABMELDEN",
            .Italy: "ESCI",
            .Spain: "SALIR",
            .Australia: "SIGN OUT",
            .Russia: "ВЫЙТИ"
        ],
        "appSettingsLink": [
            .UnitedKingdom: "App Settings",
            .UnitedStates: "App Settings",
            .France: "Réglages",
            .Germany: "App-Einstellungen",
            .Italy: "Impostazioni dell'App",
            .Spain: "Ajustes de la App",
            .Australia: "App Settings",
            .Russia: "Ajustes de la App"
        ]
    ]


    /**
    Returns a String containing the localized identifier for the element with the given `identifier`, for the currently-selected `localization`.

    - Parameter identifier: String identifier for the element you want to retrieve the localized query string for.

    - Returns: String to be used for querying the view hierarchy to find the element with `identifier`.
    */
    class func getLocalizedQueryStringForElementWithIdentifier(identifier: String) -> String? {
        let localizationsForElement = localizations[identifier]
        let queryString = localizationsForElement?[localization]
        return queryString
    }
}

You'd then use this class to retrieve the identifier you use in your query:

let textIdentifier = Localization.getLocalizedQueryStringForElementWithIdentifier("signOutLink")
let textElement = app.staticTexts[textIdentifier!]
textElement.tap()

I've finally achieved it with this code in the test function:

let locale = Locale.current.identifier
    print(locale)

    var tap1 = "STRING OF UI TEST 1 in ENGLISH"
    var tap2 = "STRING OF UI TEST 2 in ENGLISH"

    if (locale == "fr_US" || locale == "fr" || locale == "fr_FR" || locale == "fr_CA") {
        tap1 = "STRING OF UI TEST 1 in FRENCH"
        tap2 = "STRING OF UI TEST 2 in FRENCH"
    }
    else if (locale == "es_US" || locale == "es_ES" || locale == "es") {
        tap1 = "STRING OF UI TEST 1 in SPANISH"
        tap2 = "STRING OF UI TEST 2 in SPANISH"
    }

And just in the code that generates the UITest, change the "string" for the variables we've just created (tap1 and tap2).

In my case:

let textView = scrollViewsQuery.otherElements.containing(.staticText, identifier:tap1).children(matching: .textView).element

I was using SWIFT 4.

Hope it helps :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM