Skip to content

Add CDP Options (Geolocation and Timezone) #3700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/cdp_mode/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ sb.cdp.js_dumps(obj_name)
sb.cdp.maximize()
sb.cdp.minimize()
sb.cdp.medimize()
sb.cdp.set_window_rect()
sb.cdp.set_window_rect(x, y, width, height)
sb.cdp.reset_window_size()
sb.cdp.open_new_window(url=None, switch_to=True)
sb.cdp.switch_to_window(window)
Expand Down
16 changes: 16 additions & 0 deletions examples/cdp_mode/raw_geolocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Geolocation example using CDP Mode without WebDriver"""
from seleniumbase import decorators
from seleniumbase import sb_cdp


@decorators.print_runtime("Geolocation CDP Example")
def main():
url = "https://www.openstreetmap.org/"
sb = sb_cdp.Chrome(url, geoloc=(48.87645, 2.26340))
sb.click("span.geolocate")
sb.assert_url_contains("48.876450/2.263400")
sb.sleep(5)


if __name__ == "__main__":
main()
34 changes: 34 additions & 0 deletions examples/cdp_mode/raw_timezone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Timezone example using CDP Mode without WebDriver"""
import mycdp
from seleniumbase import decorators
from seleniumbase import sb_cdp


async def request_paused_handler(event, tab):
r = event.request
is_image = ".png" in r.url or ".jpg" in r.url or ".gif" in r.url
if not is_image: # Let the data through
tab.feed_cdp(mycdp.fetch.continue_request(request_id=event.request_id))
else: # Block the data (images)
TIMED_OUT = mycdp.network.ErrorReason.TIMED_OUT
tab.feed_cdp(mycdp.fetch.fail_request(event.request_id, TIMED_OUT))


@decorators.print_runtime("Timezone CDP Example")
def main():
url = "https://www.randymajors.org/what-time-zone-am-i-in"
sb = sb_cdp.Chrome(
url,
ad_block=True,
lang="bn",
tzone="Asia/Kolkata",
geoloc=(26.855323, 80.937710)
)
sb.add_handler(mycdp.fetch.RequestPaused, request_paused_handler)
sb.remove_elements("#right-sidebar")
sb.remove_elements('[id*="Footer"]')
sb.sleep(6)


if __name__ == "__main__":
main()
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pip>=25.0.1
packaging>=25.0
setuptools~=70.2;python_version<"3.10"
setuptools>=79.0.0;python_version>="3.10"
setuptools>=79.0.1;python_version>="3.10"
wheel>=0.45.1
attrs>=25.3.0
certifi>=2025.1.31
Expand All @@ -11,7 +11,7 @@ websockets>=15.0.1;python_version>="3.9"
filelock~=3.16.1;python_version<"3.9"
filelock>=3.18.0;python_version>="3.9"
fasteners>=0.19
mycdp>=1.1.1
mycdp>=1.2.0
pynose>=1.5.4
platformdirs>=4.3.6;python_version<"3.9"
platformdirs>=4.3.7;python_version>="3.9"
Expand All @@ -36,7 +36,7 @@ urllib3>=1.26.20,<2;python_version<"3.10"
urllib3>=1.26.20,<2.5.0;python_version>="3.10"
requests==2.32.3
sniffio==1.3.1
h11==0.14.0
h11==0.16.0
outcome==1.3.0.post0
trio==0.27.0;python_version<"3.9"
trio==0.30.0;python_version>="3.9"
Expand Down
2 changes: 1 addition & 1 deletion seleniumbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.37.6"
__version__ = "4.37.7"
57 changes: 55 additions & 2 deletions seleniumbase/undetected/cdp_driver/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,63 @@ async def get(
connection: tab.Tab = next(
filter(lambda item: item.type_ == "page", self.targets)
)
# Use the tab to navigate to new url
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_locale(sb_config._cdp_locale)
if (
hasattr(sb_config, "_cdp_user_agent")
and sb_config._cdp_user_agent
):
pass
elif (
hasattr(sb_config, "_cdp_platform")
and sb_config._cdp_platform
):
pass
else:
await connection.set_locale(sb_config._cdp_locale)
if hasattr(sb_config, "_cdp_timezone") and sb_config._cdp_timezone:
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_timezone(sb_config._cdp_timezone)
if (
hasattr(sb_config, "_cdp_user_agent")
and sb_config._cdp_user_agent
):
await connection.send(cdp.page.navigate("about:blank"))
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
_cdp_platform = None
if (
hasattr(sb_config, "_cdp_platform")
and sb_config._cdp_platform
):
_cdp_platform = sb_config._cdp_platform
await connection.set_user_agent(
sb_config._cdp_user_agent,
sb_config._cdp_locale,
_cdp_platform,
)
else:
await connection.set_user_agent(sb_config._cdp_user_agent)
elif (
hasattr(sb_config, "_cdp_platform") and sb_config._cdp_platform
):
await connection.send(cdp.page.navigate("about:blank"))
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
_cdp_platform = sb_config._cdp_platform
await connection.set_user_agent(
accept_language=sb_config._cdp_locale,
platform=_cdp_platform,
)
else:
await connection.set_user_agent(
platform=sb_config._cdp_platform
)
if (
hasattr(sb_config, "_cdp_geolocation")
and sb_config._cdp_geolocation
):
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_geolocation(sb_config._cdp_geolocation)
# Use the tab to navigate to new url
frame_id, loader_id, *_ = await connection.send(
cdp.page.navigate(url)
)
Expand Down
49 changes: 49 additions & 0 deletions seleniumbase/undetected/cdp_driver/cdp_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import typing
from contextlib import suppress
from seleniumbase import config as sb_config
from seleniumbase import extensions
from seleniumbase.config import settings
from seleniumbase.core import detect_b_ver
from seleniumbase.core import download_helper
from seleniumbase.core import proxy_helper
from seleniumbase.fixtures import constants
from seleniumbase.fixtures import shared_utils
Expand All @@ -25,7 +27,10 @@

logger = logging.getLogger(__name__)
IS_LINUX = shared_utils.is_linux()
DOWNLOADS_FOLDER = download_helper.get_downloads_folder()
PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK
EXTENSIONS_DIR = os.path.dirname(os.path.realpath(extensions.__file__))
AD_BLOCK_ZIP_PATH = os.path.join(EXTENSIONS_DIR, "ad_block.zip")
T = typing.TypeVar("T")


Expand Down Expand Up @@ -168,6 +173,19 @@ def __add_chrome_ext_dir(extension_dir, dir_path):
return extension_dir


def __unzip_to_new_folder(zip_file, folder):
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
with proxy_dir_lock:
with suppress(Exception):
shared_utils.make_writable(PROXY_DIR_LOCK)
if not os.path.exists(folder):
import zipfile
zip_ref = zipfile.ZipFile(zip_file, "r")
os.makedirs(folder)
zip_ref.extractall(folder)
zip_ref.close()


def __add_chrome_proxy_extension(
extension_dir,
proxy_string,
Expand Down Expand Up @@ -231,14 +249,18 @@ async def start(
browser_executable_path: Optional[PathLike] = None,
browser_args: Optional[List[str]] = None,
xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux
ad_block: Optional[bool] = False,
sandbox: Optional[bool] = True,
lang: Optional[str] = None, # Set the Language Locale Code
host: Optional[str] = None, # Chrome remote-debugging-host
port: Optional[int] = None, # Chrome remote-debugging-port
xvfb: Optional[int] = None, # Use a special virtual display on Linux
headed: Optional[bool] = None, # Override default Xvfb mode on Linux
expert: Optional[bool] = None, # Open up closed Shadow-root elements
agent: Optional[str] = None, # Set the user-agent string
proxy: Optional[str] = None, # "host:port" or "user:pass@host:port"
tzone: Optional[str] = None, # Eg "America/New_York", "Asia/Kolkata"
geoloc: Optional[list | tuple] = None, # Eg (48.87645, 2.26340)
extension_dir: Optional[str] = None, # Chrome extension directory
**kwargs: Optional[dict],
) -> Browser:
Expand Down Expand Up @@ -296,6 +318,13 @@ async def start(
proxy_user,
proxy_pass,
)
if ad_block:
incognito = False
guest = False
ad_block_zip = AD_BLOCK_ZIP_PATH
ad_block_dir = os.path.join(DOWNLOADS_FOLDER, "ad_block")
__unzip_to_new_folder(ad_block_zip, ad_block_dir)
extension_dir = __add_chrome_ext_dir(extension_dir, ad_block_dir)
if not config:
config = Config(
user_data_dir,
Expand Down Expand Up @@ -329,6 +358,26 @@ async def start(
sb_config._cdp_locale = kwargs["locale_code"]
else:
sb_config._cdp_locale = None
if tzone:
sb_config._cdp_timezone = tzone
elif "timezone" in kwargs:
sb_config._cdp_timezone = kwargs["timezone"]
else:
sb_config._cdp_timezone = None
if geoloc:
sb_config._cdp_geolocation = geoloc
elif "geolocation" in kwargs:
sb_config._cdp_geolocation = kwargs["geolocation"]
else:
sb_config._cdp_geolocation = None
if agent:
sb_config._cdp_user_agent = agent
elif "user_agent" in kwargs:
sb_config._cdp_user_agent = kwargs["user_agent"]
else:
sb_config._cdp_user_agent = None
if "platform" in kwargs:
sb_config._cdp_platform = kwargs["platform"]
return driver


Expand Down
32 changes: 31 additions & 1 deletion seleniumbase/undetected/cdp_driver/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,37 @@ async def wait(self, t: Union[int, float] = None):

async def set_locale(self, locale: Optional[str] = None):
"""Sets the Language Locale code via set_user_agent_override."""
await self.send(cdp.network.set_user_agent_override("", locale))
await self.set_user_agent(user_agent="", accept_language=locale)

async def set_timezone(self, timezone: Optional[str] = None):
"""Sets the Timezone via set_timezone_override."""
await self.send(cdp.emulation.set_timezone_override(timezone))

async def set_user_agent(
self,
user_agent: Optional[str] = "",
accept_language: Optional[str] = None,
platform: Optional[str] = None, # navigator.platform
):
"""Sets the User Agent via set_user_agent_override."""
if not user_agent:
user_agent = ""
await self.send(cdp.network.set_user_agent_override(
user_agent=user_agent,
accept_language=accept_language,
platform=platform,
))

async def set_geolocation(self, geolocation: Optional[tuple] = None):
"""Sets the User Agent via set_geolocation_override."""
await self.send(cdp.browser.grant_permissions(
permissions=["geolocation"],
))
await self.send(cdp.emulation.set_geolocation_override(
latitude=geolocation[0],
longitude=geolocation[1],
accuracy=100,
))

def __getattr__(self, item):
""":meta private:"""
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
'pip>=25.0.1',
'packaging>=25.0',
'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues
'setuptools>=79.0.0;python_version>="3.10"',
'setuptools>=79.0.1;python_version>="3.10"',
'wheel>=0.45.1',
'attrs>=25.3.0',
"certifi>=2025.1.31",
Expand All @@ -160,7 +160,7 @@
'filelock~=3.16.1;python_version<"3.9"',
'filelock>=3.18.0;python_version>="3.9"',
'fasteners>=0.19',
"mycdp>=1.1.1",
"mycdp>=1.2.0",
"pynose>=1.5.4",
'platformdirs>=4.3.6;python_version<"3.9"',
'platformdirs>=4.3.7;python_version>="3.9"',
Expand All @@ -185,7 +185,7 @@
'urllib3>=1.26.20,<2.5.0;python_version>="3.10"',
'requests==2.32.3',
'sniffio==1.3.1',
'h11==0.14.0',
'h11==0.16.0',
'outcome==1.3.0.post0',
'trio==0.27.0;python_version<"3.9"',
'trio==0.30.0;python_version>="3.9"',
Expand Down