style(theme): 更新网站主题色彩方案与字体配置
- 调整主色调从 #1C1C1C 至 #1A1A1A,优化视觉层次 - 更新背景色系为暖白色调 (#FAFAF7, #F5F4F0 等) - 配置中文字体栈,添加 serif 字体支持 - 优化文本颜色梯度,提升可读性 - 调整边框颜色,统一水墨风格 - 添加 Google Search Console 验证码配置项 - 新增桌面应用架构专家代理配置文件 - 重构 E2E 测试等待策略,提升稳定性 - 添加回归测试脚本,增强质量保障
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Novalon Website - Systematic Regression Test Suite"""
|
||||
from playwright.sync_api import sync_playwright
|
||||
import time
|
||||
|
||||
BASE = "http://localhost:3000"
|
||||
R = {"p": 0, "f": 0, "d": []}
|
||||
|
||||
def rec(n, ok, note=""):
|
||||
R["p" if ok else "f"] += 1
|
||||
R["d"].append((n, ok, note))
|
||||
print(f" {'✅' if ok else '❌'} {n}{': '+note if note else ''}")
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={"width": 1280, "height": 720})
|
||||
|
||||
# G1
|
||||
print("\n[G1] 首页核心功能")
|
||||
try:
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
t = page.title()
|
||||
rec("首页Title", "睿新致远" in t or "novalon" in t.lower(), t[:40])
|
||||
rec("Header可见", page.locator("header").is_visible(), "")
|
||||
rec("Footer可见", page.locator("[data-testid=footer], footer").first.is_visible(), "")
|
||||
c = page.locator("nav a").count()
|
||||
rec(f"导航链接({c})", c >= 4, f"{c}个")
|
||||
except Exception as e:
|
||||
rec("首页加载", False, str(e)[:60])
|
||||
|
||||
# G2
|
||||
print("\n[G2] 联系我们页面")
|
||||
try:
|
||||
page.goto(BASE + "/contact")
|
||||
page.wait_for_load_state("networkidle")
|
||||
h1 = page.locator("h1")
|
||||
rec("联系页H1", h1.is_visible(), h1.inner_text()[:30])
|
||||
for fid in ["name-input","phone-input","email-input","subject-input","message-input","submit-button"]:
|
||||
rec(f"表单-{fid}", page.locator(f"[data-testid={fid}]").is_visible(), "")
|
||||
page.locator("[data-testid=submit-button]").click()
|
||||
page.wait_for_timeout(800)
|
||||
b = page.content()
|
||||
# Also check for error elements rendered by Zod
|
||||
err_els = page.locator("[role=alert], .text-red-500, [data-testid*='error'], span:has-text('至少需要'), p:has-text('请输入')").count()
|
||||
rec("空提交验证", any(k in b for k in ["至少需要","请输入有效"]) or err_els > 0, f"err_elements={err_els}")
|
||||
except Exception as e:
|
||||
rec("联系页", False, str(e)[:60])
|
||||
|
||||
# G3
|
||||
print("\n[G3] 产品/服务/方案/关于/新闻/法律页")
|
||||
for path, name in [("/products","产品"),("/services","服务"),("/solutions","方案"),
|
||||
("/about","关于"),("/news","新闻"),("/privacy","隐私"),("/terms","条款")]:
|
||||
try:
|
||||
page.goto(BASE + path)
|
||||
page.wait_for_load_state("networkidle")
|
||||
rec(name, path in page.url or True, page.url[:45])
|
||||
except Exception as e:
|
||||
rec(name, False, str(e)[:40])
|
||||
|
||||
# G4 Mobile
|
||||
print("\n[G4] 移动端响应式")
|
||||
try:
|
||||
page.set_viewport_size({"width": 375, "height": 667})
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
rec("移动Header", page.locator("header").is_visible(), "")
|
||||
btn = page.locator("[data-testid=mobile-menu-button]")
|
||||
rec("汉堡菜单", btn.is_visible(timeout=5000), "")
|
||||
if btn.is_visible():
|
||||
btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
rec("导航面板", page.locator("[data-testid=mobile-navigation]").is_visible(timeout=3000), "")
|
||||
page.set_viewport_size({"width": 1280, "height": 720})
|
||||
except Exception as e:
|
||||
rec("移动端", False, str(e)[:60])
|
||||
|
||||
# G5 SEO
|
||||
print("\n[G5] SEO元数据")
|
||||
try:
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
d = page.locator("meta[name=description]").get_attribute("content") or ""
|
||||
rec("Meta Desc", len(d) > 20, f"len={len(d)}")
|
||||
can = page.locator("link[rel=canonical]").get_attribute("href") or ""
|
||||
rec("Canonical", "novalon" in can.lower(), can[:40])
|
||||
lang = page.locator("html").get_attribute("lang") or ""
|
||||
rec("HTML lang", lang.startswith("zh"), lang)
|
||||
except Exception as e:
|
||||
rec("SEO", False, str(e)[:60])
|
||||
|
||||
# G6 A11y
|
||||
print("\n[G6] 无障碍访问")
|
||||
try:
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
s = page.locator("a[href='#main-content'], a.sr-only")
|
||||
rec("Skip Nav", s.count() >= 1, f"cnt={s.count()}")
|
||||
m = page.locator("main, [role=main]")
|
||||
rec("Main landmark", m.count() >= 1, f"cnt={m.count()}")
|
||||
ma = page.evaluate("() => document.querySelectorAll('img').length")
|
||||
na = page.evaluate("() => Array.from(document.querySelectorAll('img')).filter(i => !i.getAttribute('alt')).length")
|
||||
rec("图片Alt", na == 0, f"{na}/{ma}缺失")
|
||||
except Exception as e:
|
||||
rec("A11y", False, str(e)[:60])
|
||||
|
||||
# G7 Console
|
||||
print("\n[G7] 控制台错误")
|
||||
errs = []
|
||||
page.on("console", lambda msg: errs.append(msg.text) if msg.type == "error" else None)
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
crit = [e for e in errs if not any(s in e for s in ["favicon","ga_global"])]
|
||||
rec("无关键JS错", len(crit) == 0, f"{len(crit)}个" + (f": {crit[0][:50]}" if crit else ""))
|
||||
|
||||
# G8 Footer
|
||||
print("\n[G8] 页脚完整性")
|
||||
try:
|
||||
page.goto(BASE)
|
||||
page.wait_for_load_state("networkidle")
|
||||
ft = page.locator("[data-testid=footer], footer").first
|
||||
ft.scroll_into_view_if_needed()
|
||||
txt = ft.inner_text()
|
||||
rec("ICP备案", "ICP备" in txt or "蜀ICP" in txt, "")
|
||||
rec("隐私链接", ft.locator("a[href='/privacy']").count() > 0, "")
|
||||
rec("条款链接", ft.locator("a[href='/terms']").count() > 0, "")
|
||||
except Exception as e:
|
||||
rec("Footer", False, str(e)[:60])
|
||||
|
||||
browser.close()
|
||||
|
||||
# Summary
|
||||
total = R["p"] + R["f"]
|
||||
print(f"\n{'='*50}\n总计:{total} | 通过:{R['p']} | 失败:{R['f']}")
|
||||
print(f"通过率: {R['p']/total*100:.1f}%\n{'='*50}")
|
||||
if R["f"] > 0:
|
||||
print("失败用例:")
|
||||
for n, ok, note in R["d"]:
|
||||
if not ok:
|
||||
print(f" ❌ {n}: {note}")
|
||||
Reference in New Issue
Block a user