【问题标题】:NSOpenPanel Breaks UI Testing on macOSNSOpenPanel 打破 macOS 上的 UI 测试
【发布时间】:2019-03-20 09:17:46
【问题描述】:

我正在使用 Xcode 在具有 com.apple.security.files.user-selected.read-write 权利(即,可以访问用户通过 NSOpenPanel GUI 明确选择的文件和文件夹)的沙盒 macOS 应用上进行 UI 测试。

我注意到,在打开的面板以模态方式呈现后,代码覆盖率立即停止。这是我的代码:

@IBAction func go(_ sender: Any) {

    let panel = NSOpenPanel()
    panel.canCreateDirectories = true
    panel.canChooseDirectories = true
    panel.canChooseFiles = false
    panel.allowsMultipleSelection = false

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

(我已经记录了我的 UI 测试,以便立即接受 NSOpenPanel,选择打开它的文件夹。)

代码覆盖率最终会像这样突出显示:

我已尝试用fatalError() 调用替换switch 语句,但UI 测试仍然成功完成,这表明紧随其后的是:

let response = panel.runModal()

...在测试期间不执行。

禁用沙盒似乎没有任何效果,所以我怀疑它以模态方式运行打开的面板会导致麻烦......

【问题讨论】:

  • view.window?.close() 是否释放 self?
  • @Willeke 不应该; AppDelegate 将窗口控制器存储在非弱属性中;窗口控制器应该一直存在,直到将新实例分配给属性...?此外,在不运行测试时(即调试时),应用程序按预期工作。
  • @Willeke 在任何情况下,删除整个 switch 子句(包括window.close())并用对fatalError() 的调用替换它什么都不做(即,测试完成时没有受到伤害)。与关闭窗口无关...
  • 如果在主线程上调用代码,你能检查一下吗?模型对话框需要这个并且通常不会自己强制执行。从 Xcode 启动时(另请参阅stackoverflow.com/questions/48787777/…)以及应用程序未正确签名时(请参阅stackoverflow.com/questions/24692540/…),我也遇到了 NSOpenPanel 的问题。
  • @mahaltertin 我的应用程序已经过签名和沙盒化,但我会尽快检查线程。

标签: macos xcode-ui-testing nsopenpanel


【解决方案1】:

我尝试了所有其他可用的方法来呈现打开的面板,即:

panel.begin { (response) in
    switch response {
    case NSApplication.ModalResponse.OK:
        self.openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

...还有:

panel.beginSheetModal(for: view.window!) { (response) in
    switch response {
    case NSApplication.ModalResponse.OK:
        self.openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

...但结果总是一样的:在测试期间不会覆盖所有在显示面板后立即显示的代码。


最后,我意识到我的 UI 测试不能依赖于打开面板所在的任何用户可选择的文件夹(上次访问的目录?),所以我选择使用 取而代之的是嘲笑。

首先,在我的 UI 测试类中,我采用了这个设置逻辑:

override func setUp() {
    continueAfterFailure = false
    let app = XCUIApplication()
    app.launchArguments.append("-Testing")
    app.launch()
}

(“Testing”之前的连字符是强制性的,否则我的基于文档的 macOS 应用程序会认为我正在启动它以打开一个名为“Testing”的文档,而不会这样做)

接下来,在应用端,我定义了一个全局计算属性来确定我们是否在测试中运行:

public var isTesting: Bool {
    return ProcessInfo().arguments.contains("-Testing")
}

最后,同样在应用程序端我将所有NSOpenPanel 调用包装成两种方法:一种用于提示用户输入要读取的文件,另一种用于提示用户输入输出目录到写入结果文件(这是我的应用程序需要来自NSOpenPanel):

public func promptImportInput(completionHandler: @escaping (([URL]) -> Void)) {
    guard isTesting == false else {
        /* 
          Always returns the URLs of the bundled resource files: 
           - 01@2x.png, 
           - 02@2x.png, 
           - 03@2x.png,
             ...
           - 09@2x.png, 
         */
        let urls = (1 ... 9).compactMap { (index) -> URL? in
            let fileName = String(format: "%02d", index) + "@2x"
            return Bundle.main.url(forResource: fileName, withExtension: "png")
        }
        return completionHandler(urls)   
    }
    // (The code below cannot be covered during automated testing)

    let panel = NSOpenPanel()
    panel.canChooseFiles = true
    panel.canChooseDirectories = true
    panel.canCreateDirectories = false
    panel.allowsMultipleSelection = true

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        completionHandler(panel.urls)
    default:
        completionHandler([])
    }
}

public func promptExportDestination(completionHandler: @escaping((URL?) -> Void)) {
    guard isTesting == false else {
        // Testing: write output to the temp directory 
        // (works even on sandboxed apps):
        let tempPath = NSTemporaryDirectory()
        return completionHandler(URL(fileURLWithPath: tempPath))
    }
    // (The code below cannot be covered during automated testing)

    let panel = NSOpenPanel()
    panel.canChooseFiles = false
    panel.canChooseDirectories = true
    panel.canCreateDirectories = true
    panel.allowsMultipleSelection = false

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        completionHandler(panel.urls.first)
    default:
        completionHandler(nil)
    }
}

这两个函数中使用实际 NSOpenPanel 而不是模拟用户选择的文件/目录的部分仍被排除收集代码覆盖率统计信息(但这一次,这是设计使然) .

但至少现在只有这两个地方。我的其余代码只是调用这两个函数,不再直接与NSOpenPanel 交互。我已将操作系统的文件浏览界面从我的应用程序中“抽象”出来......

【讨论】:

    猜你喜欢
    • 2018-06-22
    • 1970-01-01
    • 2018-10-22
    • 2019-06-23
    • 2012-06-10
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 2020-11-03
    相关资源
    最近更新 更多