vitest-dev/vitest · error · Error

Locator.filter expects at least one filter. None provided.

Error message

Locator.filter expects at least one filter. None provided.

What it means

Thrown by `Locator.prototype.filter` when the `filter` argument contains none of the recognized keys (`hasText`, `hasNotText`, `has`, `hasNot`). The method builds a compound Playwright-style internal selector by pushing one sub-selector per recognized key; if the array stays empty it cannot produce a meaningful locator, so it errors rather than returning an unchanged locator (which would silently ignore the call).

Solutions

  1. Supply at least one recognized key: `locator.filter({ hasText: 'Save' })`, `hasNotText`, `has` (a nested Locator), or `hasNot`.
  2. Check the spelling and capitalization of keys (camelCase, `hasText` not `has_text`).
  3. If no filtering is actually needed, drop the `.filter(...)` call entirely.

Example fix

// before
locator.filter({})

// after
locator.filter({ hasText: 'Save' })
Defensive patterns

Strategy: validation

Validate before calling

const recognized = ['hasText', 'hasNotText', 'has', 'hasNot'] as const
function hasRecognizedFilter(f: Record<string, unknown>): boolean {
  return recognized.some(k => k in f && f[k] != null)
}
if (!hasRecognizedFilter(filterObj)) {
  throw new Error('Provide at least one of hasText, hasNotText, has, hasNot')
}
locator.filter(filterObj)

Prevention

When it happens

Trigger: Calling `locator.filter({})`, `locator.filter()` (undefined → no keys), or passing an object with only unrecognized keys (e.g. `{ level: 2 }`).

Common situations: Misspelling a filter key (`hasText` → `hastext`); passing a predicate function instead of the expected object shape; passing an empty object as a placeholder; refactoring that drops all filter conditions.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/05333d42011db35f. Report an issue: GitHub.

Appendix: source

Thrown at packages/browser/src/client/tester/locators.ts:298

      selectors.push(`internal:has-text=${escapeForTextSelector(filter.hasText, false)}`)
    }

    if (filter?.hasNotText) {
      selectors.push(`internal:has-not-text=${escapeForTextSelector(filter.hasNotText, false)}`)
    }

    if (filter?.has) {
      const locator = filter.has as Locator
      selectors.push(`internal:has=${JSON.stringify(locator._pwSelector || locator.selector)}`)
    }

    if (filter?.hasNot) {
      const locator = filter.hasNot as Locator
      selectors.push(`internal:has-not=${JSON.stringify(locator._pwSelector || locator.selector)}`)
    }

    if (!selectors.length) {
      throw new Error(`Locator.filter expects at least one filter. None provided.`)
    }

    return this.locator(selectors.join(' >> '))
  }

  public and(locator: Locator): Locator {
    return this.locator(`internal:and=${JSON.stringify(locator._pwSelector || locator.selector)}`)
  }

  public or(locator: Locator): Locator {
    return this.locator(`internal:or=${JSON.stringify(locator._pwSelector || locator.selector)}`)
  }

  public query(): HTMLElement | SVGElement | null {
    const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
    return selectorEngine.querySelector(parsedSelector, document.documentElement, true) as HTMLElement | SVGElement
  }

View on GitHub (pinned to 1fa9837ec2)