11from __future__ import annotations
22
33import asyncio
4+ import copy
45import http
56import http .cookiejar
67import json
@@ -101,7 +102,7 @@ async def browser_atexit() -> None:
101102
102103 return instance
103104
104- def __init__ (self , config : Config , ** kwargs ):
105+ def __init__ (self , config : Config ):
105106 """
106107 constructor. to create a instance, use :py:meth:`Browser.create(...)`
107108
@@ -117,15 +118,17 @@ def __init__(self, config: Config, **kwargs):
117118 )
118119 )
119120 # weakref.finalize(self, self._quit, self)
120- self .config = config
121+
122+ # each instance gets it's own copy so this class gets a copy that it can
123+ # use to help manage the browser instance data (needed for multiple browsers)
124+ self .config = copy .deepcopy (config )
121125
122126 self .targets : List = []
123127 """current targets (all types)"""
124128 self .info : ContraDict | None = None
125129 self ._target = None
126130 self ._process = None
127131 self ._process_pid = None
128- self ._keep_user_data_dir = None
129132 self ._is_updating = asyncio .Event ()
130133 self .connection = None
131134 logger .debug ("Session object initialized: %s" % vars (self ))
@@ -254,7 +257,7 @@ async def get(
254257 raise RuntimeError ("Browser not yet started. use await browser.start()" )
255258
256259 if new_tab or new_window :
257- # creat new target using the browser session
260+ # create new target using the browser session
258261 target_id = await self .connection .send (
259262 cdp .target .create_target (
260263 url , new_window = new_window , enable_begin_frame_control = True
@@ -572,21 +575,29 @@ async def stop(self):
572575 logger .debug ("closed the connection" )
573576
574577 if self ._process :
575- self ._process .terminate ()
576- logger .debug ("gracefully stopping browser process" )
577- # wait 3 seconds for the browser to stop
578- for _ in range (12 ):
579- if self ._process .returncode is not None :
580- break
581- await asyncio .sleep (0.25 )
582- else :
583- logger .debug ("browser process did not stop. killing it" )
584- self ._process .kill ()
585- logger .debug ("killed browser process" )
578+ try :
579+ self ._process .terminate ()
580+ logger .debug ("gracefully stopping browser process" )
581+ # wait 3 seconds for the browser to stop
582+ for _ in range (12 ):
583+ if self ._process .returncode is not None :
584+ break
585+ await asyncio .sleep (0.25 )
586+ else :
587+ logger .debug ("browser process did not stop. killing it" )
588+ self ._process .kill ()
589+ logger .debug ("killed browser process" )
590+
591+ await self ._process .wait ()
592+
593+ except ProcessLookupError :
594+ # ignore this well known race condition because it only means that
595+ # the process was not found while trying to terminate or kill it
596+ pass
586597
587- await self ._process .wait ()
588598 self ._process = None
589599 self ._process_pid = None
600+
590601 await self ._cleanup_temporary_profile ()
591602
592603 async def _cleanup_temporary_profile (self ) -> None :
0 commit comments