← Back to list

Step-by-Step Playwright with Behave BDD and Allure Reports Implementation Tutorial

Test Automation Framework with Playwright, Behave BDD, POM, and Allure

Antonio Gilliam · 2025-02-24 04:57 · 5 claps · 9.1 min read
#playwright-automation #allure-reporting #behave #ui-automation-testing #ui-test-automation
Open on Medium ↗

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 install downloads browser binaries so tests can run.
  • allure-behave is 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:

  1. from playwright.sync_api import Page
  • This imports Page from Playwright's synchronous API.
  • Page represents a browser page (or tab) and allows interaction with web elements (clicking buttons, filling forms, taking screenshots, etc.).
  1. class BasePage: def __init__(self, page: Page): self.page = page
  • BasePage is a parent class for all page objects.
  • It initializes the page instance (which is passed from test steps) so that all child pages can interact with the browser.
  1. 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 call some_page.go_to("https://example.com").
  1. 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?

  1. Prevents Code Duplication → All common actions (navigation, screenshots, waiting for elements, etc.) are in one place.
  2. Encourages Reusability → Instead of redefining methods in every page class, all pages just inherit from BasePage.
  3. Simplifies Maintenance → If Playwright updates or a method needs improvement, you only change it in BasePage instead 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?

  1. Inheriting from BasePage
  • LoginPage is a child class of BasePage.
  • It needs to initialize BasePage first so that self.page (Playwright's Page instance) is properly set up.
  1. Calling Parent Class (BasePage) Constructor
  • super().__init__(page) calls BasePage’s __init__ method
  • This assigns self.page = page, allowing LoginPage to use self.page for interactions.
  1. Ensures LoginPage Can Use Common Methods
  • Since LoginPage inherits from BasePage, it can access all BasePage methods (like go_to(url) and take_screenshot(step_name)) without redefining them.

Why is this important?

  • Keeps code DRY (Don’t Repeat Yourself) → No need to reinitialize self.page in every page class.
  • Ensures proper inheritanceLoginPage gets all methods from BasePage.
  • Makes the framework scalable → Any future pages (e.g., InventoryPage) can just inherit from BasePage and 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:

  1. @then("they should be redirected to the inventory page")
  • This @then decorator is a Behave BDD step.
  • It matches the Gherkin step in the .feature file, for example:
Then they should be redirected to the inventory page
  1. def step_impl(context):
  • This defines the step implementation in Python.
  • context is a special object in Behave that holds shared data across steps.
  • It contains things like the Playwright browser, pages, and test state.
  1. context.inventory_page = InventoryPage(context.page)
  • It creates an instance of the InventoryPage class, which follows the Page Object Model (POM), thus dynamically adding ainventory_page attribute to context
  • context.page (the Playwright page) is passed to the InventoryPage, so the test can interact with elements.
  1. 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).)
  1. context.inventory_page.take_screenshot("inventory_page")
  • Calls the take_screenshot method from InventoryPage and saves screenshot as “inventory_page”
  1. 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

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