""" E2E 测试套件:验证 personal-blog 核心用户旅程 =============================================== 1. 登录流程 (test_login_success, test_login_failure) 2. 写文章并发布 (test_publish_article) 3. 前台页面访问 (test_homepage_load) 前置条件 -------- - 博客服务运行在 BLOG_URL(默认 http://127.0.0.1:3000) - 管理员账号存在于 ENV:BLOG_ADMIN_USERNAME / BLOG_ADMIN_PASSWORD - Playwright 已安装:pip install pytest-playwright 运行方式 -------- BLOG_URL=http://129.211.173.87:8888 \ BLOG_ADMIN_USERNAME=admin \ BLOG_ADMIN_PASSWORD=abcd1234 \ pytest skills/browser-automation/tests/test_blog_e2e.py -v """ import os import time from datetime import datetime import pytest from playwright.sync_api import Browser, Page, expect # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture(scope="session") def blog_url() -> str: return os.getenv("BLOG_URL", "http://127.0.0.1:3000") @pytest.fixture(scope="session") def admin_credentials() -> dict: return { "username": os.getenv("BLOG_ADMIN_USERNAME", "admin"), "password": os.getenv("BLOG_ADMIN_PASSWORD", "abcd1234"), } @pytest.fixture def page(browser: Browser) -> Page: """每个测试用例一个全新的浏览器上下文,保证隔离性。""" context = browser.new_context(viewport={"width": 1400, "height": 900}) page = context.new_page() yield page context.close() # --------------------------------------------------------------------------- # 1. 登录流程 # --------------------------------------------------------------------------- class TestLogin: def test_login_success(self, page: Page, blog_url: str, admin_credentials: dict): """正向登录:管理员输入正确凭据后,应跳转至 /admin""" page.goto(f"{blog_url}/login") page.wait_for_load_state("networkidle") time.sleep(1) page.fill("input#username", admin_credentials["username"]) page.fill("input#password", admin_credentials["password"]) page.click("button:has-text('登录')") page.wait_for_url("**/admin", timeout=10000) time.sleep(1) # 验证后台页面包含关键元素 body_text = page.locator("body").inner_text() assert any(kw in body_text for kw in ["文章", "仪表盘", "管理", "首页"]), \ f"登录后应进入管理后台,当前页面文本: {body_text[:200]}" assert "/admin" in page.url def test_login_failure(self, page: Page, blog_url: str): """负向登录:输入错误密码后,应留在登录页并显示错误提示""" page.goto(f"{blog_url}/login") page.wait_for_load_state("networkidle") time.sleep(1) page.fill("input#username", "wronguser") page.fill("input#password", "wrongpassword") page.click("button:has-text('登录')") time.sleep(2) assert "/login" in page.url, f"登录失败后应留在 /login,实际URL: {page.url}" body_text = page.locator("body").inner_text() assert any(kw in body_text for kw in ["错误", "失败", "Invalid", "incorrect", "密码", "用户名"]), \ "登录失败后页面应包含错误提示信息" # --------------------------------------------------------------------------- # 2. 发布文章 # --------------------------------------------------------------------------- class TestPublishArticle: def test_publish_article(self, browser: Browser, blog_url: str, admin_credentials: dict): """E2E: 登录 → 写文章 → 填写表单 → 发布 → 验证文章出现在列表中""" context = browser.new_context(viewport={"width": 1400, "height": 900}) page = context.new_page() try: # 1. 登录 page.goto(f"{blog_url}/login") page.wait_for_load_state("networkidle") time.sleep(1) page.fill("input#username", admin_credentials["username"]) page.fill("input#password", admin_credentials["password"]) page.click("button:has-text('登录')") page.wait_for_url("**/admin", timeout=10000) time.sleep(1) # 2. 导航到写文章页面 write_link = page.query_selector("a:has-text('写文章')") if write_link: write_link.click() else: page.goto(f"{blog_url}/admin/posts/new") page.wait_for_load_state("networkidle") time.sleep(2) # 3. 填写文章表单 timestamp = datetime.now().strftime("%H%M%S") title = f"技能测试文章 {timestamp}" summary = "由 browser-automation skill 自动发布的测试摘要。" content = f"# 自动化测试\n\n由 **browser-automation skill** 自动发布。\n\n时间戳:{timestamp}\n\n测试完成!" page.fill("input#title", title) page.fill("textarea#summary", summary) page.fill("textarea#content", content) # 4. 提交发布 page.click("button[type='submit']:has-text('发布文章')") time.sleep(3) # 5. 验证发布成功(宽松判断:URL 不是 login,且页面有内容) current_url = page.url assert "/login" not in current_url, f"发布后被踢回登录页,当前URL: {current_url}" # 如果还在写文章页,可能是成功提示还没消失,或表单提交后未跳转 # 尝试主动导航到文章列表页去验证 if "/admin/posts/new" in current_url: page.goto(f"{blog_url}/admin/posts") page.wait_for_load_state("networkidle") time.sleep(2) # 6. 验证文章出现在列表中 body_text = page.locator("body").inner_text() assert title in body_text, f"发布的文章 '{title}' 未在文章管理列表中找到" finally: context.close() # --------------------------------------------------------------------------- # 3. 前台页面 # --------------------------------------------------------------------------- class TestHomepage: def test_homepage_load(self, page: Page, blog_url: str): """验证博客首页可正常加载,并包含文章列表和基本元素""" page.goto(blog_url) page.wait_for_load_state("networkidle") time.sleep(1) expect(page.locator("body")).to_be_visible() body_text = page.locator("body").inner_text() assert len(body_text) > 100, "首页应包含可见文本内容" # 检查常见关键词 common_keywords = ["首页", "关于", "文章", "标签", "归档", "阅读更多", "Blog"] assert any(kw in body_text for kw in common_keywords), \ f"首页应包含常见关键词之一,当前页面文本: {body_text[:200]}" if __name__ == "__main__": pytest.main([__file__, "-v"])