Files
novalon-website/e2e-tests/pages/home_page.py
T
张翔 f5dec95a83 feat: 添加管理后台页面和功能,优化测试和性能配置
refactor: 重构页面导航和滚动逻辑,提升用户体验

test: 更新测试配置和用例,增加覆盖率和稳定性

perf: 优化性能指标和阈值,适应开发环境需求

ci: 添加Lighthouse CI工作流,集成性能测试

docs: 更新API文档和健康检查端点

fix: 修复登录页面和表单提交问题

style: 调整响应式布局和可访问性改进

chore: 更新依赖项和脚本配置
2026-03-24 10:11:30 +08:00

416 lines
16 KiB
Python

"""
首页测试模块
提供首页功能测试
"""
from typing import Any, Dict, List, Optional
from playwright.sync_api import Page, Locator
from pages.base_page import BasePage
from config.settings import get_settings
from utils.logger import get_logger
class HomePage(BasePage):
"""首页页面对象"""
def __init__(self, page: Page, base_url: Optional[str] = None):
"""初始化首页"""
super().__init__(page, base_url)
self.path = "/"
self.title = "四川睿新致远科技有限公司"
self.selectors = {
# 导航相关
"header": "header",
"logo": "header img[alt*='logo'], header a[href='/']",
"navigation": "header nav, nav",
"nav_links": "nav a, header nav a",
# Hero区域 - 使用实际的 section ID
"hero_section": "#home, section:first-of-type",
"hero_title": "h1",
"hero_subtitle": "#home p, section:first-of-type p",
"hero_cta": "a[href*='/contact'], a:has-text('立即咨询')",
# 关于我们区域 - 使用文本匹配
"about_section": "#about, section:has(h2:has-text('关于'))",
"about_title": "#about h2, h2:has-text('关于')",
"about_content": "#about .content",
# 核心业务区域
"services_section": "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
"services_title": "#services h2, h2:has-text('业务'), h2:has-text('服务')",
"services_cards": "#services .card, .service-card",
# 产品服务区域
"products_section": "#products, section:has(h2:has-text('产品'))",
"products_title": "#products h2, h2:has-text('产品')",
"products_grid": "#products .grid",
"product_cards": "#products .card, .product-card",
# 新闻动态区域
"news_section": "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
"news_title": "#news h2, h2:has-text('新闻'), h2:has-text('动态')",
"news_list": "#news .list, .news-list",
"news_items": "#news .news-item, .news-item",
# 联系我们区域 - 使用 /contact 页面
"contact_section": "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))",
"contact_title": "#contact h2, h2:has-text('联系'), h2:has-text('联系方式')",
"contact_form": "form",
# 页脚
"footer": "footer",
"footer_content": "footer .content, footer .footer-content",
"social_links": "footer .social-links, footer a[href*='weixin'], footer a[href*='weibo']"
}
self.logger = get_logger()
def navigate(self, **kwargs) -> 'HomePage':
"""导航到首页"""
super().navigate(**kwargs)
self.wait_for_load()
return self
def verify_page_loaded(self) -> 'HomePage':
"""验证页面加载完成"""
self.logger.section("验证首页加载")
# 检查关键元素存在
self.assert_element_visible("header", timeout=10000)
self.assert_element_visible("main", timeout=10000)
self.assert_element_visible("footer", timeout=10000)
# 检查页面标题
self.assert_title_contains("睿新致远")
self.logger.info("✅ 首页加载验证通过")
return self
def verify_header(self) -> 'HomePage':
"""验证页头"""
self.logger.section("验证页头")
# 检查Logo
if self._is_visible("logo"):
self.logger.info("Logo存在")
# 检查导航链接 - 桌面端和移动端各有导航项
nav_links = self._find_all("nav_links")
min_expected = 6 # 至少6个导航项
assert len(nav_links) >= min_expected, f"导航链接数量不足: 预期至少{min_expected}个,实际{len(nav_links)}"
self.logger.info(f"✅ 页头验证通过,发现 {len(nav_links)} 个导航链接")
return self
def verify_hero_section(self) -> 'HomePage':
"""验证Hero区域"""
self.logger.section("验证Hero区域")
if self._is_visible("hero_section"):
self.assert_element_visible("hero_title")
self.assert_element_visible("hero_subtitle")
self.logger.info("Hero区域完整")
# 获取标题文本
title = self._get_text("hero_title")
self.logger.info(f"Hero标题: {title[:50]}...")
else:
self.logger.warning("未找到Hero区域")
return self
def verify_services_section(self) -> 'HomePage':
"""验证核心业务区域"""
self.logger.section("验证核心业务区域")
if self._is_visible("services_section"):
self.assert_element_visible("services_title")
# 检查业务卡片
cards = self._find_all("services_cards")
self.logger.info(f"发现 {len(cards)} 个服务卡片")
if len(cards) > 0:
self.logger.info("✅ 服务区域验证通过")
else:
self.logger.warning("未找到服务区域")
return self
def verify_products_section(self) -> 'HomePage':
"""验证产品服务区域"""
self.logger.section("验证产品服务区域")
if self._is_visible("products_section"):
self.assert_element_visible("products_title")
# 检查产品卡片
cards = self._find_all("product_cards")
self.logger.info(f"发现 {len(cards)} 个产品卡片")
if len(cards) > 0:
self.logger.info("✅ 产品区域验证通过")
else:
self.logger.warning("未找到产品区域")
return self
def verify_news_section(self) -> 'HomePage':
"""验证新闻动态区域"""
self.logger.section("验证新闻动态区域")
if self._is_visible("news_section"):
self.assert_element_visible("news_title")
# 检查新闻列表
items = self._find_all("news_items")
self.logger.info(f"发现 {len(items)} 条新闻")
if len(items) > 0:
self.logger.info("✅ 新闻区域验证通过")
else:
self.logger.warning("未找到新闻区域")
return self
def verify_contact_section(self) -> 'HomePage':
"""验证联系我们区域"""
self.logger.section("验证联系我们区域")
if self._is_visible("contact_section"):
self.assert_element_visible("contact_title")
self.assert_element_visible("contact_form")
self.logger.info("联系区域包含表单")
# 检查表单字段
form_fields = ["name", "email", "subject", "message"]
for field in form_fields:
if self._is_visible(f"contact_form #{field}"):
self.logger.info(f"表单字段 {field} 存在")
self.logger.info("✅ 联系区域验证通过")
else:
self.logger.warning("未找到联系区域")
return self
def verify_footer(self) -> 'HomePage':
"""验证页脚"""
self.logger.section("验证页脚")
self.assert_element_visible("footer")
# 检查版权信息
footer_text = self._get_text("footer")
if "睿新致远" in footer_text or "2026" in footer_text:
self.logger.info("页脚包含版权信息")
self.logger.info("✅ 页脚验证通过")
return self
def verify_all_sections(self) -> 'HomePage':
"""验证所有区域"""
self.verify_header()
self.verify_hero_section()
self.verify_services_section()
self.verify_products_section()
self.verify_news_section()
self.verify_contact_section()
self.verify_footer()
self.logger.info("✅ 首页所有区域验证完成")
return self
def scroll_to_section(self, section: str) -> 'HomePage':
"""滚动到指定区域"""
self.logger.log_action(f"滚动到{section}区域")
section_selectors = {
"home": "#home, section:first-of-type",
"about": "#about, section:has(h2:has-text('关于'))",
"services": "#services, section:has(h2:has-text('业务')), section:has(h2:has-text('服务'))",
"products": "#products, section:has(h2:has-text('产品'))",
"news": "#news, section:has(h2:has-text('新闻')), section:has(h2:has-text('动态'))",
"contact": "#contact, section:has(h2:has-text('联系')), section:has(h2:has-text('联系方式'))"
}
selector = section_selectors.get(section, f"#{section}")
try:
element = self.page.locator(selector).first
if element.count() > 0:
element.scroll_into_view_if_needed()
self.logger.info(f"已滚动到{section}区域")
else:
self.logger.warning(f"未找到{section}区域")
except Exception as e:
self.logger.warning(f"滚动到{section}区域失败: {e}")
return self
def click_navigation_link(self, section: str) -> 'HomePage':
"""点击导航链接"""
self.logger.log_action(f"点击{section}导航链接")
nav_items = {
"home": "首页",
"about": "关于我们",
"services": "核心业务",
"products": "产品服务",
"news": "新闻动态",
"contact": "联系我们"
}
label = nav_items.get(section, section)
# 查找包含指定文本的导航链接
nav_link = self.page.locator(f"nav a:has-text('{label}'), header a:has-text('{label}')")
if nav_link.count() > 0:
nav_link.first.click()
self.wait_for_load()
self.logger.info(f"已点击{nav_items.get(section, section)}链接")
else:
self.logger.warning(f"未找到{nav_items.get(section, section)}链接")
return self
def get_company_info(self) -> Dict[str, str]:
"""获取公司信息"""
info = {}
# 从首页获取描述
hero_text = ""
if self._is_visible("hero_subtitle"):
hero_text = self._get_text("hero_subtitle")
# 如果无法从页面获取,使用默认值
info["description"] = hero_text if hero_text else "专注科技创新,驱动智慧未来"
# 从常量获取
info["name"] = "四川睿新致远科技有限公司"
info["slogan"] = "专注科技创新,驱动智慧未来"
return info
def get_statistics(self) -> Dict[str, int]:
"""获取统计数据"""
stats = {}
# 尝试从页面获取统计数据
if self._is_visible("about_section"):
# 这里需要根据实际页面结构调整
pass
# 默认值
stats = {
"customers": 50,
"cases": 100,
"projects": 200,
"experience": 8
}
return stats
def get_featured_services(self) -> List[Dict[str, str]]:
"""获取精选服务"""
services = []
if self._is_visible("services_cards"):
cards = self._find_all("services_cards")[:4]
for card in cards:
title = card.locator("h3, .title").text_content() if card.locator("h3, .title").count() > 0 else ""
description = card.locator("p, .description").text_content() if card.locator("p, .description").count() > 0 else ""
services.append({
"title": title.strip() if title else "",
"description": description.strip() if description else ""
})
return services
def get_latest_news(self) -> List[Dict[str, str]]:
"""获取最新新闻"""
news = []
if self._is_visible("news_items"):
items = self._find_all("news_items")[:3]
for item in items:
title = item.locator("h3, .title, a").first.text_content() if item.locator("h3, .title, a").count() > 0 else ""
date = item.locator(".date, time").first.text_content() if item.locator(".date, time").count() > 0 else ""
news.append({
"title": title.strip() if title else "",
"date": date.strip() if date else ""
})
return news
def verify_page_performance(self) -> Dict[str, float]:
"""验证页面性能指标"""
self.logger.section("性能测试")
performance_data = self.execute_js("""
() => {
const timing = performance.timing;
const navigation = performance.getEntriesByType('navigation')[0];
return {
// 关键指标
'pageLoadTime': timing.loadEventEnd - timing.navigationStart,
'domContentLoaded': timing.domContentLoadedEventEnd - timing.navigationStart,
'firstPaint': timing.responseStart - timing.navigationStart,
'firstContentfulPaint': navigation ? navigation.firstContentfulPaint : 0,
'largestContentfulPaint': navigation ? navigation.largestContentfulPaint : 0,
'timeToInteractive': navigation ? navigation.interactive : 0,
// 资源指标
'domainLookupTime': timing.domainLookupEnd - timing.domainLookupStart,
'serverResponseTime': timing.responseEnd - timing.requestStart,
'tcpConnectTime': timing.connectEnd - timing.connectStart,
'domInteractiveTime': timing.domInteractive - timing.domLoading
};
}
""")
# 记录性能指标
for metric, value in performance_data.items():
if value and value > 0:
threshold = get_settings().performance_thresholds.__dict__.get(
metric.replace("_", ""), 3000
)
self.logger.log_performance(metric, float(value), threshold)
return performance_data
def verify_responsive_design(self, width: int, height: int) -> 'HomePage':
"""验证响应式设计"""
self.logger.section(f"响应式测试 ({width}x{height})")
# 设置视口大小
self.page.set_viewport_size({"width": width, "height": height})
self.wait_for_load()
# 验证关键元素
self.assert_element_visible("header", timeout=5000)
self.assert_element_visible("main", timeout=5000)
self.assert_element_visible("footer", timeout=5000)
# 根据屏幕大小调整验证逻辑
if width < 768:
self.logger.info(f"移动端 {width}px: 验证基础布局")
# 移动端检查汉堡菜单
mobile_menu = self.page.locator("button:has-text('菜单'), .mobile-menu, .menu-toggle")
self.logger.info(f"发现 {mobile_menu.count()} 个移动端菜单元素")
elif width < 1024:
self.logger.info(f"平板端 {width}px: 验证平板布局")
else:
self.logger.info(f"桌面端 {width}px: 验证完整布局")
self.logger.info(f"{width}x{height} 响应式测试通过")
return self