Skip to content

Commit 1aa0221

Browse files
committed
Allow options to be passed into pyppeteer.defaultArgs
1 parent 75f9f7f commit 1aa0221

File tree

6 files changed

+131
-106
lines changed

6 files changed

+131
-106
lines changed

docs/reference.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ Environment Variables
3131
download process. Acceptable values are ``1`` or ``true`` (case-insensitive).
3232

3333

34-
Launcher
35-
--------
34+
Pyppeteer Main Module
35+
---------------------
3636

37-
.. currentmodule:: pyppeteer.launcher
37+
.. currentmodule:: pyppeteer
3838

3939
.. autofunction:: launch
4040
.. autofunction:: connect
41+
.. autofunction:: defaultArgs
4142
.. autofunction:: executablePath
4243

4344
Browser Class

pyppeteer/connection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class CDPSession(EventEmitter):
191191
* protocol events can be subscribed to with :meth:`on` method.
192192
193193
Documentation on DevTools Protocol can be found
194-
`here <https://chromedevtools.github.io/devtools-protocol/>`_.
194+
`here <https://chromedevtools.github.io/devtools-protocol/>`__.
195195
"""
196196

197197
def __init__(self, connection: Union[Connection, 'CDPSession'],

pyppeteer/launcher.py

+109-84
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import asyncio
77
import atexit
8+
from copy import copy
89
import json
910
from urllib.request import urlopen
1011
from urllib.error import URLError
@@ -56,9 +57,6 @@
5657
'--metrics-recording-only',
5758
'--no-first-run',
5859
'--safebrowsing-disable-auto-update',
59-
]
60-
61-
AUTOMATION_ARGS = [
6260
'--enable-automation',
6361
'--password-store=basic',
6462
'--use-mock-keychain',
@@ -71,80 +69,60 @@ class Launcher(object):
7169
def __init__(self, options: Dict[str, Any] = None, # noqa: C901
7270
**kwargs: Any) -> None:
7371
"""Make new launcher."""
74-
self.options = merge_dict(options, kwargs)
72+
options = merge_dict(options, kwargs)
73+
7574
self.port = get_free_port()
7675
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
7978

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')
8195
if logLevel:
8296
logging.getLogger('pyppeteer').setLevel(logLevel)
8397

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
89100

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:
122114
if not check_chromium():
123115
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
141119

142120
def _cleanup_tmp_user_data_dir(self) -> None:
143121
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):
148126
time.sleep(0.01)
149127
else:
150128
break
@@ -157,8 +135,8 @@ async def launch(self) -> Browser: # noqa: C901
157135
self.connection: Optional[Connection] = None
158136

159137
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:
162140
options['stdout'] = subprocess.PIPE
163141
options['stderr'] = subprocess.STDOUT
164142

@@ -172,29 +150,27 @@ def _close_process(*args: Any, **kwargs: Any) -> None:
172150
self._loop.run_until_complete(self.killChrome())
173151

174152
# don't forget to close browser process
175-
if self.options.get('autoClose', True):
153+
if self.autoClose:
176154
atexit.register(_close_process)
177-
if self.options.get('handleSIGINT', True):
155+
if self.handleSIGINT:
178156
signal.signal(signal.SIGINT, _close_process)
179-
if self.options.get('handleSIGTERM', True):
157+
if self.handleSIGTERM:
180158
signal.signal(signal.SIGTERM, _close_process)
181159
if not sys.platform.startswith('win'):
182160
# SIGHUP is not defined on windows
183-
if self.options.get('handleSIGHUP', True):
161+
if self.handleSIGHUP:
184162
signal.signal(signal.SIGHUP, _close_process)
185163

186-
connectionDelay = self.options.get('slowMo', 0)
164+
connectionDelay = self.slowMo
187165
self.browserWSEndpoint = self._get_ws_endpoint()
188166
logger.info(f'Browser listening on: {self.browserWSEndpoint}')
189167
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+
)
196172
browser = await Browser.create(
197-
self.connection, [], ignoreHTTPSErrors, defaultViewport,
173+
self.connection, [], self.ignoreHTTPSErrors, self.defaultViewport,
198174
self.proc, self.killChrome)
199175
await self.ensureInitialPage(browser)
200176
return browser
@@ -257,7 +233,7 @@ async def killChrome(self) -> None:
257233
except Exception as e:
258234
# ignore errors on browser termination process
259235
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
261237
# Force kill chrome only when using temporary userDataDir
262238
self.waitForChromeToClose()
263239
self._cleanup_tmp_user_data_dir()
@@ -382,10 +358,59 @@ async def connect(options: dict = None, **kwargs: Any) -> Browser:
382358

383359

384360
def executablePath() -> str:
385-
"""Get executable path of default chrome."""
361+
"""Get executable path of default chromium."""
386362
return str(chromium_executable())
387363

388364

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

spell.txt

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ createincognitobrowsercontext
2323
csp
2424
css
2525
ctrl
26+
customargs
27+
defaultargs
2628
defaultdict
2729
devtool
2830
devtools

tests/test_launcher.py

+11-15
Original file line numberDiff line numberDiff line change
@@ -39,46 +39,42 @@ def setUp(self):
3939

4040
def check_default_args(self, launcher):
4141
for opt in self.headless_options:
42-
self.assertIn(opt, launcher.chrome_args)
43-
self.assertTrue(any(opt for opt in launcher.chrome_args
42+
self.assertIn(opt, launcher.chromeArguments)
43+
self.assertTrue(any(opt for opt in launcher.chromeArguments
4444
if opt.startswith('--user-data-dir')))
4545

4646
def test_no_option(self):
4747
launcher = Launcher()
4848
self.check_default_args(launcher)
49-
self.assertEqual(launcher.exec, str(chromium_executable()))
49+
self.assertEqual(launcher.chromeExecutable, str(chromium_executable()))
5050

5151
def test_disable_headless(self):
5252
launcher = Launcher({'headless': False})
5353
for opt in self.headless_options:
54-
self.assertNotIn(opt, launcher.chrome_args)
54+
self.assertNotIn(opt, launcher.chromeArguments)
5555

5656
def test_disable_default_args(self):
5757
launcher = Launcher(ignoreDefaultArgs=True)
5858
# check default args
59-
self.assertNotIn('--no-first-run', launcher.chrome_args)
60-
# check devtools port
61-
self.assertNotIn(
62-
'--remote-debugging-port={}'.format(launcher.port),
63-
launcher.chrome_args,
64-
)
59+
self.assertNotIn('--no-first-run', launcher.chromeArguments)
6560
# check automation args
66-
self.assertNotIn('--enable-automation', launcher.chrome_args)
61+
self.assertNotIn('--enable-automation', launcher.chromeArguments)
6762

6863
def test_executable(self):
6964
launcher = Launcher({'executablePath': '/path/to/chrome'})
70-
self.assertEqual(launcher.exec, '/path/to/chrome')
65+
self.assertEqual(launcher.chromeExecutable, '/path/to/chrome')
7166

7267
def test_args(self):
7368
launcher = Launcher({'args': ['--some-args']})
7469
self.check_default_args(launcher)
75-
self.assertIn('--some-args', launcher.chrome_args)
70+
self.assertIn('--some-args', launcher.chromeArguments)
7671

7772
def test_user_data_dir(self):
7873
launcher = Launcher({'args': ['--user-data-dir=/path/to/profile']})
7974
self.check_default_args(launcher)
80-
self.assertIn('--user-data-dir=/path/to/profile', launcher.chrome_args)
81-
self.assertIsNone(launcher._tmp_user_data_dir)
75+
self.assertIn('--user-data-dir=/path/to/profile',
76+
launcher.chromeArguments)
77+
self.assertIsNone(launcher.temporaryUserDataDir)
8278

8379
@sync
8480
async def test_close_no_connection(self):

tests/test_misc.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ def test_version_info(self):
2424

2525
class TestDefaultArgs(unittest.TestCase):
2626
def test_default_args(self):
27-
args = pyppeteer.defaultArgs()
28-
self.assertIn('--no-first-run', args)
29-
self.assertIn('--enable-automation', args)
27+
self.assertIn('--no-first-run', pyppeteer.defaultArgs())
28+
self.assertIn('--headless', pyppeteer.defaultArgs())
29+
self.assertNotIn('--headless', pyppeteer.defaultArgs({'headless': False})) # noqa: E501
30+
self.assertIn('--user-data-dir=foo', pyppeteer.defaultArgs(userDataDir='foo')) # noqa: E501
3031

3132

3233
class TestToInches(unittest.TestCase):

0 commit comments

Comments
 (0)