Playwright was failing with NotImplementedError() on Windows when trying to generate accessibility snapshots.
Exact Error Location:
File "...\asyncio\base_events.py", line 533, in _make_subprocess_transport
raise NotImplementedError
Why It Happened:
- We were using
WindowsSelectorEventLoopPolicy()which creates aSelectorEventLoop SelectorEventLoopon Windows does NOT support subprocess creation- Playwright requires subprocess creation to launch browser processes
- This is a known limitation of
SelectorEventLoopon Windows
Before:
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())After:
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())Before:
uvicorn.run(
"src.main:app",
host=host,
port=port,
reload=True, # This can force SelectorEventLoop!
log_level="info"
)After:
# Disable reload on Windows when using Playwright to avoid SelectorEventLoop conflicts
use_reload = os.getenv("RELOAD", "false").lower() == "true"
if sys.platform == 'win32':
use_reload = False # Disable reload on Windows for Playwright compatibility
uvicorn.run(
"src.main:app",
host=host,
port=port,
reload=use_reload,
log_level="info"
)Added explicit policy setting in the sync function as a safety measure:
def _generate_accessibility_snapshot_sync(url: str) -> str:
# Ensure this specific thread uses ProactorEventLoop
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
# ... rest of function-
ProactorEventLoop Policy:
WindowsProactorEventLoopPolicy()creates aProactorEventLoopProactorEventLoopDOES support subprocess creation on Windows- This is the recommended event loop for Windows when subprocesses are needed
-
Disabling Uvicorn Reload:
- Uvicorn's
--reloadflag can forceSelectorEventLoopfor file watching - This overrides the module-level policy setting
- Disabling reload on Windows ensures ProactorEventLoop persists
- Uvicorn's
-
Thread-Level Safety:
- Even sync Playwright uses asyncio internally
- Setting the policy in the thread function ensures it's applied even if the module-level setting was overridden
-
Result:
- Playwright can now launch browser processes successfully
- No more
NotImplementedErrorfrom subprocess creation
-
Restart the backend server:
# Stop current server (Ctrl+C) $env:PORT="8000" py run_server.py
-
Test the endpoint:
- Go to
http://localhost:8000/docs - Try
POST /api/playwright/snapshot - Use:
{"url": "wikipedia.org", "use_cache": false} - Should work now! ✅
- Go to
-
Or test from frontend:
- Go to
http://localhost:8080/sandbox - Enter a URL and click "Generate Snapshot"
- Should work now! ✅
- Go to
- Simplified
_generate_accessibility_snapshot()to use async Playwright directly - Removed unnecessary threading wrapper (no longer needed)
- Code is cleaner and more straightforward
Status: ✅ FIXED
Date: 2025-12-23
Tested: Ready for testing