diff --git a/skyvern/library/skyvern.py b/skyvern/library/skyvern.py index 5f910e77..da86fbcf 100644 --- a/skyvern/library/skyvern.py +++ b/skyvern/library/skyvern.py @@ -1,5 +1,7 @@ import asyncio import os +import pathlib +import tempfile from typing import Any, overload import httpx @@ -389,6 +391,7 @@ class Skyvern(AsyncSkyvern): headless: bool = False, port: int = DEFAULT_CDP_PORT, args: list[str] | None = None, + user_data_dir: str | None = None, ) -> SkyvernBrowser: """Launch a new local Chromium browser with Chrome DevTools Protocol (CDP) enabled. @@ -404,13 +407,26 @@ class Skyvern(AsyncSkyvern): Returns: SkyvernBrowser: A browser instance with Skyvern capabilities. """ + playwright = await self._get_playwright() - launch_args = [f"--remote-debugging-port={port}"] + + if user_data_dir: + user_data_path = pathlib.Path(user_data_dir) + else: + user_data_path = pathlib.Path(tempfile.gettempdir()) / "skyvern-browser" + + launch_args = [ + f"--remote-debugging-port={port}", + ] if args: launch_args.extend(args) - browser = await playwright.chromium.launch(headless=headless, args=launch_args) + + browser_context = await playwright.chromium.launch_persistent_context( + user_data_dir=str(user_data_path), + headless=headless, + args=launch_args, + ) browser_address = f"http://localhost:{port}" - browser_context = browser.contexts[0] if browser.contexts else await browser.new_context() return SkyvernBrowser(self, browser_context, browser_address=browser_address) async def connect_to_browser_over_cdp(self, cdp_url: str) -> SkyvernBrowser: diff --git a/skyvern/webeye/browser_factory.py b/skyvern/webeye/browser_factory.py index bc7757f7..8a851b84 100644 --- a/skyvern/webeye/browser_factory.py +++ b/skyvern/webeye/browser_factory.py @@ -18,7 +18,7 @@ from urllib.parse import parse_qsl, urlparse import psutil import structlog -from playwright.async_api import BrowserContext, ConsoleMessage, Download, Page, Playwright +from playwright.async_api import Browser, BrowserContext, ConsoleMessage, Download, Page, Playwright from skyvern.config import settings from skyvern.constants import ( @@ -33,7 +33,6 @@ from skyvern.webeye.browser_artifacts import BrowserArtifacts, VideoArtifact LOG = structlog.get_logger() - BrowserCleanupFunc = Callable[[], None] | None @@ -148,6 +147,23 @@ def initialize_download_dir() -> str: ) +async def _apply_download_behaviour(browser: Browser) -> None: + context = ensure_context() + download_dir = get_download_dir( + context.run_id if context and context.run_id else context.workflow_run_id or context.task_id + ) + cdp_session = await browser.new_browser_cdp_session() + await cdp_session.send( + "Browser.setDownloadBehavior", + { + "behavior": "allow", + "downloadPath": download_dir, + }, + ) + + LOG.info("setDownloadBehavior applied", download_dir=download_dir) + + class BrowserContextCreator(Protocol): def __call__( self, playwright: Playwright, proxy_location: ProxyLocation | None = None, **kwargs: dict[str, Any] @@ -401,6 +417,7 @@ async def _create_headless_chromium( playwright, remote_browser_url=str(browser_address), extra_http_headers=extra_http_headers, + apply_download_behaviour=True, ) user_data_dir = make_temp_directory(prefix="skyvern_browser_") @@ -436,6 +453,7 @@ async def _create_headful_chromium( playwright, remote_browser_url=str(browser_address), extra_http_headers=extra_http_headers, + apply_download_behaviour=True, ) user_data_dir = make_temp_directory(prefix="skyvern_browser_") @@ -499,6 +517,7 @@ async def _create_cdp_connection_browser( playwright, remote_browser_url=str(browser_address), extra_http_headers=extra_http_headers, + apply_download_behaviour=True, ) browser_type = settings.BROWSER_TYPE @@ -555,6 +574,7 @@ async def _connect_to_cdp_browser( playwright: Playwright, remote_browser_url: str, extra_http_headers: dict[str, str] | None = None, + apply_download_behaviour: bool = False, ) -> tuple[BrowserContext, BrowserArtifacts, BrowserCleanupFunc]: browser_args = BrowserContextFactory.build_browser_args(extra_http_headers=extra_http_headers) @@ -565,6 +585,9 @@ async def _connect_to_cdp_browser( LOG.info("Connecting browser CDP connection", remote_browser_url=remote_browser_url) browser = await playwright.chromium.connect_over_cdp(remote_browser_url) + if apply_download_behaviour: + await _apply_download_behaviour(browser) + contexts = browser.contexts browser_context = None