GherkinLens
Navigate, validate, and format Gherkin feature files powered by your pytest-bdd step definitions — all without running pytest.
GherkinLens turns VS Code into a first-class editor for pytest-bdd projects. It indexes your Python step definition files and brings Go to Definition, Hover, Autocomplete, Diagnostics, Quick Fix, CodeLens, and full document formatting directly into your .feature files.
Requirements
- A Python project using pytest-bdd
- Step definitions written with
@given, @when, @then, or @step decorators
- VS Code 1.85.0 or newer
No special Python environment or pytest installation is required inside VS Code — GherkinLens reads your source files directly.
Features
Go to Definition
Press F12 or Ctrl+Click on any Gherkin step to jump directly to the matching Python decorator in your step definition file.
- Works for
@given, @when, @then, and @step decorators
- Supports plain string patterns,
parsers.parse(...) format strings, and parsers.re(...) regular expressions
- Holding Ctrl while hovering underlines the full step line, giving you a visual preview before you jump
Hover
Hover over any Gherkin step to see rich inline documentation pulled live from your Python source:
- The decorator line (e.g.
@given(parsers.parse("the user {username} logs in")))
- The function signature (scanned forward through multi-line decorators)
- The relative file path and line number where the definition lives
- A clickable "Open in editor" link that navigates directly to the definition
When a step matches multiple definitions (e.g. via @step which expands to all three keyword types), each match is shown as a separate section separated by a horizontal rule.
Autocomplete
Start typing a Gherkin step keyword (Given, When, Then, And, But, or *) followed by a space and GherkinLens offers completions sourced from your indexed step definitions.
- Completions show the decorator type (
@given, @when, @then) and the source file and line number in the detail line
- Each completion item shows a Python code block with the decorator and function signature in the documentation popup
- Tab-stop navigation —
{param} placeholders in parsers.parse patterns become snippet tab stops; named capture groups in parsers.re patterns become named tab stops. Press Tab to move from one to the next
- Snippet session tracking — once you accept a completion, the automatic suggestion popup is suppressed on that line while you fill in the tab stops, so Tab always moves to the next field without interference
- Deduplication —
@step decorators (which register for given/when/then simultaneously) appear only once in the completion list
- Auto-suppression — once the text on the current line already matches a known step definition, completions stop appearing automatically (manual invocation via Ctrl+Space still works)
Diagnostics
GherkinLens continuously checks every open .feature file against the indexed step definitions and reports problems in real time.
- Unmatched steps get a yellow (warning) squiggly underline from the first non-whitespace character to the end of the step text
- All unmatched steps also appear in the Problems panel (
Ctrl+Shift+M) with the message No step definition found for: "..."
- Diagnostics update automatically when:
- A
.feature file is opened or saved
- A Python step definition file is saved (auto re-index triggers and squiggles refresh immediately)
- The step cache is rebuilt manually via
GherkinLens: Reindex Step Definitions
- The status bar item shows
⚠ N unmatched when any open file has broken steps
Quick Fix — Create Step Definition
When a step is unmatched, a lightbulb (💡) appears on that line. Click it (or press Ctrl+.) and select "Create step definition" to scaffold a stub automatically.
What GherkinLens generates for you:
- A correctly named Python function in
snake_case, derived intelligently from your step text (truncated at 60 characters at a word boundary)
- A
@given / @when / @then / @step decorator matching the keyword used in the feature file (And and But map to @step)
- Smart parameter detection — quoted strings in the step text become
{param} placeholders using a context-aware naming strategy:
- The word immediately before the opening quote (e.g.
username from the user "alice")
- The quoted content itself if it is a plain identifier (e.g.
button from "button")
- A sequential fallback (
param1, param2, …)
- Numeric parameter detection — standalone floats (
1.5) become {float} and integers (42) become {int}, with suffixes ({float2}, {int2}) for duplicates
- When parameters are present,
parsers.parse("...") is used as the decorator argument so pytest-bdd captures the values at runtime
- A
raise NotImplementedError("step not implemented") body so tests fail loudly rather than silently passing
Import handling:
- Existing file — GherkinLens checks whether
parsers is already imported. If not, it prepends from pytest_bdd import parsers at the top of the file automatically
- New file — A complete import header is generated:
from pytest_bdd import given, when, then, step, parsers
File picker:
A quick-pick dropdown lists all currently indexed Python files by their relative workspace path. A separator at the bottom offers "Create new file…" — enter a .py filename and the file is created in the workspace root.
After insertion:
- The file opens and the cursor is placed directly on the
raise NotImplementedError(...) body line so you can start typing the implementation immediately
- The file is saved automatically so the FileSystemWatcher fires and the step cache re-indexes the new definition — the squiggly on your feature file disappears without any manual action
- The entire operation is a single undoable edit (
Ctrl+Z reverts the stub insertion)
CodeLens — Step Usage Count
Every step definition decorator in a Python file gets a CodeLens line above it showing how many unique scenarios across all feature files use that step.
Used in 3 scenarios
@given(parsers.parse("the user {username} is logged in"))
def step_the_user_username_is_logged_in(username):
...
- Click the CodeLens to open the References peek panel showing every scenario that uses the step, grouped by feature file with jump-to-line navigation
- The count is per unique scenario (not per step occurrence), so a step used twice in the same scenario counts as 1
- Lens counts update automatically when Python files or feature files change on disk
- Debounced refresh — rapid saves (e.g. auto-save) are collapsed into a single lens refresh to avoid flicker
Status Bar
A persistent status bar item appears on the right side of the VS Code status bar after indexing completes.
| Display |
Meaning |
✓ 42 steps |
Indexing complete, all open steps matched |
✓ 42 steps ⚠ 3 unmatched |
3 steps in open files have no matching definition |
✓ 42 steps 🔄⃠ |
Auto re-index is off — the cache will not update on file save |
✓ 42 steps ⚠ 3 unmatched 🔄⃠ |
Both conditions active |
- Click the status bar item to run GherkinLens: Show Cache Statistics
- The tooltip shows the full count:
GherkinLens: 42 step definitions in 7 files
- When auto re-index is off, the tooltip adds:
Auto re-index is OFF — run Reindex manually
Syntax Highlighting
GherkinLens registers a full TextMate grammar for the Gherkin language (.feature files) covering:
- Block keywords:
Feature, Rule, Background, Scenario, Scenario Outline, Scenario Template, Example, Examples, Scenarios
- Step keywords:
Given, When, Then, And, But
- Tags (
@tagName)
- Step parameters (
"quoted strings" and <angle bracket placeholders>)
- Data table cell separators and content
- Docstrings (triple-quote
""" and backtick ``` delimiters)
- Comments (
# ...)
The .feature file icon is also registered for light and dark themes.
Format any .feature file with Shift+Alt+F or via GherkinLens: Format Feature File.
GherkinLens implements a complete Gherkin formatter that enforces modern style without any external tool dependency.
Indentation Hierarchy
With the default indentSize of 2 spaces:
Feature: User authentication # level 0
Background: # level 1
Given the database is running # level 2
Scenario: Successful login # level 1
Given the login page is open # level 2
When the user enters "alice" # level 2
Then the dashboard is shown # level 2
| column | value | # level 3 (data table)
""" # level 3 (docstring delimiter)
content
""" # level 3
Rule: Admin users # level 1
Scenario: Admin panel access # level 2 (inside Rule)
Given the user is an admin # level 3
Blank Line Policy
| Location |
Blank lines enforced |
Before Background:, Scenario:, Scenario Outline:, Rule:, Examples: |
Exactly 1 |
Before Feature: |
0 (always first) |
| Between tags and their element |
0 |
| Between a step and its data table |
0 |
| Between a step and its docstring |
0 |
| Consecutive blank lines anywhere |
Collapsed to 1 |
Table Column Alignment
All pipe-delimited tables (data tables and Examples tables) are automatically realigned so every column is padded to the width of its longest cell:
# Before formatting
| Name | Age | City |
| Alice | 30 | New York |
| Bob | 25 | LA |
# After formatting
| Name | Age | City |
| Alice | 30 | New York |
| Bob | 25 | LA |
Keyword Casing
The gherkinLens.format.keywordCase setting controls how keywords are written:
| Mode |
Example |
title (default) |
Feature:, Scenario:, Given, When, Then |
lower |
feature:, scenario:, given, when, then |
upper |
FEATURE:, SCENARIO:, GIVEN, WHEN, THEN |
preserve |
No changes to keyword casing |
Docstring Content Preservation
Lines inside """ or ``` docstrings are passed through as-is (only trailing whitespace is trimmed). Only the opening and closing delimiters are re-indented. The formatter never alters the content you wrote inside a docstring.
Tags are attached to their element without any blank line between them, but the blank line before the tag group (and its element) is preserved:
@smoke @regression
Scenario: Login with valid credentials
Rule Blocks
Rule: blocks increase the indentation of everything inside them by one level. Consecutive Rule: blocks do not compound — each resets to the correct baseline:
Feature: Shopping cart
Rule: Guest users
Scenario: Add to cart
Given I am not logged in
Rule: Authenticated users
Scenario: Save for later
Given I am logged in
Single Undo
The entire format operation is applied as a single TextEdit.replace, so one Ctrl+Z reverts the whole document to its pre-format state.
Live File Watching
GherkinLens watches both Python step files and feature files for changes on disk:
- Python files — when a step definition file is created, modified, or deleted, the cache updates automatically (respects the
autoReindex setting)
- Feature files — when a
.feature file is created, modified, or deleted, the CodeLens usage index updates automatically
- Watchers respect the
excludePattern setting — virtual environments and build directories are never re-indexed
Decorators that appear after a # on the same line are never indexed. Commenting out a step definition removes it from the cache on the next re-index.
# @given("this step is disabled and will not appear in completions")
@given("this step is active")
def step_active():
...
Commands
Open the Command Palette (Ctrl+Shift+P) and type GherkinLens to see all available commands.
| Command |
Description |
GherkinLens: Reindex Step Definitions |
Fully re-scan all Python step files and rebuild the feature usage index. Use this after toggling Auto Re-index off, or after large batch changes. |
GherkinLens: Show Cache Statistics |
Display a notification with the total number of indexed step definitions and the number of Python files they come from. Also accessible by clicking the status bar item. |
GherkinLens: Toggle Auto Re-index |
Turn automatic re-indexing on or off for the current workspace. Saved as a workspace setting. Useful for very large projects where you prefer to reindex manually. |
GherkinLens: Format Feature File |
Format the currently active .feature file. Equivalent to pressing Shift+Alt+F while a feature file is open. |
Settings
All settings are under the gherkinLens namespace and can be set in User Settings or Workspace Settings.
Indexing
| Setting |
Type |
Default |
Description |
gherkinLens.includePattern |
string |
**/{*_steps.py,*steps.py,test_*.py,*_test.py,conftest.py} |
Glob pattern for Python files to scan for step definitions. Adjust this if your step files follow a different naming convention. |
gherkinLens.excludePattern |
string |
**/{node_modules,.venv,venv,env,virtualenv,.tox,__pycache__,dist,build,.eggs}/** |
Glob pattern for files and directories to skip during indexing. Virtual environments and build directories are excluded by default. Add any additional paths here. |
gherkinLens.autoIndexOnStartup |
boolean |
true |
When true, GherkinLens indexes all step definitions automatically when the workspace opens. Disable to skip the startup scan and index manually. |
gherkinLens.autoReindex |
boolean |
true |
When true, GherkinLens re-indexes step definitions automatically whenever a Python file matching includePattern is saved, created, or deleted. Disable this for very large projects and use GherkinLens: Reindex Step Definitions on demand. Can also be toggled via the GherkinLens: Toggle Auto Re-index command without opening Settings. |
| Setting |
Type |
Default |
Description |
gherkinLens.format.indentSize |
number (1–8) |
2 |
Number of spaces per indent level when formatting .feature files. Applies to all indented elements: scenarios, steps, table rows, and docstring delimiters. |
gherkinLens.format.keywordCase |
string (enum) |
"title" |
Casing applied to Gherkin keywords when formatting. "title" produces Feature, Scenario, Given; "lower" produces feature, scenario, given; "upper" produces FEATURE, SCENARIO, GIVEN; "preserve" leaves keyword casing untouched. |
Smart Matching
GherkinLens supports the three pattern types used by pytest-bdd:
Plain Strings
@given("the login page is open")
Matched by direct string comparison (case-insensitive, normalizing ' to ").
parsers.parse Patterns
@given(parsers.parse("the user {username} logs in as {role}"))
{param} placeholders are converted to a regex capture group (.+?) for matching. The step text the user alice logs in as admin will match.
parsers.re Patterns
@given(parsers.re(r"the user (?P<username>\w+) logs in"))
Python-specific regex syntax is translated to JavaScript-compatible equivalents before matching:
(?P<name>...) named groups → (?<name>...)
(?P=name) backreferences → \k<name>
- Inline flags
(?imsx) are extracted and applied as regex flags
Scoring and Ranking
When multiple definitions could match a step, GherkinLens ranks them by confidence:
- Exact string match (score 1000) — the normalized pattern equals the step text exactly
- Regex match with
parsers.re (score 500)
- Word overlap score for
parsers.parse patterns — a combined metric of keyword overlap ratio, string length similarity, and word count similarity. Patterns scoring below 50% word overlap are discarded
Narrow the Include Pattern
The default includePattern already pre-filters to common step file naming conventions. If your project has a specific layout, narrow it further:
"gherkinLens.includePattern": "**/tests/steps/**/*.py"
Expand the Exclude Pattern
Make sure your virtual environment and any generated code directories are excluded:
"gherkinLens.excludePattern": "**/{node_modules,.venv,venv,env,virtualenv,.tox,__pycache__,dist,build,.eggs,.mypy_cache,.pytest_cache}/**"
Disable Auto Re-index
For projects with hundreds of Python files, turn off automatic re-indexing so the cache only rebuilds when you explicitly ask for it:
"gherkinLens.autoReindex": false
Or toggle it on/off quickly without touching settings via GherkinLens: Toggle Auto Re-index. The status bar will show 🔄⃠ when auto re-index is off as a reminder to reindex manually after making changes.
How It Works
GherkinLens scans Python files using a regular expression that matches the decorator pattern:
@(given|when|then|step)\s*(\s*parsers\.(parse|re)\s*)?\s*r?(['"])(.*?)\4
It never imports or executes your Python code. Parsing is done entirely from source text, which means:
- No Python interpreter required in VS Code
- Works with any Python version your project uses
- Virtual environments are irrelevant to indexing
- No pytest plugins or conftest discovery needed
The step cache holds all definitions grouped by decorator type (given, when, then). @step definitions are expanded into all three types at index time. The feature usage index is a separate, bidirectional map from step definitions to the feature file lines that use them — this is what powers the CodeLens scenario counts.
Both indexes are rebuilt in parallel batches and emit change events to keep all providers (diagnostics, CodeLens, hover, completions) synchronized.
Extension Settings Reference (JSON)
{
// ── Indexing ──────────────────────────────────────────────────────────────
// Glob pattern for Python step definition files to index
"gherkinLens.includePattern": "**/{*_steps.py,*steps.py,test_*.py,*_test.py,conftest.py}",
// Glob pattern for files/dirs to exclude (virtual envs, build output, etc.)
"gherkinLens.excludePattern": "**/{node_modules,.venv,venv,env,virtualenv,.tox,__pycache__,dist,build,.eggs}/**",
// Index step definitions automatically when the workspace opens
"gherkinLens.autoIndexOnStartup": true,
// Re-index automatically when Python step files change on disk
// Disable for large projects and reindex manually
"gherkinLens.autoReindex": true,
// ── Formatting ────────────────────────────────────────────────────────────
// Spaces per indent level when formatting .feature files (1–8)
"gherkinLens.format.indentSize": 2,
// Keyword casing: "title" | "lower" | "upper" | "preserve"
"gherkinLens.format.keywordCase": "title"
}
Keyboard Shortcuts
| Action |
Default Shortcut |
| Go to Definition |
F12 or Ctrl+Click |
| Peek Definition |
Alt+F12 |
| Format Feature File |
Shift+Alt+F |
| Quick Fix (lightbulb) |
Ctrl+. |
| Trigger Completions |
Ctrl+Space |
| Show Problems panel |
Ctrl+Shift+M |