karatelabs/karate · error · DriverException

locator failed twice

Error message

locator failed twice: ${locator}

What it means

In W3cDriver's locator resolution, when the first (and retry) attempt to find the element via injected JavaScript fails with an exception, the driver wraps the second failure in a DriverException 'locator failed twice: <locator>' preserving the cause. It indicates the locator lookup script itself errored twice, not merely that the element was absent.

Solutions

  1. Fix the locator string syntax so the injected lookup JS does not throw (validate against supported locator formats)
  2. Ensure the page has fully loaded and the correct frame/window is active before locating
  3. Increase implicit waits / use waitFor before interacting, and re-locate after navigations
  4. Inspect the wrapped cause (e2) in logs for the real script error (e.g. 'document is not defined', CSP violations)

Example fix

// before
driver.click("//input[@nme='username']"); // typo causes JS lookup failure twice
// after
driver.waitFor("//input[@name='username']").click();
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check locator before use
Objects.requireNonNull(locator);
if (locator.isBlank()) throw new IllegalArgumentException("empty locator");

Try / catch

try {
    driver.click(locator);
} catch (Exception e) {
    if (e.getMessage().contains("locator failed twice")) {
        driver.reload(); // recover page context, then retry once
        driver.click(locator);
    }
}

Prevention

When it happens

Trigger: Calling find/locator APIs (driver.locate, waitFor, click, input on a stale/invalid locator string) where executeScript of the locator JS throws on both attempts — e.g. malformed locator syntax, the __kjs runtime not injected, or a navigation/page context invalidation mid-lookup.

Common situations: Typo'd or unsupported wildcard locator syntax; page navigated or frame changed between attempts causing JS execution errors; browser extension or CSP blocking injected script; clicking too fast after navigation so the execution context is destroyed.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/driver/w3c/W3cDriver.java:1047

        // v1 pattern: single retry on locator failure — handles transient DOM changes
        String js = "return " + Locators.selector(locator);
        try {
            Object result = session.executeScript(js);
            if (W3cSession.isElementReference(result)) {
                return W3cSession.elementIdFrom(result);
            }
        } catch (Exception e) {
            logger.warn("locator failed, will retry once: {}", e.getMessage());
        }
        // Single retry after sleep
        sleep(options.getRetryInterval());
        try {
            Object result = session.executeScript(js);
            if (W3cSession.isElementReference(result)) {
                return W3cSession.elementIdFrom(result);
            }
        } catch (Exception e2) {
            throw new DriverException("locator failed twice: " + locator, e2);
        }
        throw new DriverException("Element not found: " + locator);
    }

    /**
     * Inject the Karate JS runtime (__kjs) into the browser if not already present.
     * Same pattern as CdpDriver — loads driver.js from classpath resources.
     * Provides wildcard locator resolution, shadow DOM traversal, and shared utilities.
     */
    private void ensureKjsRuntime() {
        try {
            // Guard on __kjs.resolve (the wildcard resolver), not merely __kjs — a co-installed
            // helper may seed a partial window.__kjs without it; driver.js extends, never clobbers.
            Object exists = session.executeScript(
                    "return typeof window.__kjs !== 'undefined' && typeof window.__kjs.resolve === 'function'");
            if (!Boolean.TRUE.equals(exists)) {
                session.executeScript(DRIVER_JS);
                logger.debug("Injected __kjs runtime into browser");

View on GitHub (pinned to a22eb90246)