1- import webbrowser
2- import unittest
31import os
4- import sys
2+ import re
3+ import shlex
54import subprocess
6- from unittest import mock
5+ import sys
6+ import unittest
7+ import webbrowser
78from test import support
89from test .support import import_helper
10+ from test .support import is_apple_mobile
911from test .support import os_helper
12+ from test .support import requires_subprocess
13+ from test .support import threading_helper
14+ from unittest import mock
1015
16+ # The webbrowser module uses threading locks
17+ threading_helper .requires_working_threading (module = True )
1118
12- URL = 'http ://www.example.com'
19+ URL = 'https ://www.example.com'
1320CMD_NAME = 'test'
1421
1522
@@ -22,6 +29,7 @@ def wait(self, seconds=None):
2229 return 0
2330
2431
32+ @requires_subprocess ()
2533class CommandTestMixin :
2634
2735 def _test (self , meth , * , args = [URL ], kw = {}, options , arguments ):
@@ -92,10 +100,19 @@ def test_open_new_tab(self):
92100 options = [],
93101 arguments = [URL ])
94102
103+ def test_open_bad_new_parameter (self ):
104+ with self .assertRaisesRegex (webbrowser .Error ,
105+ re .escape ("Bad 'new' parameter to open(); "
106+ "expected 0, 1, or 2, got 999" )):
107+ self ._test ('open' ,
108+ options = [],
109+ arguments = [URL ],
110+ kw = dict (new = 999 ))
95111
96- class MozillaCommandTest (CommandTestMixin , unittest .TestCase ):
97112
98- browser_class = webbrowser .Mozilla
113+ class EdgeCommandTest (CommandTestMixin , unittest .TestCase ):
114+
115+ browser_class = webbrowser .Edge
99116
100117 def test_open (self ):
101118 self ._test ('open' ,
@@ -109,43 +126,43 @@ def test_open_with_autoraise_false(self):
109126
110127 def test_open_new (self ):
111128 self ._test ('open_new' ,
112- options = [],
113- arguments = ['-new-window' , URL ])
129+ options = ['--new-window' ],
130+ arguments = [URL ])
114131
115132 def test_open_new_tab (self ):
116133 self ._test ('open_new_tab' ,
117134 options = [],
118- arguments = ['-new-tab' , URL ])
135+ arguments = [URL ])
119136
120137
121- class NetscapeCommandTest (CommandTestMixin , unittest .TestCase ):
138+ class MozillaCommandTest (CommandTestMixin , unittest .TestCase ):
122139
123- browser_class = webbrowser .Netscape
140+ browser_class = webbrowser .Mozilla
124141
125142 def test_open (self ):
126143 self ._test ('open' ,
127- options = ['-raise' , '-remote' ],
128- arguments = ['openURL({})' . format ( URL ) ])
144+ options = [],
145+ arguments = [URL ])
129146
130147 def test_open_with_autoraise_false (self ):
131148 self ._test ('open' , kw = dict (autoraise = False ),
132- options = ['-noraise' , '-remote' ],
133- arguments = ['openURL({})' . format ( URL ) ])
149+ options = [],
150+ arguments = [URL ])
134151
135152 def test_open_new (self ):
136153 self ._test ('open_new' ,
137- options = ['-raise' , '-remote' ],
138- arguments = ['openURL({}, new-window)' . format ( URL ) ])
154+ options = [],
155+ arguments = ['- new-window' , URL ])
139156
140157 def test_open_new_tab (self ):
141158 self ._test ('open_new_tab' ,
142- options = ['-raise' , '-remote' ],
143- arguments = ['openURL({}, new-tab)' . format ( URL ) ])
159+ options = [],
160+ arguments = ['- new-tab' , URL ])
144161
145162
146- class GaleonCommandTest (CommandTestMixin , unittest .TestCase ):
163+ class EpiphanyCommandTest (CommandTestMixin , unittest .TestCase ):
147164
148- browser_class = webbrowser .Galeon
165+ browser_class = webbrowser .Epiphany
149166
150167 def test_open (self ):
151168 self ._test ('open' ,
@@ -199,22 +216,89 @@ class ELinksCommandTest(CommandTestMixin, unittest.TestCase):
199216
200217 def test_open (self ):
201218 self ._test ('open' , options = ['-remote' ],
202- arguments = ['openURL({})' . format ( URL ) ])
219+ arguments = [f 'openURL({ URL } )' ])
203220
204221 def test_open_with_autoraise_false (self ):
205222 self ._test ('open' ,
206223 options = ['-remote' ],
207- arguments = ['openURL({})' . format ( URL ) ])
224+ arguments = [f 'openURL({ URL } )' ])
208225
209226 def test_open_new (self ):
210227 self ._test ('open_new' ,
211228 options = ['-remote' ],
212- arguments = ['openURL({},new-window)' . format ( URL ) ])
229+ arguments = [f 'openURL({ URL } ,new-window)' ])
213230
214231 def test_open_new_tab (self ):
215232 self ._test ('open_new_tab' ,
216233 options = ['-remote' ],
217- arguments = ['openURL({},new-tab)' .format (URL )])
234+ arguments = [f'openURL({ URL } ,new-tab)' ])
235+
236+
237+ @unittest .skipUnless (sys .platform == "ios" , "Test only applicable to iOS" )
238+ class IOSBrowserTest (unittest .TestCase ):
239+ def _obj_ref (self , * args ):
240+ # Construct a string representation of the arguments that can be used
241+ # as a proxy for object instance references
242+ return "|" .join (str (a ) for a in args )
243+
244+ @unittest .skipIf (getattr (webbrowser , "objc" , None ) is None ,
245+ "iOS Webbrowser tests require ctypes" )
246+ def setUp (self ):
247+ # Intercept the objc library. Wrap the calls to get the
248+ # references to classes and selectors to return strings, and
249+ # wrap msgSend to return stringified object references
250+ self .orig_objc = webbrowser .objc
251+
252+ webbrowser .objc = mock .Mock ()
253+ webbrowser .objc .objc_getClass = lambda cls : f"C#{ cls .decode ()} "
254+ webbrowser .objc .sel_registerName = lambda sel : f"S#{ sel .decode ()} "
255+ webbrowser .objc .objc_msgSend .side_effect = self ._obj_ref
256+
257+ def tearDown (self ):
258+ webbrowser .objc = self .orig_objc
259+
260+ def _test (self , meth , ** kwargs ):
261+ # The browser always gets focus, there's no concept of separate browser
262+ # windows, and there's no API-level control over creating a new tab.
263+ # Therefore, all calls to webbrowser are effectively the same.
264+ getattr (webbrowser , meth )(URL , ** kwargs )
265+
266+ # The ObjC String version of the URL is created with UTF-8 encoding
267+ url_string_args = [
268+ "C#NSString" ,
269+ "S#stringWithCString:encoding:" ,
270+ b'https://www.example.com' ,
271+ 4 ,
272+ ]
273+ # The NSURL version of the URL is created from that string
274+ url_obj_args = [
275+ "C#NSURL" ,
276+ "S#URLWithString:" ,
277+ self ._obj_ref (* url_string_args ),
278+ ]
279+ # The openURL call is invoked on the shared application
280+ shared_app_args = ["C#UIApplication" , "S#sharedApplication" ]
281+
282+ # Verify that the last call is the one that opens the URL.
283+ webbrowser .objc .objc_msgSend .assert_called_with (
284+ self ._obj_ref (* shared_app_args ),
285+ "S#openURL:options:completionHandler:" ,
286+ self ._obj_ref (* url_obj_args ),
287+ None ,
288+ None
289+ )
290+
291+ def test_open (self ):
292+ self ._test ('open' )
293+
294+ def test_open_with_autoraise_false (self ):
295+ self ._test ('open' , autoraise = False )
296+
297+ def test_open_new (self ):
298+ self ._test ('open_new' )
299+
300+ def test_open_new_tab (self ):
301+ self ._test ('open_new_tab' )
218302
219303
220304class BrowserRegistrationTest (unittest .TestCase ):
@@ -269,6 +353,16 @@ def test_register_default(self):
269353 def test_register_preferred (self ):
270354 self ._check_registration (preferred = True )
271355
356+ @unittest .skipUnless (sys .platform == "darwin" , "macOS specific test" )
357+ def test_no_xdg_settings_on_macOS (self ):
358+ # On macOS webbrowser should not use xdg-settings to
359+ # look for X11 based browsers (for those users with
360+ # XQuartz installed)
361+ with mock .patch ("subprocess.check_output" ) as ck_o :
362+ webbrowser .register_standard_browsers ()
363+
364+ ck_o .assert_not_called ()
365+
272366
273367class ImportTest (unittest .TestCase ):
274368 def test_register (self ):
@@ -294,29 +388,38 @@ def test_get(self):
294388 webbrowser .get ('fakebrowser' )
295389 self .assertIsNotNone (webbrowser ._tryorder )
296390
391+ @unittest .skipIf (" " in sys .executable , "test assumes no space in path (GH-114452)" )
297392 def test_synthesize (self ):
298393 webbrowser = import_helper .import_fresh_module ('webbrowser' )
299394 name = os .path .basename (sys .executable ).lower ()
300395 webbrowser .register (name , None , webbrowser .GenericBrowser (name ))
301396 webbrowser .get (sys .executable )
302397
398+ @unittest .skipIf (
399+ is_apple_mobile ,
400+ "Apple mobile doesn't allow modifying browser with environment"
401+ )
303402 def test_environment (self ):
304403 webbrowser = import_helper .import_fresh_module ('webbrowser' )
305404 try :
306405 browser = webbrowser .get ().name
307- except ( webbrowser .Error , AttributeError ) as err :
406+ except webbrowser .Error as err :
308407 self .skipTest (str (err ))
309408 with os_helper .EnvironmentVarGuard () as env :
310409 env ["BROWSER" ] = browser
311410 webbrowser = import_helper .import_fresh_module ('webbrowser' )
312411 webbrowser .get ()
313412
413+ @unittest .skipIf (
414+ is_apple_mobile ,
415+ "Apple mobile doesn't allow modifying browser with environment"
416+ )
314417 def test_environment_preferred (self ):
315418 webbrowser = import_helper .import_fresh_module ('webbrowser' )
316419 try :
317420 webbrowser .get ()
318421 least_preferred_browser = webbrowser .get (webbrowser ._tryorder [- 1 ]).name
319- except (webbrowser .Error , AttributeError , IndexError ) as err :
422+ except (webbrowser .Error , IndexError ) as err :
320423 self .skipTest (str (err ))
321424
322425 with os_helper .EnvironmentVarGuard () as env :
@@ -330,5 +433,74 @@ def test_environment_preferred(self):
330433 self .assertEqual (webbrowser .get ().name , sys .executable )
331434
332435
333- if __name__ == '__main__' :
436+ class CliTest (unittest .TestCase ):
437+ def test_parse_args (self ):
438+ for command , url , new_win in [
439+ # No optional arguments
440+ ("https://example.com" , "https://example.com" , 0 ),
441+ # Each optional argument
442+ ("https://example.com -n" , "https://example.com" , 1 ),
443+ ("-n https://example.com" , "https://example.com" , 1 ),
444+ ("https://example.com -t" , "https://example.com" , 2 ),
445+ ("-t https://example.com" , "https://example.com" , 2 ),
446+ # Long form
447+ ("https://example.com --new-window" , "https://example.com" , 1 ),
448+ ("--new-window https://example.com" , "https://example.com" , 1 ),
449+ ("https://example.com --new-tab" , "https://example.com" , 2 ),
450+ ("--new-tab https://example.com" , "https://example.com" , 2 ),
451+ ]:
452+ args = webbrowser .parse_args (shlex .split (command ))
453+
454+ self .assertEqual (args .url , url )
455+ self .assertEqual (args .new_win , new_win )
456+
457+ def test_parse_args_error (self ):
458+ for command in [
459+ # Arguments must not both be given
460+ "https://example.com -n -t" ,
461+ "https://example.com --new-window --new-tab" ,
462+ "https://example.com -n --new-tab" ,
463+ "https://example.com --new-window -t" ,
464+ ]:
465+ with support .captured_stderr () as stderr :
466+ with self .assertRaises (SystemExit ):
467+ webbrowser .parse_args (shlex .split (command ))
468+ self .assertIn (
469+ 'error: argument -t/--new-tab: not allowed with argument -n/--new-window' ,
470+ stderr .getvalue (),
471+ )
472+
473+ # Ensure ambiguous shortening fails
474+ with support .captured_stderr () as stderr :
475+ with self .assertRaises (SystemExit ):
476+ webbrowser .parse_args (shlex .split ("https://example.com --new" ))
477+ self .assertIn (
478+ 'error: ambiguous option: --new could match --new-window, --new-tab' ,
479+ stderr .getvalue ()
480+ )
481+
482+ def test_main (self ):
483+ for command , expected_url , expected_new_win in [
484+ # No optional arguments
485+ ("https://example.com" , "https://example.com" , 0 ),
486+ # Each optional argument
487+ ("https://example.com -n" , "https://example.com" , 1 ),
488+ ("-n https://example.com" , "https://example.com" , 1 ),
489+ ("https://example.com -t" , "https://example.com" , 2 ),
490+ ("-t https://example.com" , "https://example.com" , 2 ),
491+ # Long form
492+ ("https://example.com --new-window" , "https://example.com" , 1 ),
493+ ("--new-window https://example.com" , "https://example.com" , 1 ),
494+ ("https://example.com --new-tab" , "https://example.com" , 2 ),
495+ ("--new-tab https://example.com" , "https://example.com" , 2 ),
496+ ]:
497+ with (
498+ mock .patch ("webbrowser.open" , return_value = None ) as mock_open ,
499+ mock .patch ("builtins.print" , return_value = None ),
500+ ):
501+ webbrowser .main (shlex .split (command ))
502+ mock_open .assert_called_once_with (expected_url , expected_new_win )
503+
504+
505+ if __name__ == '__main__' :
334506 unittest .main ()
0 commit comments