Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run a test with two different pytest fixtures

I am currently testing a web app using pytest and Selenium. All pages have "Home" and "Log Out" links, so I have written a test like this:

def test_can_log_out(page):
    link = page.find_element_by_partial_link_text('Log Out')
    link.click()
    assert 'YOU HAVE SUCCESSFULLY LOGGED OFF!' in starting_page.page_source

Now for the page fixture, I am simulating the login process. I have this broken into several fixtures:

  1. Get the Selenium WebDriver instances

    @pytest.fixture()
    def browser(request, data, headless):
        b = webdriver.Firefox(executable_path=DRIVERS_PATH + '/geckodriver')
        yield b
        b.quit()
    
  2. Log in to the web app

    @pytest.fixture()
    def login(browser):
        browser.get('http://example.com/login)
        user_name = browser.find_element_by_name('user_name')
        user_name.send_keys('codeapprentice')
        password = browser.find_element_by_name('password')
        password.send_keys('password1234')
        submit = browser.find_element_by_name('submit')
        submit.click()
        return browser
    
  3. Visit a page

    @pytest.fixture()
    def page(login):
        link = login.find_element_by_partial_link_text('Sub Page A')
        link.click()
        return login
    

This works very well and I can test logging out from this page. Now my question is that I have another page which can be visited from "Page A":

@pytest.fixture()
def subpage(page):
    button = login.find_element_name('button')
    button.click()
    return page

Now I want to run the exact same test with this fixture, also. Of course, I can copy/paste and make a few changes:

def test_can_log_out_subpage(subpage):
    link = page.find_element_by_partial_link_text('Log Out')
    link.click()
    assert 'YOU HAVE SUCCESSFULLY LOGGED OFF!' in starting_page.page_source

However, this violates the DRY principle. How can I reuse test_can_log_out() without this repetition?

like image 721
Code-Apprentice Avatar asked Sep 22 '17 15:09

Code-Apprentice


People also ask

Can pytest fixtures use other fixtures?

A fixture can use multiple other fixtures. Just like a test method can take multiple fixtures as arguments, a fixture can take multiple other fixtures as arguments and use them to create the fixture value that it returns.

Can a pytest fixture have arguments?

You can pass arguments to fixtures with the params keyword argument in the fixture decorator, and you can also pass arguments to tests with the @pytest.

Does pytest run tests in parallel?

By default, pytest runs tests in sequential order. In a real scenario, a test suite will have a number of test files and each file will have a bunch of tests. This will lead to a large execution time. To overcome this, pytest provides us with an option to run tests in parallel.

Can a pytest fixture be a test?

Those are the things that need to test a certain action. In pytest the fixtures are functions that we define to serve these purpose, we can pass these fixtures to our test functions (test cases) so that they can run and set up the desired state for you to perform the test.


2 Answers

Here, you can pass your fixtures which gives your pages and subpages in test parameters which would be called dynamically as a first step of test. Like below.

When fixtures are on same page where tests are:

testfile.py

import pytest

class TestABC():
    @pytest.fixture
    def browser(self,request):
        print "browser"

    @pytest.fixture
    def login(self,request,browser):
        print "login"

    @pytest.fixture
    def subpage1(self,request,login):
        print "subpage1"

    @pytest.fixture
    def subpage2(self, request, login):
        print "subpage2"

    @pytest.fixture
    def subpage3(self, request, login):
        print "subpage3"

    @pytest.mark.parametrize('sub_page',
                             ['subpage1', 'subpage2', 'subpage3'])
    def test_can_log_out_subpage(self,sub_page,request):
        request.getfixturevalue(sub_page) # with pytest>=3.0.0 use getfixturevalue instead of getfuncargvalue
        print "test output of ", sub_page

Output:

browser
login
subpage1
test output of  subpage1
browser
login
subpage2
test output of  subpage2
browser
login
subpage3
test output of  subpage3

When fixtures are at conftest.py

import pytest


@pytest.fixture
def browser(request):
    print "browser"

@pytest.fixture
def login(request):
    print "login"

@pytest.fixture
def subpage1(request,login):
    print "subpage1"

@pytest.fixture
def subpage2(request, login):
    print "subpage2"

@pytest.fixture
def subpage3(request, login):
    print "subpage3"

testfile.py

import pytest

class TestABC():

    @pytest.mark.parametrize('sub_page',
                             ['subpage1', 'subpage2', 'subpage3'])
    def test_can_log_out_subpage(self,sub_page,request):
        request.getfixturevalue(sub_page)  # with pytest>=3.0.0 use getfixturevalue instead of getfuncargvalue
        print "test output of ", sub_page

Here, you will also get same output as above.

Hope it would help you.

like image 58
Chanda Korat Avatar answered Nov 12 '22 07:11

Chanda Korat


Another solution that worked for me was to use classes. It requires a bit more setup than the solutions above, but it can be helpful if you end up having to setup multiple related tests.

You define all of your tests in a single class, and then create a dummy class for each fixture extending that first class:

@pytest.fixture(scope='class')
def fixture1(request):
    request.cls.fruit = 'apple'

@pytest.fixture(scope='class')
def fixture2(request):
    request.cls.fruit = 'banana'

class ClassOfRelatedTests:
    def test_is_banana(self):
        assert self.fruit == 'banana'

    def test_not_spinach(self):
        assert self.fruit != 'spinach'

@pytest.mark.usefixtures("fixture1")
class TestFixture1(ClassOfRelatedTests):
    pass

@pytest.mark.usefixtures("fixture2")
class TestFixture2(ClassOfRelatedTests):
    pass
like image 31
krock Avatar answered Nov 12 '22 06:11

krock