Sanity 仓库实战:playwright-cli 浏览器自动化命令行完全指南
【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity
本篇技术指南以 .agents/skills/playwright-cli/SKILL.md 为核心主体,系统讲解 playwright-cli 这一面向 AI Agent 与开发者的浏览器自动化 CLI 工具:从打开浏览器、定位元素、填表提交,到会话隔离、存储状态复用、网络请求 Mock、Trace 与视频录制、测试代码生成等完整能力。本仓库的 e2e 目录正是基于 Playwright 构建的 Sanity Studio 端到端测试体系(见 e2e/package.json 中的playwright test脚本与@playwright/test依赖),读完本文你将掌握一套可复制、可运行的浏览器自动化命令集,并能将其沉淀为真实的 Playwright 测试代码。
快速上手:五条命令完成一次浏览器交互
playwright-cli 的核心设计是:每次命令执行后返回当前页面的结构化快照(Snapshot),后续操作通过快照中的 ref 编号精准定位元素,从而避免脆弱的 CSS 选择器。
# 打开新浏览器 playwright-cli open # 导航到页面 playwright-cli goto https://playwright.dev # 使用快照中的 ref 与页面交互 playwright-cli click e15 playwright-cli type "page.click" playwright-cli press Enter # 截图(很少用,快照更常用) playwright-cli screenshot # 关闭浏览器 playwright-cli close若环境中没有全局安装的playwright-cli可执行文件,可用npx playwright-cli以本地方式运行相同命令,例如:
npx playwright-cli open https://example.com npx playwright-cli click e1命令全景:核心交互命令参考
打开与导航
playwright-cli open # 打开新浏览器 playwright-cli open https://example.com/ # 打开并立即导航 playwright-cli goto https://playwright.dev # 跳转页面 playwright-cli go-back # 后退 playwright-cli go-forward # 前进 playwright-cli reload # 刷新表单与页面交互
playwright-cli type "search query" # 键入文本 playwright-cli click e3 # 单击 playwright-cli dblclick e7 # 双击 playwright-cli fill e5 "user@example.com" # 填充输入框 playwright-cli drag e2 e8 # 拖拽(源 ref → 目标 ref) playwright-cli hover e4 # 悬停 playwright-cli select e9 "option-value" # 下拉框选择 playwright-cli upload ./document.pdf # 文件上传 playwright-cli check e12 # 勾选复选框 playwright-cli uncheck e12 # 取消勾选 playwright-cli eval "document.title" # 在页面执行表达式 playwright-cli eval "el => el.textContent" e5 # 针对指定元素求值 playwright-cli dialog-accept # 接受弹窗 playwright-cli dialog-accept "confirmation text" # 接受弹窗并输入文本 playwright-cli dialog-dismiss # 关闭弹窗 playwright-cli resize 1920 1080 # 调整视口尺寸 playwright-cli close # 关闭浏览器键盘与鼠标
playwright-cli press Enter # 按键 playwright-cli press ArrowDown playwright-cli keydown Shift # 按下并保持 playwright-cli keyup Shift # 释放按键 playwright-cli mousemove 150 300 playwright-cli mousedown # 默认左键,right 为右键 playwright-cli mousedown right playwright-cli mouseup playwright-cli mouseup right playwright-cli mousewheel 0 100 # 滚动(deltaX deltaY)多标签页
playwright-cli tab-list # 列出所有标签页 playwright-cli tab-new # 新建空白标签页 playwright-cli tab-new https://example.com/page # 新建并导航 playwright-cli tab-close # 关闭当前标签页 playwright-cli tab-close 2 # 关闭指定索引标签页 playwright-cli tab-select 0 # 切换到指定标签页截图与 PDF
playwright-cli screenshot # 全页截图 playwright-cli screenshot e5 # 截取指定元素 playwright-cli screenshot --filename=page.png # 指定文件名 playwright-cli pdf --filename=page.pdf # 导出为 PDF快照机制:基于 ref 的元素定位
每一条命令执行后,playwright-cli 都会输出当前浏览器状态的结构化快照,这是整个工具工作流的核心:
> playwright-cli goto https://example.com ### Page - Page URL: https://example.com/ - Page Title: Example Domain ### Snapshot Snapshot快照被保存为 YAML 文件(默认存放在.playwright-cli/目录,按时间戳自动命名),其中每个可交互元素都带有e1、e2这样的 ref 编号。后续的click、fill、hover等命令都直接引用这些 ref,无需手写任何选择器。
也可以随时通过playwright-cli snapshot主动获取快照:
playwright-cli snapshot playwright-cli snapshot --filename=after-click.yaml--filename参数的使用原则是:默认使用时间戳自动命名;当快照/产物属于工作流结果的一部分、需要被后续步骤引用时,才用--filename=指定固定名称。
浏览器会话管理:多会话隔离与持久化
命名会话
通过-s参数可以为每个浏览器会话命名,不同会话之间的 Cookies、LocalStorage/SessionStorage、IndexedDB、缓存、浏览历史与打开的标签页完全隔离:
# 会话1:认证流程 playwright-cli -s=auth open https://app.example.com/login # 会话2:公共浏览(独立的 cookie 与存储) playwright-cli -s=public open https://example.com # 命令按会话隔离 playwright-cli -s=auth fill e1 "user@example.com" playwright-cli -s=public snapshot会话管理命令
playwright-cli list # 列出所有浏览器会话 playwright-cli close # 关闭默认会话 playwright-cli -s=mysession close # 关闭命名会话 playwright-cli close-all # 关闭全部会话 playwright-cli kill-all # 强制杀掉全部后台进程(清理僵尸进程) playwright-cli delete-data # 删除默认会话用户数据 playwright-cli -s=mysession delete-data # 删除命名会话的用户数据(profile 目录)默认会话与环境变量
省略-s时,所有命令作用于同一个默认会话:
playwright-cli open https://example.com playwright-cli snapshot playwright-cli close # 关闭默认浏览器也可以设置环境变量指定默认会话名:
export PLAYWRIGHT_CLI_SESSION="mysession" playwright-cli open example.com # 自动使用 "mysession"持久化 Profile
默认情况下浏览器 profile 仅保存在内存中;需要持久化时使用--persistent或--profile参数:
playwright-cli open https://example.com --persistent # 自动生成位置 playwright-cli open https://example.com --profile=/path/to/profile # 自定义目录实战模式一:并发抓取多个站点
# 同时启动三个站点会话 playwright-cli -s=site1 open https://site1.com & playwright-cli -s=site2 open https://site2.com & playwright-cli -s=site3 open https://site3.com & wait # 分别获取快照 playwright-cli -s=site1 snapshot playwright-cli -s=site2 snapshot playwright-cli -s=site3 snapshot # 统一清理 playwright-cli close-all实战模式二:A/B 测试对比
playwright-cli -s=variant-a open "https://app.com?variant=a" playwright-cli -s=variant-b open "https://app.com?variant=b" playwright-cli -s=variant-a screenshot playwright-cli -s=variant-b screenshot最佳实践包括:语义化命名会话(如github-auth、docs-scrape,避免s1这类通用名)、用后即清理(close/close-all,卡死时用kill-all)、及时删除过期数据(delete-data释放磁盘空间)。
存储管理:Cookies、localStorage 与 sessionStorage
存储状态的保存与恢复
# 保存到自动命名文件 storage-state-{timestamp}.json playwright-cli state-save # 保存到指定文件 playwright-cli state-save my-auth-state.json # 恢复存储状态 playwright-cli state-load my-auth-state.json # 重新打开页面以应用 cookies playwright-cli open https://example.com保存的存储状态文件格式与 Playwright 的storageState完全一致:
{ "cookies": [ { "name": "session_id", "value": "abc123", "domain": "example.com", "path": "/", "expires": 1735689600, "httpOnly": true, "secure": true, "sameSite": "Lax" } ], "origins": [ { "origin": "https://example.com", "localStorage": [ {"name": "theme", "value": "dark"}, {"name": "user_id", "value": "12345"} ] } ] }Cookies 操作
playwright-cli cookie-list # 列出全部 playwright-cli cookie-list --domain=example.com # 按域名过滤 playwright-cli cookie-list --path=/api # 按路径过滤 playwright-cli cookie-get session_id # 获取单个 playwright-cli cookie-set session abc123 # 设置基础 cookie playwright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax playwright-cli cookie-set remember_me token123 --expires=1735689600 # Unix 时间戳过期 playwright-cli cookie-delete session_id # 删除单个 playwright-cli cookie-clear # 清空全部localStorage 操作
playwright-cli localstorage-list playwright-cli localstorage-get theme playwright-cli localstorage-set theme dark playwright-cli localstorage-set user_settings '{"theme":"dark","language":"en"}' # JSON 值 playwright-cli localstorage-delete theme playwright-cli localstorage-clearsessionStorage 操作
playwright-cli sessionstorage-list playwright-cli sessionstorage-get form_data playwright-cli sessionstorage-set step 3 playwright-cli sessionstorage-delete step playwright-cli sessionstorage-clear复杂场景:多 Cookie / 多 Key 操作
一次性写入多个 Cookie 或多个 localStorage 键值,可借助run-code直接调用 Playwright API:
playwright-cli run-code "async page => { await page.context().addCookies([ { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true }, { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' } ]); }" playwright-cli run-code "async page => { await page.evaluate(() => { localStorage.setItem('token', 'jwt_abc123'); localStorage.setItem('user_id', '12345'); localStorage.setItem('expires_at', Date.now() + 3600000); }); }"IndexedDB 的枚举与删除同样通过run-code完成(indexedDB.databases()/indexedDB.deleteDatabase('myDatabase'))。
认证状态复用(经典模式)
# 第一步:登录并保存状态 playwright-cli open https://app.example.com/login playwright-cli snapshot playwright-cli fill e1 "user@example.com" playwright-cli fill e2 "password123" playwright-cli click e3 playwright-cli state-save auth.json # 第二步:恢复状态,跳过登录直达业务页 playwright-cli state-load auth.json playwright-cli open https://app.example.com/dashboard # 已自动登录!安全注意事项
- 切勿将含认证令牌的存储状态文件提交到版本库;
- 将
*.auth-state.json加入.gitignore; - 自动化结束后删除状态文件;
- 敏感数据使用环境变量传递;
- 默认的内存模式(in-memory session)对敏感操作更安全。
网络请求拦截与 Mock
CLI 路由命令
# 自定义状态码 Mock playwright-cli route "**/*.jpg" --status=404 # JSON 响应体 Mock playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"}]' --content-type=application/json # 自定义响应头 playwright-cli route "**/api/data" --body='{"ok":true}' --header="X-Custom: value" # 移除请求头(如 cookie、authorization) playwright-cli route "**/*" --remove-header=cookie,authorization # 列出当前生效的路由 playwright-cli route-list # 移除单条路由或全部路由 playwright-cli unroute "**/*.jpg" playwright-cli unrouteURL 模式语法
**/api/users - 精确路径匹配 **/api/*/details - 路径通配 **/*.{png,jpg,jpeg} - 按扩展名匹配 **/search?q=* - 匹配查询参数高级 Mock:run-code 实现条件响应、响应改写、网络故障与延迟
# 条件响应:根据请求体分发 playwright-cli run-code "async page => { await page.route('**/api/login', route => { const body = route.request().postDataJSON(); if (body.username === 'admin') { route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) }); } else { route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) }); } }); }" # 改写真实响应 playwright-cli run-code "async page => { await page.route('**/api/user', async route => { const response = await route.fetch(); const json = await response.json(); json.isPremium = true; await route.fulfill({ response, json }); }); }" # 模拟网络故障 playwright-cli run-code "async page => { await page.route('**/api/offline', route => route.abort('internetdisconnected')); }" # abort 可选原因:connectionrefused、timedout、connectionreset、internetdisconnected # 延迟响应(模拟慢接口) playwright-cli run-code "async page => { await page.route('**/api/slow', async route => { await new Promise(r => setTimeout(r, 3000)); route.fulfill({ body: JSON.stringify({ data: 'loaded' }) }); }); }"完整的路由用法参见 .agents/skills/playwright-cli/references/request-mocking.md。
高级能力:run-code 执行任意 Playwright 代码
当 CLI 命令无法覆盖某些场景时,run-code允许直接注入一段接收page对象的异步函数,内部可访问page.context()做浏览器上下文级操作。
语法
playwright-cli run-code "async page => { // 此处编写任意 Playwright 代码 }"地理位置与权限
# 授予定位权限并设置位置(旧金山) playwright-cli run-code "async page => { await page.context().grantPermissions(['geolocation']); await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 }); }" # 设置伦敦位置 playwright-cli run-code "async page => { await page.context().grantPermissions(['geolocation']); await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 }); }" # 清除权限 playwright-cli run-code "async page => { await page.context().clearPermissions(); }"# 批量授予权限 playwright-cli run-code "async page => { await page.context().grantPermissions([ 'geolocation', 'notifications', 'camera', 'microphone' ]); }" # 针对特定源授予权限 playwright-cli run-code "async page => { await page.context().grantPermissions(['clipboard-read'], { origin: 'https://example.com' }); }"媒体模拟
# 深色 / 浅色配色 playwright-cli run-code "async page => { await page.emulateMedia({ colorScheme: 'dark' }); }" playwright-cli run-code "async page => { await page.emulateMedia({ colorScheme: 'light' }); }" # 减弱动效 playwright-cli run-code "async page => { await page.emulateMedia({ reducedMotion: 'reduce' }); }" # 打印媒体类型 playwright-cli run-code "async page => { await page.emulateMedia({ media: 'print' }); }"等待策略
# 等待网络空闲 playwright-cli run-code "async page => { await page.waitForLoadState('networkidle'); }" # 等待元素隐藏 playwright-cli run-code "async page => { await page.waitForSelector('.loading', { state: 'hidden' }); }" # 等待函数返回 true playwright-cli run-code "async page => { await page.waitForFunction(() => window.appReady === true); }" # 带超时等待 playwright-cli run-code "async page => { await page.waitForSelector('.result', { timeout: 10000 }); }"iframe、下载与剪贴板
# 操作 iframe 内部元素 playwright-cli run-code "async page => { const frame = page.locator('iframe#my-iframe').contentFrame(); await frame.locator('button').click(); }" # 列出全部 frame 的 URL playwright-cli run-code "async page => { const frames = page.frames(); return frames.map(f => f.url()); }" # 处理文件下载 playwright-cli run-code "async page => { const [download] = await Promise.all([ page.waitForEvent('download'), page.click('a.download-link') ]); await download.saveAs('./downloaded-file.pdf'); return download.suggestedFilename(); }" # 读取 / 写入剪贴板(需 clipboard-read 权限) playwright-cli run-code "async page => { await page.context().grantPermissions(['clipboard-read']); return await page.evaluate(() => navigator.clipboard.readText()); }" playwright-cli run-code "async page => { await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!'); }"页面信息与 JavaScript 执行
playwright-cli run-code "async page => { return await page.title(); }" playwright-cli run-code "async page => { return page.url(); }" playwright-cli run-code "async page => { return await page.content(); }" playwright-cli run-code "async page => { return page.viewportSize(); }" # 在页面内执行并返回结构化结果 playwright-cli run-code "async page => { return await page.evaluate(() => { return { userAgent: navigator.userAgent, language: navigator.language, cookiesEnabled: navigator.cookieEnabled }; }); }" # 向 evaluate 传参 playwright-cli run-code "async page => { const multiplier = 5; return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier); }"错误处理与复杂工作流
# try-catch 容错 playwright-cli run-code "async page => { try { await page.click('.maybe-missing', { timeout: 1000 }); return 'clicked'; } catch (e) { return 'element not found'; } }" # 登录并保存状态 playwright-cli run-code "async page => { await page.goto('https://example.com/login'); await page.fill('input[name=email]', 'user@example.com'); await page.fill('input[name=password]', 'secret'); await page.click('button[type=submit]'); await page.waitForURL('**/dashboard'); await page.context().storageState({ path: 'auth.json' }); return 'Login successful'; }" # 多页面数据抓取 playwright-cli run-code "async page => { const results = []; for (let i = 1; i <= 3; i++) { await page.goto(\`https://example.com/page/\${i}\`); const items = await page.locator('.item').allTextContents(); results.push(...items); } return results; }"更多场景(地理位置、权限、媒体模拟、等待、iframe、下载、剪贴板、页面信息、JS 执行、错误处理、复杂工作流)见 .agents/skills/playwright-cli/references/running-code.md。
调试与证据采集:DevTools、Tracing 与视频录制
DevTools 命令
playwright-cli console # 查看控制台日志 playwright-cli console warning # 按级别过滤(warning 等) playwright-cli network # 查看网络请求 # 示例:授权定位权限 playwright-cli run-code "async page => await page.context().grantPermissions(['geolocation'])"Tracing 追踪
启动追踪后,Playwright 会在traces/目录生成三类产物:
| 文件 | 内容 |
|---|---|
trace-{timestamp}.trace | 动作日志:每次点击/填写/导航、动作前后 DOM 快照、分步截图、时间信息、控制台消息、源码位置 |
trace-{timestamp}.network | 网络日志:所有 HTTP 请求/响应、请求与响应头与体、各阶段耗时(DNS、连接、TLS、TTFB、下载)、资源大小、失败请求 |
resources/ | 资源缓存:图片、字体、样式、脚本、响应体等,用于重放页面状态 |
# 开始追踪 playwright-cli tracing-start # 执行动作 playwright-cli open https://example.com playwright-cli click e1 playwright-cli fill e2 "test" # 停止追踪 playwright-cli tracing-stopTrace 捕获内容的完整清单:
| 类别 | 详情 |
|---|---|
| 动作 | 点击、填写、悬停、键盘输入、导航 |
| DOM | 每个动作前后的完整 DOM 快照 |
| 截图 | 每个步骤的视觉状态 |
| 网络 | 全部请求、响应、头、体与耗时 |
| 控制台 | 全部 console.log / warn / error |
| 时间 | 每个操作的精确耗时 |
典型用途:排查失败点击(查看动作尝试时的 DOM 状态)、性能分析(网络瀑布流定位慢资源)、沉淀文档证据(完整录制 checkout 流程)。清理旧 Trace 可用find .playwright-cli/traces -mtime +7 -delete。注意 Trace 会给自动化带来额外开销并占用磁盘空间,且部分动态内容无法完美重放。
视频录制
# 开始录制 playwright-cli video-start # 执行动作 playwright-cli open https://example.com playwright-cli snapshot playwright-cli click e1 playwright-cli fill e2 "test input" # 停止并保存为 WebM(VP8/VP9 编码) playwright-cli video-stop demo.webm建议使用带上下文的描述性文件名:playwright-cli video-stop recordings/login-flow-2024-01-15.webm。
Trace vs Video vs Screenshot 选型
| 特性 | Trace | 视频 | 截图 |
|---|---|---|---|
| 格式 | .trace 文件 | .webm 视频 | .png/.jpeg 图片 |
| DOM 检视 | 支持 | 不支持 | 不支持 |
| 网络详情 | 支持 | 不支持 | 不支持 |
| 回放方式 | 逐步回放 | 连续播放 | 单帧 |
| 文件体积 | 中等 | 大 | 小 |
| 适用场景 | 调试 | 演示 | 快速捕获 |
参考文档:.agents/skills/playwright-cli/references/tracing.md、.agents/skills/playwright-cli/references/video-recording.md。
测试代码生成:从交互到 Playwright 测试
playwright-cli 的每个动作都会同步生成对应的 Playwright TypeScript 代码并显示在输出中,可直接复制进测试文件:
# 开始会话 playwright-cli open https://example.com/login # 获取快照,输出类似:e1 [textbox "Email"], e2 [textbox "Password"], e3 [button "Sign In"] playwright-cli snapshot # 填充表单——自动生成代码 playwright-cli fill e1 "user@example.com" # Ran Playwright code: # await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com'); playwright-cli fill e2 "password123" # await page.getByRole('textbox', { name: 'Password' }).fill('password123'); playwright-cli click e3 # await page.getByRole('button', { name: 'Sign In' }).click();将生成的代码收集成完整的 Playwright 测试:
import {test, expect} from '@playwright/test' test('login flow', async ({page}) => { // Generated code from playwright-cli session: await page.goto('https://example.com/login') await page.getByRole('textbox', {name: 'Email'}).fill('user@example.com') await page.getByRole('textbox', {name: 'Password'}).fill('password123') await page.getByRole('button', {name: 'Sign In'}).click() // Add assertions await expect(page).toHaveURL(/.*dashboard/) })最佳实践:
- 优先语义化定位器:生成代码尽量使用基于 role 的定位器(如
getByRole('button', {name: 'Submit'})),远比#submit-btn这类 CSS 选择器健壮。本仓库的 E2E 测试也遵循同一原则,例如 e2e/tests/auth/cookieAuth.spec.ts 在无法使用getByRole("heading")时改用># 指定浏览器内核 playwright-cli open --browser=chrome playwright-cli open --browser=firefox playwright-cli open --browser=webkit playwright-cli open --browser=msedge # 通过浏览器扩展连接 playwright-cli open --extension # 持久化 profile(默认内存中) playwright-cli open --persistent # 自定义 profile 目录 playwright-cli open --profile=/path/to/profile # 使用配置文件启动 playwright-cli open --config=my-config.json # 有头模式 playwright-cli open https://example.com --headed # 关闭浏览器 / 删除默认会话用户数据 playwright-cli close playwright-cli delete-data注意:
--profile仅在用户明确指定时才使用;默认场景优先使用--persistent的自动生成位置。综合实战:三个开箱即用的完整流程
表单提交流程
playwright-cli open https://example.com/form playwright-cli snapshot playwright-cli fill e1 "user@example.com" playwright-cli fill e2 "password123" playwright-cli click e3 playwright-cli snapshot playwright-cli close多标签页工作流
playwright-cli open https://example.com playwright-cli tab-new https://example.com/other playwright-cli tab-list playwright-cli tab-select 0 playwright-cli snapshot playwright-cli close结合 DevTools 与 Trace 的调试流程
# 方式一:console + network 检查 playwright-cli open https://example.com playwright-cli click e4 playwright-cli fill e7 "test" playwright-cli console playwright-cli network playwright-cli close # 方式二:完整 Trace 追踪 playwright-cli open https://example.com playwright-cli tracing-start playwright-cli click e4 playwright-cli fill e7 "test" playwright-cli tracing-stop playwright-cli close总结
playwright-cli 把 Playwright 的能力封装成一组可组合的 CLI 原语:快照 + ref 定位解决了选择器脆弱问题;命名会话与持久化 profile提供了多身份隔离与状态复用;route 系列命令实现了零代码的网络 Mock;run-code保留了任意 Playwright API 的逃生通道;而Trace、视频与测试代码生成则让每次自动化既可用于调试取证,也能直接沉淀为仓库中的正式测试用例——这正是 Sanity 仓库 e2e 体系所依赖的同一套 Playwright 生态。深入研读本文引用的 7 份参考文档,即可把命令级用法进阶到完整的工作流编排。
延伸阅读
- .agents/skills/playwright-cli/references/request-mocking.md —— 请求拦截、Mock、改写与故障注入
- .agents/skills/playwright-cli/references/running-code.md —— 任意 Playwright 代码执行
- .agents/skills/playwright-cli/references/session-management.md —— 多会话管理与持久化
- .agents/skills/playwright-cli/references/storage-state.md —— 存储状态(cookies、localStorage 等)
- .agents/skills/playwright-cli/references/test-generation.md —— 测试代码生成
- .agents/skills/playwright-cli/references/tracing.md —— Trace 追踪
- .agents/skills/playwright-cli/references/video-recording.md —— 视频录制
【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content
项目地址: https://gitcode.com/GitHub_Trending/sa/sanity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考