What is Behave?
Behave is a Python implementation of Cucumber, specifically designed for Behavior-Driven Development (BDD). It allows you to write tests in natural language using Gherkin syntax, making tests readable by both technical and non-technical stakeholders.
Core Components of Behave
1. Feature Files
Feature files are written in Gherkin syntax and describe the behavior of your application. They are stored with a *.feature extension.
# login.feature
Feature: User Authentication
In order to access protected areas
As a registered user
I want to be able to log in
@smoke
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter valid credentials
And I click the login button
Then I should be logged in successfully
Scenario Outline: Login with different credentials
Given I am on the login page
When I enter "<username>" and "<password>"
Then I should see "<message>"
Examples:
| username | password | message |
| valid | valid | Welcome! |
| invalid | valid | Unknown user |
| valid | invalid | Incorrect password|
2. Step Definitions with Page Objects
Step definitions connect Gherkin steps to Python code. Using Page Objects helps create more maintainable and reusable code.
# pages/base_page.py
class BasePage:
def __init__(self, driver):
self.driver = driver
def find_element(self, locator):
return self.driver.find_element(*locator)
def type_text(self, locator, text):
self.find_element(locator).send_keys(text)
def click(self, locator):
self.find_element(locator).click()
def get_text(self, locator):
return self.find_element(locator).text
# pages/login_page.py
from selenium.webdriver.common.by import By
from .base_page import BasePage
class LoginPage(BasePage):
# Locators as class attributes
USERNAME_FIELD = (By.ID, 'username')
PASSWORD_FIELD = (By.ID, 'password')
LOGIN_BUTTON = (By.ID, 'login-button')
WELCOME_MESSAGE = (By.ID, 'welcome-text')
def navigate_to(self):
self.driver.get('http://example.com/login')
def enter_credentials(self, username, password):
self.type_text(self.USERNAME_FIELD, username)
self.type_text(self.PASSWORD_FIELD, password)
def click_login(self):
self.click(self.LOGIN_BUTTON)
def get_welcome_message(self):
return self.get_text(self.WELCOME_MESSAGE)
# steps/login_steps.py
from behave import given, when, then
from pages.login_page import LoginPage
@given('I am on the login page')
def step_impl(context):
context.login_page = LoginPage(context.driver)
context.login_page.navigate_to()
@when('I enter valid credentials')
def step_impl(context):
context.login_page.enter_credentials('user@example.com', 'password123')
context.login_page.click_login()
@then('I should be logged in successfully')
def step_impl(context):
welcome_message = context.login_page.get_welcome_message()
assert 'Welcome' in welcome_message
3. Environment Controls (environment.py)
The environment.py file is a powerful feature of Behave that provides hooks for test setup and teardown at different levels:
# environment.py
def before_all(context):
# Runs before any features or scenarios
context.config.setup_logging()
# Setup your test environment
context.base_url = 'http://example.com'
def before_feature(context, feature):
# Runs before each feature
if 'browser' in feature.tags:
context.browser = create_browser()
def before_scenario(context, scenario):
# Runs before each scenario
context.data = {} # Reset test data
def before_step(context, step):
# Runs before each step
context.step_start_time = time.time()
def after_step(context, step):
# Runs after each step
if step.status == "failed":
take_screenshot(context)
def after_scenario(context, scenario):
# Runs after each scenario
cleanup_test_data(context)
def after_feature(context, feature):
# Runs after each feature
if hasattr(context, 'browser'):
context.browser.quit()
def after_all(context):
# Runs after all features are completed
generate_test_report()
4. Behave Configuration
Behave can be configured through behave.ini or command-line arguments:
[behave]
# Define feature files location
paths = features
# Define steps directory
steps_directory = steps
# Show skipped steps in output
show_skipped = true
# Stop on first failure
stop = true
# Show timing information
show_timings = true
# Format output (can be pretty, plain, json, etc.)
format = pretty
# Don't capture stdout
capture = false
# Use JUnit format for CI integration
junit = true
junit_directory = reports/junit
5. Context and Page Objects – Sharing Data Between Steps
The context object is a key feature in Behave that allows sharing data and page objects between steps:
# models/user.py
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
age: int
# pages/user_page.py
from .base_page import BasePage
from selenium.webdriver.common.by import By
class UserPage(BasePage):
NAME_FIELD = (By.ID, 'name')
EMAIL_FIELD = (By.ID, 'email')
AGE_FIELD = (By.ID, 'age')
SUBMIT_BUTTON = (By.ID, 'submit')
def fill_user_form(self, user):
self.type_text(self.NAME_FIELD, user.name)
self.type_text(self.EMAIL_FIELD, user.email)
self.type_text(self.AGE_FIELD, str(user.age))
def submit_form(self):
self.click(self.SUBMIT_BUTTON)
# steps/user_steps.py
from behave import given, when, then
from models.user import User
from pages.user_page import UserPage
@given('I have initial user data')
def step_impl(context):
# Store model object in context
context.user = User(
name='John Doe',
email='john@example.com',
age=30
)
context.user_page = UserPage(context.driver)
# Parse table data into model objects if provided
if hasattr(context, 'table'):
context.users = [
User(
name=row['name'],
email=row['email'],
age=int(row['age'])
) for row in context.table
]
@when('I process the user data')
def step_impl(context):
context.user_page.fill_user_form(context.user)
context.user_page.submit_form()
@then('I should see the user profile')
def step_impl(context):
displayed_name = context.user_page.get_text(UserPage.NAME_FIELD)
assert displayed_name == context.user.name
6. Using Tags
Tags are powerful features in Behave for organizing and selecting tests:
@web @login
Feature: User Login
@smoke @fast
Scenario: Quick login check
Given I am on the login page
When I enter valid credentials
Then I should be logged in
@slow @regression
Scenario: Detailed login validation
Given I am on the login page
When I enter valid credentials
Then I should see welcome message
And my user preferences should be loaded
Run specific tags:
# Run scenarios tagged with @smokebehave --tags=@smoke
# Run scenarios with multiple tagsbehave --tags=@web --tags=@smoke
# Skip certain tagsbehave --tags=~@slow
# Complex tag expressionsbehave --tags="@smoke and not @slow"
7. Using Step Parameters
Behave supports different types of step parameters:
# Step with quoted string
@given('I search for "{term}"')
def step_impl(context, term):
context.search_term = term
# Step with numbers
@then('I should see {count:d} results')
def step_impl(context, count):
assert len(context.results) == count
# Step with custom type
from behave import register_type
import parse
@parse.with_pattern(r"\d+")
def parse_number(text):
return int(text)
register_type(Number=parse_number)
@then('I should see {count:Number} items')
def step_impl(context, count):
assert len(context.items) == count
8. Using Tables and Multi-line Text
Behave provides special support for tables and multi-line text:
Scenario: User registration with details
Given I have the following user details:
| name | email | age |
| John | john@email.com | 30 |
| Jane | jane@email.com | 25 |
And I have the following address:
"""
123 Test Street
Test City, 12345
Test Country
"""
When I register the users
Then they should be in the database
@given('I have the following user details')
def step_impl(context):
# context.table is a table object
context.users = []
for row in context.table:
context.users.append({
'name': row['name'],
'email': row['email'],
'age': int(row['age'])
})
@given('I have the following address')
def step_impl(context):
# context.text contains the multi-line text
context.address = context.text
Running Tests
Behave provides various command-line options for test execution:
# Run all featuresbehave
# Run specific featurebehave features/login.feature
# Run specific scenario by line numberbehave features/login.feature:10
# Generate JSON outputbehave --format=json --outfile=report.json
# Run in parallel using parallel-behave – Note: You must install parallel-behave via pip: pip install parallel-behaveparallel-behave -p 4 features/
# Debug with more verbose outputbehave --format=pretty --verbose
Conclusion
Behave is a powerful BDD framework that brings together the best of both worlds – readable specifications and Python’s simplicity. By understanding and utilizing its features effectively, you can create maintainable, readable, and robust test suites that serve as living documentation for your application.
The key to success with Behave is to:
– Write clear, focused feature files
– Create reusable step definitions
– Use the context object effectively
– Leverage hooks for proper setup and teardown
– Organize your project structure well
– Use tags for test organization and execution control
With these practices in place, Behave can help you build a strong BDD framework that supports your development process and improves communication between technical and non-technical team members.