本文目录导读:

我来详细介绍如何使用 Selenium 进行 Python 端到端测试。
环境准备
安装依赖
pip install selenium pip install webdriver-manager # 自动管理浏览器驱动
基础框架选择(推荐)
pip install pytest pip install pytest-html # HTML测试报告 pip install allure-pytest # Allure报告(可选)
完整的端到端测试示例
示例:测试登录功能
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
class TestLoginE2E:
"""端到端测试:登录功能"""
@pytest.fixture(scope="class")
def setup(self):
"""测试夹具:初始化浏览器"""
# 设置Chrome选项
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless') # 无头模式
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--window-size=1920,1080')
# 初始化驱动
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.implicitly_wait(10) # 隐式等待
yield driver
# 测试结束后清理
driver.quit()
def test_successful_login(self, setup):
"""测试成功登录"""
driver = setup
try:
# 1. 打开登录页面
driver.get("https://example.com/login")
# 2. 输入用户名
username_input = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "username"))
)
username_input.send_keys("test_user@example.com")
# 3. 输入密码
password_input = driver.find_element(By.ID, "password")
password_input.send_keys("TestPassword123!")
# 4. 点击登录按钮
login_button = driver.find_element(By.XPATH, "//button[@type='submit']")
login_button.click()
# 5. 验证登录成功 - 检查是否跳转到首页
WebDriverWait(driver, 10).until(
EC.url_contains("dashboard")
)
# 6. 验证用户信息
user_menu = driver.find_element(By.CLASS_NAME, "user-menu")
assert user_menu.is_displayed(), "用户菜单未显示"
# 7. 获取页面标题
assert "Dashboard" in driver.title
print("✓ 登录测试通过")
except Exception as e:
# 截图保存失败现场
driver.save_screenshot("login_failure.png")
raise e
def test_invalid_login(self, setup):
"""测试无效登录"""
driver = setup
# 1. 打开登录页面
driver.get("https://example.com/login")
# 2. 输入错误的凭据
username_input = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "username"))
)
username_input.send_keys("wrong@email.com")
password_input = driver.find_element(By.ID, "password")
password_input.send_keys("WrongPassword")
# 3. 点击登录
login_button = driver.find_element(By.XPATH, "//button[@type='submit']")
login_button.click()
# 4. 验证错误消息
error_message = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, "error-message"))
)
assert error_message.is_displayed(), "错误消息未显示"
assert "Invalid credentials" in error_message.text
# 5. 确认仍在登录页面
assert "login" in driver.current_url.lower()
print("✓ 无效登录测试通过")
class TestUserFlowE2E:
"""端到端测试:用户完整流程"""
@pytest.fixture(scope="class")
def authenticated_session(self):
"""预登录会话"""
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),
options=chrome_options)
driver.implicitly_wait(10)
# 执行登录
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys("test@example.com")
driver.find_element(By.ID, "password").send_keys("Test123!")
driver.find_element(By.XPATH, "//button[@type='submit']").click()
# 等待登录完成
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "user-menu"))
)
yield driver
driver.quit()
def test_complete_order_flow(self, authenticated_session):
"""测试完整订单流程"""
driver = authenticated_session
# 1. 搜索商品
search_input = driver.find_element(By.NAME, "search")
search_input.send_keys("laptop\n")
# 2. 选择第一个商品
first_product = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".product-card:first-child a"))
)
first_product.click()
# 3. 添加到购物车
add_to_cart = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "add-to-cart"))
)
add_to_cart.click()
# 4. 验证购物车更新
cart_count = driver.find_element(By.CLASS_NAME, "cart-count")
assert cart_count.text == "1", "购物车数量不正确"
# 5. 进入购物车
driver.find_element(By.CLASS_NAME, "cart-icon").click()
# 6. 结算
checkout_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "checkout"))
)
checkout_button.click()
# 7. 填写订单信息
driver.find_element(By.ID, "address").send_keys("123 Test St")
driver.find_element(By.ID, "city").send_keys("Test City")
driver.find_element(By.ID, "zip").send_keys("12345")
# 8. 提交订单
driver.find_element(By.ID, "submit-order").click()
# 9. 验证订单确认
confirmation = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, "order-confirmation"))
)
assert confirmation.is_displayed(), "订单确认页面未显示"
print("✓ 订单流程测试通过")
def test_api_response_verification(self, authenticated_session):
"""验证API响应(通过浏览器)"""
driver = authenticated_session
# 监控网络请求
logs = driver.execute_script("""
var performance = window.performance ||
window.webkitPerformance ||
window.mozPerformance ||
window.msPerformance;
var entries = performance.getEntriesByType('resource');
var apiCalls = entries.filter(entry =>
entry.name.includes('/api/')
);
return apiCalls.map(call => ({
url: call.name,
duration: call.duration,
size: call.transferSize
}));
""")
# 验证API调用
assert len(logs) > 0, "未检测到API调用"
for api_call in logs:
print(f"API: {api_call['url']} - Duration: {api_call['duration']}ms")
assert api_call['duration'] < 3000, f"API响应时间过长: {api_call['url']}"
class TestResponsiveDesign:
"""响应式设计测试"""
@pytest.mark.parametrize("width,height", [
(375, 667), # iPhone 6/7/8
(414, 896), # iPhone 11 Pro Max
(768, 1024), # iPad
(1920, 1080) # Desktop
])
def test_responsive_layout(self, width, height):
"""测试不同屏幕尺寸下的布局"""
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),
options=chrome_options)
try:
# 设置窗口大小
driver.set_window_size(width, height)
# 加载页面
driver.get("https://example.com")
# 检查响应式元素
if width < 768: # 移动端
hamburger_menu = driver.find_element(By.CLASS_NAME, "mobile-menu-toggle")
assert hamburger_menu.is_displayed(), f"移动端菜单在 {width}x{height} 下未显示"
else: # 桌面端
nav_menu = driver.find_element(By.CLASS_NAME, "desktop-nav")
assert nav_menu.is_displayed(), f"桌面导航在 {width}x{height} 下未显示"
print(f"✓ 响应式测试通过: {width}x{height}")
finally:
driver.quit()
# 运行测试
if __name__ == "__main__":
pytest.main(["-v", "--html=report.html", __file__])
实用的辅助函数
# utils/selenium_utils.py
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class SeleniumUtils:
"""Selenium 辅助工具类"""
@staticmethod
def wait_and_click(driver, locator, timeout=10):
"""等待元素可点击并点击"""
element = WebDriverWait(driver, timeout).until(
EC.element_to_be_clickable(locator)
)
element.click()
return element
@staticmethod
def wait_and_send_keys(driver, locator, text, timeout=10):
"""等待输入框并输入文本"""
element = WebDriverWait(driver, timeout).until(
EC.presence_of_element_located(locator)
)
element.clear()
element.send_keys(text)
return element
@staticmethod
def scroll_to_element(driver, element):
"""滚动到元素位置"""
driver.execute_script("arguments[0].scrollIntoView(true);", element)
time.sleep(0.5)
@staticmethod
def highlight_element(driver, element):
"""高亮元素(用于调试)"""
original_style = element.get_attribute("style")
driver.execute_script(
"arguments[0].setAttribute('style', 'background: yellow; border: 2px solid red;');",
element
)
time.sleep(0.5)
driver.execute_script(
f"arguments[0].setAttribute('style', '{original_style}');",
element
)
@staticmethod
def get_page_load_time(driver):
"""获取页面加载时间"""
return driver.execute_script("""
return window.performance.timing.loadEventEnd -
window.performance.timing.navigationStart;
""")
# conftest.py - pytest配置文件
import pytest
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
@pytest.fixture(scope="session")
def browser():
"""全局浏览器实例"""
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=chrome_options
)
driver.implicitly_wait(10)
driver.maximize_window()
yield driver
driver.quit()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""测试失败时截图"""
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
if "browser" in item.fixturenames:
driver = item.funcargs["browser"]
screenshot_name = f"screenshots/{item.name}_{int(time.time())}.png"
driver.save_screenshot(screenshot_name)
print(f"截图保存: {screenshot_name}")
运行测试
# 运行所有测试 pytest test_e2e.py -v # 运行特定测试类 pytest test_e2e.py::TestLoginE2E -v # 生成HTML报告 pytest test_e2e.py -v --html=report.html # 并行执行测试 pytest test_e2e.py -v -n 4 # 失败重试 pytest test_e2e.py -v --reruns 2
CI/CD 集成示例
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install selenium pytest pytest-html webdriver-manager
- name: Run E2E tests
run: |
pytest test_e2e.py -v --html=report.html
- name: Upload test report
uses: actions/upload-artifact@v2
if: always()
with:
name: test-report
path: report.html
- name: Upload screenshots
uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshots
path: screenshots/
关键最佳实践
- 使用显式等待:避免使用
time.sleep() - 页面对象模式:封装页面元素和操作
- 失败截图:测试失败时自动截图
- 报告生成:生成详细的测试报告
- 并行执行:提高测试效率
- 数据驱动:使用参数化测试
这个框架可以满足大部分端到端测试需求,你可以根据具体项目进行调整。