microsoft/playwright · error · Error

options.waitFor is not supported, did you mean options.state

Error message

options.waitFor is not supported, did you mean options.state?

What it means

waitForSelector rejects the legacy options.waitFor property (except the value 'visible', which maps onto the default). Any other waitFor value triggers the error with the hint to use options.state instead.

Source

Thrown at packages/playwright-core/src/server/frames.ts:842

    return await progress.race(this._evaluateExpressionHandle(expression, options, arg));
  }

  private async _evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, world?: types.World } = {}, arg?: any): Promise<js.JSHandle<any>> {
    const context = await this.context(options.world ?? 'main');
    const value = await context.evaluateExpressionHandle(expression, options, arg);
    return value;
  }

  async querySelector(progress: Progress, selector: string, options: types.StrictOptions): Promise<dom.ElementHandle<Element> | null> {
    this.apiLog(`    finding element using the selector "${selector}"`);
    return progress.race(this.selectors.query(selector, options));
  }

  async waitForSelector(progress: Progress, selector: string, performActionPreChecksAndLog: boolean, options: types.WaitForElementOptions, scope?: dom.ElementHandle): Promise<dom.ElementHandle<Element> | null> {
    if ((options as any).visibility)
      throw new Error('options.visibility is not supported, did you mean options.state?');
    if ((options as any).waitFor && (options as any).waitFor !== 'visible')
      throw new Error('options.waitFor is not supported, did you mean options.state?');
    const { state = 'visible' } = options;
    if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
      throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
    if (performActionPreChecksAndLog)
      progress.log(`waiting for ${this._asLocator(selector)}${state === 'attached' ? '' : ' to be ' + state}`);
    const promise = this.retryWithProgressAndBackoff(progress, async (progress, continuePolling) => {
      if (performActionPreChecksAndLog)
        await this._page.performActionPreChecks(progress);

      if (scope && await progress.race(scope.evaluateInUtility(([injected, node]) => node.isConnected, {})) !== true)
        throw new dom.NonRecoverableDOMError('Element is not attached to the DOM');

      const resolved = await progress.race(this.selectors.callOnSelectorHandle(selector, { ...options, scope }, ({ injected, elements }) => {
        const element: Element | undefined  = elements[0];
        const visible = element ? injected.utils.isElementVisible(element) : false;
        let log = '';
        if (elements.length > 1)
          log = `  locator resolved to ${elements.length} elements. Proceeding with the first one: ${injected.previewNode(elements[0])}`;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Remove options.waitFor and set options.state to 'visible'|'hidden'|'attached'|'detached' as needed.
  2. If you actually wanted lifecycle, call page.waitForLoadState(...) separately.

Example fix

// before
await page.waitForSelector('#x', { waitFor: 'load' }); // throws
// after
await page.waitForSelector('#x', { state: 'visible' });
await page.waitForLoadState('load');
Defensive patterns

Strategy: validation

Validate before calling

// Strip/redirect the legacy key
if ('waitFor' in opts && opts.waitFor !== 'visible') {
  // caller probably meant load-state; split the call
  await page.waitForLoadState(opts.waitFor);
  delete opts.waitFor;
}

Type guard

function hasLegacyWaitFor(o: any): boolean { return 'waitFor' in o && o.waitFor !== 'visible'; }

Try / catch

null

Prevention

When it happens

Trigger: Calling waitForSelector with options.waitFor set to a non-'visible' value (e.g. 'load', 'domcontentloaded', 'networkidle') - usually confused with page.waitForLoadState's waitUntil semantics.

Common situations: Mixing up waitForSelector's element-state option with waitForLoadState's lifecycle option; copy-pasting options across the two APIs; legacy code from an API change.

Related errors


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