#!/usr/bin/env python3 """手动登录并保存浏览器登录态(cookies + localStorage)。 目的: 绕过图形验证码——人工完成一次验证码登录后, cookies + localStorage 序列化到 JSON 文件,后续用例自动复用。 用法: python tests/web/admin/save_auth.py 流程: 1. 脚本打开浏览器到登录页 2. 自动填入账号、密码(从 tests/config/test_config.yaml 读取) 3. 你在浏览器中手动完成图形验证码并点击登录 4. 登录成功后按 Enter 键,脚本自动保存登录态 5. 后续 pytest 用例自动复用 生成的 auth 文件: tests/web/auth/super_admin_chromium.json """ import argparse import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) # Windows 中文控制台为 GBK 编码,emoji 等字符无法编码会导致 print 崩溃; # 这里把所有无法编码的字符替换为 ?,保证脚本在任何终端都能运行。 if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(errors="replace") from tests.core.config_loader import load_config from tests.core.playwright_support import fast_shutdown # 登录页元素选择器(Elementor 全路径)。密码框带 is-error 类,仅在错误态出现时匹配, # 因此 _fill_credentials 会先尝试这些选择器,失败再退回占位符/类型选择器。 USERNAME_SELECTOR = ( "#app > div > form > div:nth-child(2) > div " "> div.elInput.el-input.el-input--medium > input" ) PASSWORD_SELECTOR = ( "#app > div > form > div.el-form-item.el-tooltip.is-error.is-required.el-form-item--medium " "> div > div.elInput.el-input.el-input--medium > input" ) def _fill_credentials(page, account: dict) -> bool: """自动填写账号密码,用户名/密码独立处理,任一失败不影响另一个。""" filled_user = False for user_sel in (USERNAME_SELECTOR, "input[placeholder*='账号']"): try: page.wait_for_selector(user_sel, timeout=8000) page.fill(user_sel, account["phone"]) filled_user = True break except Exception: continue filled_pwd = False for pwd_sel in (PASSWORD_SELECTOR, "input[type='password']"): try: page.wait_for_selector(pwd_sel, timeout=5000) page.fill(pwd_sel, account["password"]) filled_pwd = True break except Exception: continue return filled_user and filled_pwd def main(): parser = argparse.ArgumentParser( description="手动登录并保存浏览器登录态(绕过图形验证码)" ) parser.add_argument( "--role", default="super_admin", help="登录角色(默认: super_admin),会体现在文件名中", ) parser.add_argument( "--browser", default="chromium", choices=["chromium", "firefox", "webkit"], help="浏览器类型(默认: chromium)", ) args = parser.parse_args() config = load_config() web_config = config["admin"] base_url = web_config["base_url"] login_url = f"{base_url}/#/login" from playwright.sync_api import sync_playwright with sync_playwright() as p: browser_launcher = getattr(p, args.browser) browser = browser_launcher.launch(headless=False) context = browser.new_context( viewport={"width": 1920, "height": 1080}, locale="zh-CN", ) page = context.new_page() print(f"\n打开登录页: {login_url}") page.goto(login_url) account = config.get("accounts", {}).get(args.role) if account and _fill_credentials(page, account): print(f"[{args.role}] 账号密码已自动填写") else: print(f"[{args.role}] 自动填写失败/无配置,请在浏览器中手动输入账号密码") print(f"\n{'='*60}") print(f" 请手动完成【图形验证码】并点击【登录】") print(f" 登录成功后回到此处按 Enter 键保存登录态...") print(f"{'='*60}\n") input(">>> 按 Enter 保存登录态...") # 检查是否真的登录成功了 current = page.url if "/login" in current or "/#/login" in current: print(f"\n[警告] 当前仍在登录页: {current}") print(" 要保存吗?(y/n): ", end="") choice = input().strip().lower() if choice != "y": print("已取消。") fast_shutdown(p) sys.exit(0) print(f"\n当前页面: {current}") auth_dir = Path(web_config.get("storage_state_dir", "tests/web/auth")) auth_dir.mkdir(parents=True, exist_ok=True) auth_file = auth_dir / f"{args.role}_{args.browser}.json" context.storage_state(path=str(auth_file)) print(f"登录态已保存到: {auth_file}") # 强杀浏览器与驱动,跳过 Playwright 在本机 ~30s 的优雅关闭等待 fast_shutdown(p) print(f"\n现在可以跑了: pytest tests/web/admin/ -m p0") if __name__ == "__main__": main()