简体   繁体   English

NSOpenPanel打破了macOS上的UI测试

[英]NSOpenPanel Breaks UI Testing on macOS

I'm using Xcode to do UI testing on a sandboxed macOS app that has the com.apple.security.files.user-selected.read-write entitlement (ie, can access files and folders explicitly selected by the user via an NSOpenPanel GUI). 我正在使用Xcode在具有com.apple.security.files.user-selected.read-write权利的沙盒macOS应用上进行UI测试(即,可以访问用户通过NSOpenPanel GUI明确选择的文件和文件夹) )。

I have noticed that code coverage stops right after the open panel is presented modally. 我注意到,打开面板以模态显示后,代码覆盖范围立即停止。 This is my code: 这是我的代码:

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

(I have recorded my UI tests so that the NSOpenPanel is accepted right away, choosing the folder where it was open.) (我已经记录了我的UI测试,以便立即选择打开它的文件夹来接受NSOpenPanel 。)

Code coverage ends up highlighted like this: 代码覆盖率最终像这样突出显示:

在此处输入图片说明

I have tried replacing the switch statement with a fatalError() call, but the UI test still completes successfully, suggesting that anything immediately after: 我尝试用fatalError()调用替换switch语句,但是UI测试仍然成功完成,并建议在执行以下操作后立即执行以下操作:

let response = panel.runModal()

...is not executed during the test. ...在测试过程中执行。

Disabling sandboxing seems to have no effect, so I suspect it is running the open panel modally that causes trouble... 禁用沙箱似乎没有任何作用,因此我怀疑它以模态方式运行打开的面板会导致问题...

I tried all other available methods for presenting the open panel, namely: 我尝试了用于呈现打开面板的所有其他可用方法,即:

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

    default:
        return
    }
}

...and also: ...并且:

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

    default:
        return
    }
}

...but the result is always the same: All code immediately after presenting the panel is not covered during tests. ...但是结果始终是相同的:测试过程中未覆盖展示面板之后的所有代码。


In the end , I realized that my UI tests cannot rely on some user-selectable folder being present wherever the open panel lands (last visited directory?), so I opted for using mocking instead. 最后 ,我意识到我的UI测试不能依赖于打开面板到达的位置(上次访问的目录?)中存在的用户可选文件夹,因此我选择了使用模拟

First, in my UI test classes, I adopted this setup logic: 首先,在我的UI测试类中,我采用了以下设置逻辑:

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

(the hyphen before "Testing" is mandatory, otherwise my document-based macOS app will think I am launching it to open a document named "Testing", and fail to do so) (“ Testing”之前的连字符是强制性的,否则我的基于文档的macOS应用会认为我正在启动它以打开名为“ Testing”的文档,但没有这样做)

Next, On the app side, I defined a global computed property to determine whether we are running under a test or not: 接下来,在应用程序端,我定义了一个全局计算属性来确定我们是否正在测试中运行:

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

Finally, also on the app side I wrapped all NSOpenPanel calls into two methods: One for prompting the user for input files to read, and another to prompt the user for an output directory into which to write the resulting files (this is all my app needs from NSOpenPanel ): 最后,在应用程序方面,还将所有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)
    }
}

The portions of these two functions that use the actual NSOpenPanel instead of mocking the user-selected files/directories are still excluded from gathering code coverage statistics (but this time, it's by design). 这两个函数中使用实际NSOpenPanel而不是NSOpenPanel用户选择的文件/目录的部分仍被排除在收集代码覆盖率统计信息之外(但这是设计NSOpenPanel )。

But at least now it's just this two places. 但是至少现在只有这两个地方。 The rest of my code just calls these two functions and does no longer interact with NSOpenPanel directly. 我其余的代码仅调用这两个函数,不再与NSOpenPanel直接交互。 I have 'abstracted' the OS's file browsing interface away from my app... 我已经从我的应用程序“抽象”了操作系统的文件浏览界面...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 MacOS沙盒应用程序:无需NSOpenPanel即可访问文件 - MacOS sandboxed application: access files without NSOpenPanel macOS Xcode黑盒测试 - macOS Xcode blackbox testing 无法在 macOS 应用程序的 FFMPEG 进程中通过 NSOpenPanel 访问用户选择的文件 - Unable to access user selected file via NSOpenPanel in FFMPEG process in macOS app 如何判断最前面的 window 是否是使用 Applescript 在 MacOS 中的 NSOpenPanel / 文件打开对话框? - How do I tell if the frontmost window is a NSOpenPanel / file open dialog in MacOS using Applescript? NSSavePanel 和 NSOpenPanel 在 macOS 10.15 上不起作用,因为它像沙盒应用程序一样不在进程中 - NSSavePanel and NSOpenPanel does not work on macOS 10.15 as its out of process like sandboxed app macOS 商店沙盒应用使用 NSOpenPanel 到 select 下载文件夹,但无法再次访问该文件夹 - macOS store sandbox app uses NSOpenPanel to select download file folder, but can not access the folder again NSOpenPanel表 - NSOpenPanel Sheet MacOS bash别名在mv命令处中断 - MacOS bash alias breaks at mv command QPushButton的大小会破坏布局高度(MacOS) - The size of QPushButton breaks layout height (MacOS) 在 MacOS 上使用 minikube 测试 Kubernetes - Testing Kubernetes using minikube on MacOS
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM