Prompting an AI Assistant to Write Behave Step Definitions
AI assistants like GitHub Copilot and ChatGPT can generate Behave step definitions fast — but "fast" and "useful" are not the same thing. I've seen teams paste a Gherkin scenario into a chat window, accept the first output, and end up with steps that technically run but are too specific to reuse, too tightly coupled to test data, or missing the context patterns that Behave actually expects. The problem is almost never the AI. It's the prompt.
A good prompt for step definition generation is really a miniature design brief. It tells the assistant what framework you're using, how your project is structured, what reuse constraints you care about, and what the generated code needs to integrate with. When I started treating prompts that way — instead of just dropping in a raw scenario — the output went from "needs heavy editing" to "needs a quick review." That's a meaningful difference in workflow speed.
This article walks through the prompting patterns I rely on, the common mistakes that produce low-quality output, and how to iterate when the first result isn't quite right. The goal is a repeatable approach you can apply to your own suite today, not a one-time trick that only works for a single scenario.
Learn Node.js, Cucumber, GitHub Copilot, APIs, CI/CD, and modern automation by building a complete framework.
What to Include in Your Prompt Before You Paste the Gherkin
The single biggest lever you have is context. An AI assistant generating Behave steps in a vacuum will make assumptions — about how you handle the context object, whether you use a shared HTTP client, how your project folders are laid out. Those assumptions are often wrong for your specific suite. Front-loading the right context collapses the gap between what the AI guesses and what you actually need.
Here's the minimum context block I include at the top of any step-generation prompt:
Framework: Python Behave
Step file location: features/steps/
Shared state: stored on the `context` object (e.g., context.response, context.token)
HTTP client: requests.Session, instantiated in environment.py before_all
Naming convention: step functions use snake_case, one step file per feature area
Reuse requirement: steps must use regex or parse patterns broad enough to cover
multiple scenarios, not hard-coded literal strings
After that block, paste the Gherkin. The assistant now knows it should write @given, @when, @then decorators with context as the first argument, that it should reach for context.response rather than a local variable, and that it should parameterize step text rather than hard-code values.
One pattern I've found especially effective: include a single example of an existing step from your codebase. Something like:
# Existing step for reference — match this style:
@then('the response status code is {status_code:d}')
def step_check_status(context, status_code):
assert context.response.status_code == status_code
That one example teaches the assistant your actual conventions faster than any amount of prose description. It sees the decorator style, the parse format string, the assertion pattern, and the naming convention all at once. This is the same principle behind keeping step definitions reusable — broad parameterization over hard-coded literals — and it's worth enforcing that standard in your prompts from the start.
Prompting for Parameterization and Avoiding the Hard-Coded Trap
The most common failure mode I see in AI-generated Behave steps is over-specificity. You paste a scenario that says When I POST to "/api/users" with username "alice", and the assistant writes:
@when('I POST to "/api/users" with username "alice"')
def step_post_users_alice(context):
context.response = context.session.post("/api/users", json={"username": "alice"})
That step is useless the moment you need a second user. The fix is to be explicit in your prompt about parameterization expectations. I add a line like this:
Parameterization rule: any string that could vary between scenarios (URLs, usernames,
status codes, field names, values) MUST be a parse-format parameter, not a literal.
Use `parse` style ({name}) or regex groups — never hard-code scenario-specific values.
With that constraint in place, the same scenario produces something much closer to:
@when('I POST to "{endpoint}" with username "{username}"')
def step_post_endpoint_with_username(context, endpoint, username):
context.response = context.session.post(endpoint, json={"username": username})
That step works for every user and every endpoint. It's the kind of output that fits naturally into a project that needs to scale past dozens of scenarios without accumulating a mountain of near-duplicate step functions.
A second thing worth prompting explicitly: where shared state lives. If you don't say, the AI may store response data in a local variable inside the step function, which means your @then step can't see it. I include: "All data shared between steps must be stored on the context object. Never use module-level variables for test state." That one sentence eliminates an entire class of subtle bugs in the generated output.
For multi-step scenarios that involve setup and teardown, also tell the AI whether you're using Behave's before_scenario / after_scenario hooks or handling setup inside @given steps. The generated code will be structured very differently depending on the answer, and the AI has no way to know which pattern your team has chosen.
Iterating on AI Output: Review Checklist and Follow-Up Prompts
Even a well-crafted initial prompt rarely produces code you can merge without reading it. What it should produce is code that passes a quick review rather than requiring a full rewrite. Here's the checklist I run through on every batch of AI-generated steps:
- Context object usage: Is every piece of shared state on
context? No module-level globals, no local variables that need to survive past the current step. - Parameterization: Are all variable values in parse-format parameters? Check endpoint strings, field names, expected values.
- Decorator format: Are decorators using the right import (
from behave import given, when, then) and the right format style for your project? - Assertion quality: Does the
@thenstep assert something specific, or just check that a request didn't throw an exception? - Step text collisions: Does any generated step text match an existing step pattern so closely that Behave might match the wrong one?
When the output fails one of those checks, a targeted follow-up prompt fixes it faster than manual editing. For example: "The step step_check_response_body stores the parsed JSON in a local variable. Refactor it to store the result on context.response_json instead, and update any steps that reference that data." That kind of precise correction prompt — naming the specific function and the specific problem — gets a reliable fix. Vague prompts like "make the state handling better" get vague results.
One more pattern worth building into your workflow: ask the AI to generate a step outline comment before writing the code. Something like: "Before writing the step functions, list each step text, its parameter names and types, and what it reads/writes on context." That outline forces the assistant to think through the data flow before generating code, and it gives you a fast way to spot design problems before they're baked into function bodies. It also makes the generated steps easier to fit alongside safely shared test data across your suite, since you can verify the context keys match what the rest of your suite expects before a single line of step code is written.
The bottom line: AI-generated Behave steps are a starting point, not a finished product — but with the right prompting habits, that starting point can be close enough that your job becomes review and integration rather than wholesale rewriting. That's the productivity gain worth chasing.