"""浏览器会话管理:基于skill-template的real_browser_rpa示例。""" from __future__ import annotations import logging import os import random from typing import Any, Optional, Tuple from jiangchang_skill_core import config from jiangchang_skill_core.activity import interruptible_sleep from jiangchang_skill_core.rpa import launch_persistent_browser, poll_until logger = logging.getLogger(__name__) # 目标网站配置 TARGET_URL = "https://sandbox.jc2009.com/industries/campus/smartcampus/app/finance/invoice-precheck" LOGIN_URL = "https://sandbox.jc2009.com/login" async def start_browser_session( profile_dir: str, headless: bool = False, ) -> Tuple[Any, Any, Any]: """启动浏览器会话。 Args: profile_dir: 浏览器配置目录 headless: 是否无头模式(生产必须为False) Returns: (playwright, context, page) 三元组 Raises: RuntimeError: 浏览器启动失败 """ if not profile_dir: raise RuntimeError("ERROR:MISSING_BROWSER 缺少浏览器配置目录") # 确保有头模式(生产要求) if headless: logger.warning("生产环境强制使用有头模式") headless = False try: # 启动持久化浏览器上下文 pw, context = await launch_persistent_browser( user_data_dir=profile_dir, headless=headless, args=[ "--start-maximized", "--disable-blink-features=AutomationControlled", ], ) # 创建新页面 page = await context.new_page() # 导航到目标页面 await page.goto(TARGET_URL, wait_until="domcontentloaded", timeout=60000) logger.info("browser_started url=%s profile_dir=%s", page.url, profile_dir) return pw, context, page except Exception as e: logger.error("browser_start_failed error=%s", str(e)) raise RuntimeError(f"ERROR:MISSING_BROWSER 浏览器启动失败: {str(e)}") async def close_browser_context(pw: Any, context: Any) -> None: """安全关闭浏览器上下文。""" try: if context: await context.close() if pw: await pw.stop() except Exception as e: logger.warning("browser_close_error error=%s", str(e)) async def wait_for_page_ready(page: Any, timeout_sec: float = 30.0) -> bool: """等待页面加载完成。""" async def _check_ready(): try: # 检查页面是否加载完成 if await page.evaluate("document.readyState") == "complete": return True # 检查是否有加载指示器 loading_selectors = [".loading", ".spinner", "[data-loading]"] for sel in loading_selectors: if await page.locator(sel).count() > 0: return False return True except Exception: return False result = await poll_until(_check_ready, timeout_sec=timeout_sec, interval_sec=0.5) return result.ok async def random_delay(min_sec: float = 1.0, max_sec: float = 3.0) -> None: """随机延迟,模拟人类操作节奏。""" delay = random.uniform(min_sec, max_sec) await interruptible_sleep(delay)