microsoft/playwright · error · NonRecoverableDOMError

Element is not ${result.missingState}

Error message

Element is not ${result.missingState}

What it means

The action returned an object with `missingState`, a state precondition that did not hold (e.g. the element should be enabled/stable/editable but was not). With force:true or noAutoWaiting the loop cannot wait for the state, so it throws NonRecoverableDOMError(`Element is not <missingState>`).

Source

Thrown at packages/playwright-core/src/server/dom.ts:372

          throw new NonRecoverableDOMError('Did not find some options');
        progress.log('  did not find some options');
        continue;
      }
      if (result === 'error:optionnotenabled') {
        if (noAutoWaiting)
          throw new NonRecoverableDOMError('Option being selected is not enabled');
        progress.log('  option being selected is not enabled');
        continue;
      }
      if (typeof result === 'object' && 'hitTargetDescription' in result) {
        if (noAutoWaiting)
          throw new NonRecoverableDOMError(`${result.hitTargetDescription} intercepts pointer events`);
        progress.log(`  ${result.hitTargetDescription} intercepts pointer events`);
        continue;
      }
      if (typeof result === 'object' && 'missingState' in result) {
        if (noAutoWaiting)
          throw new NonRecoverableDOMError(`Element is not ${result.missingState}`);
        progress.log(`  element is not ${result.missingState}`);
        continue;
      }
      return result;
    }
  }

  async _retryPointerAction(progress: Progress, actionName: ActionName, waitForEnabled: boolean, action: (progress: Progress, point: types.Point) => Promise<void>,
    options: { waitAfter: boolean | 'disabled' } & types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> {
    // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation.
    const skipActionPreChecks = actionName === 'move and up';
    return await this._retryAction(progress, actionName, async (progress, retry) => {
      // By default, we scroll with protocol method to reveal the action point.
      // However, that might not work to scroll from under position:sticky elements
      // that overlay the target element. To fight this, we cycle through different
      // scroll alignments. This works in most scenarios.
      const scrollOptions: (ScrollIntoViewOptions | undefined)[] = [
        undefined,

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Remove force:true so Playwright waits for the required state.
  2. Satisfy the precondition that brings the element into the needed state (fill required fields to enable the button).
  3. Inspect the missingState value in the message to know which state failed and address it.

Example fix

// before
await page.locator('#submit').click({ force: true }); // disabled → throws 'not enabled'
// after
await page.locator('#email').fill('a@b.com'); // enables submit
await page.locator('#submit').click();
Defensive patterns

Strategy: retry

Validate before calling

// Meet the missing state, then act without force.
await page.locator('#email').fill('a@b.com'); // enables the button
await page.locator('#submit').click();

Prevention

When it happens

Trigger: Performing a forced action when a state precondition — enabled, editable, stable — is false; e.g. clicking a button that is disabled with force:true, or typing into a readonly input with force.

Common situations: Disabled submit buttons tested with force before validation enables them; readonly fields; elements still animating (unstable) when force was used.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/8f7b7e40c67e7c53. Report an issue: GitHub.