f5dec95a83
refactor: 重构页面导航和滚动逻辑,提升用户体验 test: 更新测试配置和用例,增加覆盖率和稳定性 perf: 优化性能指标和阈值,适应开发环境需求 ci: 添加Lighthouse CI工作流,集成性能测试 docs: 更新API文档和健康检查端点 fix: 修复登录页面和表单提交问题 style: 调整响应式布局和可访问性改进 chore: 更新依赖项和脚本配置
416 lines
16 KiB
Python
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
|