microsoft/playwright · error · NonRecoverableDOMError

Element is not visible

Error message

Element is not visible

What it means

During the action retry loop (_retryAction), when an action returns 'error:notvisible' the normal behavior is to log and keep auto-waiting. But if force:true or noAutoWaiting is set, Playwright cannot retry, so it throws a NonRecoverableDOMError('Element is not visible') immediately. The element exists but has zero visible bounding box (display:none, visibility:hidden, opacity:0, or off-flow).

Source

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

      if (retry) {
        progress.log(`retrying ${actionName} action${options.trial ? ' (trial run)' : ''}`);
        const timeout = waitTime[Math.min(retry - 1, waitTime.length - 1)];
        if (timeout) {
          progress.log(`  waiting ${timeout}ms`);
          const result = await progress.race(this.evaluateInUtility(([injected, node, timeout]) => new Promise<void>(f => setTimeout(f, timeout)), timeout));
          if (result === 'error:notconnected')
            return result;
        }
      } else {
        progress.log(`attempting ${actionName} action${options.trial ? ' (trial run)' : ''}`);
      }
      if (!options.skipActionPreChecks && !options.force && !noAutoWaiting)
        await this._frame._page.performActionPreChecks(progress);
      const result = await action(progress, retry);
      ++retry;
      if (result === 'error:notvisible') {
        if (options.force || noAutoWaiting)
          throw new NonRecoverableDOMError('Element is not visible');
        progress.log('  element is not visible');
        continue;
      }
      if (result === 'error:notinviewport') {
        if (options.force || noAutoWaiting)
          throw new NonRecoverableDOMError('Element is outside of the viewport');
        progress.log('  element is outside of the viewport');
        continue;
      }
      if (result === 'error:optionsnotfound') {
        if (noAutoWaiting)
          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');

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Remove force:true so Playwright's auto-wait retries until the element becomes visible.
  2. Ensure the element is visible before the forced action: await locator.waitFor({ state: 'visible' }).
  3. Fix the page flow so the element is visible when acted upon (open the tab/accordion first).
  4. If the element is intentionally hidden and you must interact, scroll/expand it via the UI first.

Example fix

// before
await page.locator('#hidden-btn').click({ force: true }); // throws
// after
await page.locator('#open-panel').click(); // make it visible
await page.locator('#hidden-btn').click(); // auto-wait handles it
Defensive patterns

Strategy: retry

Validate before calling

// Let Playwright auto-wait by not forcing; or pre-check visibility.
await locator.waitFor({ state: 'visible' });
await locator.click(); // no force — retries until visible

Prevention

When it happens

Trigger: Calling click()/fill()/press() etc. with { force: true } on an element that is present in the DOM but not rendered visible; acting with force on an element inside a hidden tab/accordion; element whose CSS makes it non-visible at action time.

Common situations: Using force:true to bypass actionability checks when the element is genuinely hidden; toggling UI state that hasn't completed its animation; clicking inside a container that is display:none until a parent action runs.

Related errors


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