fix(browsers): solving an issues with leaving playwright loop open when cdp connection fails

This has been causing issues with tests for a long time, and now finally found the reason.
This commit is contained in:
Karim shoair
2026-01-18 16:46:48 +02:00
parent 02c9dffc77
commit bf5aa021d4
2 changed files with 76 additions and 58 deletions
+14 -5
View File
@@ -3,13 +3,11 @@ from asyncio import sleep as asyncio_sleep
from playwright.sync_api import ( from playwright.sync_api import (
Locator, Locator,
Playwright,
sync_playwright, sync_playwright,
) )
from playwright.async_api import ( from playwright.async_api import (
async_playwright, async_playwright,
Locator as AsyncLocator, Locator as AsyncLocator,
Playwright as AsyncPlaywright,
BrowserContext as AsyncBrowserContext, BrowserContext as AsyncBrowserContext,
) )
@@ -71,8 +69,9 @@ class DynamicSession(SyncSession, DynamicSessionMixin):
def start(self): def start(self):
"""Create a browser for this instance and context.""" """Create a browser for this instance and context."""
if not self.playwright: if not self.playwright:
self.playwright: Playwright = sync_playwright().start() # pyright: ignore [reportAttributeAccessIssue] self.playwright = sync_playwright().start()
try:
if self._config.cdp_url: # pragma: no cover if self._config.cdp_url: # pragma: no cover
browser = self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url) browser = self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url)
self.context = browser.new_context(**self._context_options) self.context = browser.new_context(**self._context_options)
@@ -86,6 +85,11 @@ class DynamicSession(SyncSession, DynamicSessionMixin):
self.context.add_cookies(self._config.cookies) self.context.add_cookies(self._config.cookies)
self._is_alive = True self._is_alive = True
except Exception:
# Clean up playwright if browser setup fails
self.playwright.stop()
self.playwright = None
raise
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")
@@ -209,8 +213,8 @@ class AsyncDynamicSession(AsyncSession, DynamicSessionMixin):
async def start(self): async def start(self):
"""Create a browser for this instance and context.""" """Create a browser for this instance and context."""
if not self.playwright: if not self.playwright:
self.playwright: AsyncPlaywright = await async_playwright().start() # pyright: ignore [reportAttributeAccessIssue] self.playwright = await async_playwright().start()
try:
if self._config.cdp_url: if self._config.cdp_url:
browser = await self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url) browser = await self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url)
self.context: AsyncBrowserContext = await browser.new_context(**self._context_options) self.context: AsyncBrowserContext = await browser.new_context(**self._context_options)
@@ -226,6 +230,11 @@ class AsyncDynamicSession(AsyncSession, DynamicSessionMixin):
await self.context.add_cookies(self._config.cookies) # pyright: ignore await self.context.add_cookies(self._config.cookies) # pyright: ignore
self._is_alive = True self._is_alive = True
except Exception:
# Clean up playwright if browser setup fails
await self.playwright.stop()
self.playwright = None
raise
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")
+14 -5
View File
@@ -6,12 +6,10 @@ from asyncio import sleep as asyncio_sleep
from playwright.sync_api import ( from playwright.sync_api import (
Locator, Locator,
Page, Page,
Playwright,
) )
from playwright.async_api import ( from playwright.async_api import (
Page as async_Page, Page as async_Page,
Locator as AsyncLocator, Locator as AsyncLocator,
Playwright as AsyncPlaywright,
BrowserContext as AsyncBrowserContext, BrowserContext as AsyncBrowserContext,
) )
from patchright.sync_api import sync_playwright from patchright.sync_api import sync_playwright
@@ -82,8 +80,9 @@ class StealthySession(SyncSession, StealthySessionMixin):
def start(self): def start(self):
"""Create a browser for this instance and context.""" """Create a browser for this instance and context."""
if not self.playwright: if not self.playwright:
self.playwright: Playwright = sync_playwright().start() # pyright: ignore [reportAttributeAccessIssue] self.playwright = sync_playwright().start()
try:
if self._config.cdp_url: # pragma: no cover if self._config.cdp_url: # pragma: no cover
browser = self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url) browser = self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url)
self.context = browser.new_context(**self._context_options) self.context = browser.new_context(**self._context_options)
@@ -100,6 +99,11 @@ class StealthySession(SyncSession, StealthySessionMixin):
self.context.add_cookies(self._config.cookies) self.context.add_cookies(self._config.cookies)
self._is_alive = True self._is_alive = True
except Exception:
# Clean up playwright if browser setup fails
self.playwright.stop()
self.playwright = None
raise
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")
@@ -308,8 +312,8 @@ class AsyncStealthySession(AsyncSession, StealthySessionMixin):
async def start(self): async def start(self):
"""Create a browser for this instance and context.""" """Create a browser for this instance and context."""
if not self.playwright: if not self.playwright:
self.playwright: AsyncPlaywright = await async_playwright().start() # pyright: ignore [reportAttributeAccessIssue] self.playwright = await async_playwright().start()
try:
if self._config.cdp_url: if self._config.cdp_url:
browser = await self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url) browser = await self.playwright.chromium.connect_over_cdp(endpoint_url=self._config.cdp_url)
self.context: AsyncBrowserContext = await browser.new_context(**self._context_options) self.context: AsyncBrowserContext = await browser.new_context(**self._context_options)
@@ -328,6 +332,11 @@ class AsyncStealthySession(AsyncSession, StealthySessionMixin):
await self.context.add_cookies(self._config.cookies) # pyright: ignore await self.context.add_cookies(self._config.cookies) # pyright: ignore
self._is_alive = True self._is_alive = True
except Exception:
# Clean up playwright if browser setup fails
await self.playwright.stop()
self.playwright = None
raise
else: else:
raise RuntimeError("Session has been already started") raise RuntimeError("Session has been already started")