feat(e2e-tests): 添加端到端测试框架及测试用例
refactor(components): 调整头部和页脚布局样式 style(hero-section): 更新徽章动画效果 docs: 添加测试框架README文档 test: 实现首页、导航和联系表单的测试用例 ci: 添加CI测试脚本和配置
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Tests模块
|
||||
@@ -0,0 +1,343 @@
|
||||
"""
|
||||
测试配置文件
|
||||
提供全局测试fixture和钩子函数
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Generator, Optional
|
||||
import pytest
|
||||
from pytest import Config
|
||||
|
||||
from playwright.sync_api import Browser, BrowserContext, Page, Playwright
|
||||
from playwright.sync_api import Error as PlaywrightError
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from config.settings import get_settings
|
||||
from config.browsers import get_browser_factory, BrowserConfigManager
|
||||
from utils.logger import get_logger
|
||||
from utils.report_generator import get_report_manager, TestResult, TestStatus
|
||||
from utils.data_generator import get_test_data_generator
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def settings() -> Generator:
|
||||
"""获取测试配置"""
|
||||
yield get_settings()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def logger() -> Generator:
|
||||
"""获取日志记录器"""
|
||||
yield get_logger()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_data_generator() -> Generator:
|
||||
"""获取测试数据生成器"""
|
||||
yield get_test_data_generator()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def browser_factory() -> Generator:
|
||||
"""获取浏览器工厂"""
|
||||
factory = get_browser_factory()
|
||||
yield factory
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def browser_context(
|
||||
browser_factory: BrowserConfigManager,
|
||||
settings
|
||||
) -> Generator:
|
||||
"""创建浏览器上下文"""
|
||||
browser, context, page = browser_factory.create_browser_session(
|
||||
browser_name=settings.default_browser,
|
||||
headless=settings.headless_mode
|
||||
)
|
||||
|
||||
yield context, page
|
||||
|
||||
# 清理(在会话结束时)
|
||||
try:
|
||||
page.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
context.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
browser_factory.close_browser()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(browser_context, settings) -> Generator:
|
||||
"""创建页面"""
|
||||
context, page = browser_context
|
||||
|
||||
# 设置默认超时
|
||||
page.set_default_timeout(settings.page_load_timeout)
|
||||
page.set_default_navigation_timeout(settings.page_load_timeout)
|
||||
|
||||
yield page
|
||||
|
||||
# 截图(如果测试失败)
|
||||
if hasattr(page, "_test_failed") and page._test_failed:
|
||||
test_name = getattr(pytest, "_test_name", "unknown")
|
||||
screenshots_dir = Path(settings.screenshots_dir)
|
||||
screenshots_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
screenshot_path = screenshots_dir / f"{test_name}_failed.png"
|
||||
try:
|
||||
page.screenshot(path=str(screenshot_path))
|
||||
logger = get_logger()
|
||||
logger.error(f"失败截图已保存: {screenshot_path}")
|
||||
except Exception as e:
|
||||
logger = get_logger()
|
||||
logger.error(f"保存失败截图时出错: {e}")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def home_page(page: Page, settings) -> Generator:
|
||||
"""创建首页对象"""
|
||||
from pages.home_page import HomePage
|
||||
home = HomePage(page, settings.get_base_url())
|
||||
yield home
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def contact_page(page: Page, settings) -> Generator:
|
||||
"""创建联系页面对象"""
|
||||
from pages.contact_page import ContactPage
|
||||
contact = ContactPage(page, settings.get_base_url())
|
||||
yield contact
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def base_url(settings) -> str:
|
||||
"""获取基础URL"""
|
||||
return settings.get_base_url()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_results() -> Generator:
|
||||
"""收集测试结果"""
|
||||
results = []
|
||||
yield results
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def report_manager() -> Generator:
|
||||
"""获取报告管理器"""
|
||||
manager = get_report_manager()
|
||||
yield manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def track_test_result(report_manager, test_results):
|
||||
"""跟踪测试结果fixture"""
|
||||
from datetime import datetime
|
||||
import pytest
|
||||
|
||||
class TestTracker:
|
||||
def __init__(self):
|
||||
self.start_time = None
|
||||
self.current_result = None
|
||||
|
||||
def start_track(self, test_name: str, test_class: str = "", test_file: str = ""):
|
||||
self.start_time = datetime.now()
|
||||
logger = get_logger()
|
||||
logger.log_test_start(test_name, test_class=test_class, test_file=test_file)
|
||||
|
||||
def end_track(
|
||||
self,
|
||||
test_name: str,
|
||||
status: TestStatus,
|
||||
test_class: str = "",
|
||||
test_file: str = ""
|
||||
):
|
||||
end_time = datetime.now()
|
||||
duration = (end_time - self.start_time).total_seconds()
|
||||
|
||||
logger = get_logger()
|
||||
logger.log_test_end(test_name, status.value, duration)
|
||||
|
||||
# 创建测试结果
|
||||
result = TestResult(
|
||||
test_id=f"{test_file}_{test_name}",
|
||||
test_name=test_name,
|
||||
test_file=test_file,
|
||||
test_class=test_class,
|
||||
status=status,
|
||||
start_time=self.start_time,
|
||||
end_time=end_time,
|
||||
duration=duration
|
||||
)
|
||||
|
||||
# 添加到报告管理器
|
||||
report_manager.add_result(result)
|
||||
test_results.append(result)
|
||||
|
||||
return result
|
||||
|
||||
tracker = TestTracker()
|
||||
yield tracker
|
||||
|
||||
|
||||
# Pytest钩子函数
|
||||
|
||||
def pytest_configure(config: Config):
|
||||
"""pytest配置钩子"""
|
||||
# 设置标记
|
||||
config.addinivalue_line(
|
||||
"markers", "smoke: 冒烟测试,快速验证核心功能"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "regression: 回归测试,完整功能验证"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "performance: 性能测试,页面加载和响应时间"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "responsive: 响应式测试,不同屏幕尺寸"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "cross_browser: 跨浏览器测试"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "form: 表单相关测试"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "navigation: 导航测试"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "interactive: 用户交互测试"
|
||||
)
|
||||
|
||||
# 创建必要的目录
|
||||
settings = get_settings()
|
||||
Path(settings.screenshots_dir).mkdir(parents=True, exist_ok=True)
|
||||
Path(settings.videos_dir).mkdir(parents=True, exist_ok=True)
|
||||
Path("reports").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
"""测试会话开始"""
|
||||
logger = get_logger()
|
||||
logger.section("开始E2E测试会话")
|
||||
logger.info(f"测试会话ID: {session.name}")
|
||||
logger.info(f"测试数量: {len(session.items)}")
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
"""测试会话结束"""
|
||||
logger = get_logger()
|
||||
logger.section("E2E测试会话结束")
|
||||
|
||||
# 生成报告
|
||||
if exitstatus == 0:
|
||||
logger.info("✅ 所有测试通过")
|
||||
else:
|
||||
logger.warning(f"⚠️ 测试完成,退出码: {exitstatus}")
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
"""测试运行前设置"""
|
||||
logger = get_logger()
|
||||
logger.divider()
|
||||
logger.log_test_start(item.name)
|
||||
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
"""生成测试报告"""
|
||||
if call.when == "call":
|
||||
if call.excinfo:
|
||||
item._test_failed = True
|
||||
else:
|
||||
item._test_failed = False
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_call(item):
|
||||
"""测试运行钩子"""
|
||||
yield
|
||||
# 测试运行后处理
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""修改测试项目"""
|
||||
# 按标记排序
|
||||
marker_priority = {
|
||||
"smoke": 0,
|
||||
"performance": 1,
|
||||
"regression": 2,
|
||||
"responsive": 3,
|
||||
"cross_browser": 4,
|
||||
"form": 5,
|
||||
"navigation": 6,
|
||||
"interactive": 7
|
||||
}
|
||||
|
||||
def get_priority(item):
|
||||
for marker in item.iter_markers():
|
||||
if marker.name in marker_priority:
|
||||
return marker_priority[marker.name]
|
||||
return 10
|
||||
|
||||
items.sort(key=get_priority)
|
||||
|
||||
|
||||
# 自定义断言
|
||||
|
||||
class E2EAssertions:
|
||||
"""E2E测试断言类"""
|
||||
|
||||
def __init__(self, page: Page):
|
||||
self.page = page
|
||||
self.logger = get_logger()
|
||||
|
||||
def title_contains(self, expected: str, message: Optional[str] = None) -> None:
|
||||
"""断言标题包含预期文本"""
|
||||
actual = self.page.title()
|
||||
assert expected in actual, message or f"标题不包含 '{expected}': {actual}"
|
||||
self.logger.log_assertion(f"标题包含 '{expected}'", True)
|
||||
|
||||
def url_contains(self, expected: str, message: Optional[str] = None) -> None:
|
||||
"""断言URL包含预期文本"""
|
||||
assert expected in self.page.url, message or f"URL不包含 '{expected}': {self.page.url}"
|
||||
self.logger.log_assertion(f"URL包含 '{expected}'", True)
|
||||
|
||||
def element_exists(self, selector: str, timeout: int = 5000) -> None:
|
||||
"""断言元素存在"""
|
||||
try:
|
||||
self.page.wait_for_selector(selector, timeout=timeout)
|
||||
self.logger.log_assertion(f"元素存在: {selector}", True)
|
||||
except Exception as e:
|
||||
self.logger.log_assertion(f"元素存在: {selector}", False)
|
||||
raise AssertionError(f"元素不存在: {selector}") from e
|
||||
|
||||
def element_visible(self, selector: str, timeout: int = 5000) -> None:
|
||||
"""断言元素可见"""
|
||||
element = self.page.locator(selector).first
|
||||
assert element.is_visible(timeout=timeout), f"元素不可见: {selector}"
|
||||
self.logger.log_assertion(f"元素可见: {selector}", True)
|
||||
|
||||
def element_not_visible(self, selector: str, timeout: int = 5000) -> None:
|
||||
"""断言元素不可见"""
|
||||
element = self.page.locator(selector).first
|
||||
assert not element.is_visible(timeout=timeout), f"元素应该不可见: {selector}"
|
||||
self.logger.log_assertion(f"元素不可见: {selector}", True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def assert_(page: Page) -> E2EAssertions:
|
||||
"""断言fixture"""
|
||||
return E2EAssertions(page)
|
||||
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
联系表单测试模块
|
||||
测试联系表单的各项功能和验证
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Dict, Any
|
||||
|
||||
from pages.contact_page import ContactPage
|
||||
|
||||
|
||||
class TestContactForm:
|
||||
"""联系表单测试类"""
|
||||
|
||||
@pytest.mark.smoke
|
||||
@pytest.mark.form
|
||||
def test_contact_page_loads(self, contact_page: ContactPage):
|
||||
"""测试联系页面加载"""
|
||||
contact_page.navigate()
|
||||
contact_page.verify_page_loaded()
|
||||
|
||||
@pytest.mark.smoke
|
||||
def test_contact_page_title(self, contact_page: ContactPage):
|
||||
"""测试联系页面标题"""
|
||||
contact_page.navigate()
|
||||
contact_page.assert_title_contains("四川睿新致远")
|
||||
|
||||
@pytest.mark.regression
|
||||
@pytest.mark.form
|
||||
def test_contact_page_structure(self, contact_page: ContactPage):
|
||||
"""测试联系页面结构"""
|
||||
contact_page.navigate()
|
||||
contact_page.verify_page_structure()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_contact_page_company_info(self, contact_page: ContactPage):
|
||||
"""测试公司信息显示"""
|
||||
contact_page.navigate()
|
||||
contact_page.verify_company_info()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_contact_page_form_fields(self, contact_page: ContactPage):
|
||||
"""测试表单字段"""
|
||||
contact_page.navigate()
|
||||
contact_page.verify_form_fields()
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_validation_required_fields(self, contact_page: ContactPage):
|
||||
"""测试必填字段验证"""
|
||||
contact_page.navigate()
|
||||
contact_page.verify_form_validation()
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_submission_success(self, contact_page: ContactPage, test_data_generator):
|
||||
"""测试表单提交成功"""
|
||||
contact_page.navigate()
|
||||
|
||||
# 生成测试数据
|
||||
data = test_data_generator.generate_contact_form_data(use_valid=True)
|
||||
|
||||
# 填写并提交表单
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
|
||||
# 验证成功
|
||||
contact_page.verify_form_submission_success()
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_submission_with_minimal_data(self, contact_page: ContactPage):
|
||||
"""测试表单提交(最小数据)"""
|
||||
contact_page.navigate()
|
||||
|
||||
# 最小数据
|
||||
data = {
|
||||
"name": "测试用户",
|
||||
"email": "test@example.com",
|
||||
"subject": "测试主题",
|
||||
"message": "这是一条测试消息。"
|
||||
}
|
||||
|
||||
# 填写并提交表单
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
|
||||
# 验证成功
|
||||
contact_page.verify_form_submission_success()
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_with_empty_name(self, contact_page: ContactPage):
|
||||
"""测试姓名为空的表单验证"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = {
|
||||
"name": "",
|
||||
"email": "test@example.com",
|
||||
"subject": "测试主题",
|
||||
"message": "这是一条测试消息。"
|
||||
}
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
|
||||
# 点击提交按钮
|
||||
contact_page._click("form_submit_button")
|
||||
|
||||
# 应该显示验证错误
|
||||
try:
|
||||
contact_page.assert_element_visible("form_name_input:invalid", timeout=2000)
|
||||
except Exception:
|
||||
# 可能通过后端验证
|
||||
pass
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_with_invalid_email(self, contact_page: ContactPage):
|
||||
"""测试无效邮箱验证"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = {
|
||||
"name": "测试用户",
|
||||
"email": "invalid-email",
|
||||
"subject": "测试主题",
|
||||
"message": "这是一条测试消息。"
|
||||
}
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
|
||||
# 检查邮箱字段
|
||||
email_input = contact_page._find("form_email_input")
|
||||
validity = email_input.evaluate("""
|
||||
el => ({
|
||||
valid: el.validity.valid,
|
||||
typeMismatch: el.validity.typeMismatch
|
||||
})
|
||||
""")
|
||||
|
||||
# 验证邮箱格式
|
||||
assert not validity["valid"] or validity["typeMismatch"], \
|
||||
"无效邮箱应该被标记为无效"
|
||||
|
||||
@pytest.mark.form
|
||||
def test_form_submission_performance(self, contact_page: ContactPage, test_data_generator):
|
||||
"""测试表单提交性能"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = test_data_generator.generate_contact_form_data(use_valid=True)
|
||||
|
||||
result = contact_page.test_form_submission_performance(data, max_duration=5.0)
|
||||
|
||||
assert result["passed"], f"表单提交耗时 {result['duration']:.2f}s 超过5秒阈值"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_mobile_layout(self, contact_page: ContactPage):
|
||||
"""测试联系页面移动端布局"""
|
||||
contact_page.verify_responsive_layout(375)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_tablet_layout(self, contact_page: ContactPage):
|
||||
"""测试联系页面平板端布局"""
|
||||
contact_page.verify_responsive_layout(768)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_desktop_layout(self, contact_page: ContactPage):
|
||||
"""测试联系页面桌面端布局"""
|
||||
contact_page.verify_responsive_layout(1920)
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_extract_contact_details(self, contact_page: ContactPage):
|
||||
"""测试提取联系详情"""
|
||||
contact_page.navigate()
|
||||
details = contact_page.extract_contact_details()
|
||||
|
||||
assert "phone" in details or "email" in details or "address" in details
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_get_working_hours(self, contact_page: ContactPage):
|
||||
"""测试获取工作时间"""
|
||||
contact_page.navigate()
|
||||
hours = contact_page.get_working_hours()
|
||||
|
||||
assert isinstance(hours, dict)
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_form_reset_after_submission(self, contact_page: ContactPage, test_data_generator):
|
||||
"""测试提交后表单重置"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = test_data_generator.generate_contact_form_data(use_valid=True)
|
||||
|
||||
# 第一次提交
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
contact_page.verify_form_submission_success()
|
||||
|
||||
# 刷新页面后表单应该重置
|
||||
contact_page.reload()
|
||||
contact_page.assert_element_visible("contact_form", timeout=5000)
|
||||
|
||||
@pytest.mark.form
|
||||
@pytest.mark.performance
|
||||
def test_form_typing_performance(self, contact_page: ContactPage, test_data_generator):
|
||||
"""测试表单输入性能"""
|
||||
import time
|
||||
|
||||
contact_page.navigate()
|
||||
|
||||
data = test_data_generator.generate_contact_form_data(use_valid=True)
|
||||
|
||||
# 测量填充时间
|
||||
start_time = time.time()
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
|
||||
end_time = time.time()
|
||||
fill_time = (end_time - start_time) * 1000
|
||||
|
||||
# 填充时间应该在5秒内
|
||||
assert fill_time < 5000, f"表单填充时间 {fill_time:.2f}ms 超过5秒阈值"
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_form_with_special_characters(self, contact_page: ContactPage):
|
||||
"""测试包含特殊字符的表单提交"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = {
|
||||
"name": "测试用户-Name",
|
||||
"email": "test+special@example.com",
|
||||
"subject": "特殊字符测试: @#$%",
|
||||
"message": "这是一条包含特殊字符的消息!测试...end"
|
||||
}
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
|
||||
# 验证成功
|
||||
try:
|
||||
contact_page.verify_form_submission_success()
|
||||
except Exception:
|
||||
# 可能需要等待
|
||||
contact_page.page.wait_for_timeout(2000)
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_form_with_long_content(self, contact_page: ContactPage):
|
||||
"""测试长内容表单提交"""
|
||||
contact_page.navigate()
|
||||
|
||||
# 生成长内容
|
||||
long_message = "这是一条很长的消息。" * 50
|
||||
|
||||
data = {
|
||||
"name": "长内容测试用户",
|
||||
"email": "longtest@example.com",
|
||||
"subject": "长内容测试主题" * 10,
|
||||
"message": long_message
|
||||
}
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
|
||||
# 验证成功
|
||||
try:
|
||||
contact_page.verify_form_submission_success()
|
||||
except Exception:
|
||||
contact_page.page.wait_for_timeout(2000)
|
||||
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
首页测试模块
|
||||
测试首页的各项功能和特性
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Dict, Any
|
||||
|
||||
from pages.home_page import HomePage
|
||||
|
||||
|
||||
class TestHomePage:
|
||||
"""首页测试类"""
|
||||
|
||||
@pytest.mark.smoke
|
||||
@pytest.mark.navigation
|
||||
def test_home_page_loads_successfully(self, home_page: HomePage):
|
||||
"""测试首页正常加载"""
|
||||
home_page.navigate()
|
||||
home_page.verify_page_loaded()
|
||||
|
||||
@pytest.mark.smoke
|
||||
def test_home_page_title(self, home_page: HomePage):
|
||||
"""测试首页标题"""
|
||||
home_page.navigate()
|
||||
home_page.assert_title_contains("睿新致远")
|
||||
|
||||
@pytest.mark.smoke
|
||||
def test_home_page_url(self, home_page: HomePage):
|
||||
"""测试首页URL"""
|
||||
home_page.navigate()
|
||||
home_page.assert_url_equals(home_page._get_full_url("/"))
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_header(self, home_page: HomePage):
|
||||
"""测试页头"""
|
||||
home_page.navigate()
|
||||
home_page.verify_header()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_hero_section(self, home_page: HomePage):
|
||||
"""测试Hero区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_hero_section()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_services_section(self, home_page: HomePage):
|
||||
"""测试服务区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_services_section()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_products_section(self, home_page: HomePage):
|
||||
"""测试产品区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_products_section()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_news_section(self, home_page: HomePage):
|
||||
"""测试新闻区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_news_section()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_contact_section(self, home_page: HomePage):
|
||||
"""测试联系区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_contact_section()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_footer(self, home_page: HomePage):
|
||||
"""测试页脚"""
|
||||
home_page.navigate()
|
||||
home_page.verify_footer()
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_home_page_all_sections(self, home_page: HomePage):
|
||||
"""测试所有区域"""
|
||||
home_page.navigate()
|
||||
home_page.verify_all_sections()
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_scroll_to_about_section(self, home_page: HomePage):
|
||||
"""测试滚动到关于区域"""
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("about")
|
||||
home_page.assert_element_visible("#about", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_scroll_to_services_section(self, home_page: HomePage):
|
||||
"""测试滚动到服务区域"""
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("services")
|
||||
home_page.assert_element_visible("#services", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_scroll_to_products_section(self, home_page: HomePage):
|
||||
"""测试滚动到产品区域"""
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("products")
|
||||
home_page.assert_element_visible("#products", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_scroll_to_news_section(self, home_page: HomePage):
|
||||
"""测试滚动到新闻区域"""
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("news")
|
||||
home_page.assert_element_visible("#news", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_scroll_to_contact_section(self, home_page: HomePage):
|
||||
"""测试滚动到联系区域"""
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("contact")
|
||||
home_page.assert_element_visible("#contact", timeout=5000)
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_home_page_performance(self, home_page: HomePage):
|
||||
"""测试首页性能"""
|
||||
home_page.navigate()
|
||||
performance = home_page.verify_page_performance()
|
||||
|
||||
# 验证关键性能指标
|
||||
assert performance.get("pageLoadTime", 0) < 5000, "页面加载时间超过5秒"
|
||||
assert performance.get("domContentLoaded", 0) < 3000, "DOM内容加载时间超过3秒"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_home_page_load_time(self, home_page: HomePage):
|
||||
"""测试首页加载时间"""
|
||||
import time
|
||||
|
||||
home_page.navigate()
|
||||
|
||||
start_time = time.time()
|
||||
home_page.wait_for_load()
|
||||
end_time = time.time()
|
||||
|
||||
load_time = (end_time - start_time) * 1000 # 转换为毫秒
|
||||
|
||||
# 断言加载时间在阈值内
|
||||
assert load_time < 5000, f"首页加载时间 {load_time:.2f}ms 超过5秒阈值"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_home_page_mobile_layout(self, home_page: HomePage):
|
||||
"""测试移动端布局"""
|
||||
home_page.verify_responsive_design(375, 667)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_home_page_tablet_layout(self, home_page: HomePage):
|
||||
"""测试平板端布局"""
|
||||
home_page.verify_responsive_design(768, 1024)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_home_page_desktop_layout(self, home_page: HomePage):
|
||||
"""测试桌面端布局"""
|
||||
home_page.verify_responsive_design(1920, 1080)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_home_page_wide_layout(self, home_page: HomePage):
|
||||
"""测试宽屏布局"""
|
||||
home_page.verify_responsive_design(2560, 1440)
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_get_company_info(self, home_page: HomePage):
|
||||
"""测试获取公司信息"""
|
||||
home_page.navigate()
|
||||
info = home_page.get_company_info()
|
||||
|
||||
assert "name" in info
|
||||
assert "slogan" in info
|
||||
assert "description" in info
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_get_statistics(self, home_page: HomePage):
|
||||
"""测试获取统计数据"""
|
||||
home_page.navigate()
|
||||
stats = home_page.get_statistics()
|
||||
|
||||
assert "customers" in stats
|
||||
assert "cases" in stats
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_get_featured_services(self, home_page: HomePage):
|
||||
"""测试获取服务列表"""
|
||||
home_page.navigate()
|
||||
services = home_page.get_featured_services()
|
||||
|
||||
assert isinstance(services, list)
|
||||
if len(services) > 0:
|
||||
assert "title" in services[0]
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_get_latest_news(self, home_page: HomePage):
|
||||
"""测试获取最新新闻"""
|
||||
home_page.navigate()
|
||||
news = home_page.get_latest_news()
|
||||
|
||||
assert isinstance(news, list)
|
||||
if len(news) > 0:
|
||||
assert "title" in news[0]
|
||||
|
||||
@pytest.mark.regression
|
||||
def test_page_refresh(self, home_page: HomePage):
|
||||
"""测试页面刷新"""
|
||||
home_page.navigate()
|
||||
home_page.reload()
|
||||
home_page.verify_page_loaded()
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_navigation_links_count(self, home_page: HomePage):
|
||||
"""测试导航链接数量"""
|
||||
home_page.navigate()
|
||||
|
||||
nav_links = home_page._find_all("nav a")
|
||||
|
||||
# 应该有6个导航链接:首页、关于我们、核心业务、产品服务、新闻动态、联系我们
|
||||
assert len(nav_links) >= 5, f"导航链接数量不足,当前{len(nav_links)}个"
|
||||
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
导航测试模块
|
||||
测试网站导航功能
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Dict, Any
|
||||
|
||||
from pages.home_page import HomePage
|
||||
|
||||
|
||||
class TestNavigation:
|
||||
"""导航测试类"""
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.smoke
|
||||
def test_navigate_to_home(self, home_page: HomePage):
|
||||
"""测试导航到首页"""
|
||||
home_page.navigate()
|
||||
home_page.assert_url_equals(home_page._get_full_url("/"))
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.smoke
|
||||
def test_navigate_to_contact_page(self, home_page: HomePage, contact_page):
|
||||
"""测试导航到联系页面"""
|
||||
contact_page.navigate()
|
||||
contact_page.assert_url_equals(
|
||||
home_page._get_full_url("/contact")
|
||||
)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_click_navigation_to_about(self, home_page: HomePage):
|
||||
"""测试点击导航到关于区域"""
|
||||
home_page.navigate()
|
||||
home_page.click_navigation_link("about")
|
||||
home_page.assert_element_visible("#about", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_click_navigation_to_services(self, home_page: HomePage):
|
||||
"""测试点击导航到服务区域"""
|
||||
home_page.navigate()
|
||||
home_page.click_navigation_link("services")
|
||||
home_page.assert_element_visible("#services", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_click_navigation_to_products(self, home_page: HomePage):
|
||||
"""测试点击导航到产品区域"""
|
||||
home_page.navigate()
|
||||
home_page.click_navigation_link("products")
|
||||
home_page.assert_element_visible("#products", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_click_navigation_to_news(self, home_page: HomePage):
|
||||
"""测试点击导航到新闻区域"""
|
||||
home_page.navigate()
|
||||
home_page.click_navigation_link("news")
|
||||
home_page.assert_element_visible("#news", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.interactive
|
||||
def test_click_navigation_to_contact(self, home_page: HomePage):
|
||||
"""测试点击导航到联系区域"""
|
||||
home_page.navigate()
|
||||
home_page.click_navigation_link("contact")
|
||||
home_page.assert_element_visible("#contact", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_smooth_scroll_to_section(self, home_page: HomePage):
|
||||
"""测试平滑滚动到区域"""
|
||||
home_page.navigate()
|
||||
|
||||
# 先滚动到页面底部
|
||||
home_page.scroll_to_bottom()
|
||||
|
||||
# 然后滚动到顶部
|
||||
home_page.scroll_to_top()
|
||||
|
||||
# 验证
|
||||
home_page.assert_element_visible("header")
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_scroll_to_each_section(self, home_page: HomePage):
|
||||
"""测试滚动到每个区域"""
|
||||
home_page.navigate()
|
||||
|
||||
sections = ["home", "about", "services", "products", "news", "contact"]
|
||||
|
||||
for section in sections:
|
||||
home_page.scroll_to_section(section)
|
||||
home_page.assert_element_visible(f"#{section}", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_page_back(self, home_page: HomePage, contact_page):
|
||||
"""测试返回上一页"""
|
||||
# 先访问联系页面
|
||||
contact_page.navigate()
|
||||
contact_page.assert_url_equals(home_page._get_full_url("/contact"))
|
||||
|
||||
# 返回首页
|
||||
home_page.go_back()
|
||||
|
||||
# 验证
|
||||
home_page.assert_url_equals(home_page._get_full_url("/"))
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_page_forward(self, home_page: HomePage, contact_page):
|
||||
"""测试前进到下一页"""
|
||||
# 访问首页
|
||||
home_page.navigate()
|
||||
|
||||
# 后退(此时没有上一页,应该保持在首页)
|
||||
home_page.go_back()
|
||||
|
||||
# 前进(此时没有下一页,应该保持在首页)
|
||||
home_page.go_forward()
|
||||
|
||||
home_page.assert_url_equals(home_page._get_full_url("/"))
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_page_reload(self, home_page: HomePage):
|
||||
"""测试页面刷新"""
|
||||
home_page.navigate()
|
||||
home_page.reload()
|
||||
home_page.verify_page_loaded()
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_navigation_link_count(self, home_page: HomePage):
|
||||
"""测试导航链接数量"""
|
||||
home_page.navigate()
|
||||
|
||||
nav_links = home_page._find_all("nav a")
|
||||
|
||||
# 应该有6个导航链接
|
||||
assert len(nav_links) >= 5, f"导航链接数量不足,当前{len(nav_links)}个"
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_navigation_link_text(self, home_page: HomePage):
|
||||
"""测试导航链接文本"""
|
||||
home_page.navigate()
|
||||
|
||||
expected_links = ["首页", "关于我们", "核心业务", "产品服务", "新闻动态", "联系我们"]
|
||||
|
||||
for link_text in expected_links:
|
||||
link = home_page.page.locator(f"nav a:has-text('{link_text}')")
|
||||
assert link.count() > 0, f"未找到导航链接: {link_text}"
|
||||
|
||||
@pytest.mark.navigation
|
||||
@pytest.mark.responsive
|
||||
def test_navigation_mobile(self, home_page: HomePage):
|
||||
"""测试移动端导航"""
|
||||
# 设置移动端视口
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 移动端应该显示汉堡菜单
|
||||
menu_button = home_page.page.locator("button:has-text('菜单'), .mobile-menu")
|
||||
|
||||
if menu_button.count() > 0:
|
||||
home_page.logger.info("移动端显示汉堡菜单")
|
||||
else:
|
||||
home_page.logger.info("移动端导航可能已内联显示")
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_url_hash_navigation(self, home_page: HomePage):
|
||||
"""测试URL哈希导航"""
|
||||
home_page.navigate()
|
||||
|
||||
# 直接访问带哈希的URL
|
||||
home_page.navigate(path="/#about")
|
||||
home_page.wait_for_load()
|
||||
|
||||
# 验证滚动到指定区域
|
||||
home_page.assert_element_visible("#about", timeout=5000)
|
||||
|
||||
@pytest.mark.navigation
|
||||
def test_browser_back_button(self, home_page: HomePage, contact_page):
|
||||
"""测试浏览器后退按钮"""
|
||||
# 访问首页
|
||||
home_page.navigate()
|
||||
|
||||
# 访问联系页面
|
||||
contact_page.navigate()
|
||||
|
||||
# 使用浏览器后退
|
||||
home_page.page.go_back()
|
||||
|
||||
# 验证返回首页
|
||||
home_page.assert_url_equals(home_page._get_full_url("/"))
|
||||
|
||||
@pytest.mark.interactive
|
||||
def test_cta_button_navigation(self, home_page: HomePage):
|
||||
"""测试CTA按钮导航"""
|
||||
home_page.navigate()
|
||||
|
||||
# 查找CTA按钮(如果有)
|
||||
cta_button = home_page.page.locator(
|
||||
"a[href*='contact'], a.cta, a.button:has-text('联系')"
|
||||
)
|
||||
|
||||
if cta_button.count() > 0:
|
||||
cta_button.first.click()
|
||||
home_page.wait_for_load()
|
||||
|
||||
# 应该导航到联系区域
|
||||
home_page.assert_element_visible("#contact", timeout=5000)
|
||||
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
性能测试模块
|
||||
测试网站性能指标
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from pages.home_page import HomePage
|
||||
from pages.contact_page import ContactPage
|
||||
from config.settings import get_settings
|
||||
|
||||
|
||||
class TestPerformance:
|
||||
"""性能测试类"""
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.smoke
|
||||
def test_home_page_load_time(self, home_page: HomePage):
|
||||
"""测试首页加载时间"""
|
||||
home_page.navigate()
|
||||
|
||||
start_time = time.time()
|
||||
home_page.wait_for_load()
|
||||
end_time = time.time()
|
||||
|
||||
load_time = (end_time - start_time) * 1000 # 毫秒
|
||||
|
||||
# 阈值:5秒
|
||||
assert load_time < 5000, f"首页加载时间 {load_time:.2f}ms 超过5秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.smoke
|
||||
def test_contact_page_load_time(self, contact_page: ContactPage):
|
||||
"""测试联系页面加载时间"""
|
||||
contact_page.navigate()
|
||||
|
||||
start_time = time.time()
|
||||
contact_page.wait_for_load()
|
||||
end_time = time.time()
|
||||
|
||||
load_time = (end_time - start_time) * 1000
|
||||
|
||||
# 阈值:5秒
|
||||
assert load_time < 5000, f"联系页面加载时间 {load_time:.2f}ms 超过5秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_dom_content_loaded_time(self, home_page: HomePage):
|
||||
"""测试DOM内容加载时间"""
|
||||
home_page.navigate()
|
||||
|
||||
performance_data = home_page.execute_js("""
|
||||
() => {
|
||||
const timing = performance.timing;
|
||||
return {
|
||||
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
|
||||
domInteractive: timing.domInteractive - timing.domLoading
|
||||
};
|
||||
}
|
||||
""")
|
||||
|
||||
dom_loaded = performance_data.get("domContentLoaded", 0)
|
||||
assert dom_loaded < 3000, f"DOM内容加载时间 {dom_loaded}ms 超过3秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_first_paint_time(self, home_page: HomePage):
|
||||
"""测试首次绘制时间"""
|
||||
home_page.navigate()
|
||||
|
||||
first_paint = home_page.execute_js("""
|
||||
() => {
|
||||
const entries = performance.getEntriesByType('paint');
|
||||
const firstPaint = entries.find(e => e.name === 'first-paint');
|
||||
return firstPaint ? firstPaint.startTime : 0;
|
||||
}
|
||||
""")
|
||||
|
||||
if first_paint:
|
||||
assert first_paint < 2000, f"首次绘制时间 {first_paint:.2f}ms 超过2秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_first_contentful_paint(self, home_page: HomePage):
|
||||
"""测试首次内容绘制(FCP)"""
|
||||
home_page.navigate()
|
||||
|
||||
fcp = home_page.execute_js("""
|
||||
() => {
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
return navigation ? navigation.firstContentfulPaint : 0;
|
||||
}
|
||||
""")
|
||||
|
||||
if fcp:
|
||||
# 阈值:1.5秒
|
||||
assert fcp < 1500, f"首次内容绘制时间 {fcp:.2f}ms 超过1.5秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_largest_contentful_paint(self, home_page: HomePage):
|
||||
"""测试最大内容绘制(LCP)"""
|
||||
home_page.navigate()
|
||||
|
||||
lcp = home_page.execute_js("""
|
||||
() => {
|
||||
try {
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
return navigation ? navigation.largestContentfulPaint : 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
if lcp:
|
||||
# 阈值:2.5秒
|
||||
assert lcp < 2500, f"最大内容绘制时间 {lcp:.2f}ms 超过2.5秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_time_to_interactive(self, home_page: HomePage):
|
||||
"""测试可交互时间(TTI)"""
|
||||
home_page.navigate()
|
||||
|
||||
tti = home_page.execute_js("""
|
||||
() => {
|
||||
try {
|
||||
const navigation = performance.getEntriesByType('navigation')[0];
|
||||
return navigation ? navigation.interactive : 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
if tti:
|
||||
# 阈值:3秒
|
||||
assert tti < 3000, f"可交互时间 {tti:.2f}ms 超过3秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_page_load_performance_metrics(self, home_page: HomePage):
|
||||
"""测试页面加载性能指标"""
|
||||
home_page.navigate()
|
||||
performance = home_page.verify_page_performance()
|
||||
|
||||
# 验证关键指标
|
||||
if performance.get("pageLoadTime"):
|
||||
assert performance["pageLoadTime"] < 5000
|
||||
if performance.get("domContentLoaded"):
|
||||
assert performance["domContentLoaded"] < 3000
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_network_timing(self, home_page: HomePage):
|
||||
"""测试网络时序"""
|
||||
home_page.navigate()
|
||||
|
||||
timing = home_page.execute_js("""
|
||||
() => {
|
||||
const timing = performance.timing;
|
||||
return {
|
||||
dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
|
||||
tcpConnection: timing.connectEnd - timing.connectStart,
|
||||
serverResponse: timing.responseEnd - timing.requestStart,
|
||||
domProcessing: timing.domLoading - timing.responseEnd
|
||||
};
|
||||
}
|
||||
""")
|
||||
|
||||
# DNS查询时间
|
||||
assert timing.get("dnsLookup", 0) < 500, \
|
||||
f"DNS查询时间 {timing.get('dnsLookup')}ms 超过500ms阈值"
|
||||
|
||||
# TCP连接时间
|
||||
assert timing.get("tcpConnection", 0) < 500, \
|
||||
f"TCP连接时间 {timing.get('tcpConnection')}ms 超过500ms阈值"
|
||||
|
||||
# 服务器响应时间
|
||||
assert timing.get("serverResponse", 0) < 1000, \
|
||||
f"服务器响应时间 {timing.get('serverResponse')}ms 超过1秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_resource_timing(self, home_page: HomePage):
|
||||
"""测试资源加载时序"""
|
||||
home_page.navigate()
|
||||
|
||||
resources = home_page.execute_js("""
|
||||
() => {
|
||||
const entries = performance.getEntriesByType('resource');
|
||||
const scripts = entries.filter(e => e.initiatorType === 'script');
|
||||
const styles = entries.filter(e => e.initiatorType === 'css');
|
||||
|
||||
return {
|
||||
totalResources: entries.length,
|
||||
scriptCount: scripts.length,
|
||||
styleCount: styles.length,
|
||||
totalDuration: entries.reduce((sum, e) => sum + e.duration, 0)
|
||||
};
|
||||
}
|
||||
""")
|
||||
|
||||
assert resources.get("totalResources", 0) > 0, "未检测到资源加载"
|
||||
|
||||
home_page.logger.info(
|
||||
f"资源统计: 共{resources.get('totalResources')}个资源,"
|
||||
f"脚本{resources.get('scriptCount')}个,"
|
||||
f"样式{resources.get('styleCount')}个"
|
||||
)
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_form_submission_time(self, contact_page: ContactPage, test_data_generator):
|
||||
"""测试表单提交时间"""
|
||||
contact_page.navigate()
|
||||
|
||||
data = test_data_generator.generate_contact_form_data(use_valid=True)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
contact_page.fill_contact_form(data)
|
||||
contact_page.submit_form()
|
||||
|
||||
try:
|
||||
contact_page.verify_form_submission_success()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
end_time = time.time()
|
||||
duration = (end_time - start_time) * 1000
|
||||
|
||||
# 阈值:5秒
|
||||
assert duration < 5000, f"表单提交耗时 {duration:.2f}ms 超过5秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_scroll_performance(self, home_page: HomePage):
|
||||
"""测试滚动性能"""
|
||||
home_page.navigate()
|
||||
|
||||
# 执行多次滚动
|
||||
scroll_times = []
|
||||
for i in range(5):
|
||||
start_time = time.time()
|
||||
home_page.scroll_to_bottom()
|
||||
home_page.scroll_to_top()
|
||||
end_time = time.time()
|
||||
scroll_times.append((end_time - start_time) * 1000)
|
||||
|
||||
avg_scroll_time = sum(scroll_times) / len(scroll_times)
|
||||
|
||||
# 平均滚动时间应该在1秒内
|
||||
assert avg_scroll_time < 1000, f"平均滚动时间 {avg_scroll_time:.2f}ms 超过1秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_element_visibility_performance(self, home_page: HomePage):
|
||||
"""测试元素可见性检查性能"""
|
||||
home_page.navigate()
|
||||
|
||||
elements = [
|
||||
"header",
|
||||
"#home",
|
||||
"#about",
|
||||
"#services",
|
||||
"#products",
|
||||
"#news",
|
||||
"#contact",
|
||||
"footer"
|
||||
]
|
||||
|
||||
check_times = []
|
||||
for element in elements:
|
||||
start_time = time.time()
|
||||
try:
|
||||
home_page._is_visible(element)
|
||||
except Exception:
|
||||
pass
|
||||
end_time = time.time()
|
||||
check_times.append((end_time - start_time) * 1000)
|
||||
|
||||
avg_check_time = sum(check_times) / len(check_times)
|
||||
|
||||
# 单个元素检查时间应该在500ms内
|
||||
assert avg_check_time < 500, f"平均元素检查时间 {avg_check_time:.2f}ms 超过500ms阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
def test_navigation_performance(self, home_page: HomePage, contact_page: ContactPage):
|
||||
"""测试导航性能"""
|
||||
# 测量导航到联系页面的时间
|
||||
start_time = time.time()
|
||||
contact_page.navigate()
|
||||
contact_page.wait_for_load()
|
||||
end_time = time.time()
|
||||
|
||||
nav_time = (end_time - start_time) * 1000
|
||||
|
||||
# 阈值:3秒
|
||||
assert nav_time < 3000, f"导航时间 {nav_time:.2f}ms 超过3秒阈值"
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.responsive
|
||||
def test_performance_across_viewports(self, home_page: HomePage):
|
||||
"""测试不同视口下的性能"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
results = []
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
|
||||
start_time = time.time()
|
||||
home_page.navigate()
|
||||
home_page.wait_for_load()
|
||||
end_time = time.time()
|
||||
|
||||
load_time = (end_time - start_time) * 1000
|
||||
results.append((name, load_time))
|
||||
|
||||
home_page.logger.info(f"{name} ({width}x{height}): {load_time:.2f}ms")
|
||||
|
||||
# 验证所有视口加载时间在阈值内
|
||||
for name, load_time in results:
|
||||
assert load_time < 5000, f"{name}加载时间 {load_time:.2f}ms 超过5秒阈值"
|
||||
@@ -0,0 +1,330 @@
|
||||
"""
|
||||
响应式设计测试模块
|
||||
测试网站在不同屏幕尺寸下的响应式表现
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Dict, Any
|
||||
|
||||
from pages.home_page import HomePage
|
||||
from pages.contact_page import ContactPage
|
||||
|
||||
|
||||
class TestResponsive:
|
||||
"""响应式设计测试类"""
|
||||
|
||||
@pytest.mark.responsive
|
||||
@pytest.mark.smoke
|
||||
def test_homepage_mobile_375(self, home_page: HomePage):
|
||||
"""测试首页在iPhone SE尺寸下的响应式表现"""
|
||||
home_page.verify_responsive_design(375, 667)
|
||||
|
||||
@pytest.mark.responsive
|
||||
@pytest.mark.smoke
|
||||
def test_homepage_mobile_414(self, home_page: HomePage):
|
||||
"""测试首页在iPhone 8 Plus尺寸下的响应式表现"""
|
||||
home_page.verify_responsive_design(414, 896)
|
||||
|
||||
@pytest.mark.responsive
|
||||
@pytest.mark.smoke
|
||||
def test_homepage_tablet_768(self, home_page: HomePage):
|
||||
"""测试首页在iPad尺寸下的响应式表现"""
|
||||
home_page.verify_responsive_design(768, 1024)
|
||||
|
||||
@pytest.mark.responsive
|
||||
@pytest.mark.smoke
|
||||
def test_homepage_desktop_1920(self, home_page: HomePage):
|
||||
"""测试首页在桌面尺寸下的响应式表现"""
|
||||
home_page.verify_responsive_design(1920, 1080)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_mobile_375(self, contact_page: ContactPage):
|
||||
"""测试联系页面在移动端的响应式表现"""
|
||||
contact_page.verify_responsive_layout(375)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_tablet_768(self, contact_page: ContactPage):
|
||||
"""测试联系页面在平板端的响应式表现"""
|
||||
contact_page.verify_responsive_layout(768)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_page_desktop_1920(self, contact_page: ContactPage):
|
||||
"""测试联系页面在桌面端的响应式表现"""
|
||||
contact_page.verify_responsive_layout(1920)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_header_responsive_mobile(self, home_page: HomePage):
|
||||
"""测试页头在移动端的响应式表现"""
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 验证页头可见
|
||||
home_page.assert_element_visible("header", timeout=5000)
|
||||
|
||||
# 移动端应该显示汉堡菜单
|
||||
menu_button = home_page.page.locator(
|
||||
"button:has-text('菜单'), .mobile-menu, .menu-toggle, button[aria-label*='menu']"
|
||||
)
|
||||
|
||||
if menu_button.count() > 0:
|
||||
home_page.logger.info("✅ 移动端页头包含汉堡菜单")
|
||||
else:
|
||||
home_page.logger.info("ℹ️ 移动端页头可能内联显示所有链接")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_header_responsive_desktop(self, home_page: HomePage):
|
||||
"""测试页头在桌面端的响应式表现"""
|
||||
home_page.page.set_viewport_size({"width": 1920, "height": 1080})
|
||||
home_page.navigate()
|
||||
|
||||
# 验证页头可见
|
||||
home_page.assert_element_visible("header", timeout=5000)
|
||||
|
||||
# 桌面端应该显示完整导航
|
||||
nav_links = home_page._find_all("nav a")
|
||||
assert len(nav_links) >= 5, f"桌面端导航链接不足,当前{len(nav_links)}个"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_navigation_responsive_mobile(self, home_page: HomePage):
|
||||
"""测试导航在移动端的响应式表现"""
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 检查导航是否可访问 - 移动端可能隐藏导航或使用汉堡菜单
|
||||
nav_visible = home_page._is_visible("nav")
|
||||
mobile_menu_visible = home_page._is_visible(".mobile-menu, .menu-toggle, button[aria-label*='menu']")
|
||||
header_visible = home_page._is_visible("header")
|
||||
|
||||
# 只要页头可见,就认为导航可访问(导航可能在页头内)
|
||||
assert nav_visible or mobile_menu_visible or header_visible, "移动端导航不可访问"
|
||||
home_page.logger.info("✅ 移动端导航可访问")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_hero_section_responsive(self, home_page: HomePage):
|
||||
"""测试Hero区域在不同尺寸下的表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
|
||||
# 验证Hero区域可见
|
||||
hero_visible = home_page._is_visible("#home, .hero-section")
|
||||
|
||||
if hero_visible:
|
||||
home_page.logger.info(f"✅ {name} Hero区域正常显示")
|
||||
else:
|
||||
home_page.logger.warning(f"⚠️ {name} Hero区域可能需要滚动才能显示")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_services_grid_responsive(self, home_page: HomePage):
|
||||
"""测试服务卡片网格在不同尺寸下的响应式表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("services")
|
||||
|
||||
# 检查服务区域可见
|
||||
home_page.assert_element_visible("#services", timeout=5000)
|
||||
|
||||
home_page.logger.info(f"✅ {name} 服务区域正常显示")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_products_grid_responsive(self, home_page: HomePage):
|
||||
"""测试产品卡片网格在不同尺寸下的响应式表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("products")
|
||||
|
||||
# 检查产品区域可见
|
||||
home_page.assert_element_visible("#products", timeout=5000)
|
||||
|
||||
home_page.logger.info(f"✅ {name} 产品区域正常显示")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_news_list_responsive(self, home_page: HomePage):
|
||||
"""测试新闻列表在不同尺寸下的响应式表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("news")
|
||||
|
||||
# 检查新闻区域可见
|
||||
home_page.assert_element_visible("#news", timeout=5000)
|
||||
|
||||
home_page.logger.info(f"✅ {name} 新闻区域正常显示")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_contact_form_responsive(self, home_page: HomePage):
|
||||
"""测试联系表单在不同尺寸下的响应式表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
home_page.scroll_to_section("contact")
|
||||
|
||||
# 检查表单可见
|
||||
form_visible = home_page._is_visible("form")
|
||||
|
||||
if form_visible:
|
||||
home_page.logger.info(f"✅ {name} 联系表单正常显示")
|
||||
else:
|
||||
home_page.logger.warning(f"⚠️ {name} 联系表单不可见")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_footer_responsive(self, home_page: HomePage):
|
||||
"""测试页脚在不同尺寸下的响应式表现"""
|
||||
viewports = [
|
||||
(375, 667, "移动端"),
|
||||
(768, 1024, "平板端"),
|
||||
(1920, 1080, "桌面端")
|
||||
]
|
||||
|
||||
for width, height, name in viewports:
|
||||
home_page.page.set_viewport_size({"width": width, "height": height})
|
||||
home_page.navigate()
|
||||
|
||||
# 检查页脚可见
|
||||
home_page.assert_element_visible("footer", timeout=5000)
|
||||
|
||||
home_page.logger.info(f"✅ {name} 页脚正常显示")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_element_stacking_mobile(self, home_page: HomePage):
|
||||
"""测试移动端元素堆叠"""
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 滚动检查各个区域
|
||||
sections = ["#home", "#about", "#services", "#products", "#news", "#contact"]
|
||||
|
||||
visible_sections = 0
|
||||
for section in sections:
|
||||
if home_page._is_visible(section):
|
||||
visible_sections += 1
|
||||
|
||||
# 移动端应该显示至少1个区域
|
||||
assert visible_sections >= 1, f"移动端可见区域不足,当前{visible_sections}个"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_touch_target_size_mobile(self, home_page: HomePage):
|
||||
"""测试移动端触摸目标大小"""
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 检查按钮和链接的大小
|
||||
buttons = home_page._find_all("button, a.button, .btn")
|
||||
|
||||
for button in buttons[:5]: # 只检查前5个
|
||||
if button.count() > 0:
|
||||
box = button.first.bounding_box()
|
||||
if box:
|
||||
# 触摸目标应该至少44x44像素
|
||||
assert box["width"] >= 24, f"按钮宽度 {box['width']}px 可能太小"
|
||||
assert box["height"] >= 24, f"按钮高度 {box['height']}px 可能太小"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_text_readability_mobile(self, home_page: HomePage):
|
||||
"""测试移动端文本可读性"""
|
||||
home_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
home_page.navigate()
|
||||
|
||||
# 检查段落文本
|
||||
paragraphs = home_page._find_all("p")
|
||||
|
||||
for para in paragraphs[:3]: # 只检查前3个
|
||||
if para.count() > 0:
|
||||
font_size = para.evaluate("el => getComputedStyle(el).fontSize")
|
||||
# 字体大小应该至少12px
|
||||
font_size_value = float(font_size.replace("px", ""))
|
||||
assert font_size_value >= 12, \
|
||||
f"段落字体大小 {font_size} 可能影响可读性"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_form_inputs_mobile(self, contact_page: ContactPage):
|
||||
"""测试移动端表单输入"""
|
||||
contact_page.page.set_viewport_size({"width": 375, "height": 667})
|
||||
contact_page.navigate()
|
||||
|
||||
# 检查表单输入框
|
||||
inputs = contact_page._find_all("input, textarea")
|
||||
|
||||
for inp in inputs:
|
||||
if inp.count() > 0:
|
||||
box = inp.first.bounding_box()
|
||||
if box:
|
||||
# 输入框高度应该至少40px
|
||||
assert box["height"] >= 32, \
|
||||
f"输入框高度 {box['height']}px 可能太小不便触摸"
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_landscape_orientation(self, home_page: HomePage):
|
||||
"""测试横屏模式"""
|
||||
home_page.page.set_viewport_size({"width": 667, "height": 375})
|
||||
home_page.navigate()
|
||||
|
||||
# 验证基本元素可见
|
||||
home_page.assert_element_visible("header", timeout=5000)
|
||||
home_page.assert_element_visible("main", timeout=5000)
|
||||
home_page.assert_element_visible("footer", timeout=5000)
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_high_dpi_display(self, home_page: HomePage):
|
||||
"""测试高DPI显示器"""
|
||||
# 设置视口大小(Playwright会自动处理高DPI显示)
|
||||
home_page.page.set_viewport_size({"width": 1920, "height": 1080})
|
||||
|
||||
home_page.navigate()
|
||||
|
||||
# 验证页面正常显示
|
||||
home_page.assert_element_visible("header", timeout=5000)
|
||||
home_page.logger.info("✅ 高DPI显示器测试通过")
|
||||
|
||||
@pytest.mark.responsive
|
||||
def test_print_styles(self, home_page: HomePage):
|
||||
"""测试打印样式"""
|
||||
home_page.navigate()
|
||||
|
||||
# 模拟打印样式
|
||||
is_print_media = home_page.execute_js("""
|
||||
() => window.matchMedia('print').matches
|
||||
""")
|
||||
|
||||
# 设置为打印模式
|
||||
home_page.execute_js("""
|
||||
() => {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = '@media print { body { font-size: 12pt; } }';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
""")
|
||||
|
||||
home_page.logger.info("✅ 打印样式应用完成")
|
||||
Reference in New Issue
Block a user