Xcode 7 UI 本地化测试 UI

Xcode 7 UITests with localized UI

在我的应用程序中,我使用 NSLocalizedString 来本地化我的应用程序。现在我想切换到 UITests 并拥有这样的测试代码:

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

这适用于英语,但不适用于其他语言。

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

失败 - 可能是因为 Localizable.strings 在另一个包中。我如何测试本地化的应用程序?

选项 1:设置默认语言

为 UI 测试创建一个新方案并设置默认的应用程序语言。这会将应用程序锁定到一个本地化文件中,以便您可以为该语言编写所有测试。

从产品 -> 方案 -> 管理方案或 ⌘⇧, 设置选项。然后 select 选项选项卡并设置语言。

优点:简单,一次性更改。

缺点:不能用于使用 snapshot 创建本地化的屏幕截图(一种通过 UI 测试运行您的应用程序并生成 App Store 屏幕截图的工具方式)。

选项 2:对本地化字符串使用 -accessibilityIdentifier

不要通过显示的文本或值访问项目,而是使用 accessibilityIdentifier。这由 UI 测试框架读取,但从未向用户显示或读取(即使打开了辅助功能)。在旧的 UIAutomation 文档中,Apple 提到将此用于开发人员功能,这看起来像是一个很好的用例。

然后您可以像往常一样使用本地化版本继续设置 accessibilityLabelaccessibilityValue

优点:可用于更通用的解决方案,例如自动截屏。

缺点:可能需要更多工作来更改您需要 "unlocalized" 进行测试的每个标签。

到目前为止,对我来说最简单可靠的方法是使用 elementBoundByIndex() 来引用元素 像这样:

    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()

您可以guess/experiment使用这些值并找到您需要的元素。

如果您这样做是为了 运行 快照(而不是实际 UI 测试),那么我发现最简单的解决方案是作弊并使用 HSTestingBackchannel

这是我编写的一个工具,允许您从 UI测试 class 向应用程序发送通知。然后,您在应用程序中编写直接响应通知的代码。

您可以重复使用您的项目本地化包!

当您测试消息框行为时,您需要确切地知道刚刚出现的是什么消息框。您需要在构建阶段从另一个方案复制您的本地化。

在您的 UI 测试目标 -> 构建阶段 -> 复制捆绑资源中,添加所需的本地化文件(例如 Localizable.strings)。

添加一个类似于下面的函数:

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:  */
/*2 Replace this with a class from your UI Tests 
/*3 Gets the localized string from the bundle */

然后在你的代码中你可以使用 app.buttons[localizedString("localized.string.key")]

完整文章在这里:https://github.com/fastlane-old/snapshot/issues/321#issuecomment-159660882

Volodymyr 的回答对我帮助很大,但如果本地化包文件夹名称与快照中设置的 deviceLanguage 不同,则可能会失败。此代码段在 Swift 3.0 和意大利语等语言(当前区域设置为 "it" 但设备语言为 "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
}

我想实际测试 UI 功能的内容,而不仅仅是它们的存在,因此设置默认语言或使用可访问性标识符不适合。

这建立在 's and 的答案之上。然而,他们的答案依赖于 deviceLanguage,需要在 SnapshotHelper 中明确设置。此解决方案动态获取设备正在使用的实际支持的语言。

  1. Localizable.strings 文件添加到您的 UI 测试目标。
  2. 将以下代码添加到您的UI测试目标:

    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. 通过localizedString(key)

  4. 访问方法

对于那些带有脚本代码的语言,localeCode 将是 langCode-scriptCode(例如,zh-Hans)。否则 localeCode 将是 langCode-regionCode(例如,pt-BR)。 testBundle 首先尝试通过 localeCode 解析 lproj,然后退回到 langCode.

如果仍然无法获取捆绑包,它 returns“?”对于字符串,因此它将无法通过任何 UI 查找特定字符串的测试。

除了 Joe 的回答之外,您还可以直接在测试代码中强制执行 UI 测试的语言,而无需像这样编辑方案:

- (void)setUp
{
    [super setUp];

    self.continueAfterFailure = NO;
    XCUIApplication *app = [[XCUIApplication alloc] init];
    app.launchArguments = @[@"-AppleLanguages", @"(en)", @"-AppleLocale", @"en_EN"];
    [app launch];
}

SeanR 的回答很好(+1),但有一点改进:

如果您使用基本本地化,那么您的 Localizable.strings 可能不会本地化为您的基本语言。这不是必需的,因为在这种情况下将使用基本语言。如果是这样,SeanR 的函数 localizedString 将 return „?“

下面的扩展版本额外检查基本语言,returns 基本语言的本地化字符串:

func localizedString(_ key: String) -> String {
    let testBundle = Bundle(for: ShopEasyUITests.self)
    guard let currentLanguage = currentLanguage else { return "?" }
    if let testBundlePath = testBundle.path(forResource: currentLanguage.localeCode, ofType: "lproj"),
        let localizedBundle = Bundle(path: testBundlePath) {
        return NSLocalizedString(key, bundle: localizedBundle, comment: "")
    }
    if let testBundlePath = testBundle.path(forResource: currentLanguage.langCode, ofType: "lproj"),
        let localizedBundle = Bundle(path: testBundlePath) {
        return NSLocalizedString(key, bundle: localizedBundle, comment: "")
    }
    if let testBundlePath = testBundle.path(forResource: "Base", ofType: "lproj"),
        let localizedBundle = Bundle(path: testBundlePath) {
        return NSLocalizedString(key, bundle: localizedBundle, comment: "")
    }
    return "?"
}

对于 fastlane 的快照功能,SnapshotHelper.swift 使用这些参数启动应用程序。因此,通过解释这些值,这个解决方案是确定性的,我能够为多种语言生成正确的快照:

func getLocale(str: String) -> String {
    let start = str.index(str.startIndex, offsetBy: 1)
    let end = str.index(start, offsetBy: 2)
    let range = start..<end

    var locale = str.substring(with: range)
    if locale == "en" {
        return "Base"
    }
    return locale
}

func localizedString(_ key: String) -> String {
    print("app.launchArguments \(app.launchArguments)")
    guard let localeArgIdx = app.launchArguments.index(of: "-AppleLocale") else {
        return ""
    }
    if localeArgIdx >= app.launchArguments.count {
        return ""
    }
    let str = app.launchArguments[localeArgIdx + 1]
    let locale = getLocale(str: str)
    let testBundle = Bundle(for: Snapshot.self)
    if let testBundlePath = testBundle.path(forResource: locale, ofType: "lproj") ?? testBundle.path(forResource: locale, ofType: "lproj"),
        let localizedBundle = Bundle(path: testBundlePath)
    {
        return NSLocalizedString(key, bundle: localizedBundle, comment: "")
    }
    return ""
}

希望对您有所帮助

È=12=ÈÈ=13=È 解法: 我会受到@Volodymyr Prysiazhniuk solutionsh=11=sh 的启发 w=10=sh

我已经使用下一个方案使用 Fastlane 制作了本地化的屏幕截图:

1) 添加到 Fastfile 下一个参数:

localize_simulator: 真

2) 使用此代码获取本地化字符串:

class LocalizationHelper: NSObject {

    class func localizedString(_ key: String) -> String {
        let testBundle = Bundle(for: UITests.self)
        let currentLocale = Locale.current
        if let code = currentLocale.languageCode,
            let testBundlePath = testBundle.path(forResource: code, ofType: "lproj") ?? testBundle.path(forResource: code, ofType: "lproj"), let localizedBundle = Bundle(path: testBundlePath) {
            return NSLocalizedString(key, bundle: localizedBundle, comment: "")
        }
        return ""
    }

}