Skip to main content

pytest-bdd-language-server 🥒

Python Version

A Language Server Protocol (LSP) implementation for Gherkin .feature files backed by pytest-bdd step definitions, with support for go to definition, find references, code completion, hover documentation, workspace symbols, diagnostics, and more!

Built with AI, reviewed with care 🤖

This project was built with the help of an AI coding agent, with each feature reviewed and tested (unit + end-to-end, via a real server subprocess) before moving to the next. If you find any issues, please open an issue or submit a pull request!

Table of Contents

Features

🎯 Go to Definition

Jump directly from a Gherkin step to the Python function that implements it:

  • Literal steps (@given("I am an author"))
  • parsers.parse/parsers.cfparse steps, matched with the same libraries pytest-bdd itself uses
  • parsers.re steps, matched with Python's own re module directly
  • And/But/* steps, resolved to their effective Given/When/Then keyword first

🔍 Find References

Works from either end:

  • From a step definition's def line, find every .feature step that resolves to it
  • From a Gherkin step, find every other place that same step is used
  • include_declaration is respected on both ends

📚 Hover Documentation

Hover over a step to see which definition(s) match it, without jumping:

  • Function name and source location
  • The matched pattern and its kind (literal / parse / cfparse / re)
  • A clear note when no definition matches, or when the only candidate can't be verified statically

✨ Code Completion

Autocomplete existing step patterns while writing a new step:

  • Filtered by the effective keyword (Given/When/Then, And/But/* resolved)
  • parsers.parse/cfparse patterns are offered as real LSP snippets — {start:d} becomes a tabstop you fill in, not literal placeholder text
  • parsers.re patterns are intentionally excluded — a regex's source text isn't text it matches, so there's nothing sensible to insert

🔎 Workspace Symbols

Search across the whole project, not just step definitions:

  • Features and Scenarios: search by name; matching a Feature's name surfaces every Scenario under it too
  • Step definitions: search by keyword and pattern
  • Powers editor-side pickers — see pytest-bdd.nvim's Telescope integration for an example

The per-file counterpart, textDocument/documentSymbol, gives the same Feature/Scenario/step structure for the currently open document, nested rather than flat — this is what powers your editor's outline view, breadcrumbs, or sticky scroll.

⚠️ Diagnostics

Catches problems before you ever run pytest:

  • Undefined step: a step with no matching definition anywhere — the same error pytest-bdd would raise at runtime, surfaced while you type instead
  • Unverifiable step: the only candidate definition uses parsers.cfparse(..., extra_types=...), whose custom converters live in your own code and can't be checked statically — reported as informational, not a warning, since it isn't necessarily wrong

🧹 Find Unused Steps

A custom pytest-bdd.unusedSteps command (via workspace/executeCommand, since no standard LSP method fits "list definitions with no usages") lists every step definition with no matching usage in any .feature file — likely dead code left behind after a scenario was reworded or removed. unresolved definitions are excluded, since there's no way to confirm they're truly unused rather than just unverifiable.

⏳ Progress & Scan Feedback

  • $/progress: workspace indexing reports progress if the client supports window.workDoneProgress, so a large project doesn't feel like it's hanging
  • window/showMessage: files that couldn't be read or parsed are reported in a single, capped summary — not silently skipped, and not one popup per broken file
  • workspace/didChangeWatchedFiles: the index refreshes automatically on external changes (git checkout, another process, a second editor window) if the client supports dynamic registration, in addition to on-save

Installation

# Using uv (recommended)
uv tool install pytest-bdd-language-server

# Or with pip, inside your project's virtualenv
pip install pytest-bdd-language-server

# Or with pipx (isolated environment)
pipx install pytest-bdd-language-server

Not yet published to PyPI — install from source in the meantime:

git clone https://framagit.org/RomainTT/pytest-bdd-language-server
cd pytest-bdd-language-server
uv tool install --editable .

Setup

Neovim

The pytest-bdd.nvim plugin handles this for you (filetype detection, virtualenv resolution, a Telescope picker). Without it, Neovim 0.11+'s native config works directly:

vim.filetype.add({ extension = { feature = "cucumber" } })

vim.lsp.config("pytest_bdd_language_server", {
  cmd = { "pytest-bdd-language-server" },
  filetypes = { "cucumber" },
  root_markers = { ".git", "pyproject.toml", "setup.py", "setup.cfg" },
})

vim.lsp.enable("pytest_bdd_language_server")

VS Code

The pytest-bdd-vscode extension handles server resolution and adds two commands (pytest-bdd: Browse Steps, pytest-bdd: Find Unused Steps) built on workspace/symbol and the custom command above. Not yet published to the Marketplace — see its README for building a local .vsix.

Other Editors

Any editor with LSP support can use pytest-bdd-language-server. Point it at the pytest-bdd-language-server command over stdio, with a document selector matching .feature files.

Configuration

There are currently no user-configurable settings — no initializationOptions, no workspace/didChangeConfiguration handling. Behavior is entirely determined by the workspace folder(s) reported at initialize and the client's declared capabilities (for $/progress and the file watcher). See Known Limitations for what that means in practice.

Supported Step Patterns

All four of pytest-bdd's step decorator syntaxes are recognized and matched using the same libraries pytest-bdd itself relies on at runtime — not a reimplementation of their semantics:

from pytest_bdd import given, parsers

# Literal text
@given("I am an author")
def author():
    ...

# parsers.parse (matched via the `parse` library)
@given(parsers.parse("there are {count:d} cucumbers"))
def cucumbers(count):
    ...

# parsers.cfparse (matched via parse_type.cfparse)
@given(parsers.cfparse("there are {count:d} cucumber(s)"))
def cucumbers_cf(count):
    ...

# parsers.re (matched via Python's own `re`)
@given(parsers.re(r"there are (?P<count>\d+) cucumbers"))
def cucumbers_re(count):
    ...

parsers.cfparse(..., extra_types=...) is the one case that can't be statically verified — its custom converters live in your own code, so it's flagged as unresolved rather than guessed at (see Diagnostics).

CLI Commands

Beyond the LSP server itself (pytest-bdd-language-server serve, the default when no command is given — see Setup), the same package exposes standalone commands that don't speak LSP at all, reusing the exact same scanning and matching logic:

# List step definitions with no matching .feature usage anywhere.
# Exits 1 if any are found -- built for CI pipelines and pre-commit hooks.
pytest-bdd-language-server unused-steps [PATHS...] [--format text|json]

# List .feature step usages with no matching step definition -- the mirror
# of unused-steps. Exits 1 if any are found.
pytest-bdd-language-server undefined-steps [PATHS...] [--format text|json]

# List every step definition found, optionally filtered by keyword/pattern.
pytest-bdd-language-server list-steps [PATHS...] [--format text|json] [--query TEXT]

All three default to scanning the current directory if no PATHS are given. undefined-steps catches a gap pytest-bdd's own step resolution doesn't cover: StepDefinitionNotFoundError is only raised when a step actually runs, so an undefined step in a scenario that's skipped, filtered out (-k, markers, --lf, CI sharding), or simply never wired into a test file via scenarios(...) can go undetected indefinitely. This command checks every .feature file directly, independent of what a given pytest invocation happens to execute -- and without running any test code, fixtures, or setup to find out. Usages only matched by an unresolved cfparse+extra_types candidate are reported separately as unverifiable and don't affect the exit code, consistent with how Diagnostics treats the same situation.

Example, wired into CI:

pytest-bdd-language-server undefined-steps . || {
  echo "Found .feature steps with no matching definition -- see above."
  exit 1
}
pytest-bdd-language-server unused-steps . || {
  echo "Found step definitions with no .feature usage -- see above."
  exit 1
}

Architecture

  • Language: Python 3.10+
  • LSP framework: pygls
  • Gherkin parsing: gherkin-official — the same parser pytest-bdd uses internally
  • Step pattern matching: parse and parse_type — again, pytest-bdd's own dependencies, not a reimplementation
  • Step discovery: Python's ast module — statically parses decorators rather than importing your code

Design Decisions

A few choices worth knowing if you're reading the source:

  • Step definitions are discovered via ast, not by importing your code. Importing would require resolving your project's virtualenv, running arbitrary module-level side effects, and handling failures in third-party code — none of which belong in a scan that runs on every save. The tradeoff is that extra_types custom converters can't be resolved (see Known Limitations).
  • Matching reuses pytest-bdd's own libraries rather than reimplementing their semantics, so a step either statically matches or doesn't, exactly the way pytest-bdd itself would decide at runtime.
  • Workspace scans run in a thread executor, not directly on the event loop, so a large project doesn't block other LSP requests (hover, completion...) while indexing.

Known Limitations

  • cfparse with extra_types can't be matched statically — flagged in diagnostics/hover as unverifiable rather than silently mismatched.
  • references/definition from the Python side only recognize the cursor on the def line itself, not the decorator line above it.
  • No rename, formatting, call hierarchy, code actions, or semantic tokens. Gherkin syntax highlighting and folding are expected to come from a Treesitter grammar on the client side, not this server.
  • No incremental re-indexing. A .py or .feature change triggers a full workspace rescan rather than a targeted update — fast enough for typical project sizes, but a known scaling limit.

Development

This project uses uv for dependency management, ruff for linting, and pytest (with pytest-lsp for end-to-end tests that drive a real server subprocess over LSP) for testing.

# Install dependencies (including dev dependencies)
uv sync

# Run the full test suite
uv run pytest

# Run only the fast, non-LSP unit tests
uv run pytest tests/unit

# Lint
uv run ruff check .

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

Licensed under Mozilla Public License v2

Acknowledgments

Built with:

Special thanks to the pytest-bdd team for the framework this server exists to support.


Built with AI assistance, reviewed with care.

Metadata

Release files for pytest-bdd-language-server 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-bdd-language-server 1.0.0
File Size Uploaded Provenance Actions
pytest_bdd_language_server-1.0.0.tar.gz 35.3 kB Without provenance Details for pytest_bdd_language_server-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pytest-bdd-language-server 1.0.0
File Interpreter ABI Platform Actions
pytest_bdd_language_server-1.0.0-py3-none-any.whl Python 3 none any Details for pytest_bdd_language_server-1.0.0-py3-none-any.whl

Total release size: 81.3 kB

Release files / pytest_bdd_language_server-1.0.0.tar.gz

Download URL pytest_bdd_language_server-1.0.0.tar.gz
Size 35.3 kB
Tags Source
SHA-256 checksum
How to use checksums
09f40efa48f1475e418a2b24f3a5b9b49ad44d367aa70f3acf53a64da52fa9b6
BLAKE2b-256 checksum
How to use checksums
0a420cc8329b45128f674971074df692aba302d230dd18fb0200adffb7092bb0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pytest_bdd_language_server-1.0.0-py3-none-any.whl

Download URL pytest_bdd_language_server-1.0.0-py3-none-any.whl
Size 45.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
56b4a3f030d8e06b76133eec5887eb8621eb953e9d30ef3734774c50f0c162f1
BLAKE2b-256 checksum
How to use checksums
ed8ad99cec9f9cdc87400eacd837f4b8a625883a4b1554a930b0a06658018376
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.33 {"installer":{"name":"uv","version":"0.11.33","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for pytest-bdd-language-server 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page