NSOpenPanel打破了macOS上的UI测试

时间:2019-03-20 09:17:46

标签: macos xcode-ui-testing nsopenpanel

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

我注意到模态显示打开的面板后,代码覆盖立即停止。这是我的代码:

@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,选择打开它的文件夹。)

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

enter image description here

我尝试用switch调用替换fatalError()语句,但是UI测试仍然成功完成,提示在紧随其后的所有操作:

let response = panel.runModal()
在测试期间

...不是 执行。

禁用沙箱似乎没有任何效果,因此我怀疑它以模态方式运行打开的面板会导致问题...

1 个答案:

答案 0 :(得分:0)

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

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进行交互。我已经从应用程序“抽象”了操作系统的文件浏览界面...