Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

190 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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"])