Playwright自动化测试完整示例

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的核心功能,包括浏览器控制、页面操作、数据提取、网络拦截、设备模拟等 ,并提供了可扩展的项目结构和测试示例。


参考来源

相关推荐
大鱼>6 小时前
模型可解释性:特征重要性/SHAP/LIME
人工智能·python·机器学习·lstm
代码不接地6 小时前
【时间序列预测】0.时间序列任务知识地图
python·ai编程
许彰午7 小时前
77_Python数据清洗实战技巧
开发语言·python
Sam09277 小时前
【AI 算法精讲 16】BPE 分词:从字节对到子词
人工智能·python·算法·ai
江华森7 小时前
04-python-面向对象
开发语言·python
梦帮科技7 小时前
基于EVM架构的Web3音频确权与RNS Token智能合约设计深度解析
人工智能·python·架构·web3·区块链·音视频·智能合约
AI行业学习7 小时前
Notepad++ 官方纯净下载+完整安装教程(Windows)【2026.7.5】
开发语言·windows·python·前端框架·html·notepad++
伏 念8 小时前
Hermes Agent
python
江华森8 小时前
从零构建搜索引擎:Python 异步爬虫 + 倒排索引 + Sanic 前后端实战
python