webapp-testing-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Playwright 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):
  1. data-testid
    attributes (most stable)
  2. role=
    selectors (semantic, accessible)
  3. text=
    selectors (readable, but text may change)
  4. id
    attributes (stable if not dynamic)
  5. CSS classes (less stable, may change with styling)
  6. XPath (fragile, avoid if possible)
优先级排序(从最稳定到最不稳定):
  1. data-testid
    属性(最稳定)
  2. role=
    选择器(语义化、可访问性友好)
  3. text=
    选择器(可读性强,但文本可能变更)
  4. id
    属性(若非动态则稳定)
  5. CSS类(稳定性较差,可能随样式变更)
  6. XPath(易失效,尽量避免使用)

Wait Strategies

等待策略

Load State Waits

加载状态等待

Essential for dynamic applications:
python
undefined
对动态应用至关重要:
python
undefined

Wait 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')
undefined
page.wait_for_load_state('load')
undefined

Element Waits

元素等待

Wait for specific elements before interacting:
python
undefined
在交互前等待特定元素:
python
undefined

Wait 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')
undefined
page.wait_for_selector('.error-message', state='detached')
undefined

Timeout Waits

超时等待

Fixed time delays (use sparingly):
python
undefined
固定时长延迟(谨慎使用):
python
undefined

Wait 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)
undefined
page.wait_for_timeout(2000)
undefined

Custom Wait Conditions

自定义等待条件

Wait for JavaScript conditions:
python
undefined
等待JavaScript条件满足:
python
undefined

Wait 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')
undefined
page.wait_for_function('() => window.appReady === true')
undefined

Auto-Waiting

自动等待

Playwright automatically waits for elements to be actionable:
python
undefined
Playwright会自动等待元素处于可交互状态:
python
undefined

These 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
undefined
page.click('button.submit') # 自动等待 page.fill('input.email', 'test@example.com') # 自动等待
undefined

Element Interactions

元素交互

Clicking

点击操作

python
undefined
python
undefined

Basic 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)
undefined
page.click('button.submit', force=True)
undefined

Filling Forms

表单填充

python
undefined
python
undefined

Text 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')
undefined
page.press('input[name="search"]', 'Enter') page.press('input[name="text"]', 'Control+A')
undefined

Dropdowns and Selects

下拉菜单选择

python
undefined
python
undefined

Select 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'])
undefined
page.select_option('select[multiple]', ['option1', 'option2'])
undefined

Checkboxes and Radio Buttons

复选框与单选按钮

python
undefined
python
undefined

Check 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"]')
undefined
if page.is_checked('input[type="checkbox"]'): page.uncheck('input[type="checkbox"]') else: page.check('input[type="checkbox"]')
undefined

File Uploads

文件上传

python
undefined
python
undefined

Upload 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"]', [])
undefined
page.set_input_files('input[type="file"]', [])
undefined

Hover and Focus

悬停与聚焦

python
undefined
python
undefined

Hover 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()')
undefined
page.evaluate('document.activeElement.blur()')
undefined

Assertions

断言

Element Visibility

元素可见性

python
from playwright.sync_api import expect
python
from playwright.sync_api import expect

Expect 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()
undefined
expect(page.locator('.error-message')).to_be_hidden()
undefined

Text Content

文本内容

python
undefined
python
undefined

Expect 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}'))
undefined
expect(page.locator('.code')).to_have_text(re.compile(r'\d{6}'))
undefined

Element State

元素状态

python
undefined
python
undefined

Expect 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()
undefined
expect(page.locator('input[name="email"]')).to_be_editable()
undefined

Attributes and Values

属性与值

python
undefined
python
undefined

Expect 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')
undefined
expect(page.locator('input[name="email"]')).to_have_value('user@example.com')
undefined

Count and Collections

数量与集合

python
undefined
python
undefined

Expect 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
undefined
items = page.locator('li').all() assert len(items) == 5
undefined

Test 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
        pass
python
class TestAuthentication:
    def test_successful_login(self, page):
        # 测试登录成功场景
        pass

    def test_failed_login(self, page):
        # 测试登录失败场景
        pass

    def test_logout(self, page):
        # 测试登出场景
        pass

Setup 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
undefined
python
undefined

Intercept 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')
undefined
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')
undefined

Block Resources

拦截资源

python
undefined
python
undefined

Block images and stylesheets for faster tests

拦截图片和样式表以加快测试速度

page.route('**/*.{png,jpg,jpeg,gif,svg,css}', lambda route: route.abort())
undefined
page.route('**/*.{png,jpg,jpeg,gif,svg,css}', lambda route: route.abort())
undefined

Wait for Network Responses

等待网络响应

python
undefined
python
undefined

Wait 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
undefined
with page.expect_response('**/api/users') as response_info: page.click('button.load-users') response = response_info.value assert response.status == 200
undefined

Screenshots and Videos

截图与录屏

Screenshots

截图

python
undefined
python
undefined

Full 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')
undefined
page.set_viewport_size({'width': 1920, 'height': 1080}) page.screenshot(path='/tmp/desktop.png')
undefined

Video 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
undefined
context.close() # 关闭时保存视频
undefined

Debugging

调试

Pause Execution

暂停执行

python
page.pause()  # Opens Playwright Inspector
python
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 delay
python
browser = p.chromium.launch(headless=False, slow_mo=1000)  # 1秒延迟

Verbose Logging

详细日志

python
undefined
python
undefined

Set DEBUG environment variable

设置DEBUG环境变量

DEBUG=pw:api python test.py

DEBUG=pw:api python test.py

undefined
undefined

Parallel Execution

并行执行

Pytest Parallel

Pytest并行执行

bash
undefined
bash
undefined

Install 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
undefined
pytest -n auto # 自动检测CPU核心数 pytest -n 4 # 使用4个进程
undefined

Browser Context Isolation

浏览器上下文隔离

python
undefined
python
undefined

Each 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