Spaces:
Running
Fix hanging Playwright tests: make the browser fixture function-scoped
Browse filesThe session-scoped `browser` fixture hung indefinitely on the first test
that used it. Root cause: pytest-asyncio runs each test on its own event
loop by default, but the fixture was set up once against whichever loop
was active on first use. Playwright's connection keeps a background
reader task bound to that original loop, so a later test's awaits (on a
different loop) don't error - they just wait forever for a reply the old
loop's task can never deliver.
Confirmed by reproducing the hang, then ruling out the scraping/fixture
logic itself by running the same call in a plain script outside pytest
(single event loop throughout), where it completed instantly.
Switching to a function-scoped fixture (fresh browser per test, ~1s extra
each) sidesteps the whole cross-loop class of bug instead of fighting
pytest-asyncio's loop-scope configuration. All 67 tests now pass in ~6s.
- tests/conftest.py +16 -3
|
@@ -28,10 +28,23 @@ def _find_chromium_executable() -> str | None:
|
|
| 28 |
return str(candidates[-1]) if candidates else None
|
| 29 |
|
| 30 |
|
| 31 |
-
@pytest_asyncio.fixture
|
| 32 |
async def browser():
|
| 33 |
-
"""A
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
"""
|
| 36 |
from playwright.async_api import async_playwright
|
| 37 |
|
|
|
|
| 28 |
return str(candidates[-1]) if candidates else None
|
| 29 |
|
| 30 |
|
| 31 |
+
@pytest_asyncio.fixture
|
| 32 |
async def browser():
|
| 33 |
+
"""A real headless Chromium instance, exactly like the one `app.py`'s
|
| 34 |
+
lifespan starts in production.
|
| 35 |
+
|
| 36 |
+
Function-scoped (one launch per test) rather than session-scoped: each
|
| 37 |
+
pytest-asyncio test runs on its own event loop by default, and a
|
| 38 |
+
session-scoped instance would be created on one loop's setup and then
|
| 39 |
+
awaited from a different, later loop in the next test. Playwright's
|
| 40 |
+
connection keeps a background reader task bound to the loop it was
|
| 41 |
+
created on, so calls from another loop don't error - they just hang
|
| 42 |
+
forever waiting for a reply the old loop's task will never deliver.
|
| 43 |
+
Confirmed by reproducing this exact hang with a session-scoped version
|
| 44 |
+
of this fixture, then ruling out the scraping logic itself by running
|
| 45 |
+
the same call in a plain script outside pytest (single event loop),
|
| 46 |
+
where it completed instantly. A fresh browser per test costs roughly a
|
| 47 |
+
second of extra startup but sidesteps the whole cross-loop class of bug.
|
| 48 |
"""
|
| 49 |
from playwright.async_api import async_playwright
|
| 50 |
|