pythonpytestplaywrightplaywright-pythonpytest-fixtures

Pytest Playwright: How to use same page instance of the browser, yielded by a fixture, across all test methods in single test class


Example structure of the test framework is following: every test class is one big E2E scenario, e.g. test product purchase, and every test method in it should be a step of this scenario with it's own asserts, e.g. log in -> add product -> fill checkout data -> make purchase. So i want to provide browser instance for a given class and then create single page to use it, but i have a problem to handle it across test methods

here is browser fixture from conftest.py:

@pytest.fixture(scope="class")
def browser(request):
    with sync_playwright() as playwright:
        browser = playwright.firefox.launch()
        yield browser
        browser.close()

here is example test class:

@pytest.mark.usefixtures("browser")
class TestCart:
    #load_test_data is fixture that provide some data from input setup file
    @pytest.fixture(autouse=True)
    def setup_data(self, load_test_data, browser: Browser):
        self.base_url = load_test_data['base_url']
        self.standard_user_data = load_test_data['test_users']['standard']
        self.page = browser.new_page()

    def test_login(self):
        self.page.goto(self.base_url)
        self.page.locator("#user-name").fill(self.standard_user_data['username'])
        self.page.locator("#password").fill(self.standard_user_data['password'])
        self.page.locator("#login-button").click()
        expect(self.page.locator("#header_container")).to_be_visible()


    def test_add_product(self):
        self.page.locator('.shopping_cart_link').click()
        expect(self.page.locator("#continue-shopping")).to_be_visible()

Once test_login is finished, new empty browser with empty page starts for the test_add_product execution, while old one left hanging on the background.

I can't fgure out, why it starts new browser and new page instead of using one that is created in setup_data? Is it mismatched/missing scope of fixtures somewhere or the whole setup to be redone? What is the best solution for such flows? Thanks.


Solution

  • Don't create a fixture in the class. Set up the page base URL and the test data outside. Also, I would recommend creating a proxy fixture with the function scope to reset the page state after each test to make them more independent.

    import pytest
    
    
    @pytest.fixture(scope="class")
    def fix_cls():
        data = []
        assert len(data) == 0
        yield data
        assert len(data) == 3
    
    
    @pytest.fixture(scope="function")
    def fix_func(fix_cls):
        # reset the data, reload the page
        yield fix_cls
        print('------>', fix_cls)
    
    
    class TestCls:
        def test_a1(self, fix_func):
            fix_func.append(1)
    
        def test_a2(self, fix_func):
            fix_func.append(2)
    
        def test_a3(self, fix_func):
            fix_func.append(3)
    

    If the tests must be sequential (even if it is not recommended), use the following advice:

    1. Use the pytest-ordering Plugin+
    2. Use Alphabetical Naming Convention: test_1, test_2, test_3, etc