Playwright与Python:从入门到精通的完整指南
前言
在当今快速迭代的软件开发环境中,自动化测试已成为保证产品质量的关键。Playwright作为微软推出的新一代测试框架,凭借其卓越的性能和丰富的功能,正在重新定义Web自动化测试的标准。本文将带您从零开始,全面掌握使用Playwright生成Python自动化脚本的技能。
第一部分:Playwright基础入门
1.1 Playwright简介
Playwright的核心优势:
• 🔥 跨浏览器支持:Chromium、Firefox、WebKit
• ⚡ 自动等待机制:智能等待元素,减少Flaky测试
• 🎯 强大的选择器引擎:文本、CSS、XPath等多种定位方式
• 🌐 网络拦截能力:模拟各种网络条件
• 📱 移动端测试:完整的移动设备模拟
1.2 环境安装与配置
系统要求检查
检查Python版本(需要3.7+)
python --version
检查pip版本
pip --version
安装Playwright
安装Playwright Python包
pip install playwright
安装浏览器(推荐全部安装)
playwright install
playwright install chromium
playwright install firefox
playwright install webkit
验证安装
python -c “import playwright; print(‘安装成功’)”
开发环境配置
// .vscode/settings.json(VS Code配置)
{
“python.testing.pytestEnabled”: true,
“python.testing.unittestEnabled”: false,
“python.testing.pytestArgs”: [
“tests”
]
}
第二部分:代码生成器深度解析
2.1 基础录制功能
启动代码生成器
基本录制
playwright codegen
录制指定网站
playwright codegen https://example.com
生成Python脚本
playwright codegen --target python -o my_script.py
指定浏览器录制
playwright codegen --browser firefox https://example.com
录制参数详解
完整参数示例
playwright codegen https://example.com
–target python \ # 生成Python代码
–output test_example.py \ # 输出文件
–browser chromium \ # 指定浏览器
–viewport-size 1280,720 \ # 设置视口
–user-agent “Custom UA” \ # 自定义User-Agent
–save-storage auth.json \ # 保存登录状态
–load-storage auth.json # 加载登录状态
2.2 高级录制技巧
移动端录制
模拟移动设备
playwright codegen
–device=“iPhone 12”
–viewport-size=“390,844”
https://m.example.com
可用设备列表
playwright codegen --help | grep device
认证状态管理
第一步:录制登录过程并保存状态
playwright codegen https://example.com/login --save-storage auth.json
第二步:使用保存的状态录制其他页面
playwright codegen https://example.com/dashboard --load-storage auth.json
2.3 自定义录制模板
custom_recorder.py
from playwright.sync_api import sync_playwright
import json
from datetime import datetime
class AdvancedRecorder:
def init(self, base_url, config=None):
self.base_url = base_url
self.config = config or {}
self.actions = []
def start_recording(self, output_file="recorded_script.py"):"""开始录制会话"""with sync_playwright() as p:# 浏览器配置browser = p.chromium.launch(headless=False,args=['--start-maximized'])# 上下文配置context = browser.new_context(viewport=self.config.get('viewport', {'width': 1920, 'height': 1080}),user_agent=self.config.get('user_agent'),ignore_https_errors=True)# 开始跟踪context.tracing.start(screenshots=True, snapshots=True)page = context.new_page()# 导航到目标页面page.goto(self.base_url)print(f"开始录制: {self.base_url}")print("操作浏览器,按 Ctrl+C 结束录制...")try:self._record_actions(page)except KeyboardInterrupt:self._generate_script(output_file, context)finally:browser.close()def _record_actions(self, page):"""录制用户操作"""# 这里可以添加自定义录制逻辑import timewhile True:time.sleep(0.1)def _generate_script(self, output_file, context):"""生成优化后的脚本"""script_template = self._create_script_template()with open(output_file, 'w', encoding='utf-8') as f:f.write(script_template)print(f"脚本已生成: {output_file}")# 保存跟踪信息context.tracing.stop(path="recording-trace.zip")
使用示例
recorder = AdvancedRecorder(“https://example.com”)
recorder.start_recording(“optimized_test.py”)
第三部分:Playwright Python API精通
3.1 核心API详解
浏览器管理
from playwright.sync_api import sync_playwright
def browser_management_examples():
with sync_playwright() as p:
# 浏览器启动选项
browser = p.chromium.launch(
headless=False, # 显示浏览器
slow_mo=100, # 慢动作(毫秒)
devtools=True, # 打开开发者工具
args=[‘–start-maximized’] # 启动参数
)
# 浏览器上下文context = browser.new_context(viewport={'width': 1920, 'height': 1080},user_agent='自定义User-Agent',ignore_https_errors=True,java_script_enabled=True)# 创建页面page = context.new_page()# 页面基本操作page.goto('https://example.com')page.reload()page.go_back()page.go_forward()browser.close()
异步版本
import asyncio
from playwright.async_api import async_playwright
async def async_browser_management():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(‘https://example.com’)
await browser.close()
3.2 元素定位高级技巧
选择器大全
def selector_masterclass(page):
# CSS选择器
page.click(‘button.submit’)
page.click(‘.btn-primary’)
page.click(‘#login-btn’)
# 文本选择器
page.click('text=登录')
page.click("'精确文本匹配'")
page.click('text=/正则表达式.*/')# XPath选择器
page.click('//button[@id="submit"]')
page.click('//div[contains(@class, "error")]')# 组合选择器
page.click('div.error >> text=重试')
page.click('form#login >> input[name="username"]')# 相对定位器
page.click('input:near(:text("用户名"))')
page.click('button:right-of(:text("搜索"))')# 可视化定位器
page.click('button:has-text("提交")')
page.click('div:has(> img.logo)')
最佳实践:使用Locator对象
def locator_best_practices(page):
# 创建Locator对象
submit_btn = page.locator(‘button:has-text(“提交”)’)
username_input = page.locator(‘#username’)
# Locator操作
submit_btn.click()
username_input.fill('testuser')# 链式操作
page.locator('form.login').locator('input[type="text"]').fill('value')# 等待元素
submit_btn.wait_for(state='visible')
3.3 高级交互操作
def advanced_interactions(page):
# 键盘操作
page.keyboard.press(‘Enter’)
page.keyboard.type(‘Hello World’)
page.keyboard.down(‘Shift’)
# 鼠标操作
page.mouse.click(100, 200)
page.mouse.dblclick(150, 250)
page.mouse.down()
page.mouse.move(300, 400)
page.mouse.up()# 拖放操作
page.drag_and_drop('#source', '#target')# 文件操作
page.set_input_files('input[type="file"]', 'file.txt')
page.set_input_files('input[type="file"]', ['file1.txt', 'file2.txt'])# 焦点和标签页管理
page.bring_to_front()
page.evaluate('window.focus()')
处理复杂场景
def handle_complex_scenarios(page):
# 滚动操作
page.evaluate(‘window.scrollTo(0, document.body.scrollHeight)’)
page.mouse.wheel(0, 100)
# 执行JavaScript
page.evaluate('() => document.title')
result = page.evaluate('(x) => x * 2', 5)# 元素状态检查
is_visible = page.is_visible('selector')
is_enabled = page.is_enabled('selector')
is_checked = page.is_checked('selector')
第四部分:测试框架集成
4.1 Pytest集成
基础配置
conftest.py
import pytest
from playwright.sync_api import Page, Browser
@pytest.fixture(scope=“session”)
def browser():
“”“全局浏览器实例”“”
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
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()
yield page
page.close()
配置文件 pytest.ini
[pytest]
addopts = -v --tb=short --strict-markers
markers =
slow: 慢速测试
ui: UI测试
api: API测试
测试用例编写
test_example.py
import pytest
from playwright.sync_api import Page, expect
class TestLogin:
“”“登录功能测试”“”
@pytest.fixture(autouse=True)
def setup(self, page: Page):"""测试前置条件"""self.page = pageself.page.goto("https://example.com/login")def test_successful_login(self):"""测试成功登录"""# 操作步骤self.page.fill("#username", "testuser")self.page.fill("#password", "correctpassword")self.page.click("#login-btn")# 断言验证expect(self.page).to_have_url("https://example.com/dashboard")expect(self.page.locator(".welcome-message")).to_contain_text("欢迎")def test_login_failure(self):"""测试登录失败"""self.page.fill("#username", "wronguser")self.page.fill("#password", "wrongpassword")self.page.click("#login-btn")expect(self.page.locator(".error-message")).to_be_visible()expect(self.page.locator(".error-message")).to_contain_text("用户名或密码错误")
@pytest.mark.ui
@pytest.mark.slow
class TestE2E:
“”“端到端测试”“”
def test_complete_user_journey(self, page: Page):"""完整用户流程测试"""# 登录page.goto("https://example.com/login")page.fill("#username", "testuser")page.fill("#password", "password123")page.click("#login-btn")# 导航到功能页面page.click("text=仪表板")expect(page).to_have_url("**/dashboard")# 执行操作page.click("button.new-item")page.fill("#item-name", "测试项目")page.click("#save-btn")# 验证结果expect(page.locator(".success-message")).to_be_visible()
4.2 页面对象模式(Page Object Model)
pages/base_page.py
from playwright.sync_api import Page, expect
from typing import Tuple
class BasePage:
“”“页面基类”“”
def __init__(self, page: Page):self.page = pageself.timeout = 30000def wait_for_loading(self):"""等待页面加载完成"""self.page.wait_for_load_state("networkidle")def take_screenshot(self, name: str):"""截图"""self.page.screenshot(path=f"screenshots/{name}.png")def get_element(self, selector: str):"""获取元素"""return self.page.locator(selector)
pages/login_page.py
from .base_page import BasePage
class LoginPage(BasePage):
“”“登录页面”“”
# 元素定位器
USERNAME_INPUT = "#username"
PASSWORD_INPUT = "#password"
LOGIN_BUTTON = "#login-btn"
ERROR_MESSAGE = ".error-message"def __init__(self, page: Page):super().__init__(page)self.url = "https://example.com/login"def navigate(self):"""导航到登录页面"""self.page.goto(self.url)self.wait_for_loading()def login(self, username: str, password: str):"""执行登录操作"""self.page.fill(self.USERNAME_INPUT, username)self.page.fill(self.PASSWORD_INPUT, password)self.page.click(self.LOGIN_BUTTON)def get_error_message(self) -> str:"""获取错误消息"""return self.page.text_content(self.ERROR_MESSAGE)def is_login_successful(self) -> bool:"""检查登录是否成功"""return "dashboard" in self.page.url
pages/dashboard_page.py
from .base_page import BasePage
class DashboardPage(BasePage):
“”“仪表板页面”“”
WELCOME_MESSAGE = ".welcome-message"
NEW_ITEM_BUTTON = "button.new-item"def __init__(self, page: Page):super().__init__(page)def is_loaded(self) -> bool:"""检查页面是否加载完成"""return self.page.locator(self.WELCOME_MESSAGE).is_visible()def create_new_item(self, item_name: str):"""创建新项目"""self.page.click(self.NEW_ITEM_BUTTON)# 更多创建逻辑...
使用页面对象
def test_with_page_objects(login_page: LoginPage, dashboard_page: DashboardPage):
“”“使用页面对象模式的测试”“”
login_page.navigate()
login_page.login(“testuser”, “password123”)
assert dashboard_page.is_loaded()
assert "欢迎" in dashboard_page.get_welcome_message()
第五部分:高级特性与最佳实践
5.1 网络拦截与模拟
def network_interception_examples(page):
“”“网络拦截示例”“”
# 拦截API请求
async def handle_request(route, request):if "api/users" in request.url:# 模拟响应await route.fulfill(status=200,content_type="application/json",body=json.dumps({"users": []}))else:await route.continue_()# 设置路由
await page.route("**/api/**", handle_request)# 监控网络活动
def log_request(request):print(f"请求: {request.method} {request.url}")def log_response(response):print(f"响应: {response.status} {response.url}")page.on("request", log_request)
page.on("response", log_response)# 模拟慢网络
async def slow_mobile_network(route, request):# 添加延迟模拟慢网络await asyncio.sleep(2)await route.continue_()await page.route("**/*", slow_mobile_network)
5.2 性能测试与监控
performance_monitor.py
import time
from playwright.sync_api import Page
class PerformanceMonitor:
“”“性能监控器”“”
def __init__(self, page: Page):self.page = pageself.metrics = {}def start_monitoring(self):"""开始性能监控"""self.page.evaluate("""window.performanceMetrics = {startTime: performance.now(),marks: {}};""")def mark(self, name: str):"""添加性能标记"""self.page.evaluate(f"window.performanceMetrics.marks['{name}'] = performance.now();")def get_metrics(self) -> dict:"""获取性能指标"""return self.page.evaluate("""() => {const metrics = window.performanceMetrics;const navigation = performance.getEntriesByType('navigation')[0];return {loadTime: navigation.loadEventEnd - navigation.navigationStart,domContentLoaded: navigation.domContentLoadedEventEnd - navigation.navigationStart,marks: metrics.marks,memory: performance.memory};}""")
使用示例
def test_performance(login_page: LoginPage):
monitor = PerformanceMonitor(login_page.page)
monitor.start_monitoring()
login_page.navigate()
monitor.mark("page_loaded")login_page.login("user", "pass")
monitor.mark("login_completed")metrics = monitor.get_metrics()
assert metrics["loadTime"] < 3000 # 页面加载应小于3秒
5.3 持续集成配置
.github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2- name: Set up Pythonuses: actions/setup-python@v2with:python-version: '3.8'- name: Install dependenciesrun: |python -m pip install --upgrade pippip install -r requirements.txtpip install pytest-playwright- name: Install browsersrun: playwright install- name: Run testsrun: pytest tests/ --html=report.html --self-contained-html- name: Upload test resultsuses: actions/upload-artifact@v2with:name: playwright-reportpath: report.html
第六部分:故障排除与调试
6.1 常见问题解决
troubleshooting.py
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
def robust_element_interaction(page, selector, max_attempts=3):
“”“健壮的元素交互”“”
for attempt in range(max_attempts):
try:
element = page.locator(selector)
element.wait_for(state=‘visible’, timeout=5000)
return element
except PlaywrightTimeoutError:
if attempt == max_attempts - 1:
raise
print(f"重试 {selector},尝试 {attempt + 1}/{max_attempts}")
page.wait_for_timeout(1000)
def handle_dynamic_content(page):
“”“处理动态内容”“”
# 等待特定条件
page.wait_for_function(“”"
() => {
const element = document.querySelector(‘.dynamic-content’);
return element && element.offsetHeight > 0;
}
“”")
# 重试机制
def retry_operation(operation, max_retries=3):for i in range(max_retries):try:return operation()except Exception as e:if i == max_retries - 1:raise epage.wait_for_timeout(1000)
def debug_script(script_path):
“”“调试脚本”“”
import pdb
from importlib import import_module
import sys
# 动态加载并调试脚本
sys.path.append('.')
spec = import_module(script_path.replace('.py', '').replace('/', '.'))
pdb.run('spec.main()')
总结
通过本指南,您已经全面掌握了使用Playwright生成Python自动化脚本的技能。从基础的环境搭建到高级的测试框架集成,从简单的页面录制到复杂的性能监控,Playwright为现代Web自动化测试提供了完整的解决方案。
关键要点:
- 录制是起点:代码生成器是快速入门的利器
- API是核心:深入理解Playwright API是精通的关键
- 模式很重要:页面对象模式提高代码可维护性
- 集成是生产力:与pytest等框架集成提升测试效率
- 监控保障质量:性能监控和错误处理确保测试可靠性
Playwright正在快速发展,建议持续关注官方文档和社区更新,以掌握最新的特性和最佳实践。