webapp-testing-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaywright Patterns Reference
Playwright测试模式参考手册
Complete guide to Playwright automation patterns, selectors, and best practices.
关于Playwright自动化测试模式、选择器及最佳实践的完整指南。
Table of Contents
目录
Selectors
选择器
Text Selectors
文本选择器
Most readable and maintainable approach when text is unique:
python
page.click('text=Login')
page.click('text="Sign Up"') # Exact match
page.click('text=/log.*in/i') # Regex, case-insensitive当文本唯一时,这是最具可读性和可维护性的方法:
python
page.click('text=Login')
page.click('text="Sign Up"') # 精确匹配
page.click('text=/log.*in/i') # 正则表达式,不区分大小写Role-Based Selectors
基于角色的选择器
Semantic selectors based on ARIA roles:
python
page.click('role=button[name="Submit"]')
page.fill('role=textbox[name="Email"]', 'user@example.com')
page.click('role=link[name="Learn more"]')
page.check('role=checkbox[name="Accept terms"]')基于ARIA角色的语义化选择器:
python
page.click('role=button[name="Submit"]')
page.fill('role=textbox[name="Email"]', 'user@example.com')
page.click('role=link[name="Learn more"]')
page.check('role=checkbox[name="Accept terms"]')CSS Selectors
CSS选择器
Traditional CSS selectors for precise targeting:
python
page.click('#submit-button')
page.fill('.email-input', 'user@example.com')
page.click('button.primary')
page.click('nav > ul > li:first-child')用于精准定位的传统CSS选择器:
python
page.click('#submit-button')
page.fill('.email-input', 'user@example.com')
page.click('button.primary')
page.click('nav > ul > li:first-child')XPath Selectors
XPath选择器
For complex DOM navigation:
python
page.click('xpath=//button[contains(text(), "Submit")]')
page.click('xpath=//div[@class="modal"]//button[@type="submit"]')用于复杂DOM导航:
python
page.click('xpath=//button[contains(text(), "Submit")]')
page.click('xpath=//div[@class="modal"]//button[@type="submit"]')Data Attributes
数据属性
Best practice for test-specific selectors:
python
page.click('[data-testid="submit-btn"]')
page.fill('[data-test="email-input"]', 'test@example.com')针对测试场景的最佳选择器实践:
python
page.click('[data-testid="submit-btn"]')
page.fill('[data-test="email-input"]', 'test@example.com')Chaining Selectors
链式选择器
Combine selectors for precision:
python
page.locator('div.modal').locator('button.submit').click()
page.locator('role=dialog').locator('text=Confirm').click()组合选择器以提高精准度:
python
page.locator('div.modal').locator('button.submit').click()
page.locator('role=dialog').locator('text=Confirm').click()Selector Best Practices
选择器最佳实践
Priority order (most stable to least stable):
- attributes (most stable)
data-testid - selectors (semantic, accessible)
role= - selectors (readable, but text may change)
text= - attributes (stable if not dynamic)
id - CSS classes (less stable, may change with styling)
- XPath (fragile, avoid if possible)
优先级排序(从最稳定到最不稳定):
- 属性(最稳定)
data-testid - 选择器(语义化、可访问性友好)
role= - 选择器(可读性强,但文本可能变更)
text= - 属性(若非动态则稳定)
id - CSS类(稳定性较差,可能随样式变更)
- XPath(易失效,尽量避免使用)
Wait Strategies
等待策略
Load State Waits
加载状态等待
Essential for dynamic applications:
python
undefined对动态应用至关重要:
python
undefinedWait for network to be idle (most common)
等待网络空闲(最常用)
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
Wait for DOM to be ready
等待DOM加载完成
page.wait_for_load_state('domcontentloaded')
page.wait_for_load_state('domcontentloaded')
Wait for full load including images
等待包括图片在内的全部资源加载完成
page.wait_for_load_state('load')
undefinedpage.wait_for_load_state('load')
undefinedElement Waits
元素等待
Wait for specific elements before interacting:
python
undefined在交互前等待特定元素:
python
undefinedWait for element to be visible
等待元素可见
page.wait_for_selector('button.submit', state='visible')
page.wait_for_selector('button.submit', state='visible')
Wait for element to be hidden
等待元素隐藏
page.wait_for_selector('.loading-spinner', state='hidden')
page.wait_for_selector('.loading-spinner', state='hidden')
Wait for element to exist in DOM (may not be visible)
等待元素存在于DOM中(可能不可见)
page.wait_for_selector('.modal', state='attached')
page.wait_for_selector('.modal', state='attached')
Wait for element to be removed from DOM
等待元素从DOM中移除
page.wait_for_selector('.error-message', state='detached')
undefinedpage.wait_for_selector('.error-message', state='detached')
undefinedTimeout Waits
超时等待
Fixed time delays (use sparingly):
python
undefined固定时长延迟(谨慎使用):
python
undefinedWait for animations to complete
等待动画完成
page.wait_for_timeout(500)
page.wait_for_timeout(500)
Wait for delayed content (better to use wait_for_selector)
等待延迟加载的内容(优先使用wait_for_selector)
page.wait_for_timeout(2000)
undefinedpage.wait_for_timeout(2000)
undefinedCustom Wait Conditions
自定义等待条件
Wait for JavaScript conditions:
python
undefined等待JavaScript条件满足:
python
undefinedWait for custom JavaScript condition
等待自定义JavaScript条件
page.wait_for_function('() => document.querySelector(".data").innerText !== "Loading..."')
page.wait_for_function('() => document.querySelector(".data").innerText !== "Loading..."')
Wait for variable to be set
等待变量被设置
page.wait_for_function('() => window.appReady === true')
undefinedpage.wait_for_function('() => window.appReady === true')
undefinedAuto-Waiting
自动等待
Playwright automatically waits for elements to be actionable:
python
undefinedPlaywright会自动等待元素处于可交互状态:
python
undefinedThese automatically wait for element to be:
以下操作会自动等待元素满足:
- Visible
- 可见
- Stable (not animating)
- 稳定(无动画)
- Enabled (not disabled)
- 启用状态(未禁用)
- Not obscured by other elements
- 未被其他元素遮挡
page.click('button.submit') # Auto-waits
page.fill('input.email', 'test@example.com') # Auto-waits
undefinedpage.click('button.submit') # 自动等待
page.fill('input.email', 'test@example.com') # 自动等待
undefinedElement Interactions
元素交互
Clicking
点击操作
python
undefinedpython
undefinedBasic click
基础点击
page.click('button.submit')
page.click('button.submit')
Click with options
带参数的点击
page.click('button.submit', button='right') # Right-click
page.click('button.submit', click_count=2) # Double-click
page.click('button.submit', modifiers=['Control']) # Ctrl+click
page.click('button.submit', button='right') # 右键点击
page.click('button.submit', click_count=2) # 双击
page.click('button.submit', modifiers=['Control']) # Ctrl+点击
Force click (bypass actionability checks)
强制点击(跳过可交互性检查)
page.click('button.submit', force=True)
undefinedpage.click('button.submit', force=True)
undefinedFilling Forms
表单填充
python
undefinedpython
undefinedText inputs
文本输入
page.fill('input[name="email"]', 'user@example.com')
page.type('input[name="search"]', 'query', delay=100) # Type with delay
page.fill('input[name="email"]', 'user@example.com')
page.type('input[name="search"]', 'query', delay=100) # 带延迟的输入
Clear then fill
清空后填充
page.fill('input[name="email"]', '')
page.fill('input[name="email"]', 'new@example.com')
page.fill('input[name="email"]', '')
page.fill('input[name="email"]', 'new@example.com')
Press keys
按键操作
page.press('input[name="search"]', 'Enter')
page.press('input[name="text"]', 'Control+A')
undefinedpage.press('input[name="search"]', 'Enter')
page.press('input[name="text"]', 'Control+A')
undefinedDropdowns and Selects
下拉菜单选择
python
undefinedpython
undefinedSelect by label
通过标签选择
page.select_option('select[name="country"]', label='United States')
page.select_option('select[name="country"]', label='United States')
Select by value
通过值选择
page.select_option('select[name="country"]', value='us')
page.select_option('select[name="country"]', value='us')
Select by index
通过索引选择
page.select_option('select[name="country"]', index=2)
page.select_option('select[name="country"]', index=2)
Select multiple options
选择多个选项
page.select_option('select[multiple]', ['option1', 'option2'])
undefinedpage.select_option('select[multiple]', ['option1', 'option2'])
undefinedCheckboxes and Radio Buttons
复选框与单选按钮
python
undefinedpython
undefinedCheck a checkbox
勾选复选框
page.check('input[type="checkbox"]')
page.check('input[type="checkbox"]')
Uncheck a checkbox
取消勾选复选框
page.uncheck('input[type="checkbox"]')
page.uncheck('input[type="checkbox"]')
Check a radio button
选中单选按钮
page.check('input[value="option1"]')
page.check('input[value="option1"]')
Toggle checkbox
切换复选框状态
if page.is_checked('input[type="checkbox"]'):
page.uncheck('input[type="checkbox"]')
else:
page.check('input[type="checkbox"]')
undefinedif page.is_checked('input[type="checkbox"]'):
page.uncheck('input[type="checkbox"]')
else:
page.check('input[type="checkbox"]')
undefinedFile Uploads
文件上传
python
undefinedpython
undefinedUpload single file
上传单个文件
page.set_input_files('input[type="file"]', '/path/to/file.pdf')
page.set_input_files('input[type="file"]', '/path/to/file.pdf')
Upload multiple files
上传多个文件
page.set_input_files('input[type="file"]', ['/path/to/file1.pdf', '/path/to/file2.pdf'])
page.set_input_files('input[type="file"]', ['/path/to/file1.pdf', '/path/to/file2.pdf'])
Clear file input
清空文件输入
page.set_input_files('input[type="file"]', [])
undefinedpage.set_input_files('input[type="file"]', [])
undefinedHover and Focus
悬停与聚焦
python
undefinedpython
undefinedHover over element
悬停在元素上
page.hover('button.tooltip-trigger')
page.hover('button.tooltip-trigger')
Focus element
聚焦元素
page.focus('input[name="email"]')
page.focus('input[name="email"]')
Blur element
失焦元素
page.evaluate('document.activeElement.blur()')
undefinedpage.evaluate('document.activeElement.blur()')
undefinedAssertions
断言
Element Visibility
元素可见性
python
from playwright.sync_api import expectpython
from playwright.sync_api import expectExpect element to be visible
断言元素可见
expect(page.locator('button.submit')).to_be_visible()
expect(page.locator('button.submit')).to_be_visible()
Expect element to be hidden
断言元素隐藏
expect(page.locator('.error-message')).to_be_hidden()
undefinedexpect(page.locator('.error-message')).to_be_hidden()
undefinedText Content
文本内容
python
undefinedpython
undefinedExpect exact text
断言文本完全匹配
expect(page.locator('.title')).to_have_text('Welcome')
expect(page.locator('.title')).to_have_text('Welcome')
Expect partial text
断言包含指定文本
expect(page.locator('.message')).to_contain_text('success')
expect(page.locator('.message')).to_contain_text('success')
Expect text matching pattern
断言文本匹配正则
expect(page.locator('.code')).to_have_text(re.compile(r'\d{6}'))
undefinedexpect(page.locator('.code')).to_have_text(re.compile(r'\d{6}'))
undefinedElement State
元素状态
python
undefinedpython
undefinedExpect element to be enabled/disabled
断言元素启用/禁用
expect(page.locator('button.submit')).to_be_enabled()
expect(page.locator('button.submit')).to_be_disabled()
expect(page.locator('button.submit')).to_be_enabled()
expect(page.locator('button.submit')).to_be_disabled()
Expect checkbox to be checked
断言复选框已勾选
expect(page.locator('input[type="checkbox"]')).to_be_checked()
expect(page.locator('input[type="checkbox"]')).to_be_checked()
Expect element to be editable
断言元素可编辑
expect(page.locator('input[name="email"]')).to_be_editable()
undefinedexpect(page.locator('input[name="email"]')).to_be_editable()
undefinedAttributes and Values
属性与值
python
undefinedpython
undefinedExpect attribute value
断言属性值
expect(page.locator('img')).to_have_attribute('src', '/logo.png')
expect(page.locator('img')).to_have_attribute('src', '/logo.png')
Expect CSS class
断言CSS类
expect(page.locator('button')).to_have_class('btn-primary')
expect(page.locator('button')).to_have_class('btn-primary')
Expect input value
断言输入框值
expect(page.locator('input[name="email"]')).to_have_value('user@example.com')
undefinedexpect(page.locator('input[name="email"]')).to_have_value('user@example.com')
undefinedCount and Collections
数量与集合
python
undefinedpython
undefinedExpect specific count
断言元素数量
expect(page.locator('li')).to_have_count(5)
expect(page.locator('li')).to_have_count(5)
Get all elements and assert
获取所有元素并断言
items = page.locator('li').all()
assert len(items) == 5
undefineditems = page.locator('li').all()
assert len(items) == 5
undefinedTest Organization
测试组织
Basic Test Structure
基础测试结构
python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Test logic here
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
browser.close()python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# 测试逻辑
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
browser.close()Using Pytest (Recommended)
使用Pytest(推荐)
python
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture(scope="session")
def browser():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
yield browser
browser.close()
@pytest.fixture
def page(browser):
page = browser.new_page()
yield page
page.close()
def test_login(page):
page.goto('http://localhost:3000')
page.fill('input[name="email"]', 'user@example.com')
page.fill('input[name="password"]', 'password123')
page.click('button[type="submit"]')
expect(page.locator('.welcome-message')).to_be_visible()python
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture(scope="session")
def browser():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
yield browser
browser.close()
@pytest.fixture
def page(browser):
page = browser.new_page()
yield page
page.close()
def test_login(page):
page.goto('http://localhost:3000')
page.fill('input[name="email"]', 'user@example.com')
page.fill('input[name="password"]', 'password123')
page.click('button[type="submit"]')
expect(page.locator('.welcome-message')).to_be_visible()Test Grouping with Describe Blocks
使用Describe块分组测试
python
class TestAuthentication:
def test_successful_login(self, page):
# Test successful login
pass
def test_failed_login(self, page):
# Test failed login
pass
def test_logout(self, page):
# Test logout
passpython
class TestAuthentication:
def test_successful_login(self, page):
# 测试登录成功场景
pass
def test_failed_login(self, page):
# 测试登录失败场景
pass
def test_logout(self, page):
# 测试登出场景
passSetup and Teardown
前置与后置操作
python
@pytest.fixture(autouse=True)
def setup_and_teardown(page):
# Setup - runs before each test
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
yield # Test runs here
# Teardown - runs after each test
page.evaluate('localStorage.clear()')python
@pytest.fixture(autouse=True)
def setup_and_teardown(page):
# 前置操作 - 每个测试前执行
page.goto('http://localhost:3000')
page.wait_for_load_state('networkidle')
yield # 测试执行
# 后置操作 - 每个测试后执行
page.evaluate('localStorage.clear()')Network Interception
网络拦截
Mock API Responses
模拟API响应
python
undefinedpython
undefinedIntercept and mock API response
拦截并模拟API响应
def handle_route(route):
route.fulfill(
status=200,
body='{"success": true, "data": "mocked"}',
headers={'Content-Type': 'application/json'}
)
page.route('**/api/data', handle_route)
page.goto('http://localhost:3000')
undefineddef handle_route(route):
route.fulfill(
status=200,
body='{"success": true, "data": "mocked"}',
headers={'Content-Type': 'application/json'}
)
page.route('**/api/data', handle_route)
page.goto('http://localhost:3000')
undefinedBlock Resources
拦截资源
python
undefinedpython
undefinedBlock images and stylesheets for faster tests
拦截图片和样式表以加快测试速度
page.route('**/*.{png,jpg,jpeg,gif,svg,css}', lambda route: route.abort())
undefinedpage.route('**/*.{png,jpg,jpeg,gif,svg,css}', lambda route: route.abort())
undefinedWait for Network Responses
等待网络响应
python
undefinedpython
undefinedWait for specific API call
等待指定API请求完成
with page.expect_response('**/api/users') as response_info:
page.click('button.load-users')
response = response_info.value
assert response.status == 200
undefinedwith page.expect_response('**/api/users') as response_info:
page.click('button.load-users')
response = response_info.value
assert response.status == 200
undefinedScreenshots and Videos
截图与录屏
Screenshots
截图
python
undefinedpython
undefinedFull page screenshot
整页截图
page.screenshot(path='/tmp/screenshot.png', full_page=True)
page.screenshot(path='/tmp/screenshot.png', full_page=True)
Element screenshot
元素截图
page.locator('.modal').screenshot(path='/tmp/modal.png')
page.locator('.modal').screenshot(path='/tmp/modal.png')
Screenshot with custom dimensions
自定义视口尺寸截图
page.set_viewport_size({'width': 1920, 'height': 1080})
page.screenshot(path='/tmp/desktop.png')
undefinedpage.set_viewport_size({'width': 1920, 'height': 1080})
page.screenshot(path='/tmp/desktop.png')
undefinedVideo Recording
录屏
python
browser = p.chromium.launch(headless=True)
context = browser.new_context(record_video_dir='/tmp/videos/')
page = context.new_page()python
browser = p.chromium.launch(headless=True)
context = browser.new_context(record_video_dir='/tmp/videos/')
page = context.new_page()Perform actions...
执行操作...
context.close() # Video saved on close
undefinedcontext.close() # 关闭时保存视频
undefinedDebugging
调试
Pause Execution
暂停执行
python
page.pause() # Opens Playwright Inspectorpython
page.pause() # 打开Playwright调试器Console Logs
控制台日志
python
def handle_console(msg):
print(f"[{msg.type}] {msg.text}")
page.on("console", handle_console)python
def handle_console(msg):
print(f"[{msg.type}] {msg.text}")
page.on("console", handle_console)Slow Motion
慢动作执行
python
browser = p.chromium.launch(headless=False, slow_mo=1000) # 1 second delaypython
browser = p.chromium.launch(headless=False, slow_mo=1000) # 1秒延迟Verbose Logging
详细日志
python
undefinedpython
undefinedSet DEBUG environment variable
设置DEBUG环境变量
DEBUG=pw:api python test.py
DEBUG=pw:api python test.py
undefinedundefinedParallel Execution
并行执行
Pytest Parallel
Pytest并行执行
bash
undefinedbash
undefinedInstall pytest-xdist
安装pytest-xdist
pip install pytest-xdist
pip install pytest-xdist
Run tests in parallel
并行运行测试
pytest -n auto # Auto-detect CPU cores
pytest -n 4 # Run with 4 workers
undefinedpytest -n auto # 自动检测CPU核心数
pytest -n 4 # 使用4个进程
undefinedBrowser Context Isolation
浏览器上下文隔离
python
undefinedpython
undefinedEach test gets isolated context (cookies, localStorage, etc.)
每个测试使用独立的上下文(Cookie、localStorage等)
@pytest.fixture
def context(browser):
context = browser.new_context()
yield context
context.close()
@pytest.fixture
def page(context):
return context.new_page()
undefined@pytest.fixture
def context(browser):
context = browser.new_context()
yield context
context.close()
@pytest.fixture
def page(context):
return context.new_page()
undefined