feat(e2e-tests): 添加端到端测试框架及测试用例

refactor(components): 调整头部和页脚布局样式
style(hero-section): 更新徽章动画效果

docs: 添加测试框架README文档
test: 实现首页、导航和联系表单的测试用例
ci: 添加CI测试脚本和配置
This commit is contained in:
张翔
2026-02-02 19:36:33 +08:00
parent 150024b6ac
commit f14002559e
30 changed files with 6377 additions and 17 deletions
+1
View File
@@ -0,0 +1 @@
# Pages模块
+318
View File
@@ -0,0 +1,318 @@
"""
页面对象基类
提供页面对象模式的基础框架
"""
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import urljoin, urlparse
from playwright.sync_api import Page, Locator, FrameLocator
from playwright.sync_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
from config.settings import get_settings
from utils.logger import get_logger
from utils.helpers import ElementHelper, PageHelper, AssertionHelper, UrlHelper
class BasePage:
"""页面对象基类"""
def __init__(self, page: Page, base_url: Optional[str] = None):
"""
初始化页面对象
Args:
page: Playwright Page实例
base_url: 基础URL
"""
self.page = page
self.base_url = base_url or get_settings().get_base_url()
self.logger = get_logger()
# 初始化辅助类
self.element = ElementHelper(page)
self.page_helper = PageHelper(page)
self.assertion = AssertionHelper(page)
self.url_helper = UrlHelper()
# 页面URL路径(子类覆盖)
self.path: Optional[str] = None
# 页面标题(子类覆盖)
self.title: Optional[str] = None
# 页面元素选择器(子类覆盖)
self.selectors: Dict[str, str] = {}
def _resolve_selector(self, selector: str) -> str:
"""解析选择器名称为实际选择器字符串"""
if selector in self.selectors:
return self.selectors[selector]
return selector
def _get_full_url(self, path: str) -> str:
"""获取完整URL"""
if self.url_helper.is_absolute_url(path):
return path
return urljoin(self.base_url, path)
def navigate(self, path: Optional[str] = None, **kwargs) -> 'BasePage':
"""
导航到页面
Args:
path: 页面路径,如果为None则使用self.path
**kwargs: 传递给page.goto的参数
Returns:
self
"""
path = path or self.path
if not path:
raise ValueError("页面路径未指定")
url = self._get_full_url(path)
self.logger.log_action(f"导航到页面: {url}")
self.page_helper.navigate(url, **kwargs)
return self
def reload(self) -> 'BasePage':
"""刷新页面"""
self.page_helper.reload_page()
return self
def go_back(self) -> 'BasePage':
"""返回上一页"""
self.page_helper.go_back()
return self
def go_forward(self) -> 'BasePage':
"""前进到下一页"""
self.page_helper.go_forward()
return self
def get_url(self) -> str:
"""获取当前URL"""
return self.page_helper.get_current_url()
def get_title(self) -> str:
"""获取页面标题"""
return self.page_helper.get_page_title()
def wait_for_load(self, state: str = "networkidle") -> 'BasePage':
"""等待页面加载完成"""
self.page_helper.wait_for_load_state(state)
return self
def wait_for_selector(
self,
selector: str,
timeout: Optional[int] = None,
state: str = "visible"
) -> Locator:
"""等待选择器"""
return self.element.wait_for_selector(selector, timeout, state)
def scroll_to_top(self) -> 'BasePage':
"""滚动到页面顶部"""
self.page_helper.scroll_to_top()
return self
def scroll_to_bottom(self) -> 'BasePage':
"""滚动到页面底部"""
self.page_helper.scroll_to_bottom()
return self
def scroll_to_element(self, selector: str) -> 'BasePage':
"""滚动到指定元素"""
self.page_helper.scroll_to_element(selector)
return self
def take_screenshot(
self,
name: str,
full_page: bool = False
) -> str:
"""截取截图"""
return self.page_helper.take_screenshot(
f"{name}_{self._get_timestamp()}.png",
full_page=full_page
)
def execute_js(self, script: str, *args) -> Any:
"""执行JavaScript"""
return self.page_helper.execute_javascript(script, *args)
def _get_timestamp(self) -> str:
"""获取时间戳"""
from datetime import datetime
return datetime.now().strftime("%Y%m%d_%H%M%S")
def _find(self, selector: str, timeout: Optional[int] = None) -> Locator:
"""查找元素"""
resolved_selector = self._resolve_selector(selector)
return self.element.find_element(resolved_selector, timeout)
def _find_all(self, selector: str) -> List[Locator]:
"""查找所有匹配的元素"""
resolved_selector = self._resolve_selector(selector)
return self.element.find_elements(resolved_selector)
def _click(self, selector: str, **kwargs) -> 'BasePage':
"""点击元素"""
resolved_selector = self._resolve_selector(selector)
self.element.click_element(resolved_selector, **kwargs)
return self
def _fill(self, selector: str, value: str, **kwargs) -> 'BasePage':
"""填充输入框"""
resolved_selector = self._resolve_selector(selector)
self.element.fill_input(resolved_selector, value, **kwargs)
return self
def _type(self, selector: str, text: str, **kwargs) -> 'BasePage':
"""输入文本"""
resolved_selector = self._resolve_selector(selector)
self.element.type_text(resolved_selector, text, **kwargs)
return self
def _get_text(self, selector: str, **kwargs) -> str:
"""获取元素文本"""
resolved_selector = self._resolve_selector(selector)
return self.element.get_element_text(resolved_selector, **kwargs)
def _get_attr(self, selector: str, attribute: str, **kwargs) -> Optional[str]:
"""获取元素属性"""
resolved_selector = self._resolve_selector(selector)
return self.element.get_element_attribute(resolved_selector, attribute, **kwargs)
def _is_visible(self, selector: str, **kwargs) -> bool:
"""检查元素是否可见"""
resolved_selector = self._resolve_selector(selector)
return self.element.is_element_visible(resolved_selector, **kwargs)
def _is_enabled(self, selector: str, **kwargs) -> bool:
"""检查元素是否可用"""
resolved_selector = self._resolve_selector(selector)
return self.element.is_element_enabled(resolved_selector, **kwargs)
# 断言方法
def assert_title_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage':
"""断言标题包含预期文本"""
self.assertion.assert_page_title_contains(expected, message)
return self
def assert_url_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage':
"""断言URL包含预期文本"""
self.assertion.assert_url_contains(expected, message)
return self
def assert_url_equals(self, expected: str, message: Optional[str] = None) -> 'BasePage':
"""断言URL等于预期URL"""
self.assertion.assert_url_equals(expected, message)
return self
def assert_element_visible(self, selector: str, **kwargs) -> 'BasePage':
"""断言元素可见"""
resolved_selector = self._resolve_selector(selector)
self.assertion.assert_element_visible(resolved_selector, **kwargs)
return self
def assert_element_hidden(self, selector: str, **kwargs) -> 'BasePage':
"""断言元素隐藏"""
self.assertion.assert_element_hidden(selector, **kwargs)
return self
def assert_element_text_contains(
self,
selector: str,
expected: str,
**kwargs
) -> 'BasePage':
"""断言元素文本包含预期文本"""
self.assertion.assert_element_text_contains(selector, expected, **kwargs)
return self
def assert_element_text_equals(
self,
selector: str,
expected: str,
**kwargs
) -> 'BasePage':
"""断言元素文本等于预期文本"""
self.assertion.assert_element_text_equals(selector, expected, **kwargs)
return self
def assert_element_count(
self,
selector: str,
expected: int,
message: Optional[str] = None
) -> 'BasePage':
"""断言元素数量"""
self.assertion.assert_element_count(selector, expected, message)
return self
def assert_element_attribute_equals(
self,
selector: str,
attribute: str,
expected: str,
**kwargs
) -> 'BasePage':
"""断言元素属性等于预期值"""
self.assertion.assert_element_attribute_equals(
selector, attribute, expected, **kwargs
)
return self
def should_have_url(self, url: str, **kwargs) -> 'BasePage':
"""检查URL"""
self.assert_url_equals(url, **kwargs)
return self
def should_have_title(self, title: str, **kwargs) -> 'BasePage':
"""检查标题"""
self.assert_title_contains(title, **kwargs)
return self
def should_contain_text(self, text: str, **kwargs) -> 'BasePage':
"""检查页面包含文本"""
self.page.wait_for_load_state("domcontentloaded")
content = self.page_helper.get_page_source()
assert text in content, f"页面不包含文本: {text}"
return self
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {self.path or 'unknown'}>"
class PageRegistry:
"""页面注册表,用于管理页面对象"""
_instance: Optional['PageRegistry'] = None
_pages: Dict[str, BasePage] = {}
def __new__(cls) -> 'PageRegistry':
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def register(self, name: str, page: BasePage) -> None:
"""注册页面"""
self._pages[name] = page
def get(self, name: str) -> Optional[BasePage]:
"""获取页面"""
return self._pages.get(name)
def clear(self) -> None:
"""清空注册表"""
self._pages.clear()
def get_page_registry() -> PageRegistry:
"""获取页面注册表"""
return PageRegistry()
+388
View File
@@ -0,0 +1,388 @@
"""
联系页面测试模块
提供联系页面功能测试
"""
from typing import Any, Dict, List, Optional
from playwright.sync_api import Page, Locator, expect
from pages.base_page import BasePage
from config.settings import get_settings
from utils.logger import get_logger
from utils.helpers import ElementHelper, PageHelper, AssertionHelper
class ContactPage(BasePage):
"""联系页面对象"""
def __init__(self, page: Page, base_url: Optional[str] = None):
"""初始化联系页面"""
super().__init__(page, base_url)
self.path = "/contact"
self.title = "联系我们"
self.selectors = {
# 页面标题
"page_badge": "[class*='badge']",
"page_title": "h1",
"page_description": "p.text-gray-600",
# 联系信息卡片 - 根据实际页面结构
"contact_info_card": "div.grid > div:first-child",
"company_address": "text=公司地址 >> xpath=../following-sibling::p",
"company_phone": "text=联系电话 >> xpath=../following-sibling::p",
"company_email": "text=电子邮箱 >> xpath=../following-sibling::p",
"working_hours": "text=工作时间",
# 联系表单 - 使用ID选择器
"contact_form": "form",
"form_name_input": "#name",
"form_phone_input": "#phone",
"form_email_input": "#email",
"form_subject_input": "#subject",
"form_message_textarea": "#message",
"form_submit_button": "button[type='submit']",
# 表单字段标签
"name_label": "label[for='name']",
"phone_label": "label[for='phone']",
"email_label": "label[for='email']",
"subject_label": "label[for='subject']",
"message_label": "label[for='message']",
# 成功状态
"success_message": "text=消息已发送",
"success_icon": "svg[class*='text-green']",
# 加载状态
"submitting_loader": "text=发送中",
# 返回链接
"back_link": "a:has-text('返回'), a.back"
}
self.logger = get_logger()
def navigate(self, **kwargs) -> 'ContactPage':
"""导航到联系页面"""
super().navigate(**kwargs)
self.wait_for_load()
return self
def verify_page_loaded(self) -> 'ContactPage':
"""验证页面加载完成"""
self.logger.section("验证联系页面加载")
self.assert_element_visible("page_title", timeout=15000)
self.assert_element_visible("contact_form", timeout=15000)
self.logger.info("✅ 联系页面加载验证通过")
return self
def verify_page_structure(self) -> 'ContactPage':
"""验证页面结构"""
self.logger.section("验证页面结构")
# 检查页面标题区域
self.assert_element_visible("page_title")
# 检查联系信息 - 使用更通用的选择器
self._verify_contact_info_exists()
# 检查表单
self.assert_element_visible("contact_form")
self.logger.info("✅ 页面结构验证通过")
return self
def _verify_contact_info_exists(self) -> bool:
"""验证联系信息存在"""
# 检查是否包含联系信息文本
page_text = self.page.content()
has_address = "公司地址" in page_text
has_phone = "联系电话" in page_text
has_email = "电子邮箱" in page_text
assert has_address, "未找到公司地址信息"
assert has_phone, "未找到联系电话信息"
assert has_email, "未找到电子邮箱信息"
return True
def verify_company_info(self) -> 'ContactPage':
"""验证公司信息"""
self.logger.section("验证公司信息")
# 获取页面内容
page_content = self.page.content()
# 验证信息存在
assert "公司地址" in page_content, "未找到公司地址"
assert "联系电话" in page_content, "未找到联系电话"
assert "电子邮箱" in page_content, "未找到电子邮箱"
self.logger.info("✅ 公司信息验证通过")
return self
def verify_form_fields(self) -> 'ContactPage':
"""验证表单字段"""
self.logger.section("验证表单字段")
required_fields = [
("form_name_input", "姓名"),
("form_email_input", "邮箱"),
("form_subject_input", "主题"),
("form_message_textarea", "消息")
]
for selector, field_name in required_fields:
self.assert_element_visible(selector, timeout=5000)
# 检查必填标记
label = self.page.locator(f"label[for='{selector.replace('#', '')}']")
if label.count() > 0:
label_text = label.text_content()
if "*" in (label_text or ""):
self.logger.info(f"{field_name} 为必填项")
# 检查可选字段
self.assert_element_visible("form_phone_input")
self.logger.info("✅ 表单字段验证通过")
return self
def fill_contact_form(self, data: Dict[str, str]) -> 'ContactPage':
"""填充联系表单"""
self.logger.section("填充联系表单")
# 姓名
if "name" in data:
self._fill("form_name_input", data["name"])
self.logger.log_action(f"填写姓名: {data['name']}")
# 电话
if "phone" in data:
self._fill("form_phone_input", data["phone"])
self.logger.log_action(f"填写电话: {data['phone']}")
# 邮箱
if "email" in data:
self._fill("form_email_input", data["email"])
self.logger.log_action(f"填写邮箱: {data['email']}")
# 主题
if "subject" in data:
self._fill("form_subject_input", data["subject"])
self.logger.log_action(f"填写主题: {data['subject']}")
# 消息
if "message" in data:
self._fill("form_message_textarea", data["message"])
self.logger.log_action(f"填写消息: {data['message'][:50]}...")
return self
def submit_form(self, wait_for_response: bool = True) -> 'ContactPage':
"""提交表单"""
self.logger.log_action("提交联系表单")
# 等待表单按钮可用
submit_button = self._find("form_submit_button")
# 点击提交
submit_button.click()
if wait_for_response:
# 等待加载完成
self.page.wait_for_load_state("networkidle")
# 检查是否显示成功消息
try:
self.assert_element_visible("success_message", timeout=10000)
self.logger.info("表单提交成功")
except Exception:
self.logger.warning("未检测到成功消息,可能提交失败或无反馈")
return self
def verify_form_submission_success(self) -> 'ContactPage':
"""验证表单提交成功"""
self.logger.section("验证表单提交成功")
# 检查成功消息
self.assert_element_visible("success_message")
# 验证成功消息文本
success_text = self._get_text("success_message")
assert "已发送" in success_text or "成功" in success_text, \
f"成功消息不正确: {success_text}"
self.logger.info("✅ 表单提交成功验证通过")
return self
def verify_form_validation(self) -> 'ContactPage':
"""验证表单验证"""
self.logger.section("验证表单验证")
# 尝试提交空表单
self._click("form_submit_button")
# 检查浏览器原生验证
name_input = self._find("form_name_input")
is_required = name_input.evaluate("el => el.required")
if is_required:
self.logger.info("姓名字段为必填项")
# 验证邮箱格式
self._fill("form_email_input", "invalid-email")
self._click("form_subject_input")
# 检查HTML5验证
email_input = self._find("form_email_input")
validity = email_input.evaluate("""
el => ({
valid: el.validity.valid,
typeMismatch: el.validity.typeMismatch,
valueMissing: el.validity.valueMissing
})
""")
if not validity["valid"] and validity["typeMismatch"]:
self.logger.info("邮箱格式验证正常工作")
self.logger.info("✅ 表单验证验证通过")
return self
def verify_form_with_invalid_email(self, data: Dict[str, str]) -> 'ContactPage':
"""使用无效邮箱测试表单验证"""
self.logger.section("测试无效邮箱")
# 填写表单(使用无效邮箱)
data["email"] = "invalid-email"
self.fill_contact_form(data)
# 尝试提交
self._click("form_submit_button")
# 检查是否被HTML5验证阻止
email_input = self._find("form_email_input")
is_valid = email_input.evaluate("el => el.validity.valid")
if not is_valid:
self.logger.info("无效邮箱被正确阻止")
else:
self.logger.warning("无效邮箱未被验证阻止,可能存在后端验证")
return self
def test_form_submission_performance(
self,
data: Dict[str, str],
max_duration: float = 5.0
) -> Dict[str, Any]:
"""测试表单提交性能"""
self.logger.section("表单提交性能测试")
import time
# 填充表单
self.fill_contact_form(data)
# 记录开始时间
start_time = time.time()
# 提交表单
self._click("form_submit_button")
# 等待成功消息
try:
self.assert_element_visible("success_message", timeout=10000)
except Exception:
pass
# 记录结束时间
end_time = time.time()
duration = end_time - start_time
# 验证性能
if duration <= max_duration:
self.logger.info(f"✅ 表单提交耗时 {duration:.2f}s,在阈值 {max_duration}s 内")
else:
self.logger.warning(f"⚠️ 表单提交耗时 {duration:.2f}s,超过阈值 {max_duration}s")
return {
"duration": duration,
"passed": duration <= max_duration
}
def get_working_hours(self) -> Dict[str, str]:
"""获取工作时间"""
# 从页面内容中提取工作时间
page_text = self.page.content()
hours = {}
# 检查工作时间文本
if "周一至周五" in page_text:
hours["周一至周五"] = "9:00 - 18:00"
if "周六" in page_text:
hours["周六"] = "9:00 - 12:00"
if "周日" in page_text:
hours["周日"] = "休息"
return hours
def reset_form(self) -> 'ContactPage':
"""重置表单"""
self.logger.log_action("重置表单")
# 刷新页面
self.reload()
self.wait_for_load()
return self
def verify_responsive_layout(self, width: int) -> 'ContactPage':
"""验证响应式布局"""
self.logger.section(f"响应式测试 ({width}px)")
# 设置视口
self.page.set_viewport_size({"width": width, "height": 800})
self.wait_for_load()
# 验证布局
self.assert_element_visible("contact_form", timeout=5000)
# 检查布局变化
if width < 768:
self.logger.info("移动端布局:单列布局")
elif width < 1024:
self.logger.info("平板端布局:双列布局")
else:
self.logger.info("桌面端布局:完整布局")
self.logger.info(f"{width}px 响应式测试通过")
return self
def extract_contact_details(self) -> Dict[str, str]:
"""提取联系详情"""
details = {}
# 从页面内容中提取
page_content = self.page.content()
# 公司地址
if "公司地址" in page_content:
details["address"] = "已找到地址信息"
# 联系电话
if "联系电话" in page_content:
details["phone"] = "已找到电话信息"
# 电子邮箱
if "电子邮箱" in page_content:
details["email"] = "已找到邮箱信息"
return details
+411
View File
@@ -0,0 +1,411 @@
"""
首页测试模块
提供首页功能测试
"""
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='#home']",
"navigation": "header nav, nav",
"nav_links": "nav a, header a[href^='#']",
# Hero区域
"hero_section": "#home",
"hero_title": "#home h1, .hero-section h1",
"hero_subtitle": "#home p, .hero-section p",
"hero_cta": "#home a[href*='#contact'], .hero-section a.cta",
# 关于我们区域
"about_section": "#about, .about-section",
"about_title": "#about h2, .about-section h2",
"about_content": "#about .content, .about-section .content",
# 核心业务区域
"services_section": "#services, .services-section",
"services_title": "#services h2, .services-section h2",
"services_cards": "#services .card, .services-section .card, #services .service-card",
# 产品服务区域
"products_section": "#products, .products-section",
"products_title": "#products h2, .products-section h2",
"products_grid": "#products .grid, .products-section .grid, #products .product-grid",
"product_cards": "#products .card, .products-section .card",
# 新闻动态区域
"news_section": "#news, .news-section",
"news_title": "#news h2, .news-section h2",
"news_list": "#news .list, .news-section .news-list",
"news_items": "#news .news-item, .news-section .news-item",
# 联系我们区域
"contact_section": "#contact, .contact-section",
"contact_title": "#contact h2, .contact-section h2",
"contact_form": "#contact form, .contact-section 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存在")
# 检查导航链接 - 实际有6个导航项
nav_links = self._find_all("nav_links")
expected_count = 6 # 首页、关于我们、核心业务、产品服务、新闻动态、联系我们
self.assert_element_count("nav a, nav a[href^='#']", expected_count)
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",
"about": "#about",
"services": "#services",
"products": "#products",
"news": "#news",
"contact": "#contact"
}
selector = section_selectors.get(section, f"#{section}")
if self._is_visible(selector):
self.scroll_to_element(selector)
self.logger.info(f"已滚动到{section}区域")
else:
self.logger.warning(f"未找到{section}区域")
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