6.9 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-login-auth | 01 | execute | 1 |
|
true |
|
|
Purpose: 提供二维码登录能力,访问登录页 → Canvas 截图 → 轮询检测 Cookie Output: kb_workflow/auth/zhihu_login.py 模块 + Wave 0 测试脚手架
<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-login-auth/07-CONTEXT.md @.planning/phases/07-login-auth/07-RESEARCH.mdFrom kb_workflow/metadata/zhihu_extractor.py:
class PlaywrightBrowser:
def __init__(self, headless: bool = True, timeout: int = 30)
def start(self, cookie: str = None)
def close(self)
def goto(self, url: str, wait_until: str = "networkidle")
def wait_for_selector(self, selector: str, timeout: int = None)
def query_selector(self, selector: str)
def evaluate(self, script: str)
# context 属性可用于获取 cookies
self.context # BrowserContext
self.page # Page
From kb_workflow/pipeline/config.py:
class KBSettings(BaseSettings):
intermediate_path: str # 中间目录路径
zhihu_cookie: SecretStr # 知乎 Cookie
From kb_workflow/pipeline/schemas.py:
class StepOutput(BaseModel):
success: bool
error_message: Optional[str]
completed_at: datetime
-
创建
tests/test_auth/__init__.py空文件 -
创建
tests/test_auth/conftest.py包含:mock_browserfixture: 使用 unittest.mock 模拟 PlaywrightBrowsermock_env_filefixture: 使用 tmp_path 创建临时 ~/.kb_envsample_cookiesfixture: 返回测试用 Cookie 数据
参考 pytest fixture 模式,确保 mock_browser 可以:
- 模拟 page.wait_for_selector() 返回 mock canvas
- 模拟 canvas.screenshot() 返回 bytes
- 模拟 context.cookies() 返回 Cookie 列表 python -c "from tests.test_auth.conftest import *; print('fixtures loaded')" 测试模块和 fixtures 可被 pytest 加载
-
创建
kb_workflow/auth/__init__.py:from .zhihu_login import ZhihuLogin, ZhihuLoginResult __all__ = ["ZhihuLogin", "ZhihuLoginResult"] -
创建
kb_workflow/auth/zhihu_login.py:ZhihuLoginResultPydantic model: success, cookie_str, qrcode_path, error_messageZhihuLogin类:LOGIN_URL = "https://www.zhihu.com/signin"CANVAS_SELECTORS = ["canvas.SignIn-qrcode", "canvas[class*='qrcode']", "canvas"]__init__(self, intermediate_path: str, headless: bool = True, timeout: int = 60)async def _find_qrcode_canvas(self) -> ElementHandle- 尝试多个选择器async def _save_qrcode_image(self, screenshot: bytes) -> Path- 保存到中间目录async def check_login_cookie(self) -> bool- 检测 z_c0 Cookieasync def get_cookies_str(self) -> str- 提取 Cookie 字符串async def login_by_qrcode(self) -> ZhihuLoginResult- 主流程
使用 tenacity 的 @retry 装饰器实现 60 秒轮询:
from tenacity import retry, stop_after_delay, wait_fixed, RetryError
@retry(stop=stop_after_delay(60), wait=wait_fixed(1), reraise=True)
async def _wait_for_login(self):
if not await self.check_login_cookie():
raise Exception("Not logged in yet")
二维码图片路径格式: ~/work/intermediate/zhihu_qrcode_{timestamp}.png
- 创建
tests/test_auth/test_qrcode_login.py:- test_login_page_access: mock browser.goto 验证 URL
- test_qrcode_screenshot: mock canvas.screenshot 验证保存
- test_cookie_detection: mock context.cookies 验证检测逻辑
- test_login_timeout: mock check_login_cookie 始终返回 False
- test_cookie_extraction: 验证 Cookie 字符串格式
安装依赖: pip install tenacity pytest tests/test_auth/test_qrcode_login.py -x -v - ZhihuLogin 类可实例化 - 所有单元测试通过 - 二维码截图功能可用 - Cookie 检测功能可用
1. 单元测试全部通过: `pytest tests/test_auth/test_qrcode_login.py -v` 2. ZhihuLogin 类可导入: `python -c "from kb_workflow.auth import ZhihuLogin; print(ZhihuLogin)"` 3. tenacity 依赖已安装: `pip show tenacity`<success_criteria>
- Wave 0 测试脚手架就绪 (tests/test_auth/)
- ZhihuLogin 类实现二维码截图和 Cookie 检测
- 5 个单元测试全部通过
- 代码符合现有项目模式 (pydantic, Playwright) </success_criteria>