"""浏览器会话启动(系统 Chrome/Edge;URL 仅通过 page.goto 打开)。""" from __future__ import annotations import logging import os from contextlib import contextmanager from typing import Any, Generator, Iterator, List, Optional, Tuple logger = logging.getLogger(__name__) DEFAULT_PAGE_TIMEOUT_MS = 15_000 GOTO_TIMEOUT_MS = 60_000 CHROME_LAUNCH_ARGS: List[str] = [ "--start-maximized", "--disable-blink-features=AutomationControlled", ] STEALTH_INIT_SCRIPT = """ (() => { try { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); } catch (e) {} })(); """ def chrome_launch_args() -> List[str]: return list(CHROME_LAUNCH_ARGS) def _headless_from_env() -> bool: v = (os.getenv("OPENCLAW_BROWSER_HEADLESS") or "0").strip().lower() return v in ("1", "true", "yes", "on") def _find_chrome_executable() -> str | None: try: from jiangchang_skill_core.runtime_env import find_chrome_executable chrome = find_chrome_executable() if chrome and os.path.isfile(chrome): return chrome except ImportError: pass candidates = [ os.path.expandvars(r"%ProgramFiles%\Google\Chrome\Application\chrome.exe"), os.path.expandvars(r"%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe"), os.path.expandvars(r"%ProgramFiles%\Microsoft\Edge\Application\msedge.exe"), os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft\Edge\Application\msedge.exe"), ] for path in candidates: if path and os.path.isfile(path): return path return None def _clear_node_options() -> None: os.environ.pop("NODE_OPTIONS", None) @contextmanager def browser_session( start_url: str, *, profile_dir: Optional[str] = None, headless: Optional[bool] = None, ) -> Generator[Any, None, None]: """ 同步 Playwright 浏览器会话。 - Playwright Python 包由宿主共享 runtime 提供;技能侧不要 playwright install。 - launch args 仅放 Chrome 参数,不含 URL。 - 流程:launch → new_page → goto(start_url) """ from playwright.sync_api import sync_playwright _clear_node_options() hl = headless if headless is not None else _headless_from_env() chrome = _find_chrome_executable() if not chrome: raise RuntimeError( "未检测到 Chrome 或 Edge 浏览器,请安装系统浏览器后重试。" ) args = chrome_launch_args() logger.info( "browser_launch chrome=%s profile_dir=%s start_url=%s args=%s headless=%s", chrome, profile_dir or "", start_url, args, hl, ) browser_holder = None context = None with sync_playwright() as pw: try: if profile_dir: context = pw.chromium.launch_persistent_context( user_data_dir=profile_dir, headless=hl, executable_path=chrome, locale="zh-CN", no_viewport=True, args=args, ignore_default_args=["--enable-automation"], ) else: browser_holder = pw.chromium.launch( headless=hl, executable_path=chrome, args=args, ignore_default_args=["--enable-automation"], ) context = browser_holder.new_context(no_viewport=True, locale="zh-CN") context.add_init_script(STEALTH_INIT_SCRIPT) page = context.new_page() page.set_default_timeout(DEFAULT_PAGE_TIMEOUT_MS) page.goto(start_url, wait_until="domcontentloaded", timeout=GOTO_TIMEOUT_MS) yield page finally: try: if context is not None: context.close() except Exception as exc: logger.warning("browser_context_close_failed: %s", exc) if browser_holder is not None: try: browser_holder.close() except Exception as exc: logger.warning("browser_close_failed: %s", exc) def find_chrome_executable() -> str | None: return _find_chrome_executable()