【Python知识】使用 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 自动化测试方案。这个方案包括:

  1. 环境设置与安装:安装 Playwright 和浏览器
  2. 项目结构设计:组织代码和资源
  3. 基础框架搭建:创建配置、基础页面类和夹具
  4. 页面对象模型:封装页面元素和操作
  5. 测试用例编写:实现具体的测试逻辑
  6. 测试执行与报告:运行测试并生成报告
  7. 持续集成:配置 GitHub Actions 自动化测试
  8. 高级功能:添加数据管理、错误处理等高级功能

这个方案提供了可扩展的基础架构,您可以根据具体项目需求进一步定制和扩展。Playwright 的强大功能结合良好的测试架构设计,将帮助您构建可靠、高效的自动化测试解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问道飞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值