pytest_generate_tests with different browsers

Created on 2 Aug 2018  路  2Comments  路  Source: pytest-dev/pytest

Hi,
I'm new with pytest and wish to know the most effective way for multi browsing tests (for example, run the same test on chrome, safari and firefox)
I read this page content:
https://docs.pytest.org/en/latest/parametrize.html

but it only shows example to use a simple string multiple times - doesn't help so much...

I have a fixture that gets the browser name from terminal and creates the web driver:

parser.addoption("--browser", action="append", default=["chrome"])

@pytest.fixture()
def DRIVER(request):
browser_list = request.config.getoption("--browser")
if 'chrome' in browser_list:
driver = webdriver.Chrome()
else:
driver=webdriver.Firefox()

my goal - use DRIVER fixture in a test (like the test above), and if I give 2/3 different browsers, my single test will run 2/3 times:

def test_multi_browsers(DRIVER):
DRIVER.get('www.google.com')

terminal command:
"pytest test_multi_browsers.py --browser=chrome --browser=safari"

now it runs only on 1 browser...

question

Most helpful comment

I actually spent quite some time trying to figure this one out, so I'll try to offer whatever help/insight I can.

A fixture will be "activated" for any test it can see if:

  1. it is set to autouse
  2. it's requested by another fixture that's set to autouse
  3. or the test requests it either directly or indirectly (e.g. test_a requests fixture b, which requests fixture c, so test_a indirectly requests fixture c)

However, the default scope for a fixture is function, but whatever scope it has, is a rule for it that basically says "the fixture can only be executed once per ".

It looks like you've defined your DRIVER fixture like this (no need for () on @pytest.fixture if you aren't passing any arguments):

@pytest.fixture
def DRIVER(request):
    browser_list = request.config.getoption("--browser")
    if 'chrome' in browser_list:
        driver = webdriver.Chrome()
    else:
        driver=webdriver.Firefox()

In the case of your fixture, since it's not actually parametrized, it will only be executed once. And while your parser.addoption stuff may be working as expected, you're only running through the fixture once. So if browser_list is ["chrome", "safari"], it's done once it hits driver = webdriver.Chrome(). This would explain why it would only ever run one time for the one test.

There's also another issue in that the fixture doesn't return anything. Whatever it returns is the object that will be provided to other fixtures and tests that request it.

If you want to parametrize the fixture, you'll need to do so in the fixture decorator, like so:

@pytest.fixture(params=["chrome", "firefox", "safari"])
def DRIVER(request):

For each of these params, the fixture will be executed once per applicable scope (e.g. if it's scope is class, it will be executed once per class per param). You can access the value of the param through request, using request.param. In this case, it will be one of the three strings provided.

Since the fixture will be run once for every param, request.param will be one of those three strings each time it goes through, so you'll need to have logic for each one so the proper driver is made and returned. The final bit after that, would be to skip all tests where the param is not one from the list of desired browser:

@pytest.fixture(params=["chrome", "firefox", "safari"])
def DRIVER(request):
    browser_list = request.config.getoption("--browser")
    driver_map = {
        "chrome": webdriver.Chrome,
        "firefox": webdriver.Firefox,
        "safari": webdriver.Safari,
    }
    if request.param in browser_list:
        driver = driver_map[request.param]()
        return driver
    else:
        pytest.skip("Not running tests for browser: {}".format(request.param))

This is a pretty straightforward way of going about it, but unfortunately, it will result in a lot of skipped tests depending on what your command line args are. In order to avoid this, you'd probably have to mess around with how tests are generated, which I think might be more work than it's worth.

Hope this helps!

All 2 comments

I actually spent quite some time trying to figure this one out, so I'll try to offer whatever help/insight I can.

A fixture will be "activated" for any test it can see if:

  1. it is set to autouse
  2. it's requested by another fixture that's set to autouse
  3. or the test requests it either directly or indirectly (e.g. test_a requests fixture b, which requests fixture c, so test_a indirectly requests fixture c)

However, the default scope for a fixture is function, but whatever scope it has, is a rule for it that basically says "the fixture can only be executed once per ".

It looks like you've defined your DRIVER fixture like this (no need for () on @pytest.fixture if you aren't passing any arguments):

@pytest.fixture
def DRIVER(request):
    browser_list = request.config.getoption("--browser")
    if 'chrome' in browser_list:
        driver = webdriver.Chrome()
    else:
        driver=webdriver.Firefox()

In the case of your fixture, since it's not actually parametrized, it will only be executed once. And while your parser.addoption stuff may be working as expected, you're only running through the fixture once. So if browser_list is ["chrome", "safari"], it's done once it hits driver = webdriver.Chrome(). This would explain why it would only ever run one time for the one test.

There's also another issue in that the fixture doesn't return anything. Whatever it returns is the object that will be provided to other fixtures and tests that request it.

If you want to parametrize the fixture, you'll need to do so in the fixture decorator, like so:

@pytest.fixture(params=["chrome", "firefox", "safari"])
def DRIVER(request):

For each of these params, the fixture will be executed once per applicable scope (e.g. if it's scope is class, it will be executed once per class per param). You can access the value of the param through request, using request.param. In this case, it will be one of the three strings provided.

Since the fixture will be run once for every param, request.param will be one of those three strings each time it goes through, so you'll need to have logic for each one so the proper driver is made and returned. The final bit after that, would be to skip all tests where the param is not one from the list of desired browser:

@pytest.fixture(params=["chrome", "firefox", "safari"])
def DRIVER(request):
    browser_list = request.config.getoption("--browser")
    driver_map = {
        "chrome": webdriver.Chrome,
        "firefox": webdriver.Firefox,
        "safari": webdriver.Safari,
    }
    if request.param in browser_list:
        driver = driver_map[request.param]()
        return driver
    else:
        pytest.skip("Not running tests for browser: {}".format(request.param))

This is a pretty straightforward way of going about it, but unfortunately, it will result in a lot of skipped tests depending on what your command line args are. In order to avoid this, you'd probably have to mess around with how tests are generated, which I think might be more work than it's worth.

Hope this helps!

Hi Chris!
10X for the answer - and you're right about the skipped tests - I believe I'll have to split the DRIVER fixture to 2 and each user will choose what to use.

Was this page helpful?
0 / 5 - 0 ratings