karatelabs/karate · warning

navigation aborted by browser, current document retained

Error message

navigation aborted by browser, current document retained: {}

What it means

After all Page.navigate retry attempts return net::ERR_ABORTED, the driver concludes the browser deliberately retained the current document (download, 204/205 response, or window.stop()) and logs this warning instead of waiting for a load event that will never fire. The method returns normally with the page as-is; it does not throw. Loader-bound waits are skipped because the returned loader never commits and DOMContentLoaded would never arrive.

Solutions

  1. Use Karate HTTP steps or driver.download for URLs that return files or 204/205 instead of documents.
  2. Remove or condition window.stop() in the page under test.
  3. Verify the URL actually serves an HTML document (curl -I to check Content-Disposition/status).
  4. If you expect a document but see this, retry the navigation; a racing reset may have aborted it.

Example fix

// before
driver.get('https://host/api/export'); // 204 or download, navigation aborted
// after
karate.call('classpath:helpers/download.feature'){ // use HTTP/download flow for non-document responses
  def resp = karate.http('https://host/api/export').get();
}
Defensive patterns

Strategy: fallback

Validate before calling

var resp = karate.http(url).get(); if (resp.status == 204 || resp.header('content-disposition') != null) { /* use HTTP flow, not driver.get */ }

Prevention

When it happens

Trigger: All navAttempts of driver.get()/loadUrl() end with errorText 'net::ERR_ABORTED', so the 'aborted' flag is still true after the retry loop.

Common situations: Navigating to a CSV/PDF link that downloads instead of rendering, endpoints returning 204 No Content, SPA code calling window.stop() during load, or an external link intercepted by a service worker.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/d00f850a45c33eb0. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/driver/cdp/CdpDriver.java:1324

                throw e;
            }
            aborted = "net::ERR_ABORTED".equals(navResponse.getResultAsString("errorText"));
            if (!aborted) {
                break; // committed, an error page under the same loader, or a normal response
            }
            if (attempt < navAttempts - 1) {
                logger.warn("Page.navigate returned ERR_ABORTED, retrying ({}/{}): {}", attempt + 1, navAttempts - 1, url);
                sleep(150); // let an in-flight/racing navigation (e.g. pooled-reset about:blank) settle
            }
        }

        // Every attempt aborted — a deliberate download / 204-205 / window.stop() that
        // retains the current document. The returned loader never commits, never fires
        // DOMContentLoaded, and a loader-bound wait for it could only end in a timeout,
        // so return with the page as-is (genuine load failures instead commit an error
        // page under the SAME loader, and the normal wait below handles those).
        if (aborted) {
            logger.warn("navigation aborted by browser, current document retained: {}", url);
            pendingNavigationUrl = null;
            return;
        }

        // data: and about: URLs commit locally and don't fire the normal load lifecycle,
        // so the full waitForPageLoad below would only ever time out on them. Returning
        // with no barrier at all is what let the pooled reset's about:blank still be in
        // flight when the scenario's first real navigation was issued: Chrome aborted one
        // against the other (net::ERR_ABORTED clustered on the post-reset /input, /wait,
        // /shadow-dom navigations) and the scenario continued against whichever document
        // won that race. Barrier on THIS navigation actually committing instead — cheap,
        // since a local document commits in milliseconds.
        //
        // Best-effort and bounded: if the commit signal never arrives we proceed anyway,
        // which is no worse than the unconditional return this replaces.
        if (url.startsWith("data:") || url.startsWith("about:")) {
            String localLoaderId = navResponse.getResultAsString("loaderId");
            if (localLoaderId != null && !pollUntil(LOCAL_NAV_COMMIT_TIMEOUT_MS, 10,

View on GitHub (pinned to a22eb90246)