Skip to content
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
4 changes: 4 additions & 0 deletions pydoll/browser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pydoll.browser.chrome import Chrome
from pydoll.browser.edge import Edge

__all__ = ['Chrome', 'Edge']
27 changes: 13 additions & 14 deletions pydoll/browser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from random import randint

from pydoll import exceptions
from pydoll.browser.constants import BrowserType
from pydoll.browser.managers import (
BrowserOptionsManager,
BrowserProcessManager,
Expand Down Expand Up @@ -35,6 +36,7 @@ def __init__(
self,
options: Options | None = None,
connection_port: int = None,
browser_type: BrowserType = None
):
"""
Initializes the Browser instance.
Expand All @@ -43,11 +45,15 @@ def __init__(
options (Options | None): An instance of the Options class to
configure the browser. If None, default options will be used.
connection_port (int): The port to connect to the browser.
browser_type (BrowserType): The type of browser to use.
If None, it will be inferred from the options.

Raises:
TypeError: If any of the arguments are not callable.
"""
self.options = BrowserOptionsManager.initialize_options(options)
self.options = BrowserOptionsManager.initialize_options(
options, browser_type
)
self._proxy_manager = ProxyManager(self.options)
self._connection_port = (
connection_port if connection_port else randint(9223, 9322)
Expand Down Expand Up @@ -549,20 +555,13 @@ async def _execute_command(self, command: dict):
)

def _setup_user_dir(self):
"""
Prepares the user data directory if needed.

This method creates a temporary directory for browser data if
no user directory is specified in the browser options.

Returns:
None
"""
temp_dir = self._temp_directory_manager.create_temp_dir()
if '--user-data-dir' not in [
arg.split('=')[0] for arg in self.options.arguments
"""Prepares the user data directory if necessary."""
if "--user-data-dir" not in [
arg.split("=")[0] for arg in self.options.arguments
]:
self.options.arguments.append(f'--user-data-dir={temp_dir.name}')
# For all browsers, use a temporary directory
temp_dir = self._temp_directory_manager.create_temp_dir()
self.options.arguments.append(f"--user-data-dir={temp_dir.name}")

@abstractmethod
def _get_default_binary_location(self) -> str:
Expand Down
3 changes: 2 additions & 1 deletion pydoll/browser/chrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Optional

from pydoll.browser.base import Browser
from pydoll.browser.constants import BrowserType
from pydoll.browser.managers import BrowserOptionsManager
from pydoll.browser.options import Options

Expand All @@ -28,7 +29,7 @@
connection_port (int): The port to connect to the browser.
Defaults to 9222.
"""
super().__init__(options, connection_port)
super().__init__(options, connection_port, BrowserType.CHROME)

Check warning on line 32 in pydoll/browser/chrome.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/chrome.py#L32

Added line #L32 was not covered by tests

@staticmethod
def _get_default_binary_location():
Expand Down
6 changes: 6 additions & 0 deletions pydoll/browser/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import Enum, auto


class BrowserType(Enum):
CHROME = auto()
EDGE = auto()
74 changes: 74 additions & 0 deletions pydoll/browser/edge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import platform
from typing import Optional

from pydoll.browser.base import Browser
from pydoll.browser.constants import BrowserType
from pydoll.browser.managers import BrowserOptionsManager
from pydoll.browser.options import Options


class Edge(Browser):
"""
A class that implements the Edge browser functionality.

This class provides specific implementation for launching and
controlling Microsoft Edge browsers.
"""

def __init__(
self,
options: Optional[Options] = None,
connection_port: Optional[int] = None,
):
"""
Initializes the Edge browser instance.

Args:
options (Options | None): An instance of Options class to configure
the browser. If None, default options will be used.
connection_port (int): The port to connect to the browser.
Defaults to a random port between 9223 and 9322.
"""
super().__init__(options, connection_port, BrowserType.EDGE)

@staticmethod
def _get_default_binary_location():
"""
Gets the default location of the Edge browser executable.

This method determines the default Edge executable path based
on the operating system.

Returns:
str: The path to the Edge browser executable.

Raises:
ValueError: If the operating system is not supported or
the browser executable is not found at the default location.
"""
os_name = platform.system()

browser_paths = {
"Windows": [
(r"C:\Program Files\Microsoft\Edge\Application"
r"\msedge.exe"),
(r"C:\Program Files (x86)\Microsoft\Edge"
r"\Application\msedge.exe"),
],
"Linux": [
"/usr/bin/microsoft-edge",
],
"Darwin": [
("/Applications/Microsoft Edge.app/Contents/MacOS"
"/Microsoft Edge"),
],
}

browser_path = browser_paths.get(os_name)

if not browser_path:
raise ValueError('Unsupported OS')

return BrowserOptionsManager.validate_browser_paths(
browser_path
)
92 changes: 60 additions & 32 deletions pydoll/browser/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from contextlib import suppress
from tempfile import TemporaryDirectory

from pydoll.browser.options import Options
from pydoll.browser.constants import BrowserType
from pydoll.browser.options import ChromeOptions, EdgeOptions, Options


class ProxyManager:
Expand Down Expand Up @@ -64,8 +65,8 @@
argument and the proxy value if found, None otherwise.
"""
for index, arg in enumerate(self.options.arguments):
if arg.startswith('--proxy-server='):
return index, arg.split('=', 1)[1]
if arg.startswith("--proxy-server="):
return index, arg.split("=", 1)[1]
return None

@staticmethod
Expand All @@ -87,12 +88,12 @@
- str: Password (or None if no credentials)
- str: Clean proxy URL without credentials
"""
if '@' not in proxy_value:
if "@" not in proxy_value:
return False, None, None, proxy_value

try:
creds_part, server_part = proxy_value.split('@', 1)
username, password = creds_part.split(':', 1)
creds_part, server_part = proxy_value.split("@", 1)
username, password = creds_part.split(":", 1)
return True, username, password, server_part
except ValueError:
return False, None, None, proxy_value
Expand All @@ -112,7 +113,7 @@
Returns:
None
"""
self.options.arguments[index] = f'--proxy-server={clean_proxy}'
self.options.arguments[index] = f"--proxy-server={clean_proxy}"


class BrowserProcessManager:
Expand Down Expand Up @@ -149,11 +150,13 @@
Returns:
subprocess.Popen: The started browser process.
"""
self._process = self._process_creator([
binary_location,
f'--remote-debugging-port={port}',
*arguments,
])
self._process = self._process_creator(
[
binary_location,
f"--remote-debugging-port={port}",
*arguments,
]
)
return self._process

@staticmethod
Expand Down Expand Up @@ -239,44 +242,69 @@

class BrowserOptionsManager:
@staticmethod
def initialize_options(options: Options | None) -> Options:
def initialize_options(
options: Options | None, browser_type: BrowserType = None
) -> Options:
"""
Initializes options for the browser.
Initialize browser options based on browser type.

This method ensures that a valid Options instance is available,
creating a default one if necessary.
Creates a new options instance based on browser type if none
is provided, or validates and returns the provided
options instance.

Args:
options (Options | None): An Options instance or None.
options (Options | None): Browser options instance.
If None, a new instance
will be created based on browser_type
browser_type (BrowserType): Type of browser, used to create
appropriate options instance

Returns:
Options: An initialized Options instance.
Options: The initialized browser options instance

Raises:
ValueError: If options is not None and not an instance of Options.
ValueError: If provided options is not an instance
of Options class
"""
if options is None:
return Options()
if browser_type == BrowserType.CHROME:
return ChromeOptions()

Check warning on line 271 in pydoll/browser/managers.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/managers.py#L271

Added line #L271 was not covered by tests
elif browser_type == BrowserType.EDGE:
return EdgeOptions()

Check warning on line 273 in pydoll/browser/managers.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/managers.py#L273

Added line #L273 was not covered by tests
else:
return Options()

if not isinstance(options, Options):
raise ValueError('Invalid options')
raise ValueError("Invalid options")

return options

@staticmethod
def add_default_arguments(options: Options):
"""
Adds default arguments to the provided options.
"""Adds default arguments to the provided options"""
options.arguments.append("--no-first-run")
options.arguments.append("--no-default-browser-check")

This method appends standard browser arguments that improve
reliability and automation performance.
# Add browser-specific arguments based on options type
if isinstance(options, EdgeOptions):
BrowserOptionsManager._add_edge_arguments(options)
elif isinstance(options, ChromeOptions):
BrowserOptionsManager._add_chrome_arguments(options)

Check warning on line 292 in pydoll/browser/managers.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/managers.py#L292

Added line #L292 was not covered by tests

Args:
options (Options): The options instance to modify.
@staticmethod
def _add_edge_arguments(options: Options):
"""Adds Edge-specific arguments to the options"""
options.add_argument("--disable-crash-reporter")
options.add_argument("--disable-features=TranslateUI")
options.add_argument("--disable-component-update")
options.add_argument("--disable-background-networking")
options.add_argument("--remote-allow-origins=*")

Returns:
None
"""
options.arguments.append('--no-first-run')
options.arguments.append('--no-default-browser-check')
@staticmethod
def _add_chrome_arguments(options: Options):
"""Adds Chrome-specific arguments to the options"""
options.add_argument("--remote-allow-origins=*")

Check warning on line 306 in pydoll/browser/managers.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/managers.py#L306

Added line #L306 was not covered by tests
# Add other Chrome-specific arguments here

@staticmethod
def validate_browser_paths(paths: list[str]) -> str:
Expand Down
25 changes: 23 additions & 2 deletions pydoll/browser/options.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

class Options:
"""
A class to manage command-line options for a browser instance.
Expand Down Expand Up @@ -26,6 +27,16 @@
"""
return self._arguments

@arguments.setter
def arguments(self, args_list: list):
"""
Sets the list of command-line arguments.

Args:
args_list (list): A list of command-line arguments.
"""
self._arguments = args_list

Check warning on line 38 in pydoll/browser/options.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/options.py#L38

Added line #L38 was not covered by tests

@property
def binary_location(self) -> str:
"""
Expand Down Expand Up @@ -56,7 +67,17 @@
Raises:
ValueError: If the argument is already in the list of arguments.
"""
if argument not in self.arguments:
self.arguments.append(argument)
if argument not in self._arguments:
self._arguments.append(argument)
else:
raise ValueError(f'Argument already exists: {argument}')


class ChromeOptions(Options):
def __init__(self):
super().__init__()

Check warning on line 78 in pydoll/browser/options.py

View check run for this annotation

Codecov / codecov/patch

pydoll/browser/options.py#L78

Added line #L78 was not covered by tests


class EdgeOptions(Options):
def __init__(self):
super().__init__()
1 change: 1 addition & 0 deletions tests/test_chrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,4 @@ async def test_register_event_callback_page_event():
for event in PageEvents.ALL_EVENTS:
with pytest.raises(exceptions.EventNotSupported):
await browser.on(event, AsyncMock())

Loading
Loading