Create multiple similar PyTest fixtures

Viewed 666

I have a series of pytest fixtures which are very similar to each other in nature. The fixtures are passed to tests which verify that certain CSS selectors work properly. The code within each fixture is nearly the same; this only difference is that a different URL is passed to the page.goto() function for each fixture.

Each fixture looks something like this:

import pytest


@pytest.fixture(scope="module")
def goto_pagename():
    with sync_playwright() as play:
        browser = play.chromium.launch()
        page = browser.new_page()
        page.goto(TestScrapeWebsite.address)
        yield page
        browser.close()

I tried to use a decorator which covers all of the code except page.goto() and yield page, which is below:

from playwright.sync_api import sync_playwright


def get_page(func):
    def wrapper(*args, **kwargs):
        with sync_playwright() as play:
            browser = play.chromium.launch(*args, **kwargs)
            page = browser.new_page()
            func(page)
            browser.close()
    return wrapper

Then, the fixtures and tests would look something like this:

import pytest


@pytest.fixture(scope="module")
@get_page
def google_page(page):
    page.goto(TestScrapeGoogle.address)
    yield page


@pytest.fixture(scope="module")
@get_page
def stackoverflow_page(page):
    page.goto(TestScrapeStackOverflow.address)
    yield page


@pytest.fixture(scope="module")
@get_page
def github_page(page):
    page.goto(TestScrapeGitHub.address)
    yield page


class TestScrapeGoogle:
    address = "https://google.com/"
    def test_selectors(self, google_page):
        assert google_page.url == self.address


class TestScrapeStackOverflow:
    address = "https://stackoverflow.com/"
    def test_selectors(self, stackoverflow_page):
        assert stackoverflow_page.url == self.address


class TestScrapeGitHub:
    address = "https://github.com/"
    def test_selectors(self, github_page):
        assert github_page.url == self.address

However, when running the pytest test runner, exceptions were raised concerning the fixtures:

$ pytest test_script.py

...

============================================================================================ short test summary info ============================================================================================= 
FAILED test_script.py::TestScrapeGoogle::test_selectors - AttributeError: 'NoneType' object has no attribute 'url'
FAILED test_script.py::TestScrapeStackOverflow::test_selectors - AttributeError: 'NoneType' object has no attribute 'url'
FAILED test_script.py::TestScrapeGitHub::test_selectors - AttributeError: 'NoneType' object has no attribute 'url'                                                                                                 

Is there a way to modify the approach which I have taken in order to simplify each of the fixtures? Or, is what I am asking out of the capabilities of pytest, and do I just have to fully write out each fixture?


Similar Questions:

Below I added a few Stack Overflow questions which appear when the title of my question is searched. I also included a reason why the answers to for the question would not be an ideal solution for my issue.

1 Answers

Edit: I'm not able to run this code myself, as my chromeium setup seems busted at this time I would opt for using pytest's parametrize functionality to do what you expect. First, I would create my fixtures

@pytest.fixture(scope='module')
def browser():
    with sync_playwright() as play:
        browser = play.chromium.launch(*args, **kwargs)
        try:
            yield browser
        finally:
            # Ensure the browser is gracefully closed at end of test
            browser.close() 


@pytest.fixture
def page(browser, url):
    # Provide the page
    page = browser.new_page()
    page.goto(url)
    return page

Note that the url fixture is not yet defined.

Next, I create one test with the url (and expected url) parametrized

@pytest.mark.parametrize('url, expected_url', [
    ('https://google.com/', 'https://google.com/'),
    ('https://stackoverflow.com/', 'https://stackoverflow.com/'),
    ('https://github.com/', 'https://github.com/'),
])
def test_selectors(page, expected_url):
    assert page.url == expected_url

Alternatively, if the tests for the web sites are slightly different, you can have three separate tests, each having a single parametrized entry.

@pytest.mark.parametrize('url, expected_url', [
    ('https://google.com/', 'https://google.com/'),
])
def test_google_selector(page, expected_url):
    assert page.url == expected_url


@pytest.mark.parametrize('url, expected_url', [
    ('https://stackoverflow.com/', 'https://stackoverflow.com/'),
])
def test_stackoverflow_selector(page, expected_url):
    assert page.url == expected_url
Related