最近在团队内部推动自动化测试落地时,发现不少同事对Playwright这个新兴工具既感兴趣又存在使用困惑。作为一款支持多语言(JavaScript/TypeScript、Python、.NET、Java)的现代化测试框架,Playwright确实比传统Selenium在稳定性和功能丰富度上有着明显优势。今天我就结合最近三个月的实战经验,重点分享脚本录制和元素定位这两个最基础也最关键的环节。
以Python环境为例(其他语言逻辑类似),建议使用virtualenv创建隔离环境:
bash复制python -m venv playwright-env
source playwright-env/bin/activate # Linux/Mac
playwright-env\Scripts\activate.bat # Windows
pip install playwright
playwright install # 安装浏览器驱动
注意:Playwright默认会下载Chromium、Firefox和WebKit三大浏览器引擎,首次安装需要保证网络畅通。国内用户若遇到下载慢的问题,可以通过设置环境变量
PLAYWRIGHT_DOWNLOAD_HOST指向国内镜像源。
Playwright自带的Codegen工具是录制神器,启动命令如下:
bash复制playwright codegen https://example.com
这个命令会同时打开两个窗口:浏览器操作窗口和脚本生成窗口。在实际使用中发现几个实用技巧:
--target参数指定生成语言,如--target=python--save-storage参数记录登录状态,避免每次录制都需要重复登录--viewport-size设置常用分辨率Playwright提供7种定位器(Locator),实测下来各自的适用场景如下表所示:
| 定位方式 | 示例代码 | 适用场景 | 稳定性评估 |
|---|---|---|---|
| get_by_role | page.get_by_role("button") | 标准化的ARIA角色元素 | ★★★★★ |
| get_by_text | page.get_by_text("Submit") | 可见文本匹配 | ★★★★☆ |
| get_by_label | page.get_by_label("Username") | 表单标签关联 | ★★★★★ |
| get_by_placeholder | page.get_by_placeholder("Search") | 输入框提示文本 | ★★★☆☆ |
| get_by_alt_text | page.get_by_alt_text("logo") | 图片alt属性 | ★★★★☆ |
| get_by_title | page.get_by_title("tooltip") | title属性元素 | ★★★☆☆ |
| get_by_test_id | page.get_by_test_id("nav-menu") | 专为测试添加的data-testid属性 | ★★★★★ |
经过多个项目的实战验证,总结出以下黄金法则:
and组合条件:python复制page.get_by_role("button").and_(page.get_by_text("Sign in"))
timeout参数调整:python复制page.get_by_text("Loading...").wait_for(timeout=5000) # 5秒
常见踩坑点:
直接录制的脚本通常存在以下典型问题:
python复制page.goto("https://example.com")
page.get_by_role("link", name="Login").click() # 硬编码文本
page.get_by_label("Username").fill("testuser") # 固定测试数据
page.get_by_label("Password").fill("password123")
page.get_by_role("button", name="Sign in").click()
这段代码至少有3个改进点:
优化后的版本应该包含这些要素:
python复制import os
from playwright.sync_api import expect
def test_login(page):
# 环境变量管理敏感信息
username = os.getenv("TEST_USER")
password = os.getenv("TEST_PWD")
# 添加操作注释
page.goto("https://example.com")
# 使用更稳定的定位方式
with page.expect_navigation():
page.get_by_test_id("login-btn").click()
# 添加断言验证
page.get_by_label("Username").fill(username)
page.get_by_label("Password").fill(password)
page.get_by_role("button", name=/sign in/i).click() # 正则匹配忽略大小写
# 验证登录成功
expect(page.get_by_text("Welcome back")).to_be_visible()
expect(page).to_have_url(/dashboard/) # URL验证
虽然Playwright推荐使用语义化定位器,但某些特殊场景仍需传统方式:
python复制# 父元素链式定位
page.locator("div:has-text('Cart') >> span.count")
# 复杂的XPath定位
page.locator("xpath=//button[contains(@class, 'primary') and not(disabled)]")
# 相对定位(距离某个元素50px右侧的按钮)
page.locator("button").filter(has_text="Submit").locator("..")
重要提示:XPath定位在React/Vue等框架中稳定性较差,建议仅在传统网页中使用
当定位出现问题时,我的标准排查流程:
bash复制PWDEBUG=1 pytest test.py
javascript复制$x('your_xpath') // 测试XPath
document.querySelectorAll('your_selector') // 测试CSS
python复制context.tracing.start(screenshots=True, snapshots=True)
# 执行测试...
context.tracing.stop(path="trace.zip")
除了默认的自动等待,还有多种等待方式可选:
python复制# 显式等待元素状态变化
page.get_by_role("button").wait_for(state="attached")
# 自定义等待条件
def button_has_class(button, class_name):
return class_name in button.get_attribute("class")
page.get_by_text("Submit").wait_for(button_has_class, args=["active"])
# 网络请求等待
with page.expect_response("**/api/data"):
page.get_by_text("Load Data").click()
对于难以用常规属性定位的元素,可以考虑视觉匹配:
python复制# 通过截图像素定位(需安装opencv-python)
from cv2 import matchTemplate
def find_image_position(page, template_path):
screenshot = page.screenshot()
# 图像匹配算法实现...
return x, y
page.mouse.click(*find_image_position(page, "button.png"))
不过这种方法存在明显局限:
建议仅作为最后手段,且要对图片进行模糊匹配处理。
在大型项目中落地Playwright时,推荐采用以下架构:
code复制tests/
├── locators/
│ ├── login_page.py # 页面元素集中管理
│ └── dashboard.py
├── data/
│ └── test_accounts.json
└── cases/
└── test_login.py
其中locators文件示例:
python复制# locators/login_page.py
from playwright.sync_api import Page
class LoginLocators:
def __init__(self, page: Page):
self.page = page
@property
def username(self):
return self.page.get_by_label("Username")
@property
def password(self):
return self.page.get_by_label("Password")
@property
def submit_button(self):
return self.page.get_by_test_id("login-submit")
这种模式虽然初期投入较大,但能显著提升后期维护效率,特别适合:
最后分享一个真实案例:在某电商项目中将定位器集中管理后,某次首页改版涉及的200+测试用例,仅通过修改3个定位器文件就完成了适配,维护时间从原来的2人日缩短到0.5小时。