karatelabs/karate · warning

timeout waiting for target to be removed

Error message

timeout waiting for target to be removed: {}

What it means

After closing a tab, CdpDriver waits for Target.targetDestroyed (or the page list to shrink) and falls back to a direct getPages() check. If the target still appears present after the wait, this warning is logged — the target removal event never arrived. Usually benign if the target did go away but the event/page list lagged; concerning if the tab genuinely failed to close.

Solutions

  1. Ignore if the tab is actually gone — this is a warning, not an error; verify with driver.getPages().
  2. Remove beforeunload handlers in the page before closing tabs.
  3. If targets genuinely leak, close the whole browser (quit) instead of individual tabs.
  4. Check CDP/websocket health and browser version; upgrade if event delivery is chronically delayed.

Example fix

// before
driver.close(); // closes current tab, may warn
// after
driver.script("window", "window.onbeforeunload = null");
driver.close();
Defensive patterns

Strategy: fallback

Validate before calling

// remove beforeunload before closing tabs
driver.script("window", "window.onbeforeunload = null");

Try / catch

try {
    driver.close();
} catch (RuntimeException e) {
    // if the target genuinely leaked, tear down the whole browser
    if (!driver.isTerminated()) { driver.quit(); }
}

Prevention

When it happens

Trigger: close() of a tab where Target.targetDestroyed wasn't observed within the wait window, slow CDP event delivery under load, beforeunload handlers blocking close, or a hung renderer keeping the target alive.

Common situations: Closing tabs with beforeunload dialogs, heavily loaded CI machines delaying CDP events, pages with long-running JS blocking teardown, or browser builds with delayed target cleanup.

Understand the failure class

Related errors


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

Appendix: source

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

    /**
     * Wait for a closed target to actually disappear, event-driven: the future is
     * completed by the Target.targetDestroyed handler. That event's delivery depends
     * on Target.setDiscoverTargets, which initialize() arms best-effort — so on
     * timeout, fall back to ONE page-list check before declaring a problem.
     */
    private void waitForTargetRemoved(String targetId, CompletableFuture<Void> removed) {
        try {
            removed.get(1000, TimeUnit.MILLISECONDS);
            logger.trace("target removal confirmed by event: {}", targetId);
            return;
        } catch (Exception e) {
            // fall through to the direct check
        }
        if (!getPages().contains(targetId)) {
            logger.trace("target removed from page list: {}", targetId);
            return;
        }
        logger.warn("timeout waiting for target to be removed: {}", targetId);
    }

    public boolean isTerminated() {
        return terminated;
    }

    @Override
    public boolean isReady() {
        CompletableFuture<Integer> f = mainContextReady;
        return f.isDone() && !f.isCompletedExceptionally();
    }

    @Override
    public void waitUntilReady() {
        // The main frame execution context being live is the authoritative readiness
        // signal (see mainContextReady). Block on it up to the configured timeout;
        // returns immediately when already ready.
        awaitMainContext(options.getTimeoutDuration().toMillis());

View on GitHub (pinned to a22eb90246)