-1

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?

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
Jael
  • 13
  • 2

1 Answers1

0

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.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135