Search code examples
pythonseleniumpytestgeneratorfixtures

Parametrizing a static webdriver fixture and a generator in Pytest?


I'm building an automation test for finding any possible dead links in a WP plugin. To this end, I have two helper functions.

The first spins up a Selenium webdriver:

@pytest.fixture(scope='session')
def setup():
    d = webdriver.Firefox()
    site = os.getenv('TestSite')
    d.get(site)
    login = d.find_element_by_id('user_login')
    d.implicitly_wait(5)
    user, pw = os.getenv('TestUser'), os.getenv('TestPw')
    d.find_element(By.ID, 'user_login').send_keys(user)
    d.find_element(By.ID, 'user_pass').send_keys(pw)
    d.find_element(By.ID, 'wp-submit').click()
    yield d, site
    d.quit()

The second reads in a JSON file, picking out each page in the file then yielding it to the third function:

def page_generator() -> Iterator[Dict[str, Any]]:
    try:
        json_path = '../linkList.json'
        doc = open(json_path)
        body = json.loads(doc.read())
        doc.close()
    except FileNotFoundError:
        print(f'Did not find a file at {json_path}')
        exit(1)
    for page in body['pages']:
        yield page

The third function is the meat and potatoes of the test, running through the page and looking for each link. My goal is to parametrize each page, with my function header presently looking like...

@pytest.mark.parametrize('spin_up, page', [(setup, p) for p in page_generator()])
def test_links(spin_up, page):
    driver, site = spin_up
    # Do all the things

Unfortunately, running this results in TypeError: cannot unpack non-iterable function object. Is the only option to stick yield d, site inside some sort of loop to turn the function into an iterable, or is there a way to tell test_links to iteratively pull the same setup function as its spin_up value?


Solution

  • There is an easy way to do what you want, and that's to specify the setup fixture directly as an argument to test_links. Note that pytest is smart enough to figure out that the setup argument refers to a fixture while the page argument refers to a parametrization:

    @pytest.mark.parametrize('page', page_generator())
    def test_links(setup, page):
        driver, site = setup
    

    You might also take a look at parametrize_from_file. This is a package I wrote to help with loading test cases from JSON/YAML/TOML files. It basically does the same thing as your page_generator() function, but more succinctly (and more generally).