karatelabs/karate · error · RuntimeException

page load timeout after

Error message

page load timeout after {ms}ms - url: {url}, strategy: {strategy}, domContentEventFired: {bool}, framesStillLoading: {bool}, mainFrameId: {id}, expectedLoaderId: {id}, supersededLoaderId: {id}, committedLoaderId: {id}, domContentLoaderId: {id}, readyState: {state}, jsExec: {state}, cdpOpen: {bool}

What it means

Karate waited for a page load (navigation) until the configured timeout and the load did not complete. The message embeds a rich diagnostic: whether domContentEventFired fired, frames still loading, the main frame id, and the progression of loader ids (expected/superseded/committed/domContent) plus document readyState, JS-executability and CDP connection state, to pinpoint where loading stalled.

Solutions

  1. Increase the driver timeout (e.g. configure options.timeout or karate driver config) so slow pages can finish loading
  2. Change the waitUntil strategy (e.g. from 'load' to 'domcontentloaded') if a late subresource blocks the load event
  3. Open the diagnostic fields: framesStillLoading=true points at an iframe/hung subresource; committedLoaderId null means navigation never committed (check network/DNS/TLS)
  4. Check the page manually in a browser at the same network path to identify blocking resources

Example fix

// before
DriverOptions: { type:'chromium', timeout: 30000, waitForLoad:'load' }
// after
DriverOptions: { type:'chromium', timeout: 90000, waitForLoad:'domcontentloaded' }
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check reachability before navigation
Http.Response r = Http.forUrl(url).header("Connection","close").get();
if (r.getStatus() >= 400) throw new IllegalStateException("target url unhealthy: " + r.getStatus());

Try / catch

try { driver.setUrl(url); } catch (RuntimeException e) { if (e.getMessage().startsWith("page load timeout")) { logger.warn("load stalled: {}", e.getMessage()); driver.setUrl(url); // one retry  } else throw e; }

Prevention

When it happens

Trigger: driver.setUrl(url) or navigation with waitUntil strategy where, after the timeout duration, DOMContentLoaded never fired, or frames remained in loading state, or the loader chain shows a superseded navigation, or the document never reached the expected readyState.

Common situations: Slow pages behind corporate proxies/VPN; ad-blockers or CSP blocking critical resources; pages that never finish loading (long-polling, streaming media); misjudged waitUntil strategy or too-low driver timeout config; servers hanging on a subresource.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

                // timeout (or exceptional completion): fall through and re-check
            }
        }

        // Build diagnostic message with comprehensive state info
        String url = pendingNavigationUrl != null ? pendingNavigationUrl : "(unknown)";
        String readyStateInfo = getReadyStateForDiagnostic();
        String jsExecInfo = getJsExecStateForDiagnostic();
        String diagnostic = String.format(
                "page load timeout after %dms - url: %s, strategy: %s, " +
                "domContentEventFired: %s, framesStillLoading: %s, mainFrameId: %s, " +
                "expectedLoaderId: %s, supersededLoaderId: %s, committedLoaderId: %s, domContentLoaderId: %s, " +
                "readyState: %s, jsExec: %s, cdpOpen: %s",
                timeout.toMillis(), url, strategy,
                domContentEventFired, framesStillLoading, mainFrameId,
                expectedLoaderId, supersededLoaderId, committedLoaderId, domContentLoaderId,
                readyStateInfo, jsExecInfo, cdp.isOpen());
        logger.warn(diagnostic);
        throw new RuntimeException(diagnostic);
    }

    /**
     * Verify that JS execution works in the context script() will actually use.
     * <p>
     * The page is only truly "loaded" once the main frame's default execution context
     * is live - that is the single thing the readiness future tracks. We await it
     * briefly (it is usually already complete; during a navigation swap it settles in
     * milliseconds) and probe THAT context.
     * <p>
     * Crucially, an <i>error</i> from the explicit-context probe is NOT taken as "JS not
     * ready": {@link #mainContextReady} can hand out a contextId that a loader
     * replacement has already torn down when the matching executionContextsCleared was
     * never delivered (routine under CI load). The stale id then errors forever and, on
     * a fully-loaded page, wedges waitForPageLoad() to a 30s timeout — observed in CI as
     * "page load complete but JS context not ready yet" while the timeout diagnostic
     * reports {@code jsExec: ok} (it probes the default context). So on an explicit-
     * context error we re-probe the default context: this is a liveness check

View on GitHub (pinned to a22eb90246)