""" Authentication module - Responsible for Yonyou BIP system login and logout operations """ import logging from typing import Optional from playwright.sync_api import Playwright, Browser, BrowserContext, Page, Frame, FrameLocator def login( playwright: Playwright, username: str, password: str, url: str, headless: bool, ignore_https_errors: bool, verbose: bool = True, logger: Optional[logging.Logger] = None, ) -> tuple[Browser, BrowserContext, Page, FrameLocator]: """ Login to Yonyou BIP system Args: playwright: Playwright instance username: Username (required) password: Password (required) url: Complete login page URL (required) headless: Whether to use headless mode (required) ignore_https_errors: Whether to ignore HTTPS errors (required) verbose: Whether to print detailed logs (default: True). Deprecated, use logger instead. logger: Optional logging.Logger instance. If None and verbose=True, creates default logger. Returns: tuple: (browser, context, page, main_frame) - browser: Browser instance - context: Browser context - page: Page object - main_frame: Main iframe after login (forwardFrame) """ import time from utils.logging import get_logger # Create default logger if needed if logger is None and verbose: logger = get_logger('bipauto.auth') elif logger is None: logger = None # Silent mode, no logging # Validate required parameters if not username or not username.strip(): raise ValueError("username is required and cannot be empty or whitespace") if not password or not password.strip(): raise ValueError("password is required and cannot be empty or whitespace") if not url or not url.strip(): raise ValueError("url is required and cannot be empty or whitespace") # Launch browser browser = playwright.chromium.launch(headless=headless) # Create context context = browser.new_context(ignore_https_errors=ignore_https_errors) # Create page page = context.new_page() # Navigate to login page page.goto(url) # Extract main iframe (forwardFrame) main_frame = page.locator("#forwardFrame").content_frame # Fill username main_frame.get_by_role("textbox", name="用户名").fill(username) # Fill password main_frame.get_by_role("textbox", name="密码").fill(password) # Click login button main_frame.get_by_role("button", name="登录").click() # Handle force login popup (check if exists) confirm_btn = main_frame.get_by_role("button", name="确定") if confirm_btn.count() > 0: confirm_btn.click() if logger: logger.info("Force login detected") else: if logger: logger.debug("Normal login") return browser, context, page, main_frame def logout(main_frame: FrameLocator, verbose: bool = True, logger: Optional[logging.Logger] = None) -> None: """ Execute account logout Args: main_frame: Main iframe object (forwardFrame) verbose: Whether to print detailed logs. Deprecated, use logger instead. logger: Optional logging.Logger instance. If None and verbose=True, creates default logger. """ import time from utils.logging import get_logger # Create default logger if needed if logger is None and verbose: logger = get_logger('bipauto.auth') elif logger is None: logger = None # Silent mode, no logging if logger: logger.info("Clicking account menu button...") # Element 1: Account menu button (logo icon) main_frame.get_by_role("img", name="logo").click() # Wait for menu to appear time.sleep(1) if logger: logger.info("Clicking logout button...") # Element 2: "Logout" button main_frame.get_by_text("退出登录").click() # Wait for confirmation dialog to appear time.sleep(1) if logger: logger.info("Waiting for logout confirmation dialog...") # Element 3: Logout confirmation dialog (check if appears) try: confirm_text = main_frame.get_by_text("退出确定要退出当前账号吗?") if logger: logger.info("Found confirmation dialog, clicking confirm button") # Element 4: Confirm button main_frame.get_by_role("button", name="确定(Y)").click() except: if logger: logger.warning("Confirmation dialog not found, may have auto-logged out") time.sleep(2) # Wait for logout to complete