Skip to content

Commit cab003b

Browse files
committed
feat: export build_launch_options helper for composing with existing Playwright
Adds build_launch_options() (re-exported from the package) that performs every step launch() does up to the pw.chromium.launch() call and returns the kwargs dict, so callers can drive their own Playwright instance. launch() and launch_async() both delegate to it so their arg-building can't diverge. Also adds humanize_browser() / humanize_browser_async() for parity with the JS side, applying the same humanize patching to a user-supplied browser. Closes #153.
1 parent a00bcca commit cab003b

3 files changed

Lines changed: 374 additions & 51 deletions

File tree

cloakbrowser/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
browser.close()
1212
"""
1313

14-
from .browser import launch, launch_async, launch_context, launch_context_async, launch_persistent_context, launch_persistent_context_async, ProxySettings, build_args, maybe_resolve_geoip
14+
from .browser import launch, launch_async, launch_context, launch_context_async, launch_persistent_context, launch_persistent_context_async, ProxySettings, build_args, build_launch_options, humanize_browser, humanize_browser_async, maybe_resolve_geoip
1515
from .config import CHROMIUM_VERSION, get_default_stealth_args
1616
from .download import binary_info, check_for_update, clear_cache, ensure_binary
1717
from .license import LicenseInfo, validate_license
@@ -43,6 +43,9 @@ def __getattr__(name):
4343
"CHROMIUM_VERSION",
4444
"get_default_stealth_args",
4545
"build_args",
46+
"build_launch_options",
47+
"humanize_browser",
48+
"humanize_browser_async",
4649
"maybe_resolve_geoip",
4750
"ProxySettings",
4851
"validate_license",
@@ -51,4 +54,3 @@ def __getattr__(name):
5154
"resolve_human_config",
5255
"__version__",
5356
]
54-

cloakbrowser/browser.py

Lines changed: 116 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,88 @@ class ProxySettings(_ProxySettingsRequired, total=False):
149149
password: str
150150

151151

152+
def build_launch_options(
153+
headless: bool = True,
154+
proxy: str | ProxySettings | None = None,
155+
args: list[str] | None = None,
156+
stealth_args: bool = True,
157+
timezone: str | None = None,
158+
locale: str | None = None,
159+
geoip: bool = False,
160+
extension_paths: list[str] | None = None,
161+
license_key: str | None = None,
162+
browser_version: str | None = None,
163+
_suppress_maximize: bool = False,
164+
**kwargs: Any,
165+
) -> dict[str, Any]:
166+
"""Build the kwargs dict that ``launch()`` passes to ``chromium.launch()``.
167+
168+
Use this when you already manage a Playwright instance and want to compose
169+
CloakBrowser's binary, proxy, environment, and stealth arguments yourself:
170+
``pw.chromium.launch(**build_launch_options(...))``.
171+
"""
172+
_check_removed_kwargs(kwargs)
173+
174+
binary_path = ensure_binary(license_key=license_key, browser_version=browser_version)
175+
timezone, locale, exit_ip = maybe_resolve_geoip(geoip, proxy, timezone, locale)
176+
proxy_kwargs, proxy_extra_args = _resolve_proxy_config(proxy, browser_version)
177+
args = _resolve_webrtc_args(args, proxy)
178+
args = _append_webrtc_exit_ip(args, exit_ip)
179+
180+
chrome_args = build_args(
181+
stealth_args,
182+
(args or []) + proxy_extra_args,
183+
timezone=timezone,
184+
locale=locale,
185+
headless=headless,
186+
extension_paths=extension_paths,
187+
start_maximized=(
188+
binary_supports_maximized_window(license_key, browser_version)
189+
and not _suppress_maximize
190+
),
191+
)
192+
_maybe_warn_windows_fonts(chrome_args)
193+
194+
launch_env = build_launch_env(license_key, user_env=kwargs.pop("env", None))
195+
env_kwargs = {} if launch_env is None else {"env": launch_env}
196+
197+
return {
198+
"executable_path": binary_path,
199+
"headless": headless,
200+
"args": chrome_args,
201+
"ignore_default_args": IGNORE_DEFAULT_ARGS,
202+
**env_kwargs,
203+
**proxy_kwargs,
204+
**kwargs,
205+
}
206+
207+
208+
def humanize_browser(
209+
browser: Any,
210+
human_preset: HumanPreset = "default",
211+
human_config: HumanConfigOverrides | None = None,
212+
) -> None:
213+
"""Apply the sync humanize layer to an existing Playwright Browser."""
214+
from .human import patch_browser
215+
from .human.config import resolve_config
216+
217+
cfg = resolve_config(human_preset, human_config)
218+
patch_browser(browser, cfg)
219+
220+
221+
def humanize_browser_async(
222+
browser: Any,
223+
human_preset: HumanPreset = "default",
224+
human_config: HumanConfigOverrides | None = None,
225+
) -> None:
226+
"""Apply the async humanize layer to an existing Playwright Browser."""
227+
from .human import patch_browser_async
228+
from .human.config import resolve_config
229+
230+
cfg = resolve_config(human_preset, human_config)
231+
patch_browser_async(browser, cfg)
232+
233+
152234
def launch(
153235
headless: bool = True,
154236
proxy: str | ProxySettings | None = None,
@@ -204,31 +286,26 @@ def launch(
204286

205287
from playwright.sync_api import sync_playwright
206288

207-
binary_path = ensure_binary(license_key=license_key, browser_version=browser_version)
208-
timezone, locale, exit_ip = maybe_resolve_geoip(geoip, proxy, timezone, locale)
209-
proxy_kwargs, proxy_extra_args = _resolve_proxy_config(proxy, browser_version)
210-
args = _resolve_webrtc_args(args, proxy)
211-
args = _append_webrtc_exit_ip(args, exit_ip)
212-
213-
chrome_args = build_args(stealth_args, (args or []) + proxy_extra_args, timezone=timezone, locale=locale, headless=headless, extension_paths=extension_paths, start_maximized=binary_supports_maximized_window(license_key, browser_version) and not _suppress_maximize)
214-
_maybe_warn_windows_fonts(chrome_args)
215-
216-
logger.debug("Launching stealth Chromium (headless=%s, args=%d)", headless, len(chrome_args))
217-
218-
launch_env = build_launch_env(license_key, user_env=kwargs.pop("env", None))
219-
env_kwargs = {} if launch_env is None else {"env": launch_env}
220-
221-
pw = sync_playwright().start()
222-
browser = pw.chromium.launch(
223-
executable_path=binary_path,
289+
launch_options = build_launch_options(
224290
headless=headless,
225-
args=chrome_args,
226-
ignore_default_args=IGNORE_DEFAULT_ARGS,
227-
**env_kwargs,
228-
**proxy_kwargs,
291+
proxy=proxy,
292+
args=args,
293+
stealth_args=stealth_args,
294+
timezone=timezone,
295+
locale=locale,
296+
geoip=geoip,
297+
extension_paths=extension_paths,
298+
license_key=license_key,
299+
browser_version=browser_version,
300+
_suppress_maximize=_suppress_maximize,
229301
**kwargs,
230302
)
231303

304+
logger.debug("Launching stealth Chromium (headless=%s, args=%d)", headless, len(launch_options["args"]))
305+
306+
pw = sync_playwright().start()
307+
browser = pw.chromium.launch(**launch_options)
308+
232309
# Patch close() to also stop the Playwright instance
233310
_original_close = browser.close
234311

@@ -249,10 +326,7 @@ def _close_with_cleanup() -> None:
249326

250327
# Human-like behavioral patching
251328
if humanize:
252-
from .human import patch_browser
253-
from .human.config import resolve_config
254-
cfg = resolve_config(human_preset, human_config)
255-
patch_browser(browser, cfg)
329+
humanize_browser(browser, human_preset, human_config)
256330

257331
return browser
258332

@@ -310,30 +384,26 @@ async def launch_async( # noqa: C901
310384

311385
from playwright.async_api import async_playwright
312386

313-
binary_path = ensure_binary(license_key=license_key, browser_version=browser_version)
314-
timezone, locale, exit_ip = maybe_resolve_geoip(geoip, proxy, timezone, locale)
315-
proxy_kwargs, proxy_extra_args = _resolve_proxy_config(proxy, browser_version)
316-
args = _resolve_webrtc_args(args, proxy)
317-
args = _append_webrtc_exit_ip(args, exit_ip)
318-
chrome_args = build_args(stealth_args, (args or []) + proxy_extra_args, timezone=timezone, locale=locale, headless=headless, extension_paths=extension_paths, start_maximized=binary_supports_maximized_window(license_key, browser_version) and not _suppress_maximize)
319-
_maybe_warn_windows_fonts(chrome_args)
320-
321-
logger.debug("Launching stealth Chromium async (headless=%s, args=%d)", headless, len(chrome_args))
322-
323-
launch_env = build_launch_env(license_key, user_env=kwargs.pop("env", None))
324-
env_kwargs = {} if launch_env is None else {"env": launch_env}
325-
326-
pw = await async_playwright().start()
327-
browser = await pw.chromium.launch(
328-
executable_path=binary_path,
387+
launch_options = build_launch_options(
329388
headless=headless,
330-
args=chrome_args,
331-
ignore_default_args=IGNORE_DEFAULT_ARGS,
332-
**env_kwargs,
333-
**proxy_kwargs,
389+
proxy=proxy,
390+
args=args,
391+
stealth_args=stealth_args,
392+
timezone=timezone,
393+
locale=locale,
394+
geoip=geoip,
395+
extension_paths=extension_paths,
396+
license_key=license_key,
397+
browser_version=browser_version,
398+
_suppress_maximize=_suppress_maximize,
334399
**kwargs,
335400
)
336401

402+
logger.debug("Launching stealth Chromium async (headless=%s, args=%d)", headless, len(launch_options["args"]))
403+
404+
pw = await async_playwright().start()
405+
browser = await pw.chromium.launch(**launch_options)
406+
337407
# Patch close() to also stop the Playwright instance
338408
_original_close = browser.close
339409

@@ -352,10 +422,7 @@ async def _close_with_cleanup() -> None:
352422

353423
# Human-like behavioral patching (async variant)
354424
if humanize:
355-
from .human import patch_browser_async
356-
from .human.config import resolve_config
357-
cfg = resolve_config(human_preset, human_config)
358-
patch_browser_async(browser, cfg)
425+
humanize_browser_async(browser, human_preset, human_config)
359426

360427
return browser
361428

0 commit comments

Comments
 (0)