karatelabs/karate · warning

pruned stale frame(s) from framesStillLoading, remaining

Error message

pruned {} stale frame(s) from framesStillLoading, remaining: {}

What it means

During the periodic stale-frame check in waitForPageLoad, the driver prunes frame IDs from framesStillLoading that no longer exist among activeFrameIds, because the frameStoppedLoading event can be lost in CI environments. This warning reports how many stale frames were removed and what remains. It is self-healing: pruning lets the load wait proceed instead of timing out on a phantom frame.

Solutions

  1. No action needed if the page then loads successfully — this is the driver self-healing a lost event.
  2. If it recurs with the same frame, inspect that iframe: it may genuinely never finish loading (fix the page or remove the frame).
  3. Keep Chrome updated; event loss is more common on older/loaded builds.
  4. Reduce CI parallelism if warnings correlate with timeouts.
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: domContentEventFired=true with non-empty framesStillLoading, and the stale check (every ~2s) finds frame IDs not present in the set of currently active frames.

Common situations: CI runners where CDP Frame.stoppedLoading events are dropped, removed/destroyed iframes whose stop event never arrived, heavy parallel test load causing missed CDP events.

Related errors


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

Appendix: source

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

            return;
        }
        try {
            CdpResponse response = cdp.method("Page.getFrameTree").send();
            List<Map<String, Object>> childFrames = response.getResult("frameTree.childFrames");
            Set<String> activeFrameIds = new HashSet<>();
            if (childFrames != null) {
                for (Map<String, Object> frameData : childFrames) {
                    Map<String, Object> frame = (Map<String, Object>) frameData.get("frame");
                    if (frame != null) {
                        activeFrameIds.add((String) frame.get("id"));
                    }
                }
            }
            int before = framesStillLoading.size();
            framesStillLoading.removeIf(id -> !activeFrameIds.contains(id));
            int removed = before - framesStillLoading.size();
            if (removed > 0) {
                logger.warn("pruned {} stale frame(s) from framesStillLoading, remaining: {}", removed, framesStillLoading);
            }
        } catch (Exception e) {
            logger.trace("stale frame check failed: {}", e.getMessage());
        }
    }

    /**
     * The DOMContentLoaded signal for the document the caller is actually waiting on.
     * When a navigation owns the wait (expectedLoaderId set by setUrl), the signal
     * only counts if it was recorded for that navigation's loader — a stale event
     * from the previous document (or a pool-reset about:blank) no longer qualifies.
     * With no owning navigation (refresh/reload/back/forward, external callers) the
     * legacy boolean applies unchanged.
     */
    private boolean isDomReady() {
        String expected = expectedLoaderId;
        if (expected != null) {
            if (expected.equals(domContentLoaderId)) {

View on GitHub (pinned to a22eb90246)