CloakHQ/CloakBrowser · error · ElementNotEnabledError

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

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).

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)

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.