python
import asyncio
from playwright.async_api import async_playwright, Page, BrowserContext
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class PlaywrightDemo:
"""Playwright完整演示类"""
def __init__(self, headless: bool = False):
self.headless = headless
self.browser = None
self.context = None
self.page = None
async def setup_browser(self):
"""初始化浏览器和上下文"""
playwright = await async_playwright().start()
# 启动Chromium浏览器,可切换为firefox或webkit
self.browser = await playwright.chromium.launch(
headless=self.headless,
args=['--start-maximized', '--disable-blink-features=AutomationControlled']
)
# 创建浏览器上下文,支持设备模拟、权限设置等
self.context = await self.browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
ignore_https_errors=True,
# 启用录制功能
record_video_dir='videos/' if not self.headless else None
)
# 创建新页面
self.page = await self.context.new_page()
logger.info("浏览器初始化完成")
return playwright async def navigate_and_wait(self, url: str):
"""导航到指定URL并等待页面加载"""
logger.info(f"正在访问: {url}")
await self.page.goto(url, wait_until='networkidle')
# 等待页面关键元素
await self.page.wait_for_load_state('domcontentloaded')
logger.info(f"页面加载完成: {self.page.title()}")
async def handle_login(self, username: str, password: str):
"""处理登录流程示例"""
logger.info("开始登录流程")
# 定位并填写用户名 await self.page.fill('input[name="username"]', username)
# 定位并填写密码
await self.page.fill('input[name="password"]', password)
# 点击登录按钮
await self.page.click('button[type="submit"]')
# 等待登录成功 await self.page.wait_for_selector('.dashboard', timeout=10000)
logger.info("登录成功")
async def take_screenshot(self, filename: str = 'screenshot.png'):
"""截取页面截图"""
await self.page.screenshot(path=filename, full_page=True)
logger.info(f"截图已保存: {filename}")
async def extract_data(self):
"""提取页面数据示例"""
# 提取文本内容 title = await self.page.text_content('h1')
# 提取多个元素
links = await self.page.locator('a').all()
link_texts = [await link.text_content() for link in links[:5]]
# 提取表格数据 table_data = await self.page.evaluate("""
() => {
const rows = document.querySelectorAll('table tr');
return Array.from(rows).map(row => {
const cells = row.querySelectorAll('td, th');
return Array.from(cells).map(cell => cell.textContent.trim());
});
}
""")
return {
'title': title,
'links': link_texts,
'table_data': table_data
}
async def handle_dynamic_content(self):
"""处理动态加载内容"""
# 等待特定元素出现 await self.page.wait_for_selector('.dynamic-content', timeout=5000)
# 点击加载更多
load_more_button = self.page.locator('button:has-text("加载更多")')
if await load_more_button.count() > 0:
await load_more_button.click()
await self.page.wait_for_timeout(2000) # 等待内容加载
async def intercept_requests(self):
"""拦截和修改网络请求示例"""
await self.page.route('**/*', lambda route: route.continue_())
# 拦截特定请求
await self.page.route('**/api/**', lambda route: route.continue_())
# 修改请求头 await self.page.route('**/*', lambda route: route.continue_(
headers={**route.request.headers, 'Custom-Header': 'Playwright-Demo'}
))
async def execute_js(self):
"""执行JavaScript代码"""
# 执行简单JS
result = await self.page.evaluate('1 + 2')
logger.info(f"JS执行结果: {result}")
# 修改页面样式
await self.page.evaluate("""
() => {
document.body.style.backgroundColor = '#f0f0f0';
}
""")
async def run_demo(self):
"""运行完整演示流程"""
playwright = None try:
# 1. 初始化浏览器 playwright = await self.setup_browser()
# 2. 导航到测试页面
await self.navigate_and_wait('https://example.com')
# 3. 截取截图 await self.take_screenshot('example_page.png')
# 4. 提取数据
data = await self.extract_data()
logger.info(f"提取的数据: {data}")
# 5. 处理动态内容 await self.handle_dynamic_content()
# 6. 执行JavaScript await self.execute_js()
# 7. 模拟用户操作 await self.page.mouse.move(100, 100)
await self.page.keyboard.type('Hello Playwright!')
logger.info("演示流程执行完成")
except Exception as e:
logger.error(f"执行过程中发生错误: {e}")
# 发生错误时截图
await self.take_screenshot('error_screenshot.png')
finally:
# 清理资源 if self.page:
await self.page.close()
if self.context:
await self.context.close()
if self.browser:
await self.browser.close()
if playwright:
await playwright.stop()
logger.info("资源清理完成")
class AdvancedPlaywrightDemo(PlaywrightDemo):
"""高级功能演示"""
async def multi_context_demo(self):
"""多上下文/多用户会话演示"""
# 创建多个独立上下文(模拟多个用户)
contexts = []
for i in range(3):
context = await self.browser.new_context()
page = await context.new_page()
await page.goto('https://example.com')
contexts.append((context, page))
logger.info(f"创建上下文 {i+1}")
# 在各个上下文中执行不同操作 for idx, (context, page) in enumerate(contexts):
await page.fill('input', f'User{idx+1}')
await page.screenshot(path=f'user_{idx+1}.png')
# 清理 for context, page in contexts:
await page.close()
await context.close()
async def device_emulation(self):
"""设备模拟"""
# 获取设备列表
devices = self.context.devices
iphone = devices['iPhone 12']
# 使用设备模拟创建新上下文 iphone_context = await self.browser.new_context(**iphone)
iphone_page = await iphone_context.new_page()
await iphone_page.goto('https://example.com')
await iphone_page.screenshot(path='iphone_view.png')
await iphone_page.close()
await iphone_context.close()
async def network_monitoring(self):
"""网络监控和拦截"""
# 监听网络请求
self.page.on('request', lambda request: logger.info(f"请求: {request.url}"))
self.page.on('response', lambda response: logger.info(f"响应: {response.url} - {response.status}"))
# 拦截并修改响应 await self.page.route('**/api/data', lambda route: route.fulfill(
status=200,
content_type='application/json',
body='{"custom": "data"}'
))
async def main():
"""主函数"""
# 基础演示
demo = PlaywrightDemo(headless=False)
await demo.run_demo()
# 高级功能演示
advanced_demo = AdvancedPlaywrightDemo(headless=True)
await advanced_demo.setup_browser()
# 运行高级演示
await advanced_demo.multi_context_demo()
await advanced_demo.device_emulation()
await advanced_demo.network_monitoring()
# 清理
if advanced_demo.page:
await advanced_demo.page.close()
if advanced_demo.context:
await advanced_demo.context.close()
if advanced_demo.browser:
await advanced_demo.browser.close()
if __name__ == '__main__':
# 运行异步主函数
asyncio.run(main())
配置文件示例
yaml
# config.yaml
playwright:
browser: chromium # chromium, firefox, webkit headless: false viewport:
width: 1920
height: 1080 timeout: 30000 slow_mo: 50 # 操作延迟(毫秒)
test_sites:
url: https://example.com name: Example Site - url: https://demo.testfire.net
name: Banking Demo
credentials:
username: test_user
password: test_pass123
output:
screenshots_dir: ./screenshots
videos_dir: ./videos
reports_dir: ./reports
测试用例示例
python
# test_demo.py
import pytest
from playwright.sync_api import Page, expect
@pytest.fixture(scope='function')
def page(browser):
"""创建页面fixture"""
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
record_video_dir='videos/'
)
page = context.new_page()
yield page
context.close()
def test_navigation(page: Page):
"""测试页面导航"""
page.goto('https://example.com')
expect(page).to_have_title('Example Domain')
# 验证页面元素 heading = page.locator('h1')
expect(heading).to_have_text('Example Domain')
# 验证链接 link = page.locator('a')
expect(link).to_have_text('More information...')
def test_form_submission(page: Page):
"""测试表单提交"""
page.goto('https://demo.testfire.net/login.aspx')
# 填写表单
page.fill('#uid', 'admin')
page.fill('#passw', 'admin')
# 提交表单
page.click('input[name="btnSubmit"]')
# 验证登录成功 expect(page.locator('#_ctl0__ctl0_Content_Main_message')).to_be_visible()
def test_screenshot(page: Page):
"""测试截图功能"""
page.goto('https://example.com')
screenshot = page.screenshot(path='test_screenshot.png', full_page=True)
assert screenshot is not None
项目结构建议
playwright-demo/
├── src/
│ ├── __init__.py
│ ├── browser_manager.py # 浏览器管理
│ ├── page_objects/ # 页面对象模型
│ │ ├── base_page.py
│ │ ├── login_page.py
│ │ └── dashboard_page.py
│ ├── utils/
│ │ ├── logger.py
│ │ ├── config_loader.py
│ │ └── helpers.py
│ └── demo_runner.py # 演示运行器
├── tests/
│ ├── conftest.py
│ ├── test_navigation.py
│ └── test_forms.py
├── config.yaml # 配置文件
├── requirements.txt # 依赖文件
├── .gitignore└── README.md
关键功能说明
| 功能模块 | 核心方法 | 用途说明 |
|---|---|---|
| 浏览器管理 | launch(), new_context() |
控制浏览器启动和上下文创建 |
| 页面导航 | goto(), wait_for_load_state() |
页面加载和等待策略 |
| 元素操作 | fill(), click(), type() |
表单填写和点击交互 |
| 数据提取 | text_content(), evaluate() |
获取页面文本和执行JS |
| 截图录屏 | screenshot(), record_video_dir |
可视化记录和调试 |
| 网络控制 | route(), on('request') |
请求拦截和监控 |
| 设备模拟 | devices, new_context(**device) |
移动端测试模拟 |
| 多上下文 | 多个new_context()实例 |
多用户会话隔离 |
安装依赖
bash
# requirements.txt
playwright==1.40.0
pytest==7.4.3
pytest-playwright==0.4.3
asyncio==3.4.3
pyyaml==6.0.1
# 安装命令
pip install -r requirements.txt
playwright install chromium # 安装浏览器
运行命令
bash
# 运行Python演示
python demo_runner.py
# 运行Pytest测试
pytest tests/ -v --headed
# 生成HTML报告
pytest tests/ --html=report.html
# 特定浏览器测试
pytest tests/ --browser chromium --browser firefox
此完整Demo涵盖了Playwright的核心功能,包括浏览器控制、页面操作、数据提取、网络拦截、设备模拟等 ,并提供了可扩展的项目结构和测试示例。