diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 77ae6710435..2d2bd72091e 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -2,7 +2,7 @@ # Minimum Python version: 3.10 (for generating docs only) regex>=2025.11.3 -pymdown-extensions>=10.19.1 +pymdown-extensions>=10.20 pipdeptree>=2.30.0 python-dateutil>=2.8.2 Markdown==3.10 @@ -10,7 +10,7 @@ click==8.3.1 ghp-import==2.1.0 watchdog==6.0.0 cairocffi==1.7.1 -pathspec==0.12.1 +pathspec==1.0.1 Babel==2.17.0 paginate==0.5.7 mkdocs==1.6.1 diff --git a/requirements.txt b/requirements.txt index 9e97077fa3f..2370414f210 100755 --- a/requirements.txt +++ b/requirements.txt @@ -4,11 +4,11 @@ setuptools~=70.2;python_version<"3.10" setuptools>=80.9.0;python_version>="3.10" wheel>=0.45.1 attrs>=25.4.0 -certifi>=2025.11.12 +certifi>=2026.1.4 exceptiongroup>=1.3.1 websockets>=15.0.1 filelock~=3.19.1;python_version<"3.10" -filelock>=3.20.1;python_version>="3.10" +filelock>=3.20.2;python_version>="3.10" fasteners>=0.20 mycdp>=1.3.2 pynose>=1.5.5 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 0ca6dee91b8..b8f5dbaadbb 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.45.8" +__version__ = "4.45.9" diff --git a/seleniumbase/undetected/cdp_driver/browser.py b/seleniumbase/undetected/cdp_driver/browser.py index 1f380979b0a..b39a62d848d 100644 --- a/seleniumbase/undetected/cdp_driver/browser.py +++ b/seleniumbase/undetected/cdp_driver/browser.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import atexit +import fasteners import http.cookiejar import json import logging @@ -15,7 +16,10 @@ import urllib.request import warnings from collections import defaultdict +from contextlib import suppress from seleniumbase import config as sb_config +from seleniumbase.fixtures import constants +from seleniumbase.fixtures import shared_utils from typing import List, Optional, Set, Tuple, Union import mycdp as cdp from . import cdp_util as util @@ -34,7 +38,7 @@ def get_registered_instances(): def deconstruct_browser(): for _ in __registered__instances__: if not _.stopped: - _.stop() + _.stop(deconstruct=True) for attempt in range(5): try: if _.config and not _.config.uses_custom_data_dir: @@ -732,8 +736,11 @@ async def tile_windows(self, windows=None, max_columns: int = 0): try: import mss except Exception: - from seleniumbase.fixtures import shared_utils - shared_utils.pip_install("mss") + pip_find_lock = fasteners.InterProcessLock( + constants.PipInstall.FINDLOCK + ) + with pip_find_lock: # Prevent issues with multiple processes + shared_utils.pip_install("mss") import mss m = mss.mss() screen, screen_width, screen_height = 3 * (None,) @@ -842,11 +849,17 @@ def __next__(self): else: del self._i - def stop(self): + def stop(self, deconstruct=False): + if ( + not hasattr(sb_config, "_closed_connection_ids") + or not isinstance(sb_config._closed_connection_ids, list) + ): + sb_config._closed_connection_ids = [] + connection_id = None + with suppress(Exception): + connection_id = self.connection.websocket.id.hex + close_success = False try: - # asyncio.get_running_loop().create_task( - # self.connection.send(cdp.browser.close()) - # ) if self.connection: asyncio.get_event_loop().create_task(self.connection.aclose()) logger.debug( @@ -855,19 +868,22 @@ def stop(self): except RuntimeError: if self.connection: try: - # asyncio.run(self.connection.send(cdp.browser.close())) asyncio.run(self.connection.aclose()) logger.debug("Closed the connection using asyncio.run()") except Exception: pass for _ in range(3): try: - self._process.terminate() - logger.debug( - "Terminated browser with pid %d successfully." - % self._process.pid - ) - break + if connection_id not in sb_config._closed_connection_ids: + self._process.terminate() + logger.debug( + "Terminated browser with pid %d successfully." + % self._process.pid + ) + if connection_id: + sb_config._closed_connection_ids.append(connection_id) + close_success = True + break except (Exception,): try: self._process.kill() @@ -902,6 +918,39 @@ def stop(self): raise self._process = None self._process_pid = None + if ( + hasattr(sb_config, "_xvfb_users") + and isinstance(sb_config._xvfb_users, int) + and close_success + and hasattr(sb_config, "_virtual_display") + and sb_config._virtual_display + ): + sb_config._xvfb_users -= 1 + if sb_config._xvfb_users < 0: + sb_config._xvfb_users = 0 + if ( + shared_utils.is_linux() + and ( + hasattr(sb_config, "_virtual_display") + and sb_config._virtual_display + and hasattr(sb_config._virtual_display, "stop") + ) + and sb_config._xvfb_users == 0 + ): + try: + sb_config._virtual_display.stop() + sb_config._virtual_display = None + sb_config.headless_active = False + except AttributeError: + pass + except Exception: + pass + if ( + deconstruct + and connection_id + and connection_id in sb_config._closed_connection_ids + ): + sb_config._closed_connection_ids.remove(connection_id) def quit(self): self.stop() diff --git a/seleniumbase/undetected/cdp_driver/cdp_util.py b/seleniumbase/undetected/cdp_driver/cdp_util.py index b13f03ffa19..8719bbce2b7 100644 --- a/seleniumbase/undetected/cdp_driver/cdp_util.py +++ b/seleniumbase/undetected/cdp_driver/cdp_util.py @@ -51,12 +51,27 @@ def __activate_virtual_display_as_needed( headless, headed, xvfb, xvfb_metrics ): """This is only needed on Linux.""" + reset_virtual_display = False + if IS_LINUX and (not headed or xvfb): + if ( + not hasattr(sb_config, "_closed_connection_ids") + or not isinstance(sb_config._closed_connection_ids, list) + ): + sb_config._closed_connection_ids = [] + if ( + not hasattr(sb_config, "_xvfb_users") + or not isinstance(sb_config._xvfb_users, int) + ): + reset_virtual_display = True + sb_config._xvfb_users = 0 + sb_config._xvfb_users += 1 if ( IS_LINUX and (not headed or xvfb) and ( not hasattr(sb_config, "_virtual_display") or not sb_config._virtual_display + or reset_virtual_display ) ): from sbvirtualdisplay import Display diff --git a/setup.py b/setup.py index 178a6669aa1..6606eab3cb3 100755 --- a/setup.py +++ b/setup.py @@ -152,11 +152,11 @@ 'setuptools>=80.9.0;python_version>="3.10"', 'wheel>=0.45.1', 'attrs>=25.4.0', - 'certifi>=2025.11.12', + 'certifi>=2026.1.4', 'exceptiongroup>=1.3.1', 'websockets>=15.0.1', 'filelock~=3.19.1;python_version<"3.10"', - 'filelock>=3.20.1;python_version>="3.10"', + 'filelock>=3.20.2;python_version>="3.10"', 'fasteners>=0.20', 'mycdp>=1.3.2', 'pynose>=1.5.5', @@ -268,7 +268,7 @@ # (An optional library for image-processing.) "pillow": [ 'Pillow>=11.3.0;python_version<"3.10"', - 'Pillow>=12.0.0;python_version>="3.10"', + 'Pillow>=12.1.0;python_version>="3.10"', ], # pip install -e .[pip-system-certs] # (If you see [SSL: CERTIFICATE_VERIFY_FAILED], then get this.)