scrapely-python 0.1.26__tar.gz → 0.1.27__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {scrapely_python-0.1.26/src/scrapely_python.egg-info → scrapely_python-0.1.27}/PKG-INFO +1 -1
  2. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/pyproject.toml +1 -1
  3. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/__init__.py +2 -2
  4. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/actor.py +1 -1
  5. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/patch_browser_pool.py +14 -14
  6. {scrapely_python-0.1.26 → scrapely_python-0.1.27/src/scrapely_python.egg-info}/PKG-INFO +1 -1
  7. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_lazy_crawlee.py +18 -1
  8. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_patch_browser_pool.py +9 -9
  9. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/LICENSE +0 -0
  10. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/LICENSE-APACHE +0 -0
  11. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/NOTICE +0 -0
  12. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/README.md +0 -0
  13. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/setup.cfg +0 -0
  14. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/_browser_locations.py +0 -0
  15. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/browser_configuration.py +0 -0
  16. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/cdp_retry.py +0 -0
  17. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/charging.py +0 -0
  18. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/charging_client.py +0 -0
  19. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/configuration.py +0 -0
  20. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/crypto/__init__.py +0 -0
  21. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/crypto/_secrets.py +0 -0
  22. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/errors.py +0 -0
  23. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/events/__init__.py +0 -0
  24. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/events/_types.py +0 -0
  25. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/events/event_manager.py +0 -0
  26. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/log.py +0 -0
  27. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/pricing_types.py +0 -0
  28. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/proxy_configuration.py +0 -0
  29. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/py.typed +0 -0
  30. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/request_loaders/__init__.py +0 -0
  31. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/request_loaders/_scrapely_request_list.py +0 -0
  32. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/solver.py +0 -0
  33. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/__init__.py +0 -0
  34. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/_file_system/__init__.py +0 -0
  35. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/_file_system/_key_value_store_client.py +0 -0
  36. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/_file_system/_storage_client.py +0 -0
  37. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/_metadata.py +0 -0
  38. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/api_client.py +0 -0
  39. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/client.py +0 -0
  40. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/patch_storage.py +0 -0
  41. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/scrapely_dataset_client.py +0 -0
  42. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/scrapely_kvs_client.py +0 -0
  43. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/scrapely_rq_client.py +0 -0
  44. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/scrapely_storage_client.py +0 -0
  45. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/smart_scrapely_storage_client.py +0 -0
  46. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/types.py +0 -0
  47. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage/utils.py +0 -0
  48. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storage_clients/__init__.py +0 -0
  49. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/storages/__init__.py +0 -0
  50. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/types.py +0 -0
  51. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely/webhook.py +0 -0
  52. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely_python.egg-info/SOURCES.txt +0 -0
  53. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely_python.egg-info/dependency_links.txt +0 -0
  54. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely_python.egg-info/requires.txt +0 -0
  55. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/src/scrapely_python.egg-info/top_level.txt +0 -0
  56. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_api_client_retries.py +0 -0
  57. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_apify_parity.py +0 -0
  58. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_browser_configuration.py +0 -0
  59. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_cdp_retry.py +0 -0
  60. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_charge_limit_guard.py +0 -0
  61. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_charge_retries.py +0 -0
  62. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_charging_parity.py +0 -0
  63. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_charging_pending.py +0 -0
  64. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_debug_capture.py +0 -0
  65. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_input_proxy_aliases.py +0 -0
  66. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_parity.py +0 -0
  67. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_proxy_configuration.py +0 -0
  68. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_push_idempotency.py +0 -0
  69. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_run_control.py +0 -0
  70. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_secrets.py +0 -0
  71. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_solver.py +0 -0
  72. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_storage.py +0 -0
  73. {scrapely_python-0.1.26 → scrapely_python-0.1.27}/tests/test_terminal_status_message.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scrapely-python"
7
- version = "0.1.26"
7
+ version = "0.1.27"
8
8
  description = "Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform"
9
9
  readme = "README.md"
10
10
  license = "MIT AND Apache-2.0"
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Any
19
19
  # the Crawlee patches are applied when Crawlee's browser modules load.
20
20
  from .patch_browser_pool import set_cdp_url # noqa: F401
21
21
 
22
- # Crawlee is NOT imported here: it costs ~1 s per run under gVisor and many
22
+ # Crawlee is NOT imported here: it costs ~1 s per run on the platform and many
23
23
  # actors never touch it. Everything that needs Crawlee (the re-exported Crawlee
24
24
  # classes, storage clients, events, request loaders) is resolved on first
25
25
  # attribute access by ``__getattr__`` below; ``Actor`` imports what it needs on
@@ -227,7 +227,7 @@ if TYPE_CHECKING: # Static analysers and IDEs see the lazy names as plain impor
227
227
  set_api_storage,
228
228
  )
229
229
 
230
- __version__ = '0.1.26'
230
+ __version__ = '0.1.27'
231
231
  __all__ = [
232
232
  # Core
233
233
  'Actor',
@@ -68,7 +68,7 @@ if TYPE_CHECKING:
68
68
  logger = logging.getLogger(__name__)
69
69
 
70
70
  # Names that need Crawlee, resolved on first use instead of at import: importing
71
- # Crawlee costs ~1 s per run under gVisor. They stay module attributes
71
+ # Crawlee costs ~1 s per run on the platform. They stay module attributes
72
72
  # (``scrapely.actor.Dataset`` works and can be monkeypatched): ``_lazy`` returns
73
73
  # the module-global value when one is set, else imports and caches it.
74
74
  _LAZY_ATTRS: Dict[str, Tuple[str, str]] = {
@@ -27,10 +27,10 @@ Mirrors the Node SDK ``patch-browser-pool.ts``:
27
27
  - Unpatched builds (``SCRAPELY_BROWSER_PATCH=0``, set by the worker when the
28
28
  actor's browser patch was off at build time) keep their browser binaries:
29
29
  every patched entry point falls through to the original local launch, with
30
- gVisor-safe Chromium args under ``SCRAPELY_CONTAINER_RUNTIME=runsc``.
30
+ sandbox-safe Chromium args when running on the platform.
31
31
  - Soft no-op when Playwright/Crawlee browsers are not installed (e.g. HTTP-only
32
32
  actors).
33
- - Crawlee is never imported by this module (it costs ~1 s under gVisor). The
33
+ - Crawlee is never imported by this module (it costs ~1 s on the platform). The
34
34
  Crawlee patches are applied at once when Crawlee's browser / Playwright
35
35
  crawler modules are already loaded, otherwise by a post-import hook the
36
36
  moment those modules finish loading — before actor code can use the classes.
@@ -121,8 +121,8 @@ def _browser_patch_disabled() -> bool:
121
121
  return os.environ.get('SCRAPELY_BROWSER_PATCH') == '0'
122
122
 
123
123
 
124
- # gVisor lacks crashpad support; ReadonlyRootfs + tmpfs /tmp loses crash dirs.
125
- _GVISOR_CHROME_ARGS = (
124
+ # Platform sandbox: no crash reporter; read-only rootfs + tmpfs /tmp loses crash dirs.
125
+ _SANDBOX_CHROME_ARGS = (
126
126
  '--disable-breakpad',
127
127
  '--disable-crash-reporter',
128
128
  '--disable-dev-shm-usage',
@@ -130,13 +130,13 @@ _GVISOR_CHROME_ARGS = (
130
130
  )
131
131
 
132
132
 
133
- def _with_gvisor_args(options: Optional[dict]) -> dict:
134
- """Copy of launch options with gVisor-safe Chromium args when under runsc."""
133
+ def _with_sandbox_args(options: Optional[dict]) -> dict:
134
+ """Copy of launch options with sandbox-safe Chromium args when on the platform."""
135
135
  out = dict(options or {})
136
- if os.environ.get('SCRAPELY_CONTAINER_RUNTIME') != 'runsc':
136
+ if not _is_at_home():
137
137
  return out
138
138
  args = list(out.get('args') or [])
139
- for arg in _GVISOR_CHROME_ARGS:
139
+ for arg in _SANDBOX_CHROME_ARGS:
140
140
  if arg not in args:
141
141
  args.append(arg)
142
142
  out['args'] = args
@@ -678,7 +678,7 @@ def _patch_raw_browser_type(browser_type: Any, chromium: Any, name: str, is_asyn
678
678
  async def launch(**kwargs: Any) -> Any:
679
679
  browser_configuration = _take_browser_configuration(kwargs)
680
680
  if _browser_patch_disabled():
681
- return await orig_launch(**_with_gvisor_args(kwargs))
681
+ return await orig_launch(**_with_sandbox_args(kwargs))
682
682
  cdp_url = _raw_launch_cdp_url(kwargs, browser_configuration)
683
683
  if not cdp_url:
684
684
  if not _is_at_home():
@@ -691,7 +691,7 @@ def _patch_raw_browser_type(browser_type: Any, chromium: Any, name: str, is_asyn
691
691
  async def launch_persistent_context(*args: Any, **kwargs: Any) -> Any:
692
692
  browser_configuration = _take_browser_configuration(kwargs)
693
693
  if _browser_patch_disabled():
694
- return await orig_lpc(*args, **_with_gvisor_args(kwargs))
694
+ return await orig_lpc(*args, **_with_sandbox_args(kwargs))
695
695
  cdp_url = _raw_launch_cdp_url(kwargs, browser_configuration)
696
696
  if not cdp_url:
697
697
  if not _is_at_home():
@@ -710,7 +710,7 @@ def _patch_raw_browser_type(browser_type: Any, chromium: Any, name: str, is_asyn
710
710
  def launch(**kwargs: Any) -> Any:
711
711
  browser_configuration = _take_browser_configuration(kwargs)
712
712
  if _browser_patch_disabled():
713
- return orig_launch(**_with_gvisor_args(kwargs))
713
+ return orig_launch(**_with_sandbox_args(kwargs))
714
714
  cdp_url = _raw_launch_cdp_url(kwargs, browser_configuration)
715
715
  if not cdp_url:
716
716
  if not _is_at_home():
@@ -722,7 +722,7 @@ def _patch_raw_browser_type(browser_type: Any, chromium: Any, name: str, is_asyn
722
722
  def launch_persistent_context(*args: Any, **kwargs: Any) -> Any:
723
723
  browser_configuration = _take_browser_configuration(kwargs)
724
724
  if _browser_patch_disabled():
725
- return orig_lpc(*args, **_with_gvisor_args(kwargs))
725
+ return orig_lpc(*args, **_with_sandbox_args(kwargs))
726
726
  cdp_url = _raw_launch_cdp_url(kwargs, browser_configuration)
727
727
  if not cdp_url:
728
728
  if not _is_at_home():
@@ -847,7 +847,7 @@ def _apply_pyppeteer_patch() -> None:
847
847
  options = dict(options)
848
848
  browser_configuration = _take_browser_configuration(options) or browser_configuration
849
849
  if _browser_patch_disabled():
850
- return await _orig_launch(_with_gvisor_args({**(options or {}), **kwargs}))
850
+ return await _orig_launch(_with_sandbox_args({**(options or {}), **kwargs}))
851
851
  cdp_url = get_cdp_url()
852
852
  if not cdp_url:
853
853
  if not _is_at_home():
@@ -876,7 +876,7 @@ def _apply_pyppeteer_patch() -> None:
876
876
 
877
877
  # ─── Crawlee patches without importing Crawlee ───────────────────────────────
878
878
  #
879
- # Importing Crawlee costs ~1 s per run under gVisor, so this module never
879
+ # Importing Crawlee costs ~1 s per run on the platform, so this module never
880
880
  # imports it. The Crawlee patches mutate classes in place, so they only have to
881
881
  # run before actor code uses those classes: they are applied immediately when
882
882
  # the defining modules are already loaded, otherwise by a post-import hook the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -1,6 +1,6 @@
1
1
  """Crawlee loads lazily; the browser patch stays eager.
2
2
 
3
- Importing Crawlee costs ~1 s per run under gVisor, so ``import scrapely`` must
3
+ Importing Crawlee costs ~1 s per run on the platform, so ``import scrapely`` must
4
4
  not import it. The browser patch must still be active right after import: the
5
5
  raw Playwright patch at once, the Crawlee BrowserPool / PlaywrightCrawler
6
6
  patches as soon as Crawlee's modules load.
@@ -292,3 +292,20 @@ def test_actor_module_lazy_names_are_patchable(monkeypatch: pytest.MonkeyPatch)
292
292
  sentinel = object()
293
293
  monkeypatch.setattr(actor_mod, 'Dataset', sentinel)
294
294
  assert actor_mod._lazy('Dataset') is sentinel
295
+
296
+
297
+ def test_reference_crawlee_reexports_resolve_lazily() -> None:
298
+ """Every crawlee name the reference SDK re-exports is available, and only loads crawlee on access."""
299
+ result = _run(f"""
300
+ import json, sys
301
+ import scrapely
302
+ before = {_CRAWLEE_MODULES}
303
+ names = ['Request', 'Event', 'EventAbortingData', 'EventExitData', 'EventListener',
304
+ 'EventMigratingData', 'EventPersistStateData', 'EventSystemInfoData',
305
+ 'Dataset', 'KeyValueStore', 'RequestQueue']
306
+ import crawlee, crawlee.events
307
+ ok = {{n: getattr(scrapely, n) is (getattr(crawlee, n, None) or getattr(crawlee.events, n, None) or getattr(scrapely, n)) for n in names}}
308
+ print(json.dumps({{'before': before, 'ok': ok}}))
309
+ """)
310
+ assert result['before'] == []
311
+ assert all(result['ok'].values()), result['ok']
@@ -1017,6 +1017,8 @@ async def test_pyppeteer_launch_requires_cdp_url_on_platform(pyppeteer_stub, mon
1017
1017
  pyppeteer_stub.connect.assert_not_awaited()
1018
1018
 
1019
1019
 
1020
+ _SAFE_ARGS = ['--disable-breakpad', '--disable-crash-reporter', '--disable-dev-shm-usage', '--crash-dumps-dir=/tmp']
1021
+
1020
1022
  # ─── SCRAPELY_BROWSER_PATCH=0 (unpatched build: actor launches its own browsers) ─
1021
1023
 
1022
1024
 
@@ -1025,7 +1027,6 @@ def browser_patch_off(monkeypatch):
1025
1027
  """On the platform with a CDP URL, but the build opted out of the patch."""
1026
1028
  monkeypatch.setenv('SCRAPELY_IS_AT_HOME', '1')
1027
1029
  monkeypatch.setenv('SCRAPELY_BROWSER_PATCH', '0')
1028
- monkeypatch.delenv('SCRAPELY_CONTAINER_RUNTIME', raising=False)
1029
1030
  set_cdp_url('wss://browser.test/cdp')
1030
1031
 
1031
1032
 
@@ -1099,8 +1100,8 @@ def test_patch_off_raw_sync_launch_and_lpc_run_locally(browser_patch_off):
1099
1100
  p.firefox.launch(headless=True)
1100
1101
  p.chromium.launch_persistent_context('/tmp/profile', headless=True)
1101
1102
 
1102
- p.firefox.original_launch.assert_called_once_with(headless=True)
1103
- p.chromium.original_launch_persistent_context.assert_called_once_with('/tmp/profile', headless=True)
1103
+ p.firefox.original_launch.assert_called_once_with(headless=True, args=_SAFE_ARGS)
1104
+ p.chromium.original_launch_persistent_context.assert_called_once_with('/tmp/profile', headless=True, args=_SAFE_ARGS)
1104
1105
  p.chromium.connect_over_cdp.assert_not_called()
1105
1106
 
1106
1107
 
@@ -1111,7 +1112,7 @@ def test_patch_off_raw_launch_ignores_browser_configuration(browser_patch_off):
1111
1112
 
1112
1113
  p.chromium.launch(headless=True, browser_configuration=BrowserConfiguration(os='macos'))
1113
1114
 
1114
- p.chromium.original_launch.assert_called_once_with(headless=True)
1115
+ p.chromium.original_launch.assert_called_once_with(headless=True, args=_SAFE_ARGS)
1115
1116
  p.chromium.connect_over_cdp.assert_not_called()
1116
1117
 
1117
1118
 
@@ -1134,7 +1135,7 @@ async def test_patch_off_pyppeteer_ignores_browser_configuration(browser_patch_o
1134
1135
  pbp._apply_pyppeteer_patch()
1135
1136
 
1136
1137
  await pkg.launch({'headless': True, 'browser_configuration': BrowserConfiguration(os='macos')})
1137
- original.assert_awaited_once_with({'headless': True})
1138
+ original.assert_awaited_once_with({'headless': True, 'args': _SAFE_ARGS})
1138
1139
 
1139
1140
 
1140
1141
  @pytest.mark.asyncio
@@ -1172,12 +1173,11 @@ async def test_patch_off_raw_async_launch_runs_locally(browser_patch_off):
1172
1173
  pbp._patch_playwright_object(p, is_async=True)
1173
1174
 
1174
1175
  assert await p.chromium.launch(headless=True) == 'local-browser'
1175
- chromium.original_launch.assert_awaited_once_with(headless=True)
1176
+ chromium.original_launch.assert_awaited_once_with(headless=True, args=_SAFE_ARGS)
1176
1177
  chromium.connect_over_cdp.assert_not_awaited()
1177
1178
 
1178
1179
 
1179
- def test_patch_off_adds_gvisor_args_under_runsc(browser_patch_off, monkeypatch):
1180
- monkeypatch.setenv('SCRAPELY_CONTAINER_RUNTIME', 'runsc')
1180
+ def test_patch_off_adds_sandbox_args_on_platform(browser_patch_off, monkeypatch):
1181
1181
  p = _fake_sync_driver()
1182
1182
 
1183
1183
  p.chromium.launch(args=['--x'])
@@ -1206,7 +1206,7 @@ async def test_patch_off_pyppeteer_runs_original(browser_patch_off, monkeypatch)
1206
1206
  pbp._apply_pyppeteer_patch()
1207
1207
 
1208
1208
  assert await pkg.launch(headless=True, args=['--x']) == 'local-browser'
1209
- original.assert_awaited_once_with({'headless': True, 'args': ['--x']})
1209
+ original.assert_awaited_once_with({'headless': True, 'args': ['--x', *_SAFE_ARGS]})
1210
1210
  connect.assert_not_awaited()
1211
1211
 
1212
1212