Warning!

This course was written with pytest-bdd version 3. When pytest-bdd updated to version 4, they introduced a backwards-incompatible change regarding "@given" decorators. You must now include a "fixture_target" parameter with the name of the method in order for other steps to use it as a fixture. The example project code is updated, but the videos and transcripts still show the old code.

Transcripted Summary

As we finish our course on pytest-bdd, we still need to talk about shared steps and hooks.

Shared steps are steps that can be used by any feature file regardless of where they're located in the project hierarchy.

So far in the example test that we've written, all the step functions are limited in scope to the test modules where they've been added.

Hooks are additional logic that can be inserted anywhere during the execution of a feature file, whether it's before and after a scenario, or before and after a step.

Hooks make it easy to do aspect-oriented like programming, with pytest-bdd as well as other kinds of BDD-style frameworks.

Both shared steps and hooks are related, as we'll see in a moment, so we'll cover them together in this one chapter.

To share things in pytest such as fixtures, hooks and even pytest-bdd step functions, we put them in a file called conftest.py, which is just any old Python file.



conftest.py should be co-located with test modules under the tests directory.

It essentially provides per-directory plug-ins in that all the test modules in the directory or subdirectories of the compftest.py module will be able to use those fixtures, those hooks and those steps.

The pytest-bdd docs show all the available hooks for pytest-bdd.



We have hooks such as before and after scenario, before a step, before a step call, after a step, for a step error, for a step validation error, and finally for a step function look-up error.

All these are different hooks that we can put into that conftest.py file and they are optional. They're not required. We should only provide them if we really want to use them.

Let's look at our Python code to see how we can implement a conftest.py file, with some shared steps and hooks.



In my project explorer, you can see how I've added a conftest.py file in my step definitions directory.

That means everything I put in this conftest.py file will be available for all of my test modules.


# tests/step_defs/conftest.py

import pytest
from pytest_bdd import given
from selenium import webdriver

# Constants
DUCKDUCKGO_HOME = 'https://duckduckgo.com/'

# Hooks
def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception):
    print(f'Step failed: {step}')

# Fixtures
@pytest.fixture
def browser():
    # For this example, we will use Firefox
    # You can change this fixture to use other browsers, too.
    # A better practice would be to get browser choice from a config file.

    b = webdriver.Firefox()
    b.implicitly_wait(10)
    yield b
    b.quit()

# Shared Given Steps
@given('the DuckDuckGo home page is displayed')
def ddg_home(browser):
    browser.get(DUCKDUCKGO_HOME) 

conftest.py has some basic imports for pytest and selenium.

I've added a hook here for pytest_bdd_step_error — it copies the signature from the pytest-bdd docs page, and all this will do is that whenever there is an execution error, it will print this message, “Step failed”, with the step text.

What I'll also do in my “test_web_steps” module, I'll include a deliberate error in one of my steps for verifying the results are shown, so that we'll have a test that will deliberately fail, so we can see this message being printed.



Furthermore, I've also moved over my browser fixture, and my given step from the “test_web_steps” module to this module to show now that these steps and these fixtures can be shared by more than just the test web steps module. This will be very useful if I wanted to add other web test modules for other kinds of features

And just to show here in “test_web_steps” module, I no longer have that fixture or those given steps.

Let's see what happens when I run my web tests.

I'm already in my project folder. I'm going to run only the web tests.


pipenv run python -m pytest -k “web”

We wait for the first one to run, we see that it failed, and that's because we inserted that deliberate error.

Second one runs, and this one should pass, which it does.



By passing, we know that having moved over our fixture and our given step to conftest.py actually worked, and that it was shared outside of that one module.

Now, for the failure, if we look at the pytest failure, we can see it was for “test_basic_duckduckgo_search”, which we expected. We see that trace log.



We see exactly what failed for the assertion, which is what we expected because we inserted this “not” here. But also, pytest has this feature where it will capture your standard output, and that's where our hook came into place.

For every step error that we had, it should have printed out the message “Step failed”” with the text of that step. Here, we can see very clearly — we know what step had the failure, and we see it printed here. So we validated that our hook was doing what we expected.

That's awesome.

If you've made it this far, congratulations.

You've completed _Behavior-Driven Python with pytest.bdd _ from Test Automation University. That's quite an accomplishment.

Thank you so much for taking this course, and I hope you've learned a whole lot of good stuff from it.

Please follow me on Twitter @AutomationPanda and tag me to let me know what awesome things you'll be doing with Python, pytest and pytest-bdd.



Resources