105

I'm writing selenium tests, with a set of classes, each class containing several tests. Each class currently opens and then closes Firefox, which has two consequences:

  • super slow, opening firefox takes longer than running the test in a class...
  • crashes, because after firefox has been closed, trying to reopen it really quickly, from selenium, results in an 'Error 54'

I could solve the error 54, probably, by adding a sleep, but it would still be super slow.

So, what I'd like to do is reuse the same Firefox instances across all test classes. Which means I need to run a method before all test classes, and another method after all test classes. So, 'setup_class' and 'teardown_class' are not sufficient.

Hugh Perkins
  • 7,975
  • 7
  • 63
  • 71
  • Setting the [`scope`](http://pytest.org/latest/fixture.html#working-with-a-module-shared-fixture) to `module` isn't enough? – Bakuriu Jul 23 '13 at 09:36
  • I need a function to run once, right at the end, to shutdown the webbrowser. I dont want to shut down the webbrowser after each class/module. Nor do I want it never to be shut down. – Hugh Perkins Jul 23 '13 at 09:43
  • Then it is pretty easy: use the [`atexit`](http://docs.python.org/2/library/atexit.html) and register the function that will close firefox to be executed right before the interpreter exits. – Bakuriu Jul 23 '13 at 13:13
  • Thanks. I'd prefer a 'py.test' way of doing it if possible. – Hugh Perkins Jul 23 '13 at 13:18
  • 1
    You can do [module-level teardown and setup](http://pytest.org/latest/xunit_setup.html#module-level-setup-teardown) and I was using session-level teardowns and setups, however not sure if they are still available. – Alex Okrushko Jul 24 '13 at 14:32

4 Answers4

130

Using session fixture as suggested by hpk42 is great solution for many cases, but fixture will run only after all tests are collected.

Here are two more solutions:

conftest hooks

Write a pytest_configure or pytest_sessionstart hook in your conftest.py file:

# content of conftest.py


def pytest_configure(config):
    """
    Allows plugins and conftest files to perform initial configuration.
    This hook is called for every plugin and initial conftest
    file after command line options have been parsed.
    """


def pytest_sessionstart(session):
    """
    Called after the Session object has been created and
    before performing collection and entering the run test loop.
    """
    

def pytest_sessionfinish(session, exitstatus):
    """
    Called after whole test run finished, right before
    returning the exit status to the system.
    """
    

def pytest_unconfigure(config):
    """
    called before test process is exited.
    """

pytest plugin

Create a pytest plugin with pytest_configure and pytest_unconfigure hooks.
Enable your plugin in conftest.py:

# content of conftest.py

pytest_plugins = [
    'plugins.example_plugin',
]
    

# content of plugins/example_plugin.py
def pytest_configure(config):
    pass


def pytest_unconfigure(config):
    pass
cambunctious
  • 8,391
  • 5
  • 34
  • 53
draganHR
  • 2,578
  • 2
  • 21
  • 14
100

You might want to use a session-scoped "autouse" fixture:

# content of conftest.py or a tests file (e.g. in your tests or root directory)

@pytest.fixture(scope="session", autouse=True)
def do_something(request):
    # prepare something ahead of all tests
    request.addfinalizer(finalizer_function)

This will run ahead of all tests. The finalizer will be called after the last test finished.

Mike Placentra
  • 835
  • 1
  • 14
  • 27
hpk42
  • 21,501
  • 4
  • 47
  • 53
40

Starting from version 2.10 there is a cleaner way to tear down the fixture as well as defining its scope. So you may use this syntax:

@pytest.fixture(scope="module", autouse=True)
def my_fixture():
    print('INITIALIZATION')
    yield param
    print('TEAR DOWN')

The autouse parameter: From documentation:

Here is how autouse fixtures work in other scopes:

  • autouse fixtures obey the scope= keyword-argument: if an autouse fixture has scope='session' it will only be run once, no matter where it is defined. scope='class' means it will be run once per class, etc.

  • if an autouse fixture is defined in a test module, all its test functions automatically use it.

  • if an autouse fixture is defined in a conftest.py file then all tests in all test modules below its directory will invoke the fixture.

...

The "request" parameter: Note that the "request" parameter is not necessary for your purpose although you might want to use it for other purposes. From documentation:

"Fixture function can accept the request object to introspect the “requesting” test function, class or module context.."

Neuron
  • 5,141
  • 5
  • 38
  • 59
Pavel Rogovoy
  • 528
  • 4
  • 6
14

Try to use pytest_sessionstart(session) in conftest.py

Example:

# project/tests/conftest.py

def pytest_sessionstart(session):
    print('BEFORE')
# project/tests/tests_example/test_sessionstart.py

import pytest


@pytest.fixture(scope='module', autouse=True)
def fixture():
    print('FIXTURE')


def test_sessonstart():
    print('TEST')

Log:

BEFORE
============================================================================ test session starts =============================================================================
platform darwin -- Python 3.7.0, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
cachedir: .pytest_cache
rootdir: /Users/user/Documents/test, inifile: pytest.ini
plugins: allure-pytest-2.8.12, env-0.6.2
collected 1 item                                                                                                                                                             

tests/6.1/test_sessionstart.py::test_sessonstart FIXTURE
TEST
PASSED

Neuron
  • 5,141
  • 5
  • 38
  • 59
balalaiQA
  • 141
  • 1
  • 3
  • I'm trying to use the ```autouse=True``` fixture to record additional xml attributes using the ```record_xml_attribute``` fixture , but it only seems to be working on my local machine and not in the CI pipeline – Bored002 Aug 08 '22 at 10:35