'Why are the values yielded by a pytest fixture and a function called directly different?
In the code below, what I get is a generator object
<generator object a at 0x7feb40b2d7b0>
from playwright.sync_api import sync_playwright
def get_playwright():
with sync_playwright() as playwright:
yield playwright
print(get_playwright())
But when I use pytest, what I get is a class object
<class 'playwright.sync_api._generated.Playwright'>`:
# conftest.py
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture()
def get_playwright():
with sync_playwright() as playwright:
yield playwright
# test_one.py
def test(get_playwright):
print(get_playwright)
I wonder why this is happening? How to get class without using pytest?
Solution 1:[1]
For the 1st form:
def get_playwright():
with sync_playwright() as playwright:
yield playwright
print(get_playwright()) # <generator object get_playwright at 0x108aac580>
That is expected because get_playwright
is a generator, which returns a generator iterator, which you have to call next(...)
on to get each yielded value from the iterator.
Consider a simpler, non-playwright example:
In [14]: def generate_nums():
...: for num in range(10):
...: yield num
...:
In [15]: nums = generate_nums()
In [16]: nums
Out[16]: <generator object generate_nums at 0x11115e6d0>
In [17]: next(nums)
Out[17]: 0
In [18]: next(nums)
Out[18]: 1
In [19]: next(nums)
Out[19]: 2
For more examples, see Understanding generators in Python.
Since your get_playwright
returns an iterator, you need to call next()
once to get the actual object:
from playwright.sync_api import sync_playwright
def get_playwright():
with sync_playwright() as playwright:
yield playwright
playwright_generator = get_playwright()
print(playwright_generator) # <generator object get_playwright at 0x104031580>
playwright = next(playwright_generator)
print(playwright) # <playwright._impl._playwright.Playwright object at 0x1041aabb0>
For the 2nd form:
@pytest.fixture()
def get_playwright():
with sync_playwright() as playwright:
yield playwright
def test(get_playwright):
print(get_playwright)
It should be the same case, but it's just that pytest automatically calls next()
on the fixture value if it's a generator. I could not find documentation for this behavior from the pytest docs, but it was mentioned by one of the pytest author's/maintainer's in a different answer:
Here's roughly the execution here
- pytest notices your fixture is used for the test function
- pytest calls the fixture function
- since it is a generator, it returns immediately without executing code
- pytest notices it is a generator, calls
next(...)
on it
- this causes the code to execute until the
yield
and then "pausing". you can think of it kind of as a co-routine ...- pytest then executes your test function
...which is probably why the value passed to your test function is already the next
-ed value, the playwright object.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gino Mempin |