CloakHQ/CloakBrowser · error · ElementNotEnabledError

Element failed enabled check: element is disabled

Error message

Element ${selector} failed enabled check: element is disabled

What it means

With 'enabled' in the requested checks, the probe found the element but data.enabled === false — a disabled control (disabled attribute, aria-disabled, or fieldset[disabled] ancestry).

Solutions

  1. Satisfy the gating condition first (fill required inputs, accept checkbox, await loading completion)
  2. Await the enabled state before acting: expect(locator).toBeEnabled()
  3. Retry with backoff when the disabled state depends on an async backend call
  4. Drop the 'enabled' check explicitly if acting on disabled controls is intentional

Example fix

// before
await humanClick(page, '#submit');
// after
await humanFill(page, '#email', 'a@b.c');
await humanFill(page, '#password', 'secret');
await page.waitForFunction(() => !(document.querySelector('#submit') as HTMLButtonElement).disabled);
await humanClick(page, '#submit');
Defensive patterns

Strategy: retry

Validate before calling

await page.waitForFunction((s) => { const el = document.querySelector(s); return !!el && !(el as HTMLButtonElement).disabled; }, sel, { timeout: 5000 });

Type guard

function isElementNotEnabledError(e: unknown): e is ElementNotEnabledError {
  return e instanceof Error && /failed enabled check/.test(e.message);
}

Try / catch

try { await humanClick(page, sel); } catch (e) { if (isElementNotEnabledError(e)) { await completePreconditions(); await humanClick(page, sel); } else throw e; }

Prevention

When it happens

Trigger: ensureActionable with 'enabled' (part of default checks for click/type/fill/clear) on a disabled button/input, e.g. a submit button disabled until form validity, or an input disabled during in-flight submission.

Common situations: Submitting before required fields are filled, buttons gated behind async loading/ToS checkbox, disabled state lingering after a failed request, or fieldset-level disable during form submission.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/1f451d4b256d7aec. Report an issue: GitHub.

Appendix: source

Thrown at js/src/human/actionability.ts:120

// ---------------------------------------------------------------------------
// Pre-scroll actionability
// ---------------------------------------------------------------------------

async function stealthActionable(
  pageOrFrame: Page | Frame,
  selector: string,
  checks: ReadonlySet<CheckName>,
): Promise<void> {
  const world = getWorld(pageOrFrame);
  if (!world) throw new StealthWorldUnavailableError();

  const { status, data } = await evalParsed(world, buildActionableJs(selector));
  if (status === UNSUPPORTED) throw new UnsupportedHumanizeSelectorError(selector);
  if (status === EVALUATION_FAILED) throw new StealthEvaluationError(selector);
  if (status === NOT_FOUND) throw new ElementNotAttachedError(selector);
  if (status !== OK || !data) throw new StealthEvaluationError(selector);
  if (checks.has('visible') && !data.visible) throw new ElementNotVisibleError(selector);
  if (checks.has('enabled') && !data.enabled) throw new ElementNotEnabledError(selector);
  if (checks.has('editable') && !data.editable) throw new ElementNotEditableError(selector);
}

async function readBox(
  pageOrFrame: Page | Frame,
  selector: string,
): Promise<{ x: number; y: number; width: number; height: number } | null> {
  const world = getWorld(pageOrFrame);
  if (!world) throw new StealthWorldUnavailableError();

  const { status, data } = await evalParsed(world, buildBoxJs(selector));
  if (status === OK && data?.box) return data.box;
  if (status === NOT_FOUND) return null;
  if (status === UNSUPPORTED) throw new UnsupportedHumanizeSelectorError(selector);
  throw new StealthEvaluationError(selector);
}

export async function ensureActionable(

View on GitHub (pinned to d6bad5de26)