microsoft/playwright · error · NonRecoverableDOMError

Element is outside of the viewport

Error message

Element is outside of the viewport

What it means

In the same retry loop, a result of 'error:notinviewport' means the element is visible but its bounding box does not intersect the viewport. With force:true or noAutoWaiting the loop cannot retry, so it throws NonRecoverableDOMError('Element is outside of the viewport'). Without force, Playwright would scroll the element into view and retry.

Source

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

          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');
        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`);

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Remove force:true so Playwright scrolls the element into view automatically.
  2. Explicitly scroll first: await locator.scrollIntoViewIfNeeded().
  3. Use force only when the element is already in the viewport.

Example fix

// before
await page.locator('#footer-link').click({ force: true }); // off-screen → throws
// after
await page.locator('#footer-link').scrollIntoViewIfNeeded();
await page.locator('#footer-link').click();
Defensive patterns

Strategy: retry

Validate before calling

// Drop force so Playwright scrolls into view, or scroll explicitly.
await locator.scrollIntoViewIfNeeded();
await locator.click();

Prevention

When it happens

Trigger: Calling a pointer action with { force: true } on an element that is rendered but scrolled out of the viewport; element in a long page below the fold where force suppresses the automatic scroll-into-view.

Common situations: Long pages with force-click on off-screen buttons; elements in scrollable containers where the test disabled the scroll behavior via force.

Related errors


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