【问题标题】:Xcode 7 UITests with localized UI具有本地化 UI 的 Xcode 7 UITests
【发布时间】:2016-02-10 02:39:25
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: ios swift xcode7 xcode-ui-testing


    【解决方案1】:

    选项 1:设置默认语言

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

    从 Product -> Scheme -> Manage Schemes 或 ⌘⇧, 中设置选项。然后选择选项选项卡并设置语言。

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

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

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

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

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

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

    缺点:可能需要做更多的工作来更改您需要“未本地化”进行测试的每个标签。

    【讨论】:

    • 我不想将我的应用程序锁定为一种语言,因为我想用github.com/fastlane/snapshot创建本地化屏幕截图
    • 请查看我的编辑以获取使用accessibilityIdentifier的第二个选项。
    • 谢谢,这是我一直在寻找的答案!效果很好!使用 fastlane 进行多语言 UI 测试和截图
    • @JoeMasilotti 我正在使用 KIF,所以我没有使用 XCUI,因此我无法使用快照,但我正在尝试找到一种设置多种语言的方法,因为我想启动我的模拟器在每种语言中强制我的应用程序以每种语言运行 - 以每种语言进行测试。您对解决方法有任何想法吗?谢谢。
    • 如何解决 UIAlertsAction 和 UIAlertControllers,因为它们不响应可访问性 Id。我尝试使用辅助功能标签,它们在英语中运行良好,但在其他语言中,它不起作用。
    【解决方案2】:

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

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

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

      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)访问方法

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

    如果仍然无法获取捆绑包,则返回“?”对于字符串,因此它会失败任何寻找特定字符串的 UI 测试。

    【讨论】:

    • 这非常适合我的localizable.strings!但是,我似乎无法让它与我的 Storyboard 字符串文件一起使用。你知道我该如何解决吗?构建时情节提要字符串不会转到最终的 lproj 文件夹吗?
    【解决方案3】:

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

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

    在您的 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: 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 */
    

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

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

    【讨论】:

    • 尝试了这个解决方案,但没有成功,因为 deviceLanguageen-US,但资源是 en。改为Locale(identifier: deviceLanguage).languageCode
    【解决方案4】:

    到目前为止,对我来说最简单可靠的方法是使用 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()
    

    您可以猜测/试验这些值并找到您需要的元素。

    【讨论】:

    • 以后无论出于何种原因重新排列布局,你都搞砸了。测试将失败并每次计算所有受影响的索引。抱歉,不,这与accessibilityIdentifier 的可靠程度相差甚远。为了使这可行,您至少应该将这些元素分配给有意义的变量名
    • @keeshux 我同意 :) 这就是为什么接受的答案建议使用accessibilityIdentifier。我需要这个测试用于快速通道/快照。当您的“布局发生变化”时,您会制作新的屏幕截图。这对我来说是迄今为止最快的方式。
    【解决方案5】:

    Volodymyr 的回答对我帮助很大,但是如果本地化包文件夹名称与 Snapshot 中设置的 deviceLanguage 不同,它可能会失败。这个 sn-p 在 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
    }
    

    【讨论】:

    • 谢谢。它对我有用。值得一提的是,变量 deviceLanguage 是静态的,并在 SnapshotHelper##setupSnapshot(_) 中定义。
    • 不幸的是,我的 NSLocale.current.languageCode 是“en”,而 deviceLanguage 是“de-DE”。但在对 Volodymyrs 的评论中,Kirow 给出了缺失的提示:Locale(identifier: deviceLanguage).languageCode
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      这是我编写的一个工具,它允许您将通知从 UITesting 类发送到应用程序。然后,您在应用程序中编写直接响应通知的代码。

      【讨论】:

        【解决方案8】:

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

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

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

        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 "?"
        }
        

        【讨论】:

          【解决方案9】:

          对于 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 ""
          }
          

          希望对你有帮助

          【讨论】:

            【解决方案10】:

            Objective-C 解决方案: 灵感来自@Volodymyr Prysiazhniuk 解决方案

            - (NSString*)getLocalizedStringForKey :(NSString*)stringKey forUITestClass : (id) uiTestClass{
                if (!stringKey || !uiTestClass){
                    return nil;
                }
                NSString *bundlePath = [[NSBundle bundleForClass: uiTestClass]bundlePath];
                NSBundle* bundle = [NSBundle bundleWithPath:bundlePath];
                NSString* localizedString = NSLocalizedStringWithDefaultValue(stringKey, nil, bundle, nil, nil);
                return localizedString;
            }
            

            【讨论】:

              【解决方案11】:

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

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

              localize_simulator: true

              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 ""
                  }
              
              }
              

              【讨论】:

              • 如果有人知道为什么这没有帮助,请在评论中告诉我。很乐意再次检查并更正。
              猜你喜欢
              • 2016-02-10
              • 1970-01-01
              • 2012-01-25
              • 1970-01-01
              • 2015-11-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-01-29
              相关资源
              最近更新 更多