PlayWright¶
CDP 是( Chrome DevTools Protocol) Google 开发的协议,允许外部工具(如 Playwright、Chrome DevTools)直接控制 Chrome 或 Chromium 浏览器。
常见并发量¶
场景 | 并发量 | 举例 |
---|---|---|
小型网站 | 100~1000 | 普通企业官网、博客 |
中型网站 | 1000~1 万 | 中小型电商、论坛 |
大型网站 | 1 万~10 万 | 大型电商(非高峰期)、新闻网站 |
超大型网站 | 10 万以上 | 淘宝双 11、12306 抢票 |
浏览器实例只创建一次,而不是每次循环都创建新的浏览器。具体结构是:¶
- 浏览器实例 (
browser
):整个程序运行期间只创建一次 - 浏览器上下文 (
context
):在浏览器实例内部创建,整个循环过程中重用同一个上下文 - 页面 (
page
):每次循环都会创建一个新页面,执行完后关闭
这种结构类似于:
- 浏览器实例 = 浏览器程序(如 Chrome.exe)
- 浏览器上下文 = 浏览器中的一个用户配置文件(如 Chrome 的不同用户)
- 页面 = 浏览器中的标签页
重用浏览器上下文的优势¶
- 共享状态:同一个上下文中的所有页面共享 Cookie、本地存储等状态
- 资源效率:创建新的浏览器实例非常消耗资源,而创建新页面几乎不消耗额外资源
- 速度更快:重用浏览器上下文可以避免浏览器启动的开销
与方案二的对比¶
特征 | 方案一 | 方案二 |
---|---|---|
浏览器实例 | 1 个(整个程序运行期间) | 1 个(整个程序运行期间) |
浏览器上下文 | 1 个(循环中重用) | 每个循环创建新的上下文 |
页面 | 每个循环创建新页面 | 每个循环创建新页面 |
Cookie 状态 | 共享(同一个上下文中) | 每次重新加载存储状态 |
资源消耗 | 低(重用上下文) | 中(创建新上下文) |
性能 | 高(上下文只需初始化一次) | 中(每次循环初始化上下文) |
总结¶
方案一中的浏览器实例不是全新的,而是在整个程序运行期间只创建一次。每次循环只会创建新的页面,而浏览器上下文是重用的,这样可以保持 Cookie 状态并提高性能。
如果您需要完全隔离的环境(例如测试不同用户的登录状态),可以选择方案二。但如果您只是想定期检查 Cookie 是否有效,方案一更高效。
在 Playwright 中,同步模式下使用 with
语句可以简化资源管理,无需手动关闭浏览器和页面。以下是同步模式的示例代码:¶
1. 基本用法:启动浏览器并创建页面¶
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 启动浏览器(支持 chromium、firefox、webkit)
browser = p.chromium.launch(headless=False) # 非无头模式,可看到浏览器窗口
# 创建页面
page = browser.new_page()
page.goto("https://example.com")
print(page.title()) # 输出页面标题
# 无需手动关闭,with 块结束后自动释放资源
2. 自定义浏览器启动参数¶
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
slow_mo=500, # 操作延迟(毫秒)
args=["--window-size=1920,1080"] # 浏览器窗口大小
)
page = browser.new_page()
page.goto("https://example.com")
# 执行其他操作...
3. 使用浏览器上下文(Context)¶
浏览器上下文用于隔离 Cookie、本地存储等数据:
with sync_playwright() as p:
browser = p.chromium.launch()
# 创建独立上下文
with browser.new_context() as context:
page = context.new_page()
page.goto("https://example.com")
page.fill("input[name='username']", "test_user")
page.screenshot(path="screenshot.png")
核心区别对比¶
场景 | 不创建独立上下文 | 创建独立上下文 |
---|---|---|
Cookie / 会话共享 | 所有页面共享同一套 Cookie 和会话 | 每个上下文独立管理 Cookie 和会话 |
内存占用 | 所有页面共用一个上下文,占用少 | 每个上下文独立,内存占用更高 |
多用户测试 | 难:需手动清理 Cookie 切换用户 | 易:创建多个上下文并行测试不同用户 |
无痕模式 | 不支持(除非手动清除数据) | 支持(上下文默认类似无痕模式) |
资源释放 | 关闭浏览器时统一释放 | 上下文结束时自动释放,更灵活 |
4. 创建多个页面¶
with sync_playwright() as p:
browser = p.chromium.launch()
# 创建多个页面
page1 = browser.new_page()
page2 = browser.new_page()
page1.goto("https://example.com")
page2.goto("https://playwright.dev")
print(page1.title()) # 输出第一个页面标题
print(page2.title()) # 输出第二个页面标题
关键说明¶
- 同步 vs 异步:同步模式(无
async/await
)适合简单脚本,异步模式适合高并发场景。 - 资源管理:
with
语句会自动关闭浏览器、上下文和页面,无需手动调用browser.close()
。 - 执行顺序:代码按顺序执行,适合线性自动化流程。
在 Playwright 中,上传文件主要通过 set_input_files
方法实现,以下分**普通场景**(页面有 <input type="file">
元素 )和**特殊场景**(文件选择框由动态事件触发 )详细说明写法,结合 Python 示例:¶
一、普通场景:页面存在 <input type="file">
元素¶
如果页面里有明确的文件上传输入框(即 <input type="file">
标签 ),直接定位该元素,调用 set_input_files
传入文件路径即可,支持单文件、多文件上传。
1. 单文件上传示例¶
from playwright.sync_api import sync_playwright
def upload_single_file():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # 非无头模式,方便查看过程
page = browser.new_page()
page.goto("https://example.com/upload") # 替换为实际上传页面地址
# 定位文件上传 input 元素,可通过 CSS 选择器、Label 等方式,这里以 CSS 为例
file_input = page.locator('input[type="file"]')
# 上传单个文件,传入文件的绝对路径
file_input.set_input_files("D:\\test\\single_file.pdf")
# 若有提交按钮,可模拟点击提交(根据实际页面调整)
page.locator('button[type="submit"]').click()
browser.close()
upload_single_file()
2. 多文件上传示例¶
from playwright.sync_api import sync_playwright
def upload_multiple_files():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com/upload")
file_input = page.locator('input[type="file"]')
# 上传多个文件,传入文件路径列表
file_input.set_input_files([
"D:\\test\\file1.pdf",
"D:\\test\\file2.png"
])
# 模拟提交(按需调整)
page.locator('button[type="submit"]').click()
browser.close()
upload_multiple_files()
二、特殊场景:文件选择框由点击事件动态触发¶
有些页面的文件上传,不是直接显示 <input type="file">
元素,而是点击某个按钮后,才弹出系统文件选择框。这时需要用 page.expect_file_chooser()
监听文件选择框事件,再操作上传。
示例代码:
from playwright.sync_api import sync_playwright
def upload_with_file_chooser():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com/upload")
# 先监听文件选择框事件,再点击触发上传的按钮
with page.expect_file_chooser() as fc_info:
# 点击触发文件选择框的按钮(比如“选择文件”按钮,根据实际页面定位)
page.locator('text=选择文件').click()
file_chooser = fc_info.value # 获取文件选择框对象
# 上传文件,支持单文件或多文件列表
file_chooser.set_files("D:\\test\\target_file.pdf")
# 若多文件:file_chooser.set_files(["file1.pdf", "file2.pdf"])
# 若有后续提交等操作,继续执行
page.locator('button[type="submit"]').click()
browser.close()
upload_with_file_chooser()
关键要点说明¶
- 路径格式:Windows 系统里文件路径用
\\
或原始字符串r"D:\test\file.pdf"
;Linux、macOS 用/
分隔路径,如/Users/user/test/file.pdf
。 - 元素定位:除了示例里的 CSS 选择器、文本定位,还能用
page.get_by_label("上传文件")
(根据标签关联文本定位 )等 Playwright 丰富的定位策略,精准找到文件上传控件。 - 异步处理:如果用 Playwright 异步 API(
async_playwright
),需配合async
/await
,逻辑和同步类似,只是语法调整为异步风格,比如await page.locator(...).click()
。
按照页面实际的上传控件形式,选对应方式就能实现文件上传自动化,解决手动操作繁琐问题,尤其适合批量上传、自动化测试等场景 。
无头浏览器**主要区别在于运行方式**¶
特性 | 无头浏览器(默认) | 有头浏览器(显式启用) |
---|---|---|
界面显示 | 无浏览器窗口,后台运行 | 显示真实浏览器窗口,可观察操作过程 |
调试便利性 | 需依赖日志、截图或视频 | 可直接查看页面状态,便于调试 |
性能 | 通常更快(无需渲染界面) | 略慢(需处理界面渲染) |
默认启动参数 | 部分浏览器可能使用不同的默认参数(如 Chromium 的沙盒模式) | 与真实用户使用的浏览器更接近 |
在 Playwright 中,判断元素是否存在于页面上有多种方式,具体取决于你的场景需求。以下是几种常见方法及其适用场景:¶
1. 使用 query_selector()
(同步检查,不等待)¶
直接查询元素,立即返回结果(无论元素是否可见)。
element = page.query_selector("selector")
if element:
print("元素存在(无论是否可见)")
else:
print("元素不存在")
2. 使用 is_visible()
或 is_hidden()
(判断可见性)¶
element = page.locator("selector")
if element.is_visible():
print("元素可见")
else:
print("元素隐藏或不存在")
核心区别对比¶
条件 | is_visible() |
is_hidden() |
---|---|---|
元素存在且可见 | True |
False |
元素存在但被隐藏(如 display: none ) |
False |
True |
元素不存在 | False |
True |
3. 使用 wait_for_selector()
(带超时的等待)¶
- 等待元素出现:
try:
page.wait_for_selector("selector", timeout=3000) # 超时时间3秒
print("元素存在或已出现")
except TimeoutError:
print("元素不存在或超时未出现")
- 等待元素消失:
try:
page.wait_for_selector("selector", timeout=3000, state="detached")
print("元素不存在或已消失")
except TimeoutError:
print("元素仍然存在")
4. 使用 count()
方法(批量判断)¶
elements = page.locator("selector")
if elements.count() > 0:
print("存在至少一个匹配的元素")
else:
print("没有匹配的元素")
选择建议¶
- 立即判断(不等待):用
query_selector()
或count()
。 - 判断可见性:用
is_visible()
或is_hidden()
。 - 等待元素状态变化:用
wait_for_selector()
并指定state
参数("attached"
、"detached"
、"visible"
、"hidden"
)。
在 Playwright 中,"录制" 通常指两种功能:操作视频录制**和**代码生成录制器。下面分别介绍它们的用法:¶
一、录制操作视频(Video Recording)¶
通过 recordVideo
选项可录制浏览器操作的视频,适用于测试回放和问题定位。
基础用法¶
在创建浏览器上下文时启用视频录制,并指定保存目录:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
record_video_dir="videos/" # 视频保存目录
)
page = context.new_page()
page.goto("https://example.com")
page.click("text=Sign Up")
# 获取视频路径(需在关闭上下文后)
video_path = page.video.path()
print(f"视频已保存至: {video_path}")
context.close()
browser.close()
关键参数¶
record_video_dir
(必需):视频保存目录。record_video_size
(可选):视频分辨率,如{"width": 800, "height": 600}
。
异步模式用法¶
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(record_video_dir="videos/")
page = await context.new_page()
await page.goto("https://example.com")
# 关闭上下文后获取视频路径
await context.close()
video_path = await page.video.path()
print(f"视频保存路径: {video_path}")
asyncio.run(main())
二、代码生成录制器(Codegen)¶
Playwright 提供的 codegen
工具可录制你的浏览器操作并自动生成对应的代码,适合快速创建测试脚本。
启动录制器¶
在终端中运行以下命令,然后手动操作浏览器:
playwright codegen https://example.com
- 操作会实时转换为代码并显示在终端。
- 支持多种语言(Python、JavaScript 等),使用
--lang python
指定语言。
常用选项¶
选项 | 作用 |
---|---|
--target python |
生成 Python 代码 |
--output script.py |
将代码保存到文件 |
--viewport-size 1920,1080 |
设置视口尺寸 |
--save-storage auth.json |
保存认证状态(如 Cookie) |
示例:生成登录测试代码¶
playwright codegen --target python --output test_login.py --viewport-size 1280,720 http://localhost:8080/
然后在浏览器中完成登录流程,工具会自动生成对应的 Python 代码。
Playwright 的 codegen
工具在录制过程中提供了一系列按钮,用于控制录制流程、调整设置和生成代码。以下是这些按钮的功能解释和使用场景:¶
一、主界面按钮¶
当你运行 playwright codegen https://example.com
后,会看到以下核心按钮:
1. 录制控制按钮¶
按钮图标 / 名称 | 功能描述 |
---|---|
▶️ Start Recording | 开始录制浏览器操作(默认自动开始)。 |
⏸️ Pause | 暂停录制,暂停后操作不会生成代码。 |
⏹️ Stop | 停止录制并退出 codegen。 |
🔄 Restart | 重置录制,清空已生成的代码并重新开始。 |
2. 代码控制按钮¶
按钮图标 / 名称 | 功能描述 |
---|---|
💾 Save | 将生成的代码保存到文件(如 script.py )。 |
📋 Copy | 复制生成的代码到剪贴板。 |
🌐 Change Language | 切换生成代码的语言(Python、JavaScript 等)。 |
🎯 Selector Highlight | 启用 / 禁用元素选择器高亮显示(鼠标悬停时显示匹配的元素)。 |
二、高级设置按钮¶
点击界面右上角的 ⚙️ Settings 按钮,可打开以下选项:
1. 录制选项¶
设置项 | 功能描述 |
---|---|
Slow Motion | 减慢操作速度,便于调试复杂交互。 |
Skip Navigation | 跳过页面导航操作(如 goto() ),只记录页面内交互。 |
Screenshots | 在操作步骤中插入截图代码(使用 page.screenshot() )。 |
Mask Text | 对敏感文本(如密码)进行掩码处理(生成 page.fill("input", "*****") )。 |
2. 选择器选项¶
设置项 | 功能描述 |
---|---|
Prefer CSS | 优先使用 CSS 选择器而非 XPath。 |
Use aria-* Attributes | 在选择器中包含 ARIA 属性(如 role 、aria-label )。 |
Use Data Attributes | 在选择器中包含自定义数据属性(如 data-testid )。 |
三、元素交互辅助按钮¶
在浏览器中操作时,鼠标悬停在元素上会显示辅助按钮:
1. 元素操作按钮¶
按钮图标 / 名称 | 功能描述 |
---|---|
🖱️ Click | 生成点击该元素的代码(等价于 page.click(selector) )。 |
📝 Fill | 生成填充文本到输入框的代码(等价于 page.fill(selector, "text") )。 |
🔍 Hover | 生成鼠标悬停的代码(等价于 page.hover(selector) )。 |
⏳ Wait for Element | 生成等待元素出现的代码(等价于 page.wait_for_selector(selector) )。 |
2. 选择器信息按钮¶
按钮图标 / 名称 | 功能描述 |
---|---|
📋 Copy Selector | 复制当前元素的选择器(如 button:has-text("Sign Up") )。 |
🔍 Inspect | 在浏览器开发者工具中检查该元素。 |
四、使用技巧¶
-
暂停录制复杂操作 遇到不需要录制的操作(如登录)时,点击 ⏸️ 暂停,操作完成后再继续录制。
-
自定义选择器 若自动生成的选择器不稳定,可手动编辑代码,使用更具描述性的选择器(如
get_by_role
或get_by_text
)。 -
结合调试选项
-
使用
--debug
选项启动 codegen,查看详细的录制日志。 -
使用
--target python-async
生成异步代码。 -
保存认证状态 使用
--save-storage auth.json
保存登录状态,避免重复录制登录流程:
playwright codegen --save-storage auth.json https://example.com/login
五、常见问题¶
- 按钮未生成预期代码
- 确保元素可交互(如按钮未被禁用)。
- 尝试使用
--wait-for-navigation
选项等待页面加载完成。 - 选择器过于复杂
- 在元素上添加
data-testid
属性,使选择器更简洁稳定。 - 手动编辑生成的代码,替换为
get_by_role
或get_by_text
。 - 无法录制某些操作
- 文件上传、弹窗等特殊操作可能需要手动补充代码。
- 使用
page.wait_for_event()
处理动态内容。
总结¶
Playwright Codegen 的录制按钮提供了直观的方式来生成自动化脚本,通过合理使用这些工具,你可以:
- 快速创建测试用例的基础框架。
- 学习 Playwright API 的使用方法。
- 定位复杂交互的选择器。
对于高级场景,建议手动优化生成的代码,结合语义化选择器(如 get_by_role
)和断言,使测试更健壮。
在 Playwright 中,page.get_by_role()
和 page.get_by_text()
是两种强大的元素定位方法,它们基于语义和文本内容定位元素,比传统的 CSS/XPath 选择器更直观、更具可读性。下面详细介绍它们的用法和区别:¶
一、page.get_by_role()
:基于语义角色定位¶
核心概念¶
- 角色(Role):指元素在页面中的语义功能,如
button
、link
、heading
、checkbox
等。 - 可访问性(Accessibility):该方法基于浏览器的无障碍树(Accessibility Tree)定位元素,更符合用户实际交互方式。
基本用法¶
# 定位"Sign Up"按钮
page.get_by_role("button", name="Sign Up").click()
# 定位一级标题
page.get_by_role("heading", level=1).text_content()
# 定位带有特定标签的链接
page.get_by_role("link", name="Learn more", exact=True).hover()
常用角色参数¶
角色类型 | 对应元素示例 |
---|---|
button |
<button> , <input type="button"> |
link |
<a href> |
heading |
<h1> 到 <h6> |
checkbox |
<input type="checkbox"> |
radio |
<input type="radio"> |
textbox |
<input type="text"> , <textarea> |
listitem |
<li> |
option |
<option> in <select> |
高级过滤¶
# 定位禁用的按钮
page.get_by_role("button", disabled=True)
# 定位包含特定文本的卡片
page.get_by_role("card").get_by_text("Featured Product")
# 定位第2个菜单项
page.get_by_role("menuitem").nth(1)
二、page.get_by_text()
:基于文本内容定位¶
核心概念¶
- 直接通过元素的可见文本内容定位,无需关心元素类型或结构。
- 支持模糊匹配(默认)和精确匹配(
exact=True
)。
基本用法¶
# 点击包含"Submit"的按钮(模糊匹配)
page.get_by_text("Submit").click()
# 精确匹配文本(必须完全一致)
page.get_by_text("Hello, world!", exact=True)
# 定位嵌套在特定元素中的文本
page.get_by_role("article").get_by_text("Read more")
文本匹配规则¶
- 模糊匹配:文本包含指定内容即可(如
"Sign"
匹配"Sign Up"
)。 - 精确匹配:文本必须与指定内容完全一致(大小写敏感)。
三、对比与选择建议¶
场景 | get_by_role() |
get_by_text() |
---|---|---|
已知元素语义角色 | ✅ 推荐(如按钮、链接) | ❌ 需先定位父元素 |
仅知文本内容 | ❌ 需猜测角色类型 | ✅ 直接定位 |
文本在特定元素内 | ❌ 需嵌套选择 | ✅ 可结合父元素定位 |
无障碍测试 | ✅ 基于无障碍树 | ❌ 不保证语义正确性 |
动态文本(如时间戳) | ✅ 可结合其他属性过滤 | ❌ 文本可能变化 |
四、组合使用示例¶
# 定位包含"Product"文本的卡片中的"Add to Cart"按钮
product_card = page.get_by_role("card").get_by_text("Product")
product_card.get_by_role("button", name="Add to Cart").click()
# 在导航栏中定位"About"链接
nav = page.get_by_role("navigation")
nav.get_by_role("link", name="About").hover()
五、最佳实践¶
- 优先使用
get_by_role()
:语义更明确,更贴近用户行为,代码更稳定。 - 结合使用:用
get_by_role()
定位容器,用get_by_text()
筛选内容。 - 避免过度依赖文本:如果文本可能变化(如翻译或动态内容),考虑使用
get_by_role()
结合其他属性。 - 精确匹配:使用
exact=True
避免意外匹配相似文本。
总结¶
get_by_role()
:基于元素语义角色定位,推荐用于交互元素(按钮、链接等)。get_by_text()
:基于文本内容定位,适合快速筛选包含特定文本的元素。
两者结合可使定位逻辑更清晰、更具可读性,同时提高测试的稳定性和可维护性。
在 Playwright 中,语义化选择器**是指基于元素的**无障碍角色(Role)、**文本内容(Text)**和**标签语义**进行定位的方法。这些方法比传统的 CSS/XPath 更贴近用户视角,能提高测试的稳定性和可读性。以下是 Playwright 提供的主要语义化方法:¶
一、核心语义化定位方法¶
1. page.get_by_role()
¶
基于元素的无障碍角色和名称定位,适用于各种交互元素。
常用角色参数:
# 按钮
page.get_by_role("button", name="Submit")
# 链接
page.get_by_role("link", name="Learn more")
# 标题(h1~h6)
page.get_by_role("heading", name="Welcome", level=1)
# 复选框
page.get_by_role("checkbox", name="Remember me").check()
# 单选按钮
page.get_by_role("radio", name="Option 1").click()
# 输入框
page.get_by_role("textbox", name="Username").fill("test")
# 下拉框选项
page.get_by_role("option", name="English").select_option()
2. page.get_by_text()
¶
基于元素的可见文本内容定位,支持模糊匹配和精确匹配。
示例:
# 模糊匹配(包含"Sign"即可)
page.get_by_text("Sign").click()
# 精确匹配
page.get_by_text("Hello, world!", exact=True)
# 在特定区域内查找文本
page.get_by_role("article").get_by_text("Read more")
3. page.get_by_label()
¶
基于关联的标签文本定位表单元素,适用于输入框、复选框等。
示例:
<label for="email">Email:</label>
<input type="email" id="email">
page.get_by_label("Email:").fill("test@example.com")
4. page.get_by_placeholder()
¶
基于输入框的占位文本定位。
示例:
<input type="text" placeholder="Enter your name">
page.get_by_placeholder("Enter your name").fill("John")
5. page.get_by_alt_text()
¶
基于图像的 alt
属性定位图片元素。
示例:
<img src="logo.png" alt="Company logo">
page.get_by_alt_text("Company logo").screenshot()
6. page.get_by_title()
¶
基于元素的 title
属性定位。
示例:
<button title="Close this dialog">X</button>
page.get_by_title("Close this dialog").click()
二、组合语义化选择器¶
可以链式调用多个语义化方法,缩小定位范围:
# 在导航栏中查找"Products"链接
nav = page.get_by_role("navigation")
nav.get_by_role("link", name="Products").click()
# 在卡片中查找带有"Add to Cart"按钮的商品
product_card = page.get_by_role("card").get_by_text("Featured Product")
product_card.get_by_role("button", name="Add to Cart").click()
三、与非语义化方法的结合¶
语义化方法可与 CSS/XPath 混合使用,处理复杂场景:
# 使用 CSS 选择容器,再用语义化方法定位内部元素
container = page.locator(".product-list")
container.get_by_role("button", name="View Details").nth(0).click()
# 使用 XPath 定位复杂结构,再用文本筛选
parent_element = page.locator('//div[contains(@class, "custom-component")]')
parent_element.get_by_text("Expand").click()
四、优势总结¶
优势 | 说明 |
---|---|
稳定性高 | 不依赖具体的 CSS 类名或 ID,DOM 结构变化时不易失效。 |
可读性强 | 代码直接表达业务含义(如 "点击登录按钮"),无需理解底层选择器。 |
符合无障碍标准 | 基于浏览器的无障碍树定位,确保测试覆盖真实用户体验。 |
维护成本低 | 页面样式调整时,语义化选择器通常无需修改。 |
五、使用建议¶
- 优先使用
get_by_role()
:对于交互元素(按钮、链接、表单控件),这是最稳定的定位方式。 - 结合
get_by_text()
:当角色不明确时,通过文本内容定位是第二选择。 - 添加测试标识:对于自定义组件,使用
data-testid
属性并结合 CSS 选择器。 - 避免过度依赖 XPath:仅在语义化方法无法实现时使用,因其可读性和性能较差。
通过合理使用语义化选择器,你的 Playwright 测试将更加健壮、易读,且更贴近用户实际操作。