5
5
6
6
import asyncio
7
7
import atexit
8
+ from copy import copy
8
9
import json
9
10
from urllib .request import urlopen
10
11
from urllib .error import URLError
56
57
'--metrics-recording-only' ,
57
58
'--no-first-run' ,
58
59
'--safebrowsing-disable-auto-update' ,
59
- ]
60
-
61
- AUTOMATION_ARGS = [
62
60
'--enable-automation' ,
63
61
'--password-store=basic' ,
64
62
'--use-mock-keychain' ,
@@ -71,80 +69,60 @@ class Launcher(object):
71
69
def __init__ (self , options : Dict [str , Any ] = None , # noqa: C901
72
70
** kwargs : Any ) -> None :
73
71
"""Make new launcher."""
74
- self .options = merge_dict (options , kwargs )
72
+ options = merge_dict (options , kwargs )
73
+
75
74
self .port = get_free_port ()
76
75
self .url = f'http://127.0.0.1:{ self .port } '
77
- self .chrome_args : List [ str ] = []
78
- self ._loop = self . options . get ( 'loop' , asyncio . get_event_loop ())
76
+ self ._loop = options . get ( 'loop' , asyncio . get_event_loop ())
77
+ self .chromeClosed = True
79
78
80
- logLevel = self .options .get ('logLevel' )
79
+ ignoreDefaultArgs = options .get ('ignoreDefaultArgs' , False )
80
+ args : List [str ] = list ()
81
+
82
+ self .dumpio = options .get ('dumpio' , False )
83
+ executablePath = options .get ('executablePath' )
84
+ self .env = options .get ('env' )
85
+ self .handleSIGINT = options .get ('handleSIGINT' , True )
86
+ self .handleSIGTERM = options .get ('handleSIGTERM' , True )
87
+ self .handleSIGHUP = options .get ('handleSIGHUP' , True )
88
+ self .ignoreHTTPSErrors = options .get ('ignoreHTTPSErrors' , False )
89
+ self .defaultViewport = options .get ('defaultViewport' , {'width' : 800 , 'height' : 600 }) # noqa: E501
90
+ self .slowMo = options .get ('slowMo' , 0 )
91
+ self .timeout = options .get ('timeout' , 30000 )
92
+ self .autoClose = options .get ('autoClose' , True )
93
+
94
+ logLevel = options .get ('logLevel' )
81
95
if logLevel :
82
96
logging .getLogger ('pyppeteer' ).setLevel (logLevel )
83
97
84
- if not self .options .get ('ignoreDefaultArgs' , False ):
85
- self .chrome_args .extend (DEFAULT_ARGS )
86
- self .chrome_args .append (
87
- f'--remote-debugging-port={ self .port } ' ,
88
- )
98
+ self .chromeArguments : List [str ] = defaultArgs (options ) if not ignoreDefaultArgs else args # noqa: E501
99
+ self .temporaryUserDataDir : Optional [str ] = None
89
100
90
- self .chromeClosed = True
91
- if not self .options .get ('ignoreDefaultArgs' , False ):
92
- self .chrome_args .extend (AUTOMATION_ARGS )
93
-
94
- self ._tmp_user_data_dir : Optional [str ] = None
95
- self ._parse_args ()
96
-
97
- if self .options .get ('devtools' ):
98
- self .chrome_args .append ('--auto-open-devtools-for-tabs' )
99
- self .options ['headless' ] = False
100
-
101
- if 'headless' not in self .options or self .options .get ('headless' ):
102
- self .chrome_args .extend ([
103
- '--headless' ,
104
- '--hide-scrollbars' ,
105
- '--mute-audio' ,
106
- ])
107
- if current_platform ().startswith ('win' ):
108
- self .chrome_args .append ('--disable-gpu' )
109
-
110
- def _is_default_url () -> bool :
111
- for arg in self .options ['args' ]:
112
- if not arg .startswith ('-' ):
113
- return False
114
- return True
115
-
116
- if isinstance (self .options .get ('args' ), list ) and _is_default_url ():
117
- self .chrome_args .append ('about:blank' )
118
-
119
- if 'executablePath' in self .options :
120
- self .exec = self .options ['executablePath' ]
121
- else :
101
+ if not any (arg for arg in self .chromeArguments
102
+ if arg .startswith ('--remote-debugging-' )):
103
+ self .chromeArguments .append (f'--remote-debugging-port={ self .port } ' )
104
+
105
+ if not any (arg for arg in self .chromeArguments
106
+ if arg .startswith ('--user-data-dir' )):
107
+ if not CHROME_PROFILE_PATH .exists ():
108
+ CHROME_PROFILE_PATH .mkdir (parents = True )
109
+ self .temporaryUserDataDir = tempfile .mkdtemp (dir = str (CHROME_PROFILE_PATH )) # noqa: E501
110
+ self .chromeArguments .append (f'--user-data-dir={ self .temporaryUserDataDir } ' ) # noqa: E501
111
+
112
+ self .chromeExecutable = executablePath
113
+ if not self .chromeExecutable :
122
114
if not check_chromium ():
123
115
download_chromium ()
124
- self .exec = str (chromium_executable ())
125
-
126
- self .cmd = [self .exec ] + self .chrome_args
127
-
128
- def _parse_args (self ) -> None :
129
- if (not isinstance (self .options .get ('args' ), list ) or
130
- not any (opt for opt in self .options ['args' ]
131
- if opt .startswith ('--user-data-dir' ))):
132
- if 'userDataDir' not in self .options :
133
- if not CHROME_PROFILE_PATH .exists ():
134
- CHROME_PROFILE_PATH .mkdir (parents = True )
135
- self ._tmp_user_data_dir = tempfile .mkdtemp (
136
- dir = str (CHROME_PROFILE_PATH ))
137
- self .chrome_args .append ('--user-data-dir={}' .format (
138
- self .options .get ('userDataDir' , self ._tmp_user_data_dir )))
139
- if isinstance (self .options .get ('args' ), list ):
140
- self .chrome_args .extend (self .options ['args' ])
116
+ self .chromeExecutable = str (chromium_executable ())
117
+
118
+ self .cmd = [self .chromeExecutable ] + self .chromeArguments
141
119
142
120
def _cleanup_tmp_user_data_dir (self ) -> None :
143
121
for retry in range (100 ):
144
- if self ._tmp_user_data_dir and os .path .exists (
145
- self ._tmp_user_data_dir ):
146
- shutil .rmtree (self ._tmp_user_data_dir , ignore_errors = True )
147
- if os .path .exists (self ._tmp_user_data_dir ):
122
+ if self .temporaryUserDataDir and os .path .exists (
123
+ self .temporaryUserDataDir ):
124
+ shutil .rmtree (self .temporaryUserDataDir , ignore_errors = True )
125
+ if os .path .exists (self .temporaryUserDataDir ):
148
126
time .sleep (0.01 )
149
127
else :
150
128
break
@@ -157,8 +135,8 @@ async def launch(self) -> Browser: # noqa: C901
157
135
self .connection : Optional [Connection ] = None
158
136
159
137
options = dict ()
160
- options ['env' ] = self .options . get ( ' env' )
161
- if not self .options . get ( ' dumpio' ) :
138
+ options ['env' ] = self .env
139
+ if not self .dumpio :
162
140
options ['stdout' ] = subprocess .PIPE
163
141
options ['stderr' ] = subprocess .STDOUT
164
142
@@ -172,29 +150,27 @@ def _close_process(*args: Any, **kwargs: Any) -> None:
172
150
self ._loop .run_until_complete (self .killChrome ())
173
151
174
152
# don't forget to close browser process
175
- if self .options . get ( ' autoClose' , True ) :
153
+ if self .autoClose :
176
154
atexit .register (_close_process )
177
- if self .options . get ( ' handleSIGINT' , True ) :
155
+ if self .handleSIGINT :
178
156
signal .signal (signal .SIGINT , _close_process )
179
- if self .options . get ( ' handleSIGTERM' , True ) :
157
+ if self .handleSIGTERM :
180
158
signal .signal (signal .SIGTERM , _close_process )
181
159
if not sys .platform .startswith ('win' ):
182
160
# SIGHUP is not defined on windows
183
- if self .options . get ( ' handleSIGHUP' , True ) :
161
+ if self .handleSIGHUP :
184
162
signal .signal (signal .SIGHUP , _close_process )
185
163
186
- connectionDelay = self .options . get ( ' slowMo' , 0 )
164
+ connectionDelay = self .slowMo
187
165
self .browserWSEndpoint = self ._get_ws_endpoint ()
188
166
logger .info (f'Browser listening on: { self .browserWSEndpoint } ' )
189
167
self .connection = Connection (
190
- self .browserWSEndpoint , self ._loop , connectionDelay )
191
- ignoreHTTPSErrors = bool (self .options .get ('ignoreHTTPSErrors' , False ))
192
- defaultViewport = self .options .get ('defaultViewport' , {
193
- 'width' : 800 ,
194
- 'height' : 600 ,
195
- })
168
+ self .browserWSEndpoint ,
169
+ self ._loop ,
170
+ connectionDelay ,
171
+ )
196
172
browser = await Browser .create (
197
- self .connection , [], ignoreHTTPSErrors , defaultViewport ,
173
+ self .connection , [], self . ignoreHTTPSErrors , self . defaultViewport ,
198
174
self .proc , self .killChrome )
199
175
await self .ensureInitialPage (browser )
200
176
return browser
@@ -257,7 +233,7 @@ async def killChrome(self) -> None:
257
233
except Exception as e :
258
234
# ignore errors on browser termination process
259
235
debugError (logger , e )
260
- if self ._tmp_user_data_dir and os .path .exists (self ._tmp_user_data_dir ):
236
+ if self .temporaryUserDataDir and os .path .exists (self .temporaryUserDataDir ): # noqa: E501
261
237
# Force kill chrome only when using temporary userDataDir
262
238
self .waitForChromeToClose ()
263
239
self ._cleanup_tmp_user_data_dir ()
@@ -382,10 +358,59 @@ async def connect(options: dict = None, **kwargs: Any) -> Browser:
382
358
383
359
384
360
def executablePath () -> str :
385
- """Get executable path of default chrome ."""
361
+ """Get executable path of default chromium ."""
386
362
return str (chromium_executable ())
387
363
388
364
389
- def defaultArgs () -> List [str ]:
390
- """Get list of default chrome args."""
391
- return DEFAULT_ARGS + AUTOMATION_ARGS
365
+ def defaultArgs (options : Dict = None , ** kwargs : Any ) -> List [str ]: # noqa: C901,E501
366
+ """Get the default flags the chromium will be launched.
367
+
368
+ ``options`` or keyword arguments are set of configurable options to set on
369
+ the browser. Can have the following fields:
370
+
371
+ * ``headless`` (bool): Whether to run browser in headless mode. Defaults to
372
+ ``True`` unless the ``devtools`` option is ``True``.
373
+ * ``args`` (List[str]): Additional arguments to pass to the browser
374
+ instance. The list of chromium flags can be found
375
+ `here <http://peter.sh/experiments/chromium-command-line-switches/>`__.
376
+ * ``userDataDir`` (str): Path to a User Data Directory.
377
+ * ``devtools`` (bool): Whether to auto-open DevTools panel for each tab. If
378
+ this option is ``True``, the ``headless`` option will be set ``False``.
379
+
380
+ ``pyppeteer.defaultArgs`` can be used to turn off some flags that pyppeteer
381
+ usually launches chromium with:
382
+
383
+ .. code:: python
384
+
385
+ customArgs = [arg for arg in pyppeteer.defaultArgs()
386
+ if arg != '--mute-audio']
387
+ browser = await pyppeteer.launch(
388
+ ignoreDefaultArgs=True,
389
+ args=customArgs,
390
+ )
391
+ """
392
+ options = merge_dict (options , kwargs )
393
+ devtools = options .get ('devtools' , False )
394
+ headless = options .get ('headless' , not devtools )
395
+ args = options .get ('args' , list ())
396
+ userDataDir = options .get ('userDataDir' )
397
+ chromeArguments = copy (DEFAULT_ARGS )
398
+
399
+ if userDataDir :
400
+ chromeArguments .append (f'--user-data-dir={ userDataDir } ' )
401
+ if devtools :
402
+ chromeArguments .append ('--auto-open-devtools-for-tabs' )
403
+ if headless :
404
+ chromeArguments .extend ((
405
+ '--headless' ,
406
+ '--hide-scrollbars' ,
407
+ '--mute-audio' ,
408
+ ))
409
+ if current_platform ().startswith ('win' ):
410
+ chromeArguments .append ('--disable-gpu' )
411
+
412
+ if all (map (lambda arg : arg .startswith ('-' ), args )): # type: ignore
413
+ chromeArguments .append ('about:blank' )
414
+ chromeArguments .extend (args )
415
+
416
+ return chromeArguments
0 commit comments