Skip to content

Commit bd3f92e

Browse files
committed
fix: removing options preferences private attributes
1 parent 066b52b commit bd3f92e

File tree

3 files changed

+119
-28
lines changed

3 files changed

+119
-28
lines changed

pydoll/browser/options.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ def __init__(self):
2121
self._binary_location = ''
2222
self._start_timeout = 10
2323
self._browser_preferences = {}
24-
self._prompt_for_download = True
25-
self._block_popups = False
26-
self._password_manager_enabled = True
27-
self._block_notifications = False
28-
self._allow_automatic_downloads = False
29-
self._open_pdf_externally = False
3024

3125
@property
3226
def arguments(self) -> list[str]:
@@ -131,6 +125,25 @@ def _set_pref_path(self, path: list, value):
131125
d = d.setdefault(key, {})
132126
d[path[-1]] = value
133127

128+
def _get_pref_path(self, path: list):
129+
"""
130+
Safely gets a nested value from self._browser_preferences.
131+
132+
Arguments:
133+
path -- List of keys representing the nested
134+
path (e.g., ['plugins', 'always_open_pdf_externally'])
135+
136+
Returns:
137+
The value at the given path, or None if path doesn't exist
138+
"""
139+
d = self._browser_preferences
140+
try:
141+
for key in path:
142+
d = d[key]
143+
return d
144+
except (KeyError, TypeError):
145+
return None
146+
134147
def set_default_download_directory(self, path: str):
135148
"""
136149
Set the default directory where downloaded files will be saved.
@@ -155,7 +168,7 @@ def set_accept_languages(self, languages: str):
155168

156169
@property
157170
def prompt_for_download(self) -> bool:
158-
return self._prompt_for_download
171+
return self._get_pref_path(['download', 'prompt_for_download'])
159172

160173
@prompt_for_download.setter
161174
def prompt_for_download(self, enabled: bool):
@@ -167,12 +180,11 @@ def prompt_for_download(self, enabled: bool):
167180
Arguments:
168181
enabled -- If True, Chrome will ask for confirmation before downloading.
169182
"""
170-
self._prompt_for_download = enabled
171-
self._set_pref_path(['download', 'prompt_for_download'], self._prompt_for_download)
183+
self._set_pref_path(['download', 'prompt_for_download'], enabled)
172184

173185
@property
174186
def block_popups(self) -> bool:
175-
return self._block_popups == 0
187+
return self._get_pref_path(['profile', 'default_content_setting_values', 'popups']) == 0
176188

177189
@block_popups.setter
178190
def block_popups(self, block: bool):
@@ -184,14 +196,13 @@ def block_popups(self, block: bool):
184196
Arguments:
185197
block -- If True, pop-ups will be blocked (value = 0); otherwise allowed (value = 1).
186198
"""
187-
self._block_popups = 0 if block else 1
188199
self._set_pref_path(
189-
['profile', 'default_content_setting_values', 'popups'], self._block_popups
200+
['profile', 'default_content_setting_values', 'popups'], 0 if block else 1
190201
)
191202

192203
@property
193204
def password_manager_enabled(self) -> bool:
194-
return self._password_manager_enabled
205+
return self._get_pref_path(['profile', 'password_manager_enabled'])
195206

196207
@password_manager_enabled.setter
197208
def password_manager_enabled(self, enabled: bool):
@@ -203,16 +214,16 @@ def password_manager_enabled(self, enabled: bool):
203214
Arguments:
204215
enabled -- If True, the password manager is active.
205216
"""
206-
self._password_manager_enabled = enabled
207-
self._set_pref_path(['profile', 'password_manager_enabled'], self._password_manager_enabled)
208-
self._set_pref_path(
209-
['credentials_enable_service'], self._password_manager_enabled
210-
) # todo: colocar em outra propriedade
217+
self._set_pref_path(['profile', 'password_manager_enabled'], enabled)
218+
self._set_pref_path(['credentials_enable_service'], enabled)
211219

212220
@property
213221
def block_notifications(self) -> bool:
214222
block_notifications_true_value = 2
215-
return self._block_notifications == block_notifications_true_value
223+
return (
224+
self._get_pref_path(['profile', 'default_content_setting_values', 'notifications'])
225+
== block_notifications_true_value
226+
)
216227

217228
@block_notifications.setter
218229
def block_notifications(self, block: bool):
@@ -225,15 +236,19 @@ def block_notifications(self, block: bool):
225236
block -- If True, notifications will be blocked (value = 2);
226237
otherwise allowed (value = 1).
227238
"""
228-
self._block_notifications = 2 if block else 1
229239
self._set_pref_path(
230240
['profile', 'default_content_setting_values', 'notifications'],
231-
self._block_notifications,
241+
2 if block else 1,
232242
)
233243

234244
@property
235245
def allow_automatic_downloads(self) -> bool:
236-
return self._allow_automatic_downloads == 1
246+
return (
247+
self._get_pref_path(
248+
['profile', 'default_content_setting_values', 'automatic_downloads']
249+
)
250+
== 1
251+
)
237252

238253
@allow_automatic_downloads.setter
239254
def allow_automatic_downloads(self, allow: bool):
@@ -246,15 +261,14 @@ def allow_automatic_downloads(self, allow: bool):
246261
allow -- If True, automatic downloads are allowed (value = 1);
247262
otherwise blocked (value = 2).
248263
"""
249-
self._allow_automatic_downloads = 1 if allow else 2
250264
self._set_pref_path(
251265
['profile', 'default_content_setting_values', 'automatic_downloads'],
252-
self._allow_automatic_downloads,
266+
1 if allow else 2,
253267
)
254268

255269
@property
256270
def open_pdf_externally(self) -> bool:
257-
return self._open_pdf_externally
271+
return self._get_pref_path(['plugins', 'always_open_pdf_externally'])
258272

259273
@open_pdf_externally.setter
260274
def open_pdf_externally(self, enabled: bool):
@@ -266,5 +280,4 @@ def open_pdf_externally(self, enabled: bool):
266280
Arguments:
267281
block -- If True, location access is blocked (value = 2); otherwise allowed (value = 1).
268282
"""
269-
self._open_pdf_externally = enabled
270-
self._set_pref_path(['plugins', 'always_open_pdf_externally'], self._open_pdf_externally)
283+
self._set_pref_path(['plugins', 'always_open_pdf_externally'], enabled)

tests/test_browser/test_browser_chrome.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,59 @@ async def test_chrome_user_data_dir_and_preferences(self, tmp_path):
516516
with open(prefs_file, 'r', encoding='utf-8') as f:
517517
final_prefs = json.load(f)
518518
assert final_prefs == initial_prefs
519+
520+
@pytest.mark.asyncio
521+
async def test_chrome_user_data_dir_with_invalid_json_preferences(self, tmp_path):
522+
"""Test Chrome with user data directory containing invalid JSON preferences."""
523+
user_data_dir = tmp_path / 'chrome_profile'
524+
user_data_dir.mkdir()
525+
526+
prefs_dir = user_data_dir / 'Default'
527+
prefs_dir.mkdir()
528+
prefs_file = prefs_dir / 'Preferences'
529+
530+
# Write invalid JSON to the Preferences file
531+
invalid_json = '{ "profile": { "exit_type": "Normal", "exited_cleanly": true, } }' # trailing comma makes it invalid
532+
prefs_file.write_text(invalid_json, encoding='utf-8')
533+
534+
custom_options = ChromiumOptions()
535+
custom_options.add_argument(f'--user-data-dir={user_data_dir}')
536+
custom_options.browser_preferences = {
537+
'test_pref': 'new_value',
538+
'new_pref': 'some_value'
539+
}
540+
custom_options.prompt_for_download = False
541+
542+
with patch.multiple(
543+
Chrome,
544+
_get_default_binary_location=MagicMock(return_value='/fake/chrome'),
545+
), patch(
546+
'pydoll.browser.managers.browser_process_manager.BrowserProcessManager',
547+
autospec=True,
548+
), patch(
549+
'pydoll.browser.managers.temp_dir_manager.TempDirectoryManager',
550+
autospec=True,
551+
), patch(
552+
'pydoll.connection.connection_handler.ConnectionHandler',
553+
autospec=True,
554+
), patch(
555+
'pydoll.browser.managers.proxy_manager.ProxyManager',
556+
autospec=True,
557+
):
558+
async with Chrome(options=custom_options) as chrome:
559+
chrome._setup_user_dir()
560+
assert f'--user-data-dir={user_data_dir}' in chrome.options.arguments
561+
562+
# The invalid JSON should be handled gracefully by suppress(json.JSONDecodeError)
563+
# and the preferences should be written with only the new preferences
564+
with open(prefs_file, 'r', encoding='utf-8') as f:
565+
updated_prefs = json.load(f)
566+
assert updated_prefs['test_pref'] == 'new_value'
567+
assert updated_prefs['new_pref'] == 'some_value'
568+
569+
# The original invalid JSON should be backed up
570+
backup_file = user_data_dir / 'Default' / 'Preferences.backup'
571+
assert backup_file.exists()
572+
with open(backup_file, 'r', encoding='utf-8') as f:
573+
backup_content = f.read()
574+
assert backup_content == invalid_json

tests/test_browser/test_browser_options.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ def test_set_open_pdf_externally():
102102
assert options.open_pdf_externally is True
103103

104104

105-
106105
def test_set_accept_languages():
107106
options = Options()
108107
options.set_accept_languages('pt-BR,pt,en-US,en')
@@ -161,27 +160,50 @@ def test_wrong_dict_prefs_error():
161160
}
162161
}
163162

163+
def test_set_arguments():
164+
options = Options()
165+
options.arguments = ['--headless']
166+
assert options.arguments == ['--headless']
167+
168+
def test_get_pref_path():
169+
options = Options()
170+
options.set_default_download_directory('/tmp/downloads')
171+
assert options._get_pref_path(['download', 'default_directory']) == '/tmp/downloads'
172+
173+
174+
def test_get_pref_path_none():
175+
options = Options()
176+
assert options._get_pref_path(['download', 'default_directory']) is None
177+
164178

165179
def test_options_interface_enforcement():
166180
with pytest.raises(TypeError):
167181
OptionsInterface()
182+
168183
class IncompleteOptions(OptionsInterface):
169184
pass
185+
170186
with pytest.raises(TypeError):
171187
IncompleteOptions()
188+
172189
class CompleteOptions(OptionsInterface):
173190
@property
174191
def arguments(self):
175192
return []
193+
176194
@property
177195
def binary_location(self):
178196
return ''
197+
179198
@property
180199
def start_timeout(self):
181200
return 0
201+
182202
def add_argument(self, argument):
183203
pass
204+
184205
@property
185206
def browser_preferences(self):
186207
return {}
208+
187209
CompleteOptions()

0 commit comments

Comments
 (0)