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
- No action needed if the page then loads successfully — this is the driver self-healing a lost event.
- If it recurs with the same frame, inspect that iframe: it may genuinely never finish loading (fix the page or remove the frame).
- Keep Chrome updated; event loss is more common on older/loaded builds.
- Reduce CI parallelism if warnings correlate with timeouts.
Defensive patterns
Strategy: fallback
Prevention
- Treat the warning as benign when the page then loads
- Fix or remove iframes that genuinely never stop loading
- Keep Chrome updated to reduce lost CDP events
- Reduce CI parallelism if it correlates with timeouts
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
- main-frame context not ready, retry
- CDP connection failed readiness check
- CDP timeout for
- CDP error
- dialog accept failed
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)