cvocvo
cvocvo

Reputation: 1636

NSInternalInconsistencyException Completion handler passed to webView:decidePolicyForNavigationAction:decisionHandle was not called

I'm running into an issue with one of our iOS app tests. Any help to solve is much appreciated!

Test code:

    func testOpenExternalBrowser_for_valid_urls() {
    // Given
    let vc = MockDashboardViewController()
    vc.openInternalBrowserCalled = false
    vc.openExternalBrowserCalled = false
    vc.webview = WKWebView()
    vc.webview.navigationDelegate = vc

    // When
    let url = URL.init(string: "https://example.com/restOfUrl")
    XCTAssertTrue(url!.absoluteString.contains("https://example.com"), "initial url string is wrong")
    vc.webview.load(URLRequest(url: url!));
    waitSeconds(duration: 2)

    // Then
    XCTAssertFalse(vc.openExternalBrowserCalled, "openExternalBrowserCalled value is wrong")
    XCTAssertTrue(vc.openInternalBrowserCalled, "openInternalBrowserCalled value is wrong")
}

This gives me the error:

error: -[Tests.ViewControllerTests testOpenExternalBrowser_for_valid_urls] : failed: caught "NSInternalInconsistencyException", "Completion handler passed to -[ViewController webView:decidePolicyForNavigationAction:decisionHandler:] was not called"

This is the relevant WKWebKit ViewController code:

    // MARK: WKNavigationDelegate methods
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url, let urlScheme = url.scheme, let urlHost = url.host else {
        //if we can't convert the URL, deny the action
        decisionHandler(WKNavigationActionPolicy.cancel);
        return;
    }

    var isPortValid = false;
    if let urlPort = url.port {
        isPortValid = urlPort == HostDefinitions.PORT;
    } else {
        isPortValid = urlScheme == DashboardConstants.HTTPS;
    }

    if(isPortValid && urlScheme == HostDefinitions.SCHEME && urlHost.compare(HostDefinitions.HOST) == ComparisonResult.orderedSame) {
        openInternalBrowser(url: url, decisionHandler: decisionHandler)
        return;
    }

    decisionHandler(WKNavigationActionPolicy.cancel);
    openExternalBrowser(url: url)
}

func openInternalBrowser(url: URL, decisionHandler:@escaping (WKNavigationActionPolicy) -> Void) {
    if(url.path == PathDefinitions.Login) {
        //the user has logged out or visited the login page. We will now de-auth them and direct them back to the login page
        OperationQueue.main.addOperation {
            AuthManager.logout()
            _ = self.navigationController?.popToRootViewController(animated: true);
        }
        decisionHandler(WKNavigationActionPolicy.cancel);
    } else {
        decisionHandler(WKNavigationActionPolicy.allow);
    }
}

func openExternalBrowser(url: URL) {
    AppManager.openExternalBrowser(url: url)
}

Any ideas? I've tried adding a return statement and adding the decision handler to the function openExternalBrowser but neither work. The app functions properly (opening external URLs in Safari) but the test fails.

Thank you!

Edit: Here's the MockDashboardViewController code:

class MockDashboardViewController: DashboardViewController {

    var openInternalBrowserCalled = false
    var openExternalBrowserCalled = false

    var presentNotificationCalled = false

    var webviewURLString = ""

    override func openInternalBrowser(url: URL, decisionHandler:@escaping (WKNavigationActionPolicy) -> Void) {
        openInternalBrowserCalled = true
    }

    override func openExternalBrowser(url: URL) {
        openExternalBrowserCalled = true
    }

    override func presentNotification(notification: DashboardNotification, showAlertView: Bool) {
        presentNotificationCalled = true
    }
}

Upvotes: 3

Views: 7573

Answers (1)

matt
matt

Reputation: 535231

The error message is correct. You are saying:

let vc = MockDashboardViewController()
vc.webview.navigationDelegate = vc

So the web view's navigation is not your "relevant WKWebKit ViewController", which I presume is a DashboardViewController, but a subclass of DashboardViewController, namely MockDashboardViewController. And it says:

override func openInternalBrowser(url: URL, decisionHandler:@escaping (WKNavigationActionPolicy) -> Void) {
    openInternalBrowserCalled = true
}

Okay, so what happens when decidePolicyFor is called? It isn't implemented in MockDashboardViewController, so DashboardViewController's implementation is called:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if(isPortValid && urlScheme == HostDefinitions.SCHEME && urlHost.compare(HostDefinitions.HOST) == ComparisonResult.orderedSame) {
        openInternalBrowser(url: url, decisionHandler: decisionHandler)
        return;
    }
}

And that's all that happens along that path of execution. Now, where in all of that is the decisionHandler called? Nowhere. The runtime is perfectly correct.

It's true that decisionHandler would have been called in DashboardViewController's implementation of openInternalBrowser(url:decisionHandler:). That is why the app runs fine when you run the app (not testing). But in your testing subclass, you overrode that — and you didn't call super, so you threw away those calls to the decisionHandler.

Upvotes: 2

Related Questions