Step-by-Step Playwright with Behave BDD and Allure Reports Implementation Tutorial
Test Automation Framework with Playwright, Behave BDD, POM, and Allure
Step-by-Step Playwright with Behave BDD and Allure Reports Implementation Tutorial
This article explores how to build a BDD (Behavior-Driven Development) test automation framework using Playwright for powerful browser automation, Behave for BDD-style test execution, Page Object Model (POM) design for maintainability, and Allure for comprehensive reporting. By integrating these technologies, we create a robust framework that enhances test readability, reusability, and visibility, making it easier to manage UI test cases in a scalable manner. Whether you’re new to automation or looking to improve your existing setup, this guide will provide step-by-step instructions to get you started with a well-structured testing solution using SauceDemo as the application under test.
If you’re interested in API testing, be sure to check out the second part of the series.
Pre-requisite:
- Python version 3.8+
- VS Code (can use preferred IDE or code editor)
- Allure CLI version 2.24+

Why This Framework
- Playwright → A powerful browser automation tool
- Behave (BDD) → Helps write tests in a human-readable format
- Page Object Model (POM) → Keeps test code modular and maintainable
- Allure Reports → Generates detailed, visual test reports
Step 1: Installing the Required dependencies
Make sure you have Allure CLI installed and accessible in System PATH variable along with Python 3.8+.
Now, install the required Python libraries:
pip install playwright behave allure-behave
playwright install
playwright installdownloads browser binaries so tests can run.allure-behaveis needed to generate Allure reports.
Step 2: Setting Up the Project Structure
A clean and well-organized project structure makes tests easy to maintain. Here’s how we structure our framework:
saucedemo_tests/
│── features/ # BDD test scenarios
│ ├── login.feature
│ ├── cart.feature
│ │── steps/ # Step definitions for BDD scenarios
│ │ ├── login_steps.py
│ │ ├── cart_steps.py
│── pages/ # Page Object Model (POM) implementation
│ ├── base_page.py
│ ├── login_page.py
│ ├── inventory_page.py
│── reports/ # Stores screenshots and test reports
│ ├── screenshots/
│ ├── allure-results/
│── environment.py # Hooks for test setup & teardown
│── behave.ini # Configuration file for Behave
│── requirements.txt # Dependencies
│── README.md # Project documentation
Why This Structure?
features/→ Stores human-readable test scenarios (Gherkin syntax).steps/→ Step definitions link Gherkin steps to Python code.pages/→ Implements the Page Object Model (POM) to keep test logic separate from test steps.reports/→ Contains screenshots & Allure reports.environment.py→ Handles test setup/cleanup like launching and closing browsers
Step 3: Implementing the Page Object Model (POM)
Page Object Model (POM) keeps your test automation neat and straightforward. By encapsulating page elements and actions within dedicated page classes, we eliminate redundant code across multiple tests, ensuring that modifications (like UI changes) only need to be updated in one place.
This approach maps methods directly to application functionalities, making test scripts more readable and intuitive. Additionally, since tests call reusable page methods instead of hardcoded element locators, maintenance is minimal, reducing the risk of brittle tests when the UI evolves.
Base Page (base_page.py)
A reusable class for common actions for all pages.
from playwright.sync_api import Page
class BasePage:
def __init__(self, page: Page):
self.page = page
def go_to(self, url):
self.page.goto(url)
def take_screenshot(self, step_name):
screenshot_path = f"reports/screenshots/{step_name}.png"
self.page.screenshot(path=screenshot_path)
Step-by-step explanation:
from playwright.sync_api import Page
- This imports
Pagefrom Playwright's synchronous API. Pagerepresents a browser page (or tab) and allows interaction with web elements (clicking buttons, filling forms, taking screenshots, etc.).
class BasePage: def __init__(self, page: Page): self.page = page
BasePageis a parent class for all page objects.- It initializes the
pageinstance (which is passed from test steps) so that all child pages can interact with the browser.
def got_to(self, url):
- Purpose: A reusable method to navigate to a given
url. - Instead of calling
page.goto("https://example.com")directly in each test, you simply callsome_page.go_to("https://example.com").
def take_screenshot(self, url):
- Purpose: Captures a screenshot and saves it under
reports/screenshots/. - The step name (e.g.,
"login_page_loaded") becomes part of the file name, making it easy to track test progress. - If a test fails, this helps with debugging since you can visually check the screenshots.
Why Have a Base Page?
- Prevents Code Duplication → All common actions (navigation, screenshots, waiting for elements, etc.) are in one place.
- Encourages Reusability → Instead of redefining methods in every page class, all pages just inherit from
BasePage. - Simplifies Maintenance → If Playwright updates or a method needs improvement, you only change it in
BasePageinstead of every test file.
Login Page (login_page.py)
Handles login functionality.
from pages.base_page import BasePage
class LoginPage(BasePage):
def __init__(self, page):
super().__init__(page)
self.username_input = page.locator("#user-name")
self.password_input = page.locator("#password")
self.login_button = page.locator("#login-button")
def login(self, username, password):
self.username_input.fill(username)
self.password_input.fill(password)
self.login_button.click()
Inventory Page (inventory_page.py)
Handles inventory-related actions.
from pages.base_page import BasePage
class InventoryPage(BasePage):
def __init__(self, page):
super().__init__(page)
self.add_to_cart_button = page.locator("button[id^='add-to-cart']")
self.cart_icon = page.locator("#shopping_cart_container")
def add_first_item_to_cart(self):
self.add_to_cart_button.first.click()
def go_to_cart(self):
self.cart_icon.click()
What Does super().__init__(page) Do?
- Inheriting from
BasePage
LoginPageis a child class ofBasePage.- It needs to initialize
BasePagefirst so thatself.page(Playwright'sPageinstance) is properly set up.
- Calling Parent Class (
BasePage) Constructor
super().__init__(page)callsBasePage’s__init__method- This assigns
self.page = page, allowingLoginPageto useself.pagefor interactions.
- Ensures
LoginPageCan Use Common Methods
- Since
LoginPageinherits fromBasePage, it can access allBasePagemethods (likego_to(url)andtake_screenshot(step_name)) without redefining them.
Why is this important?
- Keeps code DRY (Don’t Repeat Yourself) → No need to reinitialize
self.pagein every page class. - Ensures proper inheritance →
LoginPagegets all methods fromBasePage. - Makes the framework scalable → Any future pages (e.g.,
InventoryPage) can just inherit fromBasePageand instantly gain common functionality.
Step 4: Writing Feature Files
Feature files define test scenarios in plain English (Gherkin syntax).
Login Feature (features/login.feature)
Feature: Login to SauceDemo
Scenario: Successful login
Given the user is on the SauceDemo login page
When they enter valid credentials
Then they should be redirected to the inventory page
Step 5: Writing Step Definitions
Now, we map each feature step to Python code.
Login Steps (features/steps/login_steps.py)
from behave import given, when, then
from playwright.sync_api import sync_playwright
from pages.login_page import LoginPage
from pages.inventory_page import InventoryPage
import allure
@given("the user is on the SauceDemo login page")
def step_impl(context):
playwright = sync_playwright().start()
context.browser = playwright.chromium.launch(headless=False)
context.page = context.browser.new_page()
context.login_page = LoginPage(context.page)
context.login_page.go_to("https://www.saucedemo.com/")
context.login_page.take_screenshot("login_page")
@when("they enter valid credentials")
def step_impl(context):
context.login_page.login("standard_user", "secret_sauce")
context.login_page.take_screenshot("after_login")
@then("they should be redirected to the inventory page")
def step_impl(context):
context.inventory_page = InventoryPage(context.page)
assert "inventory" in context.page.url
context.inventory_page.take_screenshot("inventory_page")
context.browser.close()
Step-by-step explanation:
@then("they should be redirected to the inventory page")
- This @then decorator is a Behave BDD step.
- It matches the Gherkin step in the
.featurefile, for example:
Then they should be redirected to the inventory page
def step_impl(context):
- This defines the step implementation in Python.
contextis a special object in Behave that holds shared data across steps.- It contains things like the Playwright browser, pages, and test state.
context.inventory_page = InventoryPage(context.page)
- It creates an instance of the
InventoryPageclass, which follows the Page Object Model (POM), thus dynamically adding ainventory_pageattribute tocontext context.page(the Playwright page) is passed to theInventoryPage, so the test can interact with elements.
assert "inventory in context.page.url
- Assertion check: Ensures the browser is redirected to the Inventory page.
- The URL should contain
"inventory"(e.g.,[https://www.saucedemo.com/inventory.html).](https://www.saucedemo.com/inventory.html).)
context.inventory_page.take_screenshot("inventory_page")
- Calls the
take_screenshotmethod fromInventoryPageand saves screenshot as “inventory_page”
context.browser.close()
- Closes the Playwright browser after completing the test.
Step 6: Configuring Behave
environment.py (Setup and Teardown Hooks)
This file manages the setup and teardown for each test scenario.
from playwright.sync_api import sync_playwright
def before_all(context):
"""Set up Playwright before all tests."""
context.playwright = sync_playwright().start()
context.browser = context.playwright.chromium.launch(headless=False) # Change to True for headless execution
def before_scenario(context, scenario):
"""Set up a new browser page before each scenario."""
context.page = context.browser.new_page()
def after_scenario(context, scenario):
"""Capture a screenshot on failure and close the page."""
if scenario.status == "failed":
screenshot_path = f"reports/screenshots/{scenario.name}.png"
context.page.screenshot(path=screenshot_path)
context.page.close()
def after_all(context):
"""Close the browser and stop Playwright after all tests."""
context.browser.close()
context.playwright.stop()
- Before all tests: Starts Playwright and launches a browser.
- Before each scenario: Opens a new browser page.
- After each scenario: Captures a screenshot if the test fails.
- After all tests: Closes the browser and stops Playwright.
behave.ini (Configuration)
This file configures Behave to use Allure reporting and structured output.
[behave]
format = pretty
show_timings = true
show_skipped = false
junit = true
outfile = reports/behave-report.xml
default_tags = ~@wip
Key configurations:
- Pretty output formatting for better readability.
- JUnit report generation (
behave-report.xml) for integration with CI/CD tools. - Skips tests tagged with
@wip(Work In Progress).
requirements.txt
This file lists all dependencies required for the project.
playwright
behave
allure-behave
pytest
Step 7: Running Tests & Generating Allure Reports
Run a single feature test with Allure reporting
behave features/login.feature -f allure_behave.formatter:AllureFormatter -o reports/allure-results
Generate Allure report
allure serve reports/allure-results

Allure report generated after executing features/login.feature test scenario
Conclusion
You’ve now set up a solid BDD test automation framework using Playwright, Behave, the Page Object Model (POM), and Allure. By combining these tools, you get a setup that’s easier to read, reuse, and scale, plus great reporting to keep everything in check. Now that you’ve got a better understanding of the process, can you complete the Test Case scenario forcart.feature and cart_steps.py as mentioned in Step 2?!
P.S. Here is a python script to automate the entire project setup to get you started:
setup_project.py
# The following script will automatically create the
# required directories and files for a Playwright + Behave + POM + Allure project. https://medium.com/@antonio.uxcreator
import os
# Define project directories
directories = [
"saucedemo_tests/features/steps",
"saucedemo_tests/pages",
"saucedemo_tests/reports/screenshots",
"saucedemo_tests/reports/allure-results"
]
# Define files and their contents
files_content = {
"saucedemo_tests/pages/base_page.py": """\
from playwright.sync_api import Page
class BasePage:
def __init__(self, page: Page):
self.page = page
def go_to(self, url):
self.page.goto(url)
def take_screenshot(self, step_name):
screenshot_path = f"reports/screenshots/{step_name}.png"
self.page.screenshot(path=screenshot_path)
""",
"saucedemo_tests/pages/login_page.py": """\
from pages.base_page import BasePage
class LoginPage(BasePage):
def __init__(self, page):
super().__init__(page)
self.username_input = page.locator("#user-name")
self.password_input = page.locator("#password")
self.login_button = page.locator("#login-button")
def login(self, username, password):
self.username_input.fill(username)
self.password_input.fill(password)
self.login_button.click()
""",
"saucedemo_tests/pages/inventory_page.py": """\
from pages.base_page import BasePage
class InventoryPage(BasePage):
def __init__(self, page):
super().__init__(page)
self.add_to_cart_button = page.locator("button[id^='add-to-cart']")
self.cart_icon = page.locator("#shopping_cart_container")
def add_first_item_to_cart(self):
self.add_to_cart_button.first.click()
def go_to_cart(self):
self.cart_icon.click()
""",
"saucedemo_tests/features/login.feature": """\
Feature: Login to SauceDemo
Scenario: Successful login
Given the user is on the SauceDemo login page
When they enter valid credentials
Then they should be redirected to the inventory page
""",
"saucedemo_tests/features/steps/login_steps.py": """\
from behave import given, when, then
from playwright.sync_api import sync_playwright
from pages.login_page import LoginPage
from pages.inventory_page import InventoryPage
import allure
@given("the user is on the SauceDemo login page")
def step_impl(context):
playwright = sync_playwright().start()
context.browser = playwright.chromium.launch(headless=False)
context.page = context.browser.new_page()
context.login_page = LoginPage(context.page)
context.login_page.go_to("https://www.saucedemo.com/")
context.login_page.take_screenshot("login_page")
@when("they enter valid credentials")
def step_impl(context):
context.login_page.login("standard_user", "secret_sauce")
context.login_page.take_screenshot("after_login")
@then("they should be redirected to the inventory page")
def step_impl(context):
context.inventory_page = InventoryPage(context.page)
assert "inventory" in context.page.url
context.inventory_page.take_screenshot("inventory_page")
context.browser.close()
""",
"saucedemo_tests/environment.py": """\
from playwright.sync_api import sync_playwright
def before_all(context):
\"\"\"Set up Playwright before all tests.\"\"\"
context.playwright = sync_playwright().start()
context.browser = context.playwright.chromium.launch(headless=False) # Change to True for headless execution
def before_scenario(context, scenario):
\"\"\"Set up a new browser page before each scenario.\"\"\"
context.page = context.browser.new_page()
def after_scenario(context, scenario):
\"\"\"Capture a screenshot on failure and close the page.\"\"\"
if scenario.status == "failed":
screenshot_path = f"reports/screenshots/{scenario.name}.png"
context.page.screenshot(path=screenshot_path)
context.page.close()
def after_all(context):
\"\"\"Close the browser and stop Playwright after all tests.\"\"\"
context.browser.close()
context.playwright.stop()
""",
"saucedemo_tests/behave.ini": """\
[behave]
format = pretty
show_timings = true
show_skipped = false
junit = true
outfile = reports/behave-report.xml
default_tags = ~@wip
""",
"saucedemo_tests/requirements.txt": """\
playwright
behave
allure-behave
pytest
""",
"saucedemo_tests/README.md": """\
# SauceDemo Test Automation
## Overview
This is a **Playwright + Behave (BDD) + POM** based test automation framework for [SauceDemo](https://www.saucedemo.com/).
## Tech Stack
- **Playwright**: Browser automation
- **Behave**: BDD framework
- **Page Object Model (POM)**: Better test structure
- **Allure**: Test reporting
---
## Setup Instructions
### 1. Install Dependencies
```bash
pip install -r requirements.txt
2. Install Playwright Browsers
playwright install
Project Structure
your_project/
├── features/
│ ├── steps/
│ │ └── step_definitions.py
│ └── your_feature.feature
├── pages/
│ ├── __init__.py
│ └── your_page.py
├── reports/
│ ├── screenshots/
│ ├── allure-results/
├── environment.py
├── behave.ini
├── requirements.txt
└── README.md
- features/: Contains "".feature"" files and step definitions.
- pages/: Houses Page Object Model classes.
- reports/: Contains test reports.
- environment.py: This file manages the setup and teardown for each test scenario.
- behave.ini: This file configures Behave to use Allure reporting and structured output.
- requirements.txt: This file lists all dependencies required for the project.
- READEME.md: Explains how to set up, run, and use the project.
Running Tests
Run all tests with Allure reporting
behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results
Run a single feature with Allure reporting
behave features/login.feature -f allure_behave.formatter:AllureFormatter -o reports/allure-results
Run tests in headless mode
Modify environment.py to set headless=True in the before_all function.
context.browser = context.playwright.chromium.launch(headless=True)
Reporting
Generate an Allure Report
allure generate --single-file reports/allure-results --clean -o reports/allure-report
View Screenshots
Screenshots are saved in the reports/screenshots/ directory.
Troubleshooting
Playwright is not installed?
Run the following command to install Playwright:
playwright install
Playwright is not working?
Check the Playwright installation by running the following command:
playwright install --check
Alure is not recognized?
Ensure you have Allure CLI installed:
-
Download the Allure Archive:
curl -L -o allure.zip https://github.com/allure-framework/allure2/releases/download/2.32.2/allure-2.32.2.zip -
Extract the Allure Archive:
unzip allure.zip -d allure -
Add the Allure executable to your PATH:
setx PATH "%PATH%;C:/path/to/allure/bin" -
Restart your terminal to apply the changes
-
Verify the installation:
allure version""" } # Properly closed dictionary
Create directories
for directory in directories: os.makedirs(directory, exist_ok=True)
Create files with predefined content
for file_path, content in files_content.items(): with open(file_path, "w", encoding="utf-8") as file: file.write(content)
print("Project setup complete! You can now start writing tests")
메타데이터
- post_id
- 34dbe2ff009a
- slug
- step-by-step-playwright-with-behave-bdd-and-allure-reports-implementation-tutorial-34dbe2ff009a
- url
- https://medium.com/@antonio.uxcreator/step-by-step-playwright-with-behave-bdd-and-allure-reports-implementation-tutorial-34dbe2ff009a
- canonical_url
- https://medium.com/@antonio.uxcreator/step-by-step-playwright-with-behave-bdd-and-allure-reports-implementation-tutorial-34dbe2ff009a
- author_url
- https://medium.com/@antonio.uxcreator
- status
- ok
- fetched_at
- 2026-07-20 21:08:49