使用 Playwright for Python 从零开始搭建自动化测试方案
使用 Playwright for Python 从零开始搭建自动化测试方案
Playwright 是一个强大的浏览器自动化框架,由 Microsoft 开发,支持 Chromium、Firefox 和 WebKit。本指南将带您从零开始搭建完整的自动化测试方案。
第一阶段:环境设置与安装
1.1 安装 Playwright
# 创建项目目录
mkdir playwright-automation
cd playwright-automation
# 创建虚拟环境(推荐)
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate
# 安装 Playwright
pip install playwright
# 安装浏览器二进制文件
playwright install
1.2 验证安装
创建简单的测试脚本验证安装:
# test_installation.py
from playwright.sync_api import sync_playwright
def test_browser_launch():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
print(f"页面标题: {page.title()}")
browser.close()
if __name__ == "__main__":
test_browser_launch()
运行脚本:
python test_installation.py
第二阶段:项目结构设计
创建以下项目结构:
playwright-automation/
├── tests/ # 测试用例目录
│ ├── __init__.py
│ ├── test_login.py
│ └── test_search.py
├── pages/ # 页面对象模型
│ ├── __init__.py
│ ├── base_page.py
│ ├── login_page.py
│ └── search_page.py
├── utils/ # 工具函数
│ ├── __init__.py
│ ├── helpers.py
│ └── config.py
├── fixtures/ # 测试夹具
│ ├── __init__.py
│ └── conftest.py
├── reports/ # 测试报告目录
├── requirements.txt # 项目依赖
└── pytest.ini # Pytest配置
第三阶段:编写基础测试框架
3.1 配置文件
# utils/config.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# 浏览器配置
BROWSER = os.getenv("BROWSER", "chromium")
HEADLESS = os.getenv("HEADLESS", "true").lower() == "true"
SLOW_MO = int(os.getenv("SLOW_MO", "0"))
# 测试环境配置
BASE_URL = os.getenv("BASE_URL", "https://demoqa.com")
# 报告配置
REPORT_DIR = BASE_DIR / "reports"
SCREENSHOT_DIR = REPORT_DIR / "screenshots"
3.2 基础页面类
# pages/base_page.py
from playwright.sync_api import Page, expect
from utils.config import SCREENSHOT_DIR
class BasePage:
def __init__(self, page: Page):
self.page = page
def navigate(self, url):
"""导航到指定URL"""
self.page.goto(url)
def click(self, selector):
"""点击元素"""
self.page.click(selector)
def fill(self, selector, text):
"""填充文本"""
self.page.fill(selector, text)
def get_text(self, selector):
"""获取元素文本"""
return self.page.text_content(selector)
def wait_for_selector(self, selector, timeout=10000):
"""等待元素出现"""
self.page.wait_for_selector(selector, timeout=timeout)
def take_screenshot(self, name):
"""截图"""
SCREENSHOT_DIR.mkdir(exist_ok=True, parents=True)
self.page.screenshot(path=str(SCREENSHOT_DIR / f"{name}.png"))
def assert_element_visible(self, selector):
"""验证元素可见"""
expect(self.page.locator(selector)).to_be_visible()
def assert_text_present(self, text):
"""验证文本存在"""
expect(self.page.locator(f"text={text}")).to_be_visible()
3.3 Pytest 配置
# fixtures/conftest.py
import pytest
from playwright.sync_api import sync_playwright
from utils.config import BROWSER, HEADLESS, SLOW_MO, BASE_URL
@pytest.fixture(scope="session")
def browser():
"""启动浏览器实例"""
with sync_playwright() as p:
browser = getattr(p, BROWSER).launch(
headless=HEADLESS,
slow_mo=SLOW_MO
)
yield browser
browser.close()
@pytest.fixture
def context(browser):
"""创建浏览器上下文"""
context = browser.new_context()
yield context
context.close()
@pytest.fixture
def page(context):
"""创建页面实例"""
page = context.new_page()
page.set_viewport_size({"width": 1920, "height": 1080})
yield page
page.close()
@pytest.fixture
def base_url():
"""返回基础URL"""
return BASE_URL
3.4 Pytest 配置文件
# pytest.ini
[pytest]
addopts = -v --tb=short -n auto
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
第四阶段:创建页面对象模型
4.1 登录页面对象
# pages/login_page.py
from .base_page import BasePage
class LoginPage(BasePage):
# 元素选择器
USERNAME_INPUT = "#userName"
PASSWORD_INPUT = "#password"
LOGIN_BUTTON = "#login"
ERROR_MESSAGE = "#name"
def __init__(self, page):
super().__init__(page)
def navigate_to_login(self, base_url):
"""导航到登录页面"""
self.navigate(f"{base_url}/login")
def enter_username(self, username):
"""输入用户名"""
self.fill(self.USERNAME_INPUT, username)
def enter_password(self, password):
"""输入密码"""
self.fill(self.PASSWORD_INPUT, password)
def click_login(self):
"""点击登录按钮"""
self.click(self.LOGIN_BUTTON)
def login(self, username, password):
"""执行完整登录操作"""
self.enter_username(username)
self.enter_password(password)
self.click_login()
def get_error_message(self):
"""获取错误消息"""
return self.get_text(self.ERROR_MESSAGE)
4.2 搜索页面对象
# pages/search_page.py
from .base_page import BasePage
class SearchPage(BasePage):
# 元素选择器
SEARCH_INPUT = "#searchBox"
SEARCH_RESULTS = ".rt-tbody"
NO_DATA_MESSAGE = ".rt-noData"
def __init__(self, page):
super().__init__(page)
def navigate_to_books(self, base_url):
"""导航到图书页面"""
self.navigate(f"{base_url}/books")
def search_book(self, keyword):
"""搜索图书"""
self.fill(self.SEARCH_INPUT, keyword)
def get_search_results_count(self):
"""获取搜索结果数量"""
return self.page.locator(self.SEARCH_RESULTS + " .rt-tr-group").count()
def is_no_data_displayed(self):
"""检查是否显示无数据消息"""
return self.page.locator(self.NO_DATA_MESSAGE).is_visible()
第五阶段:编写测试用例
5.1 登录测试用例
# tests/test_login.py
import pytest
from pages.login_page import LoginPage
class TestLogin:
@pytest.mark.parametrize("username,password,expected", [
("testuser", "Password123", True),
("invaliduser", "wrongpass", False),
("", "Password123", False),
])
def test_login_functionality(self, page, base_url, username, password, expected):
"""测试登录功能"""
login_page = LoginPage(page)
login_page.navigate_to_login(base_url)
login_page.login(username, password)
if expected:
# 验证登录成功
login_page.assert_text_present("Profile")
login_page.take_screenshot("login_success")
else:
# 验证登录失败
error_message = login_page.get_error_message()
assert "Invalid username or password" in error_message
login_page.take_screenshot("login_failure")
5.2 搜索测试用例
# tests/test_search.py
import pytest
from pages.search_page import SearchPage
class TestSearch:
def test_search_existing_book(self, page, base_url):
"""测试搜索存在的图书"""
search_page = SearchPage(page)
search_page.navigate_to_books(base_url)
search_page.search_book("JavaScript")
# 验证搜索结果
results_count = search_page.get_search_results_count()
assert results_count > 0
search_page.take_screenshot("search_results")
def test_search_nonexistent_book(self, page, base_url):
"""测试搜索不存在的图书"""
search_page = SearchPage(page)
search_page.navigate_to_books(base_url)
search_page.search_book("NonexistentBook123")
# 验证无数据消息
assert search_page.is_no_data_displayed()
search_page.take_screenshot("no_search_results")
第六阶段:运行测试与报告生成
6.1 运行测试
# 运行所有测试
pytest
# 运行特定测试文件
pytest tests/test_login.py
# 运行特定测试类
pytest tests/test_login.py::TestLogin
# 运行带有标记的测试
pytest -m "login"
# 并行运行测试
pytest -n 4
# 生成HTML报告
pytest --html=reports/report.html
6.2 添加自定义报告
安装额外的报告插件:
pip install pytest-html pytest-xdist
创建自定义报告配置:
# utils/helpers.py
import datetime
import json
def generate_test_report(results):
"""生成自定义测试报告"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
report_data = {
"timestamp": timestamp,
"total_tests": results["total"],
"passed": results["passed"],
"failed": results["failed"],
"skipped": results["skipped"],
"duration": results["duration"]
}
report_path = f"reports/test_report_{timestamp}.json"
with open(report_path, "w") as f:
json.dump(report_data, f, indent=4)
return report_path
第七阶段:持续集成配置
7.1 GitHub Actions 配置
创建 .github/workflows/playwright.yml
:
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
playwright install
playwright install-deps
- name: Run tests
run: |
pytest --html=reports/report.html --self-contained-html
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: reports/
retention-days: 30
7.2 依赖文件
创建 requirements.txt
:
playwright==1.35.0
pytest==7.3.1
pytest-html==3.2.0
pytest-xdist==3.3.1
第八阶段:高级功能与最佳实践
8.1 测试数据管理
创建测试数据管理模块:
# utils/data_loader.py
import json
import os
from pathlib import Path
def load_test_data(filename):
"""加载测试数据"""
data_path = Path(__file__).parent.parent / "test_data" / filename
with open(data_path, "r") as f:
return json.load(f)
# 使用示例
test_users = load_test_data("users.json")
8.2 自定义夹具
# fixtures/conftest.py (扩展)
@pytest.fixture
def authenticated_page(page, base_url):
"""创建已认证的页面"""
login_page = LoginPage(page)
login_page.navigate_to_login(base_url)
login_page.login("testuser", "Password123")
# 等待登录完成
page.wait_for_selector("#userName-value")
yield page
# 在测试中使用
def test_authenticated_operation(authenticated_page):
"""使用已认证的页面进行测试"""
# 直接进行需要认证的操作
authenticated_page.goto(f"{BASE_URL}/profile")
# ...
8.3 并行测试优化
# fixtures/conftest.py (添加)
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
"""配置浏览器上下文参数"""
return {
**browser_context_args,
"viewport": {
"width": 1920,
"height": 1080,
},
"ignore_https_errors": True
}
8.4 错误处理与重试机制
# utils/retry.py
import time
from functools import wraps
def retry(max_attempts=3, delay=1):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise e
time.sleep(delay)
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, delay=2)
def click_with_retry(page, selector):
"""带重试的点击操作"""
page.click(selector)
总结
通过以上步骤,您已经成功搭建了一个完整的 Playwright for Python 自动化测试方案。这个方案包括:
- 环境设置与安装:安装 Playwright 和浏览器
- 项目结构设计:组织代码和资源
- 基础框架搭建:创建配置、基础页面类和夹具
- 页面对象模型:封装页面元素和操作
- 测试用例编写:实现具体的测试逻辑
- 测试执行与报告:运行测试并生成报告
- 持续集成:配置 GitHub Actions 自动化测试
- 高级功能:添加数据管理、错误处理等高级功能
这个方案提供了可扩展的基础架构,您可以根据具体项目需求进一步定制和扩展。Playwright 的强大功能结合良好的测试架构设计,将帮助您构建可靠、高效的自动化测试解决方案。