chore: import zh skill playwright-skill

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:47 +08:00
commit 5d144ced31
77 changed files with 54629 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
---
name: playwright-cli
description: 使用 playwright-cli 自动化浏览器交互,用于 Web 测试、表单填写、截图和数据提取。当用户需要浏览网站、与网页交互、填写表单、截图、测试 Web 应用、从网页提取信息、模拟网络请求、管理浏览器会话或生成测试代码时使用。
allowed-tools: Bash(playwright-cli:*)
---
# 使用 playwright-cli 的浏览器自动化
> 全面的 CLI 驱动浏览器自动化——导航、交互、模拟、调试、录制和生成测试,无需编写任何脚本文件。
## 快速开始
```bash
# 安装与设置
playwright-cli install --skills
playwright-cli install-browser
# 打开浏览器并导航
playwright-cli open https://playwright.dev
# 截取快照以查看可交互元素(引用如 e1、e2、e3……)
playwright-cli snapshot
# 使用快照中的元素引用进行交互
playwright-cli click e15
playwright-cli fill e5 "search query"
playwright-cli press Enter
# 截图
playwright-cli screenshot
# 关闭浏览器
playwright-cli close
```
## 黄金法则
1. **始终先执行 `snapshot`**——在交互前先识别元素引用;切勿猜测引用编号
2. **输入用 `fill`,按钮用 `click`**——`type` 逐个发送按键,`fill` 替换整个值
3. **并行工作使用命名会话**——`-s=name` 按会话隔离 cookie、存储和标签页
4. **保存认证状态**——登录后执行 `state-save auth.json`,下次使用 `state-load auth.json` 跳过登录
5. **调试前先追踪**——在失败的步骤之前执行 `tracing-start`,而非之后
6. **高级场景使用 `run-code`**——当 CLI 命令不足时,直接使用完整的 Playwright API
7. **清理会话**——完成后执行 `close``close-all`;对僵尸进程使用 `kill-all`
8. **文件名要有描述性**——`screenshot --filename=checkout-step3.png`,而非 `screenshot`
9. **仅模拟外部 API**——使用 `route` 拦截第三方服务,而非你自己的应用
10. **有状态流程使用持久化配置**——`--persistent` 在重启之间保留 cookie 和存储
## 命令参考
### 核心交互
```bash
playwright-cli open [url] # 启动浏览器,可选导航到网址
playwright-cli goto <url> # 导航到指定网址
playwright-cli snapshot # 显示带引用编号的页面元素
playwright-cli snapshot --filename=snap.yaml # 将快照保存到文件
playwright-cli click <ref> # 点击元素
playwright-cli dblclick <ref> # 双击
playwright-cli fill <ref> "value" # 清空并填写输入框
playwright-cli type "text" # 逐个按键输入文本
playwright-cli select <ref> "option-value" # 选择下拉选项
playwright-cli check <ref> # 勾选复选框
playwright-cli uncheck <ref> # 取消勾选复选框
playwright-cli hover <ref> # 悬停在元素上
playwright-cli drag <src-ref> <dst-ref> # 拖放操作
playwright-cli upload <ref> ./file.pdf # 上传文件
playwright-cli eval "document.title" # 执行 JS 表达式
playwright-cli eval "el => el.textContent" <ref> # 在元素上执行表达式
playwright-cli close # 关闭浏览器
```
### 导航
```bash
playwright-cli go-back # 浏览器后退
playwright-cli go-forward # 浏览器前进
playwright-cli reload # 刷新当前页面
```
### 键盘与鼠标
```bash
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 mouseup [right] # 释放鼠标按钮
playwright-cli mousewheel 0 100 # 滚动(deltaX, deltaY
```
### 对话框
```bash
playwright-cli dialog-accept # 接受 alert/confirm/prompt
playwright-cli dialog-accept "text" # 接受 prompt 并输入文本
playwright-cli dialog-dismiss # 关闭/取消对话框
```
### 标签页
```bash
playwright-cli tab-list # 列出所有打开的标签页
playwright-cli tab-new [url] # 打开新标签页
playwright-cli tab-select <index> # 按索引切换标签页
playwright-cli tab-close [index] # 关闭标签页(当前或按索引)
```
### 截图与媒体
```bash
playwright-cli screenshot # 截取当前页面截图
playwright-cli screenshot <ref> # 截取指定元素的截图
playwright-cli screenshot --filename=pg.png # 使用自定义文件名保存
playwright-cli pdf --filename=page.pdf # 将页面保存为 PDF
playwright-cli video-start # 开始录制视频
playwright-cli video-stop output.webm # 停止并保存视频
playwright-cli resize 1920 1080 # 调整视口大小
```
### 存储与认证
```bash
playwright-cli state-save [file.json] # 保存 cookie + localStorage
playwright-cli state-load <file.json> # 恢复已保存的状态
playwright-cli cookie-list [--domain=...] # 列出 cookie
playwright-cli cookie-get <name> # 获取指定 cookie
playwright-cli cookie-set <name> <value> [opts] # 设置 cookie
playwright-cli cookie-delete <name> # 删除 cookie
playwright-cli cookie-clear # 清除所有 cookie
playwright-cli localstorage-list # 列出 localStorage 项
playwright-cli localstorage-get <key> # 获取 localStorage 值
playwright-cli localstorage-set <key> <val> # 设置 localStorage 值
playwright-cli localstorage-delete <key> # 删除 localStorage 项
playwright-cli localstorage-clear # 清除所有 localStorage
playwright-cli sessionstorage-list # 列出 sessionStorage
playwright-cli sessionstorage-get <key> # 获取 sessionStorage 值
playwright-cli sessionstorage-set <key> <val> # 设置 sessionStorage 值
playwright-cli sessionstorage-delete <key> # 删除 sessionStorage 项
playwright-cli sessionstorage-clear # 清除所有 sessionStorage
```
### 网络模拟
```bash
playwright-cli route "<pattern>" [opts] # 拦截匹配的请求
playwright-cli route-list # 列出活跃的路由覆盖规则
playwright-cli unroute "<pattern>" # 移除特定路由规则
playwright-cli unroute # 移除所有路由规则
```
### DevTools 与调试
```bash
playwright-cli console [level] # 显示控制台消息
playwright-cli network # 显示网络请求
playwright-cli tracing-start # 开始记录追踪
playwright-cli tracing-stop # 停止并保存追踪
playwright-cli run-code "async page => {}" # 执行 Playwright API 代码
```
### 会话与配置
```bash
playwright-cli -s=<name> <command> # 在命名会话中执行命令
playwright-cli list # 列出所有活跃会话
playwright-cli close-all # 关闭所有浏览器
playwright-cli kill-all # 强制终止所有进程
playwright-cli delete-data # 删除会话用户数据
playwright-cli open --browser=firefox # 使用指定浏览器
playwright-cli open --persistent # 将配置文件持久化到磁盘
playwright-cli open --profile=/path # 自定义配置文件目录
playwright-cli open --config=config.json # 使用配置文件
playwright-cli open --extension # 通过扩展连接
```
## 指南索引
### 入门
| 你要做的事情 | 指南 |
| ------------------------- | --------------------------------------------------- |
| 核心浏览器交互 | [core-commands.md](core-commands.md) |
| 生成测试代码 | [test-generation.md](test-generation.md) |
| 截图、视频、PDF | [screenshots-and-media.md](screenshots-and-media.md) |
### 测试与调试
| 你要做的事情 | 指南 |
| ----------------------------- | --------------------------------------------------- |
| 追踪与调试 | [tracing-and-debugging.md](tracing-and-debugging.md) |
| 网络模拟与拦截 | [request-mocking.md](request-mocking.md) |
| 运行自定义 Playwright 代码 | [running-custom-code.md](running-custom-code.md) |
### 状态与会话
| 你要做的事情 | 指南 |
| ---------------------------------- | --------------------------------------------- |
| Cookie、localStorage、认证状态 | [storage-and-auth.md](storage-and-auth.md) |
| 多会话管理 | [session-management.md](session-management.md) |
### 高级
| 你要做的事情 | 指南 |
| ----------------------------- | --------------------------------------------- |
| 设备与环境模拟 | [device-emulation.md](device-emulation.md) |
| 复杂多步骤工作流 | [advanced-workflows.md](advanced-workflows.md) |
+679
View File
@@ -0,0 +1,679 @@
# 高级工作流
> **适用场景**:复杂的多步骤自动化场景——多页面抓取、弹窗与新窗口处理、无障碍审计、文件下载、OAuth 认证流程、无限滚动提取、表单向导自动化,以及将多个 CLI 功能组合使用。
> **前置知识**[core-commands.md](core-commands.md)、[running-custom-code.md](running-custom-code.md)、[session-management.md](session-management.md)
## 快速参考
```bash
# 多页面抓取
playwright-cli run-code "async page => {
const data = [];
for (let i = 1; i <= 5; i++) {
await page.goto(\`https://example.com/page/\${i}\`);
const items = await page.locator('.item').allTextContents();
data.push(...items);
}
return data;
}"
# 处理弹窗
playwright-cli run-code "async page => {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target=_blank]')
]);
return await popup.title();
}"
# 无障碍快照
playwright-cli run-code "async page => {
return await page.accessibility.snapshot();
}"
```
## 弹窗与新窗口处理
当点击的链接会打开新窗口或弹窗时:
### 捕获弹窗内容
```bash
playwright-cli run-code "async page => {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target=_blank]')
]);
await popup.waitForLoadState();
const title = await popup.title();
const url = popup.url();
return { title, url };
}"
```
### 与弹窗交互
```bash
playwright-cli run-code "async page => {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('#open-settings')
]);
// 在弹窗中填写表单
await popup.fill('input[name=email]', 'user@example.com');
await popup.click('button:text(\"Save\")');
// 等待弹窗关闭
await popup.waitForEvent('close');
return 'Popup handled';
}"
```
### 使用弹窗进行 OAuth 登录
```bash
playwright-cli run-code "async page => {
await page.goto('https://app.example.com/login');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('button:text(\"Sign in with Google\")')
]);
// 在弹窗中处理 Google OAuth
await popup.fill('input[type=email]', 'user@gmail.com');
await popup.click('#identifierNext');
await popup.waitForSelector('input[type=password]', { state: 'visible' });
await popup.fill('input[type=password]', 'password');
await popup.click('#passwordNext');
// 认证后弹窗关闭,主页面跳转
await popup.waitForEvent('close');
await page.waitForURL('**/dashboard');
return 'OAuth login complete: ' + page.url();
}"
```
## 多页面导航与抓取
### 分页数据提取
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com/products');
const allProducts = [];
while (true) {
// 从当前页面提取数据
const products = await page.locator('.product-card').evaluateAll(cards =>
cards.map(card => ({
name: card.querySelector('.name')?.textContent?.trim(),
price: card.querySelector('.price')?.textContent?.trim(),
rating: card.querySelector('.rating')?.getAttribute('data-score')
}))
);
allProducts.push(...products);
// 检查是否有下一页
const nextBtn = page.locator('a.next-page');
if (await nextBtn.isVisible()) {
await nextBtn.click();
await page.waitForLoadState('networkidle');
} else {
break;
}
}
return { total: allProducts.length, products: allProducts };
}"
```
### 无限滚动提取
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com/feed');
const seen = new Set();
const items = [];
const maxItems = 100;
while (items.length < maxItems) {
// 收集可见条目
const newItems = await page.locator('.feed-item').evaluateAll(
els => els.map(el => ({
id: el.getAttribute('data-id'),
text: el.textContent.trim()
}))
);
for (const item of newItems) {
if (item.id && !seen.has(item.id)) {
seen.add(item.id);
items.push(item);
}
}
// 滚动到底部
const previousHeight = await page.evaluate(() => document.body.scrollHeight);
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(2000);
// 检查是否已到达底部
const newHeight = await page.evaluate(() => document.body.scrollHeight);
if (newHeight === previousHeight) break;
}
return { collected: items.length, items: items.slice(0, maxItems) };
}"
```
### 多站点数据聚合
```bash
#!/bin/bash
# 使用命名会话实现并发站点抓取
playwright-cli -s=site1 open https://news.example.com &
playwright-cli -s=site2 open https://blog.example.com &
playwright-cli -s=site3 open https://docs.example.com &
wait
# 从每个站点提取标题
playwright-cli -s=site1 run-code "async page => {
return await page.locator('h2.headline').allTextContents();
}"
playwright-cli -s=site2 run-code "async page => {
return await page.locator('.post-title').allTextContents();
}"
playwright-cli -s=site3 run-code "async page => {
return await page.locator('.doc-title').allTextContents();
}"
playwright-cli close-all
```
## 文件上传与下载
### 下载文件
```bash
playwright-cli run-code "async page => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a.download-link')
]);
const filename = download.suggestedFilename();
await download.saveAs('./downloads/' + filename);
return {
filename,
url: download.url()
};
}"
```
### 下载多个文件
```bash
playwright-cli run-code "async page => {
const downloadLinks = await page.locator('a.download').all();
const files = [];
for (const link of downloadLinks) {
const [download] = await Promise.all([
page.waitForEvent('download'),
link.click()
]);
const name = download.suggestedFilename();
await download.saveAs('./downloads/' + name);
files.push(name);
}
return files;
}"
```
### 上传文件
```bash
# 通过元素引用进行简单上传
playwright-cli snapshot
playwright-cli upload e5 ./document.pdf
# 上传多个文件
playwright-cli upload e5 ./photo1.jpg ./photo2.jpg ./photo3.jpg
# 编程式上传(适用于隐藏的文件输入)
playwright-cli run-code "async page => {
const fileInput = page.locator('input[type=file]');
await fileInput.setInputFiles('./report.pdf');
}"
# 编程式上传多个文件
playwright-cli run-code "async page => {
const fileInput = page.locator('input[type=file]');
await fileInput.setInputFiles([
'./document1.pdf',
'./document2.pdf',
'./image.png'
]);
}"
# 清除文件输入
playwright-cli run-code "async page => {
await page.locator('input[type=file]').setInputFiles([]);
}"
```
### 拖放文件上传
```bash
playwright-cli run-code "async page => {
// 为拖放区域创建模拟文件
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await page.dispatchEvent('.dropzone', 'drop', { dataTransfer });
}"
```
## 无障碍审计
### 无障碍树快照
```bash
playwright-cli run-code "async page => {
const snapshot = await page.accessibility.snapshot();
return JSON.stringify(snapshot, null, 2);
}"
```
### 检查缺失的 ARIA 标签
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
const issues = [];
// 没有 alt 文本的图片
document.querySelectorAll('img:not([alt])').forEach(img => {
issues.push({ type: 'img-no-alt', src: img.src.substring(0, 50) });
});
// 没有无障碍名称的按钮
document.querySelectorAll('button').forEach(btn => {
if (!btn.textContent.trim() && !btn.getAttribute('aria-label')) {
issues.push({ type: 'button-no-name', html: btn.outerHTML.substring(0, 80) });
}
});
// 没有标签的表单输入
document.querySelectorAll('input:not([type=hidden])').forEach(input => {
const id = input.id;
const hasLabel = id && document.querySelector(\`label[for=\${id}]\`);
const hasAriaLabel = input.getAttribute('aria-label') || input.getAttribute('aria-labelledby');
if (!hasLabel && !hasAriaLabel) {
issues.push({ type: 'input-no-label', name: input.name, type: input.type });
}
});
// 没有文本的链接
document.querySelectorAll('a').forEach(link => {
if (!link.textContent.trim() && !link.getAttribute('aria-label')) {
issues.push({ type: 'link-no-text', href: link.href.substring(0, 50) });
}
});
return { issueCount: issues.length, issues };
});
}"
```
### 颜色对比度检查
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
const getContrast = (rgb1, rgb2) => {
const luminance = (r, g, b) => {
const [rs, gs, bs] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
};
const l1 = luminance(...rgb1);
const l2 = luminance(...rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
};
const parseRGB = (str) => {
const match = str.match(/\d+/g);
return match ? match.slice(0, 3).map(Number) : [0, 0, 0];
};
const lowContrast = [];
document.querySelectorAll('p, span, a, button, label, h1, h2, h3, h4, h5, h6').forEach(el => {
const style = window.getComputedStyle(el);
const fg = parseRGB(style.color);
const bg = parseRGB(style.backgroundColor);
const ratio = getContrast(fg, bg);
if (ratio < 4.5) {
lowContrast.push({
text: el.textContent.trim().substring(0, 30),
ratio: ratio.toFixed(2),
fg: style.color,
bg: style.backgroundColor
});
}
});
return { lowContrastCount: lowContrast.length, elements: lowContrast.slice(0, 20) };
});
}"
```
### Tab 键顺序验证
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
const focusable = Array.from(document.querySelectorAll(
'a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'
));
return focusable.map((el, i) => ({
order: i + 1,
tag: el.tagName.toLowerCase(),
text: (el.textContent || el.getAttribute('aria-label') || el.name || '').trim().substring(0, 40),
tabIndex: el.tabIndex
}));
});
}"
```
## 表单向导自动化
### 多步骤表单
```bash
# 步骤 1:个人信息
playwright-cli open https://example.com/apply
playwright-cli snapshot
playwright-cli fill e1 "Jane" # 名
playwright-cli fill e2 "Doe" # 姓
playwright-cli fill e3 "jane@example.com" # 邮箱
playwright-cli fill e4 "+1-555-0123" # 电话
playwright-cli click e5 # 下一步
# 步骤 2:地址
playwright-cli snapshot
playwright-cli fill e1 "123 Main Street"
playwright-cli fill e2 "Apt 4B"
playwright-cli fill e3 "Springfield"
playwright-cli select e4 "IL"
playwright-cli fill e5 "62701"
playwright-cli click e6 # 下一步
# 步骤 3:上传文档
playwright-cli snapshot
playwright-cli upload e1 ./resume.pdf
playwright-cli upload e2 ./cover-letter.pdf
playwright-cli click e3 # 下一步
# 步骤 4:审核与提交
playwright-cli snapshot
playwright-cli screenshot --filename=review-page.png
playwright-cli check e1 # 接受条款
playwright-cli click e2 # 提交
playwright-cli snapshot # 确认页面
```
### 带条件字段的动态表单
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com/registration');
// 选择账户类型——这会显示/隐藏字段
await page.selectOption('#account-type', 'business');
// 等待企业字段出现
await page.waitForSelector('#company-name', { state: 'visible' });
// 填写企业专属字段
await page.fill('#company-name', 'Acme Corp');
await page.fill('#tax-id', '12-3456789');
await page.fill('#company-size', '50-100');
// 填写通用字段
await page.fill('#contact-name', 'Jane Doe');
await page.fill('#contact-email', 'jane@acme.com');
await page.click('button:text(\"Register\")');
await page.waitForURL('**/welcome');
return 'Registration complete';
}"
```
## 数据提取模式
### 表格数据提取
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com/reports');
const data = await page.evaluate(() => {
const table = document.querySelector('table.data-table');
const headers = Array.from(table.querySelectorAll('thead th'))
.map(th => th.textContent.trim());
const rows = Array.from(table.querySelectorAll('tbody tr'))
.map(tr => {
const cells = Array.from(tr.querySelectorAll('td'))
.map(td => td.textContent.trim());
return Object.fromEntries(headers.map((h, i) => [h, cells[i]]));
});
return { headers, rowCount: rows.length, rows };
});
return JSON.stringify(data, null, 2);
}"
```
### 提取结构化数据(JSON-LD、Meta 标签)
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
// JSON-LD 结构化数据
const jsonLd = Array.from(document.querySelectorAll('script[type=\"application/ld+json\"]'))
.map(s => JSON.parse(s.textContent));
// Open Graph meta 标签
const og = {};
document.querySelectorAll('meta[property^=\"og:\"]').forEach(m => {
og[m.getAttribute('property')] = m.content;
});
// 标准 meta 标签
const meta = {};
document.querySelectorAll('meta[name]').forEach(m => {
meta[m.name] = m.content;
});
return { jsonLd, openGraph: og, meta };
});
}"
```
### 截图每个链接的目标页面
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com');
const links = await page.locator('nav a').evaluateAll(
anchors => anchors.map(a => ({ text: a.textContent.trim(), href: a.href }))
);
for (const link of links) {
const safeName = link.text.toLowerCase().replace(/[^a-z0-9]/g, '-');
await page.goto(link.href);
await page.waitForLoadState('networkidle');
await page.screenshot({ path: \`screenshots/\${safeName}.png\` });
}
return links.map(l => l.text);
}"
```
## 错误恢复模式
### 失败时重试
```bash
playwright-cli run-code "async page => {
const maxRetries = 3;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await page.goto('https://flaky-site.example.com', { timeout: 15000 });
await page.waitForSelector('.content', { timeout: 5000 });
return 'Success on attempt ' + attempt;
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(\`Attempt \${attempt} failed, retrying...\`);
await page.waitForTimeout(2000 * attempt); // 指数退避
}
}
}"
```
### 自动关闭 Cookie 横幅
```bash
playwright-cli run-code "async page => {
// 尝试常见的 Cookie 同意选择器
const selectors = [
'button:text(\"Accept\")',
'button:text(\"Accept All\")',
'button:text(\"Accept Cookies\")',
'button:text(\"I Agree\")',
'#cookie-accept',
'.cookie-consent-accept',
'[data-testid=cookie-accept]'
];
for (const sel of selectors) {
try {
const btn = page.locator(sel).first();
if (await btn.isVisible({ timeout: 1000 })) {
await btn.click();
return 'Dismissed cookie banner with: ' + sel;
}
} catch (e) {
// 尝试下一个选择器
}
}
return 'No cookie banner found';
}"
```
### 处理意外弹框
```bash
# 设置自动关闭所有弹框
playwright-cli run-code "async page => {
page.on('dialog', async dialog => {
console.log(\`Auto-handled \${dialog.type()} dialog: \${dialog.message()}\`);
await dialog.dismiss();
});
}"
```
## 组合 CLI 功能
### 完整的端到端测试工作流
```bash
#!/bin/bash
set -e
# 1. 启动新的会话
playwright-cli open https://app.example.com --persistent
# 2. 设置监控
playwright-cli run-code "async page => {
page.on('console', msg => {
if (msg.type() === 'error') console.log('[ERROR]', msg.text());
});
}"
# 3. 启动追踪
playwright-cli tracing-start
# 4. 登录
playwright-cli goto https://app.example.com/login
playwright-cli snapshot
playwright-cli fill e1 "test@example.com"
playwright-cli fill e2 "password123"
playwright-cli click e3
playwright-cli state-save auth-state.json
# 5. 验证仪表盘
playwright-cli snapshot
playwright-cli screenshot --filename=dashboard.png
# 6. 测试某个功能
playwright-cli click e5 # 导航至功能页面
playwright-cli snapshot
playwright-cli fill e1 "test data"
playwright-cli click e3 # 提交
playwright-cli screenshot --filename=feature-result.png
# 7. 停止追踪并清理
playwright-cli tracing-stop
playwright-cli close
echo "Test complete. Check screenshots/ and traces/"
```
### 对比测试脚本
```bash
#!/bin/bash
# 对比两个环境
STAGING="https://staging.example.com"
PROD="https://www.example.com"
PAGES=("/" "/pricing" "/about" "/features")
for path in "${PAGES[@]}"; do
safePath=$(echo "$path" | tr '/' '-' | sed 's/^-//')
[ -z "$safePath" ] && safePath="home"
playwright-cli -s=staging open "${STAGING}${path}"
playwright-cli -s=staging screenshot --filename="compare-staging-${safePath}.png"
playwright-cli -s=prod open "${PROD}${path}"
playwright-cli -s=prod screenshot --filename="compare-prod-${safePath}.png"
done
playwright-cli close-all
echo "Screenshots saved. Compare staging vs prod visually."
```
## 提示
- **从简到繁**——从基本的 CLI 命令开始,仅在必要时使用 `run-code`
- **保存状态检查点**——在关键节点执行 `state-save checkpoint.json`,以便出问题时可以恢复
- **使用命名会话实现隔离**——切勿在单个会话中混杂不同任务
- **监控控制台和网络**——尽早设置监听器,以便实时捕获错误
- **追踪复杂流程**——调试多步骤工作流时始终开启追踪
- **错误恢复至关重要**——网站不稳定,请在复杂脚本中构建重试逻辑
- **清理资源**——每个脚本末尾执行 `close-all`;出问题时执行 `kill-all`
+375
View File
@@ -0,0 +1,375 @@
# 核心命令
> **适用场景**:通过 CLI 启动浏览器自动化会话、浏览页面、与元素交互、填写表单以及执行基本浏览器操作。
> **前置条件**`playwright-cli install --skills && playwright-cli install-browser`
## 快速参考
```bash
playwright-cli open https://example.com # 启动并跳转
playwright-cli snapshot # 查看所有可交互元素
playwright-cli fill e1 "user@example.com" # 填写输入框
playwright-cli click e3 # 点击按钮
playwright-cli screenshot --filename=pg.png # 截取页面截图
playwright-cli close # 完成
```
## 快照工作流
每次交互都从**快照**开始。`snapshot` 命令会渲染页面的无障碍树,并为每个可交互元素分配简短的引用(如 `e1``e2``e3`)。
```bash
playwright-cli open https://example.com/login
playwright-cli snapshot
# 输出:
# e1 [textbox "邮箱"]
# e2 [textbox "密码"]
# e3 [button "登录"]
# e4 [link "忘记密码?"]
# e5 [link "创建账号"]
```
使用引用来精确定位元素:
```bash
playwright-cli fill e1 "user@example.com"
playwright-cli fill e2 "secretpassword"
playwright-cli click e3
```
**页面变化后务必重新截图**——引用仅对当前页面状态有效。在导航、表单提交或动态内容更新后,应重新生成快照。
```bash
playwright-cli click e3 # 提交表单
playwright-cli snapshot # 重新截图以查看新页面元素
```
### 保存快照
将快照保存为 YAML 文件,以便后续参考或对比:
```bash
playwright-cli snapshot --filename=before-submit.yaml
playwright-cli click e3
playwright-cli snapshot --filename=after-submit.yaml
```
## 打开与关闭浏览器
### 基本启动
```bash
# 打开浏览器,显示空白页面
playwright-cli open
# 打开并立即跳转
playwright-cli open https://example.com
# 使用特定浏览器引擎打开
playwright-cli open --browser=chrome # Google Chrome
playwright-cli open --browser=firefox # Mozilla Firefox
playwright-cli open --browser=webkit # Safari/WebKit
playwright-cli open --browser=msedge # Microsoft Edge
```
### 持久化配置
默认情况下,每个会话在内存中运行——关闭后不会保留 Cookie 或存储数据。使用 `--persistent` 可在重启后保持状态:
```bash
# 自动生成的配置目录
playwright-cli open https://example.com --persistent
# 自定义配置目录
playwright-cli open https://example.com --profile=/tmp/my-profile
# 使用配置文件
playwright-cli open https://example.com --config=my-config.json
```
### 浏览器扩展模式
通过扩展连接到现有浏览器,而非启动新浏览器:
```bash
playwright-cli open --extension
```
### 关闭
```bash
playwright-cli close # 关闭当前浏览器
playwright-cli close-all # 关闭所有已打开的浏览器
playwright-cli kill-all # 强制杀死僵尸进程
playwright-cli delete-data # 删除已存储的配置数据
```
## 导航
```bash
playwright-cli goto https://example.com/dashboard # 跳转到 URL
playwright-cli go-back # 浏览器后退
playwright-cli go-forward # 浏览器前进
playwright-cli reload # 刷新页面
```
### 等待导航
在执行触发导航的操作(表单提交、链接点击)后,CLI 会等待页面达到 `load` 状态后再返回。如果需要等待特定条件,可使用 `run-code`
```bash
playwright-cli run-code "async page => {
await page.waitForURL('**/dashboard');
}"
# 等待网络空闲
playwright-cli run-code "async page => {
await page.waitForLoadState('networkidle');
}"
```
## 元素交互
### 点击
```bash
playwright-cli click e3 # 标准左键单击
playwright-cli dblclick e7 # 双击
playwright-cli hover e4 # 悬停(不点击)
```
### 表单输入
```bash
# fill:清除现有值,然后一次性输入完整值
playwright-cli fill e5 "user@example.com"
# type:逐个发送按键事件(每个字符触发 keydown/keypress/keyup
playwright-cli type "search query"
# select:按值选择下拉选项
playwright-cli select e9 "option-value"
# check/uncheck:勾选/取消勾选复选框
playwright-cli check e12
playwright-cli uncheck e12
```
**何时使用 `fill` 与 `type`**
| 命令 | 行为 | 适用场景 |
| -------- | ------------------------------ | ---------------------------------------------- |
| `fill` | 清除字段,一次性设置值 | 表单、登录字段、标准输入框 |
| `type` | 逐个发送按键事件 | 自动补全、即输即搜、监听 keydown 事件的自定义输入 |
### 文件上传
```bash
playwright-cli upload e8 ./document.pdf
playwright-cli upload e8 ./photo1.jpg ./photo2.jpg # 多文件
```
### 拖放
```bash
playwright-cli drag e2 e8 # 将元素 e2 拖放到元素 e8 上
```
### 视口调整
```bash
playwright-cli resize 1920 1080 # 桌面端
playwright-cli resize 375 812 # iPhone X 视口
playwright-cli resize 768 1024 # iPad 视口
```
## 键盘输入
### 单键按下
```bash
playwright-cli press Enter
playwright-cli press Tab
playwright-cli press Escape
playwright-cli press Backspace
playwright-cli press Delete
playwright-cli press Space
```
### 方向键
```bash
playwright-cli press ArrowUp
playwright-cli press ArrowDown
playwright-cli press ArrowLeft
playwright-cli press ArrowRight
```
### 修饰键组合
```bash
playwright-cli press Control+a # 全选
playwright-cli press Control+c # 复制
playwright-cli press Control+v # 粘贴
playwright-cli press Control+z # 撤销
playwright-cli press Meta+a # 全选(macOS 下为 Cmd+A
playwright-cli press Shift+Tab # 反向切换焦点
playwright-cli press Alt+Enter # Alt+Enter
```
### 按键保持与释放
用于拖拽操作或多键序列:
```bash
playwright-cli keydown Shift
playwright-cli click e5 # Shift+点击
playwright-cli click e8 # Shift+点击(范围选择)
playwright-cli keyup Shift
```
## 鼠标控制
用于像素级精确操作(画布、地图、自定义控件):
```bash
playwright-cli mousemove 150 300 # 将光标移动到指定坐标
playwright-cli mousedown # 按下左键
playwright-cli mouseup # 释放左键
playwright-cli mousedown right # 按下右键
playwright-cli mouseup right # 释放右键
playwright-cli mousewheel 0 100 # 向下滚动 100px
playwright-cli mousewheel 0 -100 # 向上滚动 100px
playwright-cli mousewheel 100 0 # 向右滚动 100px
```
### 在画布上绘制
```bash
playwright-cli open https://example.com/canvas-app
playwright-cli mousemove 100 100
playwright-cli mousedown
playwright-cli mousemove 200 200
playwright-cli mousemove 300 150
playwright-cli mouseup
```
## 对话框处理
对话框(`alert``confirm``prompt`)会阻塞浏览器交互,需立即处理:
```bash
# 接受 alert 或 confirm 对话框
playwright-cli dialog-accept
# 接受 prompt 对话框并输入文本
playwright-cli dialog-accept "my response"
# 驳回/取消对话框
playwright-cli dialog-dismiss
```
**提示**:如果预期某个点击会触发对话框,可在触发操作前通过 `run-code` 配置处理逻辑:
```bash
playwright-cli run-code "async page => {
page.on('dialog', dialog => dialog.accept('confirmed'));
}"
playwright-cli click e5 # 此操作会触发对话框
```
## JavaScript 求值
在页面上下文中直接执行 JavaScript 表达式:
```bash
# 简单表达式
playwright-cli eval "document.title"
playwright-cli eval "window.location.href"
playwright-cli eval "document.querySelectorAll('li').length"
# 对特定元素求值
playwright-cli eval "el => el.textContent" e5
playwright-cli eval "el => el.getAttribute('href')" e3
playwright-cli eval "el => el.getBoundingClientRect()" e1
# 复杂求值
playwright-cli eval "JSON.stringify(performance.timing)"
playwright-cli eval "window.innerWidth + 'x' + window.innerHeight"
```
## 示例工作流
### 登录流程
```bash
playwright-cli open https://app.example.com/login
playwright-cli snapshot
playwright-cli fill e1 "admin@example.com"
playwright-cli fill e2 "password123"
playwright-cli click e3
playwright-cli snapshot # 查看仪表盘
playwright-cli screenshot --filename=dashboard.png
playwright-cli close
```
### 含验证的表单提交
```bash
playwright-cli open https://example.com/contact
playwright-cli snapshot
# 填写表单字段
playwright-cli fill e1 "Jane Doe"
playwright-cli fill e2 "jane@example.com"
playwright-cli fill e3 "+1-555-0123"
playwright-cli select e4 "support"
playwright-cli fill e5 "I need help with my account"
playwright-cli check e6 # 同意条款
# 提交
playwright-cli click e7
playwright-cli snapshot # 验证成功消息
playwright-cli close
```
### 搜索结果浏览
```bash
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli fill e1 "playwright automation"
playwright-cli press Enter
playwright-cli snapshot # 查看搜索结果
playwright-cli click e5 # 点击第一个结果
playwright-cli snapshot # 查看结果页
playwright-cli go-back # 返回搜索结果
playwright-cli close
```
### 多标签研究
```bash
playwright-cli open https://docs.example.com
playwright-cli tab-new https://api.example.com/reference
playwright-cli tab-new https://github.com/example/repo
playwright-cli tab-list # 查看所有 3 个标签页
playwright-cli tab-select 0 # 切换到文档标签页
playwright-cli snapshot
playwright-cli tab-select 2 # 切换到 GitHub 标签页
playwright-cli snapshot
playwright-cli close # 关闭所有标签页
```
## 小贴士
- **频繁截图**:每次页面发生变化后,重新截图以获取最新引用
- **表单使用 `fill`**:对于标准输入,`fill``type` 更快更可靠
- **注意视口**:截图前使用 `resize` 测试响应式布局
- **结合 `run-code`**:当 CLI 命令受限时,可直接使用完整的 Playwright API
- **检查 `console`**:运行 `playwright-cli console` 查看可能解释异常行为的 JavaScript 错误
+481
View File
@@ -0,0 +1,481 @@
# 设备和环境模拟
> **适用场景**:无需物理设备,全部在 CLI 中测试应用在不同设备、屏幕尺寸、地理位置、语言区域、时区、配色方案和网络条件下的表现。
> **前置条件**[core-commands.md](core-commands.md) 了解基本 CLI 用法,[running-custom-code.md](running-custom-code.md) 了解 `run-code` 语法
## 快速参考
```bash
# 通过配置文件模拟设备
playwright-cli open https://example.com --config=iphone.json
# 调整视口大小
playwright-cli resize 375 812 # iPhone 视口
playwright-cli resize 1920 1080 # 桌面视口
# 地理位置
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 40.7128, longitude: -74.0060 });
}"
# 配色方案
playwright-cli run-code "async page => {
await page.emulateMedia({ colorScheme: 'dark' });
}"
```
## 视口模拟
设备测试的最简单形式——将视口大小设置为目标设备的尺寸:
### 常见视口尺寸
```bash
# 桌面
playwright-cli resize 1920 1080 # Full HD 显示器
playwright-cli resize 1440 900 # MacBook Pro 15"
playwright-cli resize 1366 768 # 常见笔记本电脑
playwright-cli resize 2560 1440 # QHD / 2K 显示器
# 平板
playwright-cli resize 1024 768 # iPad 横屏
playwright-cli resize 768 1024 # iPad 竖屏
playwright-cli resize 834 1194 # iPad Pro 11"
playwright-cli resize 1194 834 # iPad Pro 11" 横屏
playwright-cli resize 820 1180 # iPad Air
# 手机
playwright-cli resize 430 932 # iPhone 14 Pro Max
playwright-cli resize 393 852 # iPhone 14 Pro
playwright-cli resize 390 844 # iPhone 14 / 13 / 12
playwright-cli resize 375 812 # iPhone X / 11
playwright-cli resize 360 800 # Samsung Galaxy S21
playwright-cli resize 412 915 # Pixel 7
playwright-cli resize 320 568 # iPhone SE(第 1 代)
```
### 测试响应式断点
```bash
# 常见 CSS 断点
playwright-cli resize 320 568 # xs: 特小屏
playwright-cli screenshot --filename=responsive-xs.png
playwright-cli resize 576 768 # sm: 小屏
playwright-cli screenshot --filename=responsive-sm.png
playwright-cli resize 768 1024 # md: 中屏
playwright-cli screenshot --filename=responsive-md.png
playwright-cli resize 992 768 # lg: 大屏
playwright-cli screenshot --filename=responsive-lg.png
playwright-cli resize 1200 900 # xl: 特大屏
playwright-cli screenshot --filename=responsive-xl.png
playwright-cli resize 1400 900 # xxl: 超大屏
playwright-cli screenshot --filename=responsive-xxl.png
```
## 完整设备模拟
要进行精确的设备测试,仅设置视口是不够的——还需要设备缩放因子、User Agent、触摸支持和移动端行为。请使用配置文件或 `run-code`
### 配置文件方式
为特定设备创建配置文件:
**`iphone14.json`**
```json
{
"viewport": { "width": 390, "height": 844 },
"deviceScaleFactor": 3,
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
"isMobile": true,
"hasTouch": true
}
```
```bash
playwright-cli open https://example.com --config=iphone14.json
```
**`pixel7.json`**
```json
{
"viewport": { "width": 412, "height": 915 },
"deviceScaleFactor": 2.625,
"userAgent": "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
"isMobile": true,
"hasTouch": true
}
```
**`ipad-pro.json`**
```json
{
"viewport": { "width": 834, "height": 1194 },
"deviceScaleFactor": 2,
"userAgent": "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
"isMobile": true,
"hasTouch": true
}
```
### 程序化设备模拟
使用 `run-code` 检查或修改设备属性:
```bash
# 检查当前设备属性
playwright-cli run-code "async page => {
return await page.evaluate(() => ({
userAgent: navigator.userAgent,
viewport: { width: window.innerWidth, height: window.innerHeight },
devicePixelRatio: window.devicePixelRatio,
touchSupport: 'ontouchstart' in window,
platform: navigator.platform
}));
}"
```
## 地理位置
覆盖浏览器报告的 GPS 位置——测试门店定位器、配送区域、基于位置的内容。
### 设置位置
```bash
# 纽约
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 40.7128, longitude: -74.0060 });
}"
# 伦敦
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().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 35.6762, longitude: 139.6503 });
}"
# 悉尼
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: -33.8688, longitude: 151.2093 });
}"
# 圣保罗
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: -23.5505, longitude: -46.6333 });
}"
```
### 在会话中更新位置
模拟用户在不同位置之间移动:
```bash
# 从旧金山开始
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });
}"
playwright-cli goto https://example.com/nearby-stores
playwright-cli screenshot --filename=sf-stores.png
# 移动到洛杉矶
playwright-cli run-code "async page => {
await page.context().setGeolocation({ latitude: 34.0522, longitude: -118.2437 });
}"
playwright-cli reload
playwright-cli screenshot --filename=la-stores.png
```
### 清除地理位置覆盖
```bash
playwright-cli run-code "async page => {
await page.context().clearPermissions();
}"
```
### 通过配置文件设置地理位置
在打开浏览器时设置地理位置:
**`geo-nyc.json`**
```json
{
"geolocation": { "latitude": 40.7128, "longitude": -74.006 },
"permissions": ["geolocation"]
}
```
```bash
playwright-cli open https://example.com --config=geo-nyc.json
```
## 语言区域和时区
通过更改浏览器的语言区域和时区来测试国际化。
### 通过配置文件
**`locale-de.json`**
```json
{
"locale": "de-DE",
"timezoneId": "Europe/Berlin"
}
```
**`locale-ja.json`**
```json
{
"locale": "ja-JP",
"timezoneId": "Asia/Tokyo"
}
```
```bash
playwright-cli open https://example.com --config=locale-de.json
```
### 验证语言区域和时区
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => ({
language: navigator.language,
languages: navigator.languages,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
dateFormat: new Date('2024-01-15').toLocaleDateString(),
numberFormat: (1234567.89).toLocaleString(),
currencyFormat: new Intl.NumberFormat(navigator.language, {
style: 'currency',
currency: 'USD'
}).format(1234.56)
}));
}"
```
### 常见语言区域 + 时区组合
| 区域 | 语言区域 | 时区 |
| --------- | -------- | ---------------------- |
| 美国东部 | `en-US` | `America/New_York` |
| 美国西部 | `en-US` | `America/Los_Angeles` |
| 英国 | `en-GB` | `Europe/London` |
| 德国 | `de-DE` | `Europe/Berlin` |
| 法国 | `fr-FR` | `Europe/Paris` |
| 日本 | `ja-JP` | `Asia/Tokyo` |
| 中国 | `zh-CN` | `Asia/Shanghai` |
| 印度 | `hi-IN` | `Asia/Kolkata` |
| 巴西 | `pt-BR` | `America/Sao_Paulo` |
| 澳大利亚 | `en-AU` | `Australia/Sydney` |
| 阿拉伯地区 | `ar-SA` | `Asia/Riyadh` |
## 配色方案
测试深色模式、浅色模式和高对比度:
```bash
# 深色模式
playwright-cli run-code "async page => {
await page.emulateMedia({ colorScheme: 'dark' });
}"
playwright-cli screenshot --filename=dark-mode.png
# 浅色模式
playwright-cli run-code "async page => {
await page.emulateMedia({ colorScheme: 'light' });
}"
playwright-cli screenshot --filename=light-mode.png
# 系统偏好(不覆盖)
playwright-cli run-code "async page => {
await page.emulateMedia({ colorScheme: 'no-preference' });
}"
```
## 减少动画
测试偏好减少动画的用户的可访问性:
```bash
# 启用减少动画
playwright-cli run-code "async page => {
await page.emulateMedia({ reducedMotion: 'reduce' });
}"
# 检查你的 CSS 是否遵循该设置
playwright-cli run-code "async page => {
return await page.evaluate(() =>
window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
}"
```
## 强制颜色(高对比度)
测试 Windows 高对比度模式:
```bash
playwright-cli run-code "async page => {
await page.emulateMedia({ forcedColors: 'active' });
}"
playwright-cli screenshot --filename=high-contrast.png
```
## 权限
授予或拒绝浏览器权限:
```bash
# 授予多个权限
playwright-cli run-code "async page => {
await page.context().grantPermissions([
'geolocation',
'notifications',
'camera',
'microphone'
]);
}"
# 为特定来源授予权限
playwright-cli run-code "async page => {
await page.context().grantPermissions(['notifications'], {
origin: 'https://example.com'
});
}"
# 清除所有权限(重置为默认值)
playwright-cli run-code "async page => {
await page.context().clearPermissions();
}"
```
可用权限:`geolocation``notifications``camera``microphone``clipboard-read``clipboard-write``payment-handler``midi``midi-sysex``ambient-light-sensor``accelerometer``gyroscope``magnetometer``background-sync`
## 网络条件模拟
模拟慢速网络以测试加载状态和性能:
```bash
# 模拟慢速 3G
playwright-cli run-code "async page => {
const client = await page.context().newCDPSession(page);
await client.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: 500 * 1024 / 8, // 500 kbps
uploadThroughput: 500 * 1024 / 8, // 500 kbps
latency: 400 // 400ms RTT
});
}"
# 模拟离线模式
playwright-cli run-code "async page => {
const client = await page.context().newCDPSession(page);
await client.send('Network.emulateNetworkConditions', {
offline: true,
downloadThroughput: 0,
uploadThroughput: 0,
latency: 0
});
}"
# 恢复正常网络
playwright-cli run-code "async page => {
const client = await page.context().newCDPSession(page);
await client.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: -1,
uploadThroughput: -1,
latency: 0
});
}"
```
**注意**:CDP 会话仅适用于基于 Chromium 的浏览器。
## 常见模式
### 多设备截图套件
```bash
#!/bin/bash
URL="https://example.com"
playwright-cli open $URL
devices=("1920:1080:desktop" "768:1024:tablet" "375:812:mobile")
for device in "${devices[@]}"; do
IFS=':' read -r w h name <<< "$device"
playwright-cli resize $w $h
playwright-cli screenshot --filename="device-$name.png"
done
playwright-cli close
```
### 基于地理位置的内容测试
```bash
locations=("40.7128:-74.0060:nyc" "51.5074:-0.1278:london" "35.6762:139.6503:tokyo")
for loc in "${locations[@]}"; do
IFS=':' read -r lat lng name <<< "$loc"
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: $lat, longitude: $lng });
}"
playwright-cli reload
playwright-cli screenshot --filename="geo-$name.png"
done
```
### 可访问性模拟套件
```bash
playwright-cli open https://example.com
# 标准视图
playwright-cli screenshot --filename=a11y-standard.png
# 深色模式
playwright-cli run-code "async page => { await page.emulateMedia({ colorScheme: 'dark' }); }"
playwright-cli screenshot --filename=a11y-dark.png
# 高对比度
playwright-cli run-code "async page => { await page.emulateMedia({ forcedColors: 'active' }); }"
playwright-cli screenshot --filename=a11y-high-contrast.png
# 减少动画
playwright-cli run-code "async page => { await page.emulateMedia({ reducedMotion: 'reduce', forcedColors: 'none' }); }"
playwright-cli screenshot --filename=a11y-reduced-motion.png
playwright-cli close
```
## 提示
- **仅设置视口不等于设备模拟**——真正的设备测试需要通过配置文件设置 User Agent、触摸支持和设备缩放因子
- **地理位置需要权限**——在 `setGeolocation()` 之前务必先调用 `grantPermissions(['geolocation'])`
- **语言区域/时区必须在打开时设置**——使用配置文件,因为会话中途无法更改
- **CDP 功能仅限 Chromium**——通过 CDP 实现的网络限速在 Firefox 或 WebKit 中不适用
- **测试组合场景**——深色模式 + 减少动画 + 特定语言区域组合在一起时,能发现单独测试无法暴露的问题
+373
View File
@@ -0,0 +1,373 @@
# 请求拦截(Request Mocking
> **适用场景**:在浏览器自动化过程中拦截、模拟、修改或阻断网络请求——API 桩代码、模拟错误、测试离线行为、移除追踪,或通过拦截大资源来加速页面加载。
> **前置条件**:请先阅读 [core-commands.md](core-commands.md) 了解基本 CLI 用法
## 快速参考
```bash
# 拦截所有图片
playwright-cli route "**/*.jpg" --status=404
# 以 JSON 响应体模拟 API
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"
# 移除请求头(例如测试时去掉认证信息)
playwright-cli route "**/*" --remove-header=cookie,authorization
# 列出当前活跃的路由规则
playwright-cli route-list
# 移除某条路由规则
playwright-cli unroute "**/*.jpg"
# 移除所有路由规则
playwright-cli unroute
```
## URL 匹配模式
playwright-cli 使用 glob 模式进行 URL 匹配:
| 模式 | 匹配规则 | 示例 URL |
| ------------------------------- | -------------------------------- | --------------------------------------------- |
| `**/api/users` | 匹配任意来源的精确路径 | `https://api.example.com/api/users` |
| `**/api/*/details` | 路径段中的通配符 | `https://api.example.com/api/123/details` |
| `**/*.{png,jpg,jpeg}` | 多个文件扩展名 | `https://cdn.example.com/hero.png` |
| `**/search?q=*` | 查询参数 | `https://example.com/search?q=test` |
| `https://api.example.com/**` | 匹配特定来源的所有请求 | `https://api.example.com/v2/users` |
| `**/*` | 匹配所有请求 | 所有请求 |
## CLI 路由命令
### 模拟状态码
```bash
# 对所有图片请求返回 404
playwright-cli route "**/*.jpg" --status=404
# 返回 503 Service Unavailable
playwright-cli route "**/api/health" --status=503
# 返回 401 Unauthorized
playwright-cli route "**/api/protected" --status=401
```
### 模拟 JSON 响应
```bash
# 简单的 JSON 响应体
playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]' --content-type=application/json
# 模拟单个资源
playwright-cli route "**/api/users/1" --body='{"id":1,"name":"Alice","email":"alice@example.com"}' --content-type=application/json
# 返回空数组(无结果场景)
playwright-cli route "**/api/search*" --body='[]' --content-type=application/json
```
### 模拟自定义响应头
```bash
# 测试时设置 CORS 头
playwright-cli route "**/api/**" --body='{"ok":true}' --header="Access-Control-Allow-Origin: *" --header="X-Request-Id: mock-123"
# 设置缓存头
playwright-cli route "**/static/**" --header="Cache-Control: max-age=3600"
```
### 移除请求头
从发出的请求中移除指定请求头——适用于测试未认证访问:
```bash
# 移除认证头
playwright-cli route "**/*" --remove-header=cookie,authorization
# 移除追踪头
playwright-cli route "**/*" --remove-header=x-tracking-id
```
### 管理活跃路由
```bash
# 查看当前拦截了哪些请求
playwright-cli route-list
# 移除某条特定路由
playwright-cli unroute "**/*.jpg"
# 清除所有路由
playwright-cli unroute
```
## 使用 run-code 实现高级模拟
如需条件响应、请求体检查、响应修改或定时延迟,可使用 `run-code` 调用完整的 Playwright 路由 API。
### 基于请求体的条件响应
```bash
playwright-cli run-code "async page => {
await page.route('**/api/login', route => {
const body = route.request().postDataJSON();
if (body.username === 'admin' && body.password === 'secret') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'mock-jwt-token', user: { role: 'admin' } })
});
} else {
route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'Invalid credentials' })
});
}
});
}"
```
### 基于 HTTP 方法的条件响应
```bash
playwright-cli run-code "async page => {
await page.route('**/api/users', route => {
const method = route.request().method();
switch (method) {
case 'GET':
route.fulfill({
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Alice' }])
});
break;
case 'POST':
route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: 2, name: 'New User' })
});
break;
case 'DELETE':
route.fulfill({ status: 204 });
break;
default:
route.continue();
}
});
}"
```
### 修改真实响应
让请求实际发出,然后在页面接收到响应之前对其进行修改:
```bash
playwright-cli run-code "async page => {
await page.route('**/api/user/profile', async route => {
const response = await route.fetch();
const json = await response.json();
// 覆盖特定字段
json.isPremium = true;
json.subscription = 'enterprise';
await route.fulfill({ response, json });
});
}"
```
### 为真实响应添加响应头
```bash
playwright-cli run-code "async page => {
await page.route('**/api/**', async route => {
const response = await route.fetch();
const headers = { ...response.headers(), 'x-mock': 'true' };
await route.fulfill({ response, headers });
});
}"
```
### 模拟网络故障
```bash
# 网络断开
playwright-cli run-code "async page => {
await page.route('**/api/offline', route => route.abort('internetdisconnected'));
}"
# 连接被拒绝
playwright-cli run-code "async page => {
await page.route('**/api/down', route => route.abort('connectionrefused'));
}"
# 超时
playwright-cli run-code "async page => {
await page.route('**/api/slow', route => route.abort('timedout'));
}"
# 连接重置
playwright-cli run-code "async page => {
await page.route('**/api/reset', route => route.abort('connectionreset'));
}"
```
可用的中止原因:`connectionrefused`, `timedout`, `connectionreset`, `internetdisconnected`, `blockedbyclient`, `failed`
### 模拟慢响应(延迟)
```bash
playwright-cli run-code "async page => {
await page.route('**/api/slow-endpoint', async route => {
await new Promise(resolve => setTimeout(resolve, 3000));
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ data: 'finally loaded' })
});
});
}"
```
### 从文件读取响应进行模拟
```bash
playwright-cli run-code "async page => {
const fs = require('fs');
await page.route('**/api/config', route => {
const body = fs.readFileSync('./fixtures/mock-config.json', 'utf8');
route.fulfill({
contentType: 'application/json',
body
});
});
}"
```
### 请求计数与验证
```bash
playwright-cli run-code "async page => {
let apiCallCount = 0;
await page.route('**/api/analytics', route => {
apiCallCount++;
route.continue();
});
// ... 执行页面操作 ...
// 之后检查计数:
return \`API was called \${apiCallCount} times\`;
}"
```
## 常见模式
### 拦截大资源以提升速度
```bash
# 拦截图片、字体和样式表
playwright-cli route "**/*.{png,jpg,jpeg,gif,svg,webp}" --status=404
playwright-cli route "**/*.{woff,woff2,ttf,eot}" --status=404
playwright-cli route "**/*.css" --status=404
```
### 拦截第三方脚本
```bash
# 拦截分析及追踪脚本
playwright-cli run-code "async page => {
await page.route('**/*', route => {
const url = route.request().url();
const blocked = [
'google-analytics.com',
'googletagmanager.com',
'facebook.net',
'hotjar.com',
'segment.io'
];
if (blocked.some(domain => url.includes(domain))) {
route.abort('blockedbyclient');
} else {
route.continue();
}
});
}"
```
### 模拟 GraphQL 请求
```bash
playwright-cli run-code "async page => {
await page.route('**/graphql', route => {
const { query } = route.request().postDataJSON();
if (query.includes('GetUser')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { user: { id: '1', name: 'Alice', email: 'alice@example.com' } }
})
});
} else if (query.includes('ListProducts')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { products: [{ id: '1', name: 'Widget', price: 9.99 }] }
})
});
} else {
route.continue();
}
});
}"
```
### 基于 HAR 文件的重放
录制真实网络流量,之后回放——非常适合确定性测试:
```bash
# 将所有网络流量录制到 HAR 文件
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
update: true,
url: '**/api/**'
});
await page.goto('https://example.com');
// 在页面上执行操作——所有匹配的请求都会被录制
}"
# 之后从 HAR 文件回放(无需真实网络)
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
url: '**/api/**'
});
await page.goto('https://example.com');
// API 响应来自 HAR 文件
}"
```
### 模拟 WebSocket 消息
```bash
playwright-cli run-code "async page => {
const ws = await page.waitForEvent('websocket');
ws.on('framereceived', event => {
console.log('WS received:', event.payload);
});
ws.on('framesent', event => {
console.log('WS sent:', event.payload);
});
}"
```
## 提示
- **顺序很重要**:路由按照添加的顺序依次评估。更具体的模式应放在全匹配模式之前添加。
- **`route.continue()`**:让请求继续发往真实服务器——在条件路由中用于"透传"场景。
- **`route.fetch()`**:发起真实请求并返回响应,以便你在兑现前检查或修改它。
- **性能**:拦截图片和第三方脚本可以大幅提升自动化过程中的页面加载速度。
- **清理**:使用完毕后务必执行 `unroute`,否则路由规则在整个会话中持续生效,可能会干扰后续操作。
+553
View File
@@ -0,0 +1,553 @@
# 运行自定义 Playwright 代码
> **何时使用**:当 CLI 命令不够用时——地理定位、权限、媒体模拟、等待策略、iframe 交互、文件下载、剪贴板访问、复杂的多步骤工作流,或任何需要完整 Playwright API 的场景。
> **前置条件**[core-commands.md](core-commands.md) 了解基本的 CLI 用法
## 快速参考
```bash
# 语法:run-code 接受一个以 page 为参数的 async 函数
playwright-cli run-code "async page => {
// 此处可使用完整的 Playwright API
// page.context() 用于浏览器上下文操作
// 返回一个值即可在输出中查看
return await page.title();
}"
```
## 地理定位
覆盖浏览器报告的位置——对于测试基于位置的功能(如门店查找器、配送区域和天气应用)至关重要。
```bash
# 授予权限并将位置设置为纽约
playwright-cli run-code "async page => {
await page.context().grantPermissions(['geolocation']);
await page.context().setGeolocation({ latitude: 40.7128, longitude: -74.0060 });
}"
# 伦敦
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().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: 35.6762, longitude: 139.6503 });
}"
# 在会话中更新位置(模拟用户移动)
playwright-cli run-code "async page => {
await page.context().setGeolocation({ latitude: 34.0522, longitude: -118.2437 });
}"
# 清除地理定位覆盖
playwright-cli run-code "async page => {
await page.context().clearPermissions();
}"
```
## 权限
控制浏览器权限授予——通知、摄像头、麦克风、剪贴板等。
```bash
# 授予多个权限
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', 'clipboard-write'], {
origin: 'https://example.com'
});
}"
# 撤销所有权限
playwright-cli run-code "async page => {
await page.context().clearPermissions();
}"
```
可用权限:`geolocation``notifications``camera``microphone``clipboard-read``clipboard-write``payment-handler``midi``midi-sysex``ambient-light-sensor``accelerometer``gyroscope``magnetometer``accessibility-events``background-sync`
## 媒体模拟
测试你的应用在不同媒体条件下的表现——深色模式、减弱动效、打印布局。
### 色彩方案
```bash
# 深色模式
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({ colorScheme: 'no-preference' });
}"
```
### 减弱动效
```bash
# 模拟 prefers-reduced-motion: reduce
playwright-cli run-code "async page => {
await page.emulateMedia({ reducedMotion: 'reduce' });
}"
# 重置为无偏好
playwright-cli run-code "async page => {
await page.emulateMedia({ reducedMotion: 'no-preference' });
}"
```
### 强制色彩(高对比度)
```bash
playwright-cli run-code "async page => {
await page.emulateMedia({ forcedColors: 'active' });
}"
```
### 打印媒体
```bash
# 模拟打印样式表(在生成 PDF 前使用)
playwright-cli run-code "async page => {
await page.emulateMedia({ media: 'print' });
}"
# 重置为屏幕模式
playwright-cli run-code "async page => {
await page.emulateMedia({ media: 'screen' });
}"
```
### 组合多个模拟
```bash
playwright-cli run-code "async page => {
await page.emulateMedia({
colorScheme: 'dark',
reducedMotion: 'reduce',
forcedColors: 'none'
});
}"
```
## 区域设置和时区
覆盖浏览器区域设置和时区以测试国际化:
```bash
# 设置区域设置(影响数字格式、日期显示等)
playwright-cli run-code "async page => {
// 区域设置在上下文创建时设定;对于现有上下文,使用 evaluate
return await page.evaluate(() => navigator.language);
}"
# 对于新上下文,在打开时设置区域设置和时区:
# playwright-cli open --config=locale-config.json
# 其中 locale-config.json 包含:{ "locale": "de-DE", "timezoneId": "Europe/Berlin" }
```
## 等待策略
当页面在操作后需要时间加载、渲染或稳定时使用。
### 等待加载状态
```bash
# 等待所有网络请求完成
playwright-cli run-code "async page => {
await page.waitForLoadState('networkidle');
}"
# 等待 DOM 内容加载完成
playwright-cli run-code "async page => {
await page.waitForLoadState('domcontentloaded');
}"
# 等待完全加载(包括图片、样式表)
playwright-cli run-code "async page => {
await page.waitForLoadState('load');
}"
```
### 等待元素
```bash
# 等待加载旋转器消失
playwright-cli run-code "async page => {
await page.waitForSelector('.loading-spinner', { state: 'hidden' });
}"
# 等待内容出现
playwright-cli run-code "async page => {
await page.waitForSelector('.search-results', { state: 'visible', timeout: 10000 });
}"
# 等待元素从 DOM 中完全移除
playwright-cli run-code "async page => {
await page.waitForSelector('.skeleton-loader', { state: 'detached' });
}"
```
### 等待 URL 变化
```bash
playwright-cli run-code "async page => {
await page.waitForURL('**/dashboard');
}"
playwright-cli run-code "async page => {
await page.waitForURL(/.*\/order\/\d+/);
}"
```
### 等待自定义条件
```bash
# 等待 JavaScript 变量被设置
playwright-cli run-code "async page => {
await page.waitForFunction(() => window.appReady === true);
}"
# 等待特定数量的元素
playwright-cli run-code "async page => {
await page.waitForFunction(() => document.querySelectorAll('.item').length >= 10);
}"
# 轮询等待
playwright-cli run-code "async page => {
await page.waitForFunction(
() => document.querySelector('.status')?.textContent === 'Complete',
{ polling: 500, timeout: 30000 }
);
}"
```
### 等待网络请求
```bash
# 等待特定 API 调用完成
playwright-cli run-code "async page => {
const responsePromise = page.waitForResponse('**/api/users');
await page.click('button#load-users');
const response = await responsePromise;
return { status: response.status(), url: response.url() };
}"
# 等待发起请求
playwright-cli run-code "async page => {
const requestPromise = page.waitForRequest('**/api/submit');
await page.click('button#submit');
const request = await requestPromise;
return request.postDataJSON();
}"
```
## 框架和 Iframe
与 iframe 内部的内容交互:
```bash
# 点击 iframe 内的按钮
playwright-cli run-code "async page => {
const frame = page.locator('iframe#my-iframe').contentFrame();
await frame.locator('button.submit').click();
}"
# 填写 iframe 内的表单
playwright-cli run-code "async page => {
const frame = page.locator('iframe[name=\"checkout\"]').contentFrame();
await frame.locator('input[name=\"card-number\"]').fill('4111111111111111');
await frame.locator('input[name=\"expiry\"]').fill('12/25');
await frame.locator('input[name=\"cvc\"]').fill('123');
}"
# 获取所有框架的 URL
playwright-cli run-code "async page => {
const frames = page.frames();
return frames.map(f => ({ name: f.name(), url: f.url() }));
}"
# 嵌套 iframe
playwright-cli run-code "async page => {
const outerFrame = page.locator('iframe#outer').contentFrame();
const innerFrame = outerFrame.locator('iframe#inner').contentFrame();
await innerFrame.locator('button').click();
}"
```
## 文件下载
```bash
# 触发下载并保存文件
playwright-cli run-code "async page => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a.download-link')
]);
const filename = download.suggestedFilename();
await download.saveAs('./downloads/' + filename);
return '已下载:' + filename;
}"
# 自定义路径下载
playwright-cli run-code "async page => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'Export CSV' }).click()
]);
await download.saveAs('/tmp/export.csv');
return '已保存至 /tmp/export.csv';
}"
# 获取下载 URL(不保存)
playwright-cli run-code "async page => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#download-btn')
]);
return download.url();
}"
```
## 剪贴板
```bash
# 读取剪贴板内容
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!');
}"
# 将元素中的文本复制到剪贴板
playwright-cli run-code "async page => {
const text = await page.locator('.api-key').textContent();
await page.evaluate(t => navigator.clipboard.writeText(t), text);
return '已复制:' + text;
}"
```
## 页面信息
```bash
# 页面标题
playwright-cli run-code "async page => {
return await page.title();
}"
# 当前 URL
playwright-cli run-code "async page => {
return page.url();
}"
# 完整页面 HTML 内容
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(() => ({
userAgent: navigator.userAgent,
language: navigator.language,
languages: navigator.languages,
cookiesEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
platform: navigator.platform,
screenSize: { width: screen.width, height: screen.height }
}));
}"
```
## JavaScript 执行
```bash
# 执行并返回结果
playwright-cli run-code "async page => {
return await page.evaluate(() => {
return {
title: document.title,
url: window.location.href,
elementCount: document.querySelectorAll('*').length,
scripts: document.querySelectorAll('script').length
};
});
}"
# 向 evaluate 传递参数
playwright-cli run-code "async page => {
const selector = '.product-card';
const count = await page.evaluate(
sel => document.querySelectorAll(sel).length,
selector
);
return '找到了 ' + count + ' 个产品';
}"
# 修改 DOM
playwright-cli run-code "async page => {
await page.evaluate(() => {
document.querySelector('.banner')?.remove();
document.body.style.zoom = '80%';
});
}"
```
## 错误处理
```bash
# 对可选元素使用 try-catch
playwright-cli run-code "async page => {
try {
await page.click('.cookie-consent-accept', { timeout: 2000 });
return 'Cookie 横幅已关闭';
} catch (e) {
return '未找到 Cookie 横幅';
}
}"
# 重试模式
playwright-cli run-code "async page => {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await page.click('.flaky-button', { timeout: 3000 });
return '在第 ' + attempt + ' 次尝试时点击成功';
} catch (e) {
if (attempt === 3) throw e;
await page.waitForTimeout(1000);
}
}
}"
```
## 复杂工作流
### 登录并保存认证状态
```bash
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 '登录成功,状态已保存至 auth.json';
}"
```
### 从多个页面抓取数据
```bash
playwright-cli run-code "async page => {
const results = [];
for (let i = 1; i <= 5; i++) {
await page.goto(\`https://example.com/products?page=\${i}\`);
await page.waitForSelector('.product-card');
const items = await page.locator('.product-card').evaluateAll(cards =>
cards.map(card => ({
name: card.querySelector('.title')?.textContent?.trim(),
price: card.querySelector('.price')?.textContent?.trim(),
rating: card.querySelector('.rating')?.getAttribute('data-value')
}))
);
results.push(...items);
}
return JSON.stringify(results, null, 2);
}"
```
### 填写多步骤表单(向导)
```bash
playwright-cli run-code "async page => {
// 步骤 1:个人信息
await page.fill('#firstName', 'Jane');
await page.fill('#lastName', 'Doe');
await page.fill('#email', 'jane@example.com');
await page.click('button:text(\"Next\")');
// 步骤 2:地址
await page.waitForSelector('#street');
await page.fill('#street', '123 Main St');
await page.fill('#city', 'Springfield');
await page.selectOption('#state', 'IL');
await page.fill('#zip', '62701');
await page.click('button:text(\"Next\")');
// 步骤 3:确认并提交
await page.waitForSelector('.review-summary');
await page.click('button:text(\"Submit\")');
await page.waitForURL('**/confirmation');
return '表单提交成功';
}"
```
### 无限滚动数据提取
```bash
playwright-cli run-code "async page => {
await page.goto('https://example.com/feed');
const items = [];
while (items.length < 50) {
const newItems = await page.locator('.feed-item').evaluateAll(
els => els.map(el => el.textContent.trim())
);
items.push(...newItems.filter(item => !items.includes(item)));
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(1000);
// 检查是否已到达底部
const hasMore = await page.locator('.load-more').isVisible().catch(() => false);
if (!hasMore && newItems.length === items.length) break;
}
return items.slice(0, 50);
}"
```
## 提示
- **返回值**:始终从 `run-code``return` 数据,以便在 CLI 输出中查看
- **`page.context()`**:访问浏览器上下文以操作权限、cookies、存储、地理定位
- **错误信息**Playwright 错误包含元素快照和调用日志——请仔细阅读
- **与 CLI 结合使用**:使用 `run-code` 进行设置(权限、路由),然后使用 CLI 命令进行交互
- **Async/await**:所有 Playwright 操作都是异步的——务必使用 `await`
+393
View File
@@ -0,0 +1,393 @@
# 屏幕截图与媒体
> **适用场景**:捕获页面状态的视觉证据——截图用于验证、视频录制用于演示或调试、PDF 导出用于文档、视口调整用于响应式测试。
> **前置条件**[core-commands.md](core-commands.md) 了解基本 CLI 用法
## 快速参考
```bash
# 屏幕截图
playwright-cli screenshot # 全页截图
playwright-cli screenshot e5 # 元素截图
playwright-cli screenshot --filename=checkout.png # 自定义文件名
# PDF
playwright-cli pdf --filename=report.pdf # 将页面保存为 PDF
# 视频
playwright-cli video-start # 开始录制
playwright-cli video-stop demo.webm # 停止并保存
# 视口
playwright-cli resize 1920 1080 # 桌面端
playwright-cli resize 375 812 # 移动端
```
## 屏幕截图
### 页面截图
捕获当前可见视口:
```bash
# 自动生成文件名
playwright-cli screenshot
# 自定义文件名
playwright-cli screenshot --filename=homepage.png
playwright-cli screenshot --filename=screenshots/checkout-step3.png
```
### 元素截图
仅捕获特定元素——适用于组件级别的验证:
```bash
# 按引用截取单个元素
playwright-cli snapshot # 先获取引用
playwright-cli screenshot e5 # 仅捕获元素 e5
# 自定义文件名
playwright-cli screenshot e5 --filename=product-card.png
```
### 全页截图
捕获整个可滚动页面,而不仅仅是当前视口:
```bash
playwright-cli run-code "async page => {
await page.screenshot({ path: 'full-page.png', fullPage: true });
return '已保存全页截图';
}"
```
### 带选项的截图
使用 `run-code` 实现高级截图选项:
```bash
# 全页加质量设置(JPEG
playwright-cli run-code "async page => {
await page.screenshot({
path: 'optimized.jpg',
type: 'jpeg',
quality: 80,
fullPage: true
});
}"
# 裁剪到指定区域
playwright-cli run-code "async page => {
await page.screenshot({
path: 'header-region.png',
clip: { x: 0, y: 0, width: 1280, height: 200 }
});
}"
# 透明背景(适用于带透明度的元素)
playwright-cli run-code "async page => {
await page.screenshot({
path: 'transparent.png',
omitBackground: true
});
}"
# 带遮罩的截图(隐藏动态内容)
playwright-cli run-code "async page => {
await page.screenshot({
path: 'masked.png',
mask: [
page.locator('.timestamp'),
page.locator('.user-avatar'),
page.locator('.ad-banner')
]
});
}"
# 截图前禁用动画
playwright-cli run-code "async page => {
await page.evaluate(() => {
document.querySelectorAll('*').forEach(el => {
el.style.animation = 'none';
el.style.transition = 'none';
});
});
await page.screenshot({ path: 'no-animations.png' });
}"
```
### 带选项的元素截图
```bash
playwright-cli run-code "async page => {
const element = page.getByTestId('pricing-card');
await element.screenshot({
path: 'pricing-card.png',
omitBackground: true
});
}"
```
## 响应式截图
捕获页面在不同视口尺寸下的表现:
```bash
# 桌面端(1920x1080
playwright-cli resize 1920 1080
playwright-cli screenshot --filename=desktop.png
# 笔记本电脑(1366x768
playwright-cli resize 1366 768
playwright-cli screenshot --filename=laptop.png
# 平板横屏(1024x768
playwright-cli resize 1024 768
playwright-cli screenshot --filename=tablet-landscape.png
# 平板竖屏(768x1024
playwright-cli resize 768 1024
playwright-cli screenshot --filename=tablet-portrait.png
# 移动端(375x812 — iPhone X
playwright-cli resize 375 812
playwright-cli screenshot --filename=mobile.png
# 小屏移动端(320x568 — iPhone SE
playwright-cli resize 320 568
playwright-cli screenshot --filename=mobile-small.png
```
### 自动化响应式截图
```bash
playwright-cli run-code "async page => {
const viewports = [
{ name: 'desktop', width: 1920, height: 1080 },
{ name: 'laptop', width: 1366, height: 768 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'mobile', width: 375, height: 812 },
{ name: 'mobile-sm', width: 320, height: 568 }
];
for (const vp of viewports) {
await page.setViewportSize({ width: vp.width, height: vp.height });
await page.waitForTimeout(500); // 等待布局稳定
await page.screenshot({ path: \`responsive-\${vp.name}.png\` });
}
return '已捕获 ' + viewports.length + ' 张响应式截图';
}"
```
## PDF 导出
从网页生成 PDF 文档——适用于报告、发票和文档。
```bash
# 基础 PDF
playwright-cli pdf --filename=page.pdf
```
### 高级 PDF 选项
```bash
# 带自定义选项的 PDF
playwright-cli run-code "async page => {
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
return 'PDF 已保存';
}"
# Letter 格式加页眉/页脚
playwright-cli run-code "async page => {
await page.pdf({
path: 'document.pdf',
format: 'Letter',
printBackground: true,
displayHeaderFooter: true,
headerTemplate: '<div style=\"font-size:10px; text-align:center; width:100%;\">Company Report</div>',
footerTemplate: '<div style=\"font-size:10px; text-align:center; width:100%;\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>',
margin: { top: '2cm', bottom: '2cm', left: '1cm', right: '1cm' }
});
}"
# 横向 PDF
playwright-cli run-code "async page => {
await page.pdf({
path: 'landscape.pdf',
landscape: true,
format: 'A4',
printBackground: true
});
}"
# 仅指定页面
playwright-cli run-code "async page => {
await page.pdf({
path: 'partial.pdf',
pageRanges: '1-3',
format: 'A4'
});
}"
```
**注意**PDF 生成仅适用于 Chromium 内核浏览器,不支持 Firefox 或 WebKit。
### 打印样式预览
在生成 PDF 之前,切换到打印媒体模式查看打印布局:
```bash
playwright-cli run-code "async page => {
await page.emulateMedia({ media: 'print' });
}"
playwright-cli screenshot --filename=print-preview.png
playwright-cli pdf --filename=output.pdf
```
## 视频录制
将浏览器会话录制为 WebM 视频文件。
### 基础录制
```bash
# 开始录制
playwright-cli video-start
# 执行操作(所有操作均被记录)
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli click e1
playwright-cli fill e2 "test input"
playwright-cli click e5
# 停止并保存
playwright-cli video-stop demo.webm
```
### 带描述性名称的录制
```bash
playwright-cli video-start
# ... 登录流程 ...
playwright-cli video-stop recordings/login-flow-2024-01-15.webm
playwright-cli video-start
# ... 结算流程 ...
playwright-cli video-stop recordings/checkout-happy-path.webm
```
### 使用场景
| 场景 | 优势 |
| -------------- | ---------------------------- |
| Bug 复现 | 向开发者分享精确的操作步骤 |
| 演示制作 | 向利益相关者展示功能流程 |
| 文档编写 | 录制 UI 操作指南 |
| QA 证据 | 证明测试场景已执行完成 |
| 调试 | 逐帧观察发生的情况 |
## 视口管理
控制浏览器视口以进行响应式测试:
```bash
# 常见桌面尺寸
playwright-cli resize 1920 1080 # 全高清
playwright-cli resize 1440 900 # MacBook Pro 15"
playwright-cli resize 1366 768 # 常见笔记本
playwright-cli resize 1280 720 # 高清
# 平板尺寸
playwright-cli resize 1024 768 # iPad 横屏
playwright-cli resize 768 1024 # iPad 竖屏
playwright-cli resize 834 1194 # iPad Pro 11"
# 移动端尺寸
playwright-cli resize 430 932 # iPhone 14 Pro Max
playwright-cli resize 390 844 # iPhone 14
playwright-cli resize 375 812 # iPhone X/11/12/13
playwright-cli resize 360 800 # Galaxy S21
playwright-cli resize 320 568 # iPhone SE
```
## 常见模式
### 前后对比
```bash
# 操作前
playwright-cli screenshot --filename=before.png
playwright-cli click e5
# 操作后
playwright-cli screenshot --filename=after.png
```
### 完整文档套件
```bash
playwright-cli open https://app.example.com
# 登录页面
playwright-cli screenshot --filename=docs/01-login.png
# 填写并提交
playwright-cli fill e1 "demo@example.com"
playwright-cli fill e2 "demo-password"
playwright-cli screenshot --filename=docs/02-login-filled.png
playwright-cli click e3
playwright-cli screenshot --filename=docs/03-dashboard.png
# 导航到设置
playwright-cli goto https://app.example.com/settings
playwright-cli screenshot --filename=docs/04-settings.png
# 生成文档页面的 PDF
playwright-cli goto https://app.example.com/docs
playwright-cli pdf --filename=docs/user-guide.pdf
```
### 深色模式与浅色模式截图
```bash
playwright-cli open https://example.com
# 浅色模式
playwright-cli run-code "async page => { await page.emulateMedia({ colorScheme: 'light' }); }"
playwright-cli screenshot --filename=light-mode.png
# 深色模式
playwright-cli run-code "async page => { await page.emulateMedia({ colorScheme: 'dark' }); }"
playwright-cli screenshot --filename=dark-mode.png
```
### 跨浏览器截图对比
```bash
#!/bin/bash
URL="https://example.com"
for browser in chrome firefox webkit; do
playwright-cli -s=$browser open $URL --browser=$browser
playwright-cli -s=$browser screenshot --filename="comparison-$browser.png"
done
playwright-cli close-all
```
## 小贴士
- **截图前务必先设置视口**——先 `resize``screenshot`,确保尺寸一致
- **使用描述性文件名**——`checkout-step3-error.png` 优于 `screenshot-1.png`
- **先创建输出目录**——使用子目录路径前先 `mkdir -p screenshots/`
- **长内容使用全页截图**——通过 `run-code` 使用 `fullPage: true` 截取可滚动页面
- **遮罩动态内容**——隐藏时间戳、头像和广告,获得稳定的截图
- **生成 PDF 前先切换到打印媒体**——使用 `emulateMedia({ media: 'print' })` 预览 PDF 效果
- **视频录制有额外开销**——仅在需要时录制;调试时使用 Tracing 更轻量
+356
View File
@@ -0,0 +1,356 @@
---
name: 会话管理
description: 同时运行多个隔离的浏览器会话、管理持久化配置文件、并排比较不同浏览器状态、并行抓取或A/B测试流程。
---
# 会话管理
> **何时使用**:同时运行多个隔离的浏览器会话、管理持久化配置文件、并排比较不同浏览器状态、并行抓取或 A/B 测试流程。
> **前置条件**[core-commands.md](core-commands.md) 了解基本 CLI 用法
## 快速参考
```bash
# 命名会话:每个会话拥有独立的 Cookie、存储、标签页
playwright-cli -s=auth open https://app.example.com/login
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 -s=auth close
playwright-cli close-all # 关闭所有会话
playwright-cli kill-all # 强制杀死僵尸进程
```
## 命名会话
使用 `-s=<name>` 标志创建隔离的浏览器实例。每个命名会话拥有独立的:
- Cookie
- localStorage / sessionStorage
- IndexedDB
- 缓存
- 浏览历史
- 打开的标签页
### 创建会话
```bash
# 为身份验证测试创建会话
playwright-cli -s=admin open https://app.example.com/login
# 为不同用户创建独立会话
playwright-cli -s=viewer open https://app.example.com/login
# 会话中的所有命令相互隔离
playwright-cli -s=admin fill e1 "admin@company.com"
playwright-cli -s=admin fill e2 "admin-password"
playwright-cli -s=admin click e3
playwright-cli -s=viewer fill e1 "viewer@company.com"
playwright-cli -s=viewer fill e2 "viewer-password"
playwright-cli -s=viewer click e3
```
### 默认会话
当省略 `-s` 时,所有命令共享同一个默认会话:
```bash
# 以下命令均使用同一个默认会话
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli click e1
playwright-cli close
```
## 会话隔离
会话完全独立——一个会话中的操作不会影响其他会话:
```bash
# 会话 A 以管理员身份登录
playwright-cli -s=admin open https://app.example.com
playwright-cli -s=admin fill e1 "admin@example.com"
playwright-cli -s=admin fill e2 "admin-pass"
playwright-cli -s=admin click e3
# 会话 B 访问同一网站——未登录状态
playwright-cli -s=guest open https://app.example.com
playwright-cli -s=guest snapshot
# 显示登录页面,而非管理面板
# 会话 C 可使用完全不同的浏览器
playwright-cli -s=firefox-test open https://app.example.com --browser=firefox
```
## 会话命令
```bash
# 列出所有活跃会话及其状态
playwright-cli list
# 关闭指定的命名会话
playwright-cli -s=mysession close
# 关闭默认会话
playwright-cli close
# 同时关闭所有会话
playwright-cli close-all
# 强制杀死所有浏览器守护进程(用于卡死/僵尸浏览器)
playwright-cli kill-all
# 删除会话的持久化配置文件数据
playwright-cli -s=mysession delete-data
# 删除默认会话数据
playwright-cli delete-data
```
## 持久化配置文件
默认情况下,会话在**内存**中运行——关闭会话后所有 Cookie、存储和浏览数据都将丢失。使用 `--persistent` 将状态保存到磁盘。
### 自动生成的配置文件
```bash
# Playwright-cli 自动管理配置文件目录
playwright-cli -s=myapp open https://example.com --persistent
# 关闭后重新打开——Cookie 和存储将被保留
playwright-cli -s=myapp close
playwright-cli -s=myapp open https://example.com --persistent
# 仍然处于登录状态!
```
### 自定义配置文件目录
```bash
# 指定配置文件数据的存储位置
playwright-cli -s=myapp open https://example.com --profile=/tmp/my-browser-profile
# 可用于在会话之间共享配置文件
playwright-cli -s=session-a open https://example.com --profile=/shared/profile
playwright-cli -s=session-a close
playwright-cli -s=session-b open https://example.com --profile=/shared/profile
```
### 清理持久化数据
```bash
# 删除存储的配置文件数据(必须先关闭会话)
playwright-cli -s=myapp close
playwright-cli -s=myapp delete-data
```
## 会话配置
按会话配置浏览器引擎和选项:
```bash
# 不同会话使用不同浏览器
playwright-cli -s=chrome-test open https://example.com --browser=chrome
playwright-cli -s=firefox-test open https://example.com --browser=firefox
playwright-cli -s=webkit-test open https://example.com --browser=webkit
playwright-cli -s=edge-test open https://example.com --browser=msedge
# 使用配置文件
playwright-cli -s=configured open https://example.com --config=my-config.json
# 有头模式(显示浏览器窗口)
playwright-cli -s=visible open https://example.com --headed
# 通过扩展连接现有浏览器
playwright-cli -s=extension open --extension
```
## 环境变量
设置默认会话名称,使所有命令无需 `-s` 标志即可使用:
```bash
export PLAYWRIGHT_CLI_SESSION="mysession"
# 以下命令将自动使用 "mysession"
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli close
```
## 常见模式
### 多用户角色测试
同时以不同用户角色测试同一应用:
```bash
# 管理员会话
playwright-cli -s=admin open https://app.example.com/login
playwright-cli -s=admin snapshot
playwright-cli -s=admin fill e1 "admin@company.com"
playwright-cli -s=admin fill e2 "admin-pass"
playwright-cli -s=admin click e3
# 普通用户会话
playwright-cli -s=user open https://app.example.com/login
playwright-cli -s=user fill e1 "user@company.com"
playwright-cli -s=user fill e2 "user-pass"
playwright-cli -s=user click e3
# 比较每个角色看到的内容
playwright-cli -s=admin snapshot # 应显示管理面板
playwright-cli -s=user snapshot # 不应显示管理面板
playwright-cli -s=admin screenshot --filename=admin-view.png
playwright-cli -s=user screenshot --filename=user-view.png
```
### 并发抓取
并行抓取多个网站以提高速度:
```bash
#!/bin/bash
# 同时启动所有浏览器
playwright-cli -s=site1 open https://site1.example.com &
playwright-cli -s=site2 open https://site2.example.com &
playwright-cli -s=site3 open https://site3.example.com &
wait
# 从每个会话收集数据
playwright-cli -s=site1 snapshot --filename=site1.yaml
playwright-cli -s=site2 snapshot --filename=site2.yaml
playwright-cli -s=site3 snapshot --filename=site3.yaml
# 截取屏幕截图
playwright-cli -s=site1 screenshot --filename=site1.png
playwright-cli -s=site2 screenshot --filename=site2.png
playwright-cli -s=site3 screenshot --filename=site3.png
# 清理
playwright-cli close-all
```
### A/B 测试比较
```bash
# 变体 A
playwright-cli -s=variant-a open "https://app.example.com?variant=a"
playwright-cli -s=variant-a screenshot --filename=variant-a.png
# 变体 B
playwright-cli -s=variant-b open "https://app.example.com?variant=b"
playwright-cli -s=variant-b screenshot --filename=variant-b.png
# 并排比较
playwright-cli close-all
```
### 跨浏览器测试
在多个浏览器中运行同一流程以验证兼容性:
```bash
#!/bin/bash
for browser in chrome firefox webkit; do
playwright-cli -s=$browser open https://example.com --browser=$browser
playwright-cli -s=$browser snapshot
playwright-cli -s=$browser screenshot --filename="$browser-home.png"
playwright-cli -s=$browser goto https://example.com/features
playwright-cli -s=$browser screenshot --filename="$browser-features.png"
done
playwright-cli close-all
```
### 跨会话共享认证状态
```bash
# 登录一次并保存状态
playwright-cli -s=login open https://app.example.com/login
playwright-cli -s=login fill e1 "user@example.com"
playwright-cli -s=login fill e2 "password123"
playwright-cli -s=login click e3
playwright-cli -s=login state-save auth.json
playwright-cli -s=login close
# 在多个会话中复用认证状态
playwright-cli -s=session-a open https://app.example.com
playwright-cli -s=session-a state-load auth.json
playwright-cli -s=session-a goto https://app.example.com/dashboard
playwright-cli -s=session-b open https://app.example.com
playwright-cli -s=session-b state-load auth.json
playwright-cli -s=session-b goto https://app.example.com/settings
```
## 最佳实践
### 1. 使用语义化命名会话
```bash
# 良好:用途清晰
playwright-cli -s=github-auth open https://github.com
playwright-cli -s=docs-scrape open https://docs.example.com
playwright-cli -s=checkout-flow open https://shop.example.com
# 避免:泛化命名
playwright-cli -s=s1 open https://github.com
playwright-cli -s=test open https://docs.example.com
```
### 2. 始终进行清理
```bash
# 完成后关闭单个会话
playwright-cli -s=auth close
playwright-cli -s=scrape close
# 或一次性关闭所有会话
playwright-cli close-all
# 如果浏览器无响应
playwright-cli kill-all
```
### 3. 删除过期的持久化数据
```bash
# 删除旧的持久化配置文件以释放磁盘空间
playwright-cli -s=old-session delete-data
```
### 4. 单任务工作使用默认会话
当只需要一个浏览器时,无需创建命名会话:
```bash
# 简单的单会话工作流——不需要 -s 标志
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli click e1
playwright-cli close
```
### 5. 结合状态管理
对于长时间运行的任务,定期保存状态:
```bash
playwright-cli -s=long-task open https://app.example.com --persistent
# ……多次交互……
playwright-cli -s=long-task state-save checkpoint.json
# ……更多交互……
# 如果出现问题,恢复状态:
playwright-cli -s=long-task state-load checkpoint.json
+494
View File
@@ -0,0 +1,494 @@
---
name: 存储与认证
description: 管理 Cookie、localStorage、sessionStorage、浏览器存储状态,保存与恢复认证,或测试依赖存储的功能
metadata:
type: reference
---
# 存储与认证
> **使用场景**:管理 Cookie、localStorage、sessionStorage、浏览器存储状态,保存与恢复认证,或测试依赖存储的功能。
> **前置条件**[core-commands.md](core-commands.md)——了解基本 CLI 用法
## 快速参考
```bash
# 将所有浏览器状态(Cookie + localStorage)保存到文件
playwright-cli state-save auth.json
# 在新会话中恢复状态
playwright-cli state-load auth.json
# 快速 Cookie 操作
playwright-cli cookie-list
playwright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure
playwright-cli cookie-delete session_id
playwright-cli cookie-clear
# localStorage
playwright-cli localstorage-set theme dark
playwright-cli localstorage-get theme
playwright-cli localstorage-clear
# sessionStorage
playwright-cli sessionstorage-set step 3
playwright-cli sessionstorage-get step
playwright-cli sessionstorage-clear
```
## 存储状态(保存与恢复)
最强大的功能——将整个浏览器状态(所有来源的 Cookie + localStorage)保存到一个 JSON 文件中,之后恢复该状态以跳过登录流程。
### 保存存储状态
```bash
# 保存到自动生成的文件名(storage-state-{timestamp}.json
playwright-cli state-save
# 保存到指定文件
playwright-cli state-save auth.json
playwright-cli state-save ./states/admin-session.json
```
### 恢复存储状态
```bash
# 从文件加载状态
playwright-cli state-load auth.json
# 加载后导航——Cookie 和 localStorage 已设置好
playwright-cli goto https://app.example.com/dashboard
# 已通过认证!
```
### 存储状态文件格式
保存的 JSON 同时包含 Cookie 和 localStorage
```json
{
"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" },
{ "name": "auth_token", "value": "jwt.token.here" }
]
}
]
}
```
## 认证模式
最常用的工作流程——登录一次,保存状态,跨会话复用。
### 步骤 1:登录并保存
```bash
playwright-cli open https://app.example.com/login
playwright-cli snapshot
playwright-cli fill e1 "admin@example.com"
playwright-cli fill e2 "secure-password"
playwright-cli click e3
# 等待重定向以确认登录成功
playwright-cli run-code "async page => {
await page.waitForURL('**/dashboard');
return 'Login successful: ' + page.url();
}"
# 保存已认证的状态
playwright-cli state-save auth.json
playwright-cli close
```
### 步骤 2:复用认证状态
```bash
# 新会话——完全跳过登录
playwright-cli open https://app.example.com
playwright-cli state-load auth.json
playwright-cli goto https://app.example.com/dashboard
# 已以管理员身份登录!
playwright-cli snapshot # 查看仪表盘
```
### 多角色认证
```bash
# 为每个角色保存状态
playwright-cli open https://app.example.com/login
playwright-cli fill e1 "admin@example.com"
playwright-cli fill e2 "admin-pass"
playwright-cli click e3
playwright-cli state-save admin-auth.json
playwright-cli close
playwright-cli open https://app.example.com/login
playwright-cli fill e1 "user@example.com"
playwright-cli fill e2 "user-pass"
playwright-cli click e3
playwright-cli state-save user-auth.json
playwright-cli close
# 现在使用它们
playwright-cli -s=admin open https://app.example.com
playwright-cli -s=admin state-load admin-auth.json
playwright-cli -s=admin goto https://app.example.com/admin
playwright-cli -s=user open https://app.example.com
playwright-cli -s=user state-load user-auth.json
playwright-cli -s=user goto https://app.example.com/profile
```
### OAuth / SSO 认证
针对涉及重定向和弹出窗口的 OAuth 流程:
```bash
playwright-cli run-code "async page => {
await page.goto('https://app.example.com/login');
// 点击"使用 Google 登录"
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('button:text(\"Login with Google\")')
]);
// 在弹出窗口中填写凭据
await popup.fill('input[type=email]', 'user@gmail.com');
await popup.click('#identifierNext');
await popup.fill('input[type=password]', 'password');
await popup.click('#passwordNext');
// 等待弹出窗口关闭且主页面完成重定向
await popup.waitForEvent('close');
await page.waitForURL('**/dashboard');
// 保存已认证状态
await page.context().storageState({ path: 'oauth-auth.json' });
return 'OAuth login complete';
}"
```
## Cookies
### 列出所有 Cookie
```bash
playwright-cli cookie-list
```
### 按域名筛选
```bash
playwright-cli cookie-list --domain=example.com
```
### 按路径筛选
```bash
playwright-cli cookie-list --path=/api
```
### 获取指定 Cookie
```bash
playwright-cli cookie-get session_id
playwright-cli cookie-get __cf_bm
```
### 设置 Cookie
```bash
# 基础 Cookie
playwright-cli cookie-set session abc123
# 带完整选项的 Cookie
playwright-cli cookie-set session abc123 \
--domain=example.com \
--path=/ \
--httpOnly \
--secure \
--sameSite=Lax
# 带过期时间的 Cookie(Unix 时间戳)
playwright-cli cookie-set remember_me token123 --expires=1735689600
```
### 删除 Cookie
```bash
playwright-cli cookie-delete session_id
playwright-cli cookie-delete __cf_bm
```
### 清除所有 Cookie
```bash
playwright-cli cookie-clear
```
### 高级:一次设置多个 Cookie
```bash
playwright-cli run-code "async page => {
await page.context().addCookies([
{
name: 'session_id',
value: 'sess_abc123',
domain: 'example.com',
path: '/',
httpOnly: true,
secure: true,
sameSite: 'Strict'
},
{
name: 'preferences',
value: JSON.stringify({ theme: 'dark', lang: 'en' }),
domain: 'example.com',
path: '/'
},
{
name: 'tracking_opt_out',
value: 'true',
domain: '.example.com',
path: '/'
}
]);
}"
```
### 高级:编程方式读取所有 Cookie
```bash
playwright-cli run-code "async page => {
const cookies = await page.context().cookies();
return cookies.map(c => ({
name: c.name,
value: c.value.substring(0, 20) + '...',
domain: c.domain,
httpOnly: c.httpOnly,
secure: c.secure,
expires: new Date(c.expires * 1000).toISOString()
}));
}"
```
## Local Storage
### 列出所有项目
```bash
playwright-cli localstorage-list
```
### 获取值
```bash
playwright-cli localstorage-get theme
playwright-cli localstorage-get auth_token
```
### 设置值
```bash
playwright-cli localstorage-set theme dark
playwright-cli localstorage-set language en-US
# 设置 JSON 值(对 JSON 加引号)
playwright-cli localstorage-set user_settings '{"theme":"dark","fontSize":14,"sidebar":true}'
```
### 删除项目
```bash
playwright-cli localstorage-delete auth_token
```
### 清除所有
```bash
playwright-cli localstorage-clear
```
### 高级:批量操作
```bash
playwright-cli run-code "async page => {
await page.evaluate(() => {
localStorage.setItem('token', 'jwt_abc123');
localStorage.setItem('user_id', '12345');
localStorage.setItem('user_name', 'Jane Doe');
localStorage.setItem('preferences', JSON.stringify({
theme: 'dark',
notifications: true,
language: 'en'
}));
localStorage.setItem('onboarding_complete', 'true');
});
}"
```
### 高级:读取所有 localStorage
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
return items;
});
}"
```
## Session Storage
Session storage 按标签页隔离,关闭标签页时自动清除。
### 列出所有项目
```bash
playwright-cli sessionstorage-list
```
### 获取 / 设置 / 删除
```bash
playwright-cli sessionstorage-get form_step
playwright-cli sessionstorage-set form_step 3
playwright-cli sessionstorage-set form_data '{"name":"Jane","email":"jane@example.com"}'
playwright-cli sessionstorage-delete form_step
playwright-cli sessionstorage-clear
```
## IndexedDB
IndexedDB 需要通过 `run-code` 进行访问:
### 列出数据库
```bash
playwright-cli run-code "async page => {
return await page.evaluate(async () => {
const databases = await indexedDB.databases();
return databases.map(db => ({ name: db.name, version: db.version }));
});
}"
```
### 删除数据库
```bash
playwright-cli run-code "async page => {
await page.evaluate(dbName => {
indexedDB.deleteDatabase(dbName);
}, 'myDatabase');
return 'Database deleted';
}"
```
### 从对象存储中读取数据
```bash
playwright-cli run-code "async page => {
return await page.evaluate(() => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('myDatabase');
request.onsuccess = () => {
const db = request.result;
const tx = db.transaction('myStore', 'readonly');
const store = tx.objectStore('myStore');
const getAll = store.getAll();
getAll.onsuccess = () => resolve(getAll.result);
getAll.onerror = () => reject(getAll.error);
};
request.onerror = () => reject(request.error);
});
});
}"
```
## 常见模式
### Token 刷新测试
```bash
# 设置一个已过期的 Token 以测试刷新逻辑
playwright-cli localstorage-set auth_token "expired-jwt-token"
playwright-cli localstorage-set token_expiry "1609459200"
# 导航以触发 Token 刷新
playwright-cli goto https://app.example.com/dashboard
# 检查 Token 是否已刷新
playwright-cli localstorage-get auth_token
```
### 功能开关测试
```bash
# 通过 localStorage 启用功能开关
playwright-cli localstorage-set feature_flags '{"newCheckout":true,"darkMode":true,"betaFeatures":false}'
# 重新加载以生效
playwright-cli reload
playwright-cli snapshot
```
### 清除所有内容,从头开始
```bash
playwright-cli cookie-clear
playwright-cli localstorage-clear
playwright-cli sessionstorage-clear
playwright-cli reload
```
### 保存与恢复的完整流程
```bash
# 手动设置状态
playwright-cli open https://example.com
playwright-cli cookie-set session abc123 --domain=example.com
playwright-cli localstorage-set user john
playwright-cli localstorage-set theme dark
# 保存所有内容
playwright-cli state-save my-session.json
# 之后——在新会话中恢复状态
playwright-cli open https://example.com
playwright-cli state-load my-session.json
playwright-cli reload
# Cookie 和 localStorage 已恢复
```
## 安全注意事项
- **切勿提交认证状态文件**——将 `*.auth-state.json``auth.json` 添加到 `.gitignore`
- **使用后删除状态文件**——自动化完成后执行 `rm auth.json`
- **使用环境变量保存凭据**——绝不在脚本中硬编码密码
- **内存会话更安全**——默认会话不会持久化到磁盘,降低了暴露风险
- **轮换已保存的状态**——认证 Token 会过期;请定期重新生成状态文件
- **避免在共享机器上保存状态**——存储状态文件包含会话 Token 和个人数据
+336
View File
@@ -0,0 +1,336 @@
# 测试生成
> **使用场景**:从交互式 CLI 会话生成 Playwright 测试代码——录制用户操作流程、构建测试脚手架、将手动测试转化为自动化测试。
> **前置条件**[core-commands.md](core-commands.md) 基础 CLI 用法
## 快速参考
```bash
# 每个 CLI 操作都会输出等价的 Playwright 代码
playwright-cli open https://example.com/login
playwright-cli snapshot
playwright-cli fill e1 "user@example.com"
# 输出: 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-cli` 执行的每个操作都会自动在输出中生成对应的 Playwright TypeScript 代码。这些代码使用了 Playwright 在生产测试中推荐的**基于角色的定位器**。
工作流程:
1. **打开页面** → 生成 `await page.goto(url)`
2. **拍摄快照** → 查看元素引用及其可访问角色
3. **交互** → 每个操作生成一行 Playwright 代码
4. **收集代码** → 将生成的代码行组装成完整的测试
## 录制操作流程
### 示例:登录流程
```bash
playwright-cli open https://example.com/login
# 已执行 Playwright 代码:
# await page.goto('https://example.com/login');
playwright-cli snapshot
# 输出:
# e1 [textbox "Email"]
# e2 [textbox "Password"]
# e3 [button "Sign In"]
# e4 [link "Forgot password?"]
playwright-cli fill e1 "user@example.com"
# 已执行 Playwright 代码:
# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
playwright-cli fill e2 "password123"
# 已执行 Playwright 代码:
# await page.getByRole('textbox', { name: 'Password' }).fill('password123');
playwright-cli click e3
# 已执行 Playwright 代码:
# await page.getByRole('button', { name: 'Sign In' }).click();
```
### 组装成测试用例
收集生成的代码并包装成 Playwright 测试:
```typescript
import { test, expect } from "@playwright/test"
test("user can log in", async ({ page }) => {
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()
// 添加断言(非自动生成——需手动添加)
await expect(page).toHaveURL(/.*dashboard/)
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible()
})
```
```javascript
const { test, expect } = require("@playwright/test")
test("user can log in", async ({ page }) => {
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()
await expect(page).toHaveURL(/.*dashboard/)
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible()
})
```
## 生成代码中的定位器策略
生成的代码使用 Playwright 推荐的定位器优先级:
| 优先级 | 定位器类型 | 示例 | 使用场景 |
|--------|-----------|----------------------------------------|--------------------------------|
| 1 | 基于角色 | `getByRole('button', { name: 'Submit' })` | 具有 ARIA 角色的元素 |
| 2 | 基于标签 | `getByLabel('Email')` | 带标签的表单输入 |
| 3 | 基于占位符 | `getByPlaceholder('Search...')` | 带占位符文本的输入 |
| 4 | 基于文本 | `getByText('Welcome back')` | 静态文本内容 |
| 5 | 测试 ID | `getByTestId('submit-btn')` | 带有 `data-testid` 的元素 |
这些定位器**对标记结构变化具有弹性**——它们反映用户感知页面的方式,而不是依赖 CSS 选择器或 XPath。
## 录制复杂操作流程
### 电商结算
```bash
playwright-cli open https://shop.example.com
# 浏览商品
playwright-cli snapshot
playwright-cli click e5 # "Add to Cart" 按钮
# await page.getByRole('button', { name: 'Add to Cart' }).click();
playwright-cli click e12 # 购物车图标
# await page.getByRole('link', { name: 'Cart' }).click();
playwright-cli snapshot
playwright-cli click e3 # "Proceed to Checkout"
# await page.getByRole('button', { name: 'Proceed to Checkout' }).click();
# 配送信息
playwright-cli snapshot
playwright-cli fill e1 "Jane Doe"
# await page.getByRole('textbox', { name: 'Full Name' }).fill('Jane Doe');
playwright-cli fill e2 "123 Main St"
# await page.getByRole('textbox', { name: 'Address' }).fill('123 Main St');
playwright-cli fill e3 "Springfield"
# await page.getByRole('textbox', { name: 'City' }).fill('Springfield');
playwright-cli select e4 "IL"
# await page.getByRole('combobox', { name: 'State' }).selectOption('IL');
playwright-cli fill e5 "62701"
# await page.getByRole('textbox', { name: 'ZIP Code' }).fill('62701');
playwright-cli click e6 # "Continue to Payment"
```
### 多步骤向导
```bash
playwright-cli open https://example.com/onboarding
# 步骤 1
playwright-cli snapshot
playwright-cli fill e1 "Acme Corp"
playwright-cli select e2 "technology"
playwright-cli click e3 # 下一步
# 步骤 2
playwright-cli snapshot
playwright-cli check e1 # 功能复选框
playwright-cli check e3 # 另一功能
playwright-cli click e5 # 下一步
# 步骤 3
playwright-cli snapshot
playwright-cli click e2 # "Complete Setup"
```
### 带动态结果的搜索
```bash
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli fill e1 "playwright testing"
# await page.getByRole('searchbox', { name: 'Search' }).fill('playwright testing');
playwright-cli press Enter
# await page.keyboard.press('Enter');
playwright-cli snapshot # 查看搜索结果——分配了新引用
playwright-cli click e3 # 第一个结果
# await page.getByRole('link', { name: 'Getting Started with Playwright' }).click();
```
## 添加断言
生成的代码捕获的是**操作**而非**断言**。务必手动添加断言以创建有意义的测试。
### 常用断言
```typescript
// 导航后 URL 改变
await expect(page).toHaveURL(/.*dashboard/)
await expect(page).toHaveURL("https://example.com/success")
// 元素可见
await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible()
await expect(page.getByText("Order confirmed")).toBeVisible()
// 元素包含文本
await expect(page.getByTestId("total")).toHaveText("$99.99")
await expect(page.getByRole("alert")).toContainText("saved")
// 元素具有特定属性
await expect(page.getByRole("button", { name: "Submit" })).toBeDisabled()
await expect(page.getByRole("checkbox")).toBeChecked()
// 元素数量
await expect(page.getByRole("listitem")).toHaveCount(5)
// 页面标题
await expect(page).toHaveTitle(/Dashboard/)
// 截图对比
await expect(page).toHaveScreenshot("checkout.png")
```
### 断言的放置位置
```typescript
test("complete checkout flow", async ({ page }) => {
await page.goto("https://shop.example.com/products")
// 操作:将商品加入购物车
await page.getByRole("button", { name: "Add to Cart" }).click()
// 断言:购物车角标更新
await expect(page.getByTestId("cart-count")).toHaveText("1")
// 操作:前往购物车
await page.getByRole("link", { name: "Cart" }).click()
// 断言:页面正确
await expect(page).toHaveURL(/.*cart/)
await expect(page.getByRole("heading", { name: "Your Cart" })).toBeVisible()
// 操作:前往结算
await page.getByRole("button", { name: "Checkout" }).click()
// 操作:填写配送信息
await page.getByRole("textbox", { name: "Full Name" }).fill("Jane Doe")
await page.getByRole("textbox", { name: "Address" }).fill("123 Main St")
await page.getByRole("button", { name: "Place Order" }).click()
// 断言:订单已确认
await expect(page.getByText("Order confirmed")).toBeVisible()
await expect(page.getByTestId("order-number")).toBeVisible()
})
```
## 最佳实践
### 1. 先探索再录制
先拍摄快照了解页面结构,不要盲目点击——先弄清有哪些元素可用:
```bash
playwright-cli open https://example.com
playwright-cli snapshot
# 审查元素,规划操作流程,然后开始交互
```
### 2. 使用语义化定位器
生成的代码已优先使用基于角色的定位器。如果在生成输出中看到 CSS 选择器,请考虑提交 issue——基于角色的定位器更具弹性:
```typescript
// 生成(好——语义化,有弹性)
await page.getByRole("button", { name: "Submit" }).click()
// 避免手动编写(脆弱——CSS 变化即失效)
await page.locator("#submit-btn").click()
await page.locator(".btn.btn-primary").click()
```
### 3. 保持测试专注
一个测试 = 一个用户行为。不要将整个会话录制进一个测试:
```typescript
// 好:专注的测试
test("user can add item to cart", async ({ page }) => {
// 仅限加入购物车流程
})
test("user can complete checkout", async ({ page }) => {
// 仅限结算流程(使用认证状态跳过登录)
})
// 差:单体测试
test("user journey", async ({ page }) => {
// 登录 + 浏览 + 加入购物车 + 结算 + 验证邮件……
})
```
### 4. 参数化测试数据
将录制中的硬编码值替换为变量或测试数据:
```typescript
// 而非录制中的硬编码值
test("registration", async ({ page }) => {
const user = {
name: "Jane Doe",
email: `test+${Date.now()}@example.com`,
password: "SecurePass123!",
}
await page.goto("/register")
await page.getByRole("textbox", { name: "Name" }).fill(user.name)
await page.getByRole("textbox", { name: "Email" }).fill(user.email)
await page.getByRole("textbox", { name: "Password" }).fill(user.password)
await page.getByRole("button", { name: "Create Account" }).click()
await expect(page).toHaveURL(/.*welcome/)
})
```
### 5. 为不稳定步骤添加等待策略
如果录制的操作依赖于异步内容加载,请添加显式等待:
```typescript
// 在点击动态加载的元素之前
await page.waitForSelector(".results-loaded")
await page.getByRole("link", { name: "First Result" }).click()
// 或使用 Playwright 的自动等待(推荐)
await expect(page.getByRole("link", { name: "First Result" })).toBeVisible()
await page.getByRole("link", { name: "First Result" }).click()
```
## 提示
- **生成的代码只是一个起点**——在提交到测试套件之前,务必审查、添加断言并参数化
- **动态变化后重新快照**——DOM 更新后引用会改变
- **结合 `state-save`**——录制一次登录流程,保存状态,然后所有其他录制都从已认证状态开始
- **为快照使用 `--filename`**——保存复杂流程的前/后快照,以便后续编写断言
+425
View File
@@ -0,0 +1,425 @@
# 追踪与调试
> **使用场景**:调试失败的操作、分析性能瓶颈、捕获执行证据、理解交互未生效的原因,或在自动化过程中检查网络及控制台活动。
> **前置条件**[core-commands.md](core-commands.md) —— 掌握基本 CLI 用法
## 快速参考
```bash
# 追踪会话
playwright-cli tracing-start
playwright-cli open https://example.com
playwright-cli click e4
playwright-cli fill e7 "test"
playwright-cli tracing-stop
# 查看控制台消息
playwright-cli console # 所有级别
playwright-cli console error # 仅错误
playwright-cli console warning # 仅警告
# 查看网络活动
playwright-cli network
```
## 追踪
追踪会捕获**一切** —— 包括 DOM 快照、截图、网络活动、控制台日志以及时间信息,覆盖会话中的每一个操作。
### 基本用法
```bash
# 在要调试的流程之前开始录制
playwright-cli tracing-start
# 执行操作
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli click e4
playwright-cli fill e7 "test data"
playwright-cli click e9
# 停止并保存追踪
playwright-cli tracing-stop
```
### 追踪捕获的内容
| 类别 | 详情 |
| --------------- | ----------------------------------------------------------- |
| **操作** | 每次点击、填写、悬停、键盘输入、导航 —— 附带时间信息 |
| **DOM 快照** | 每个操作前后的完整 DOM 状态 |
| **截图** | 每一步的视觉状态 |
| **网络** | 所有 HTTP 请求、响应、请求头、响应体、时间信息 |
| **控制台** | 所有 `console.log``console.warn``console.error` 消息 |
| **时间信息** | 每个操作的具体耗时 |
| **来源** | 每个操作由哪个命令触发 |
### 追踪输出文件
当追踪激活时,Playwright 会创建一个 `traces/` 目录:
**`trace-{时间戳}.trace`** —— 主追踪文件,包含:
- 附带 DOM 快照的操作日志
- 每一步的截图
- 时间信息
- 控制台消息
**`trace-{时间戳}.network`** —— 网络活动:
- 所有 HTTP 请求与响应
- 请求/响应请求头与响应体
- DNS、连接、TLS、TTFB、下载时间
- 资源大小及失败的请求
**`resources/`** —— 用于追踪回放的缓存资源:
- 图片、字体、样式表、脚本
- 用于离线回放的响应体
### 查看追踪
在 Playwright 的 Trace Viewer 中打开追踪 —— 这是一个用于逐步调试的丰富 GUI:
```bash
# 在 Trace Viewer Web 应用中打开追踪
npx playwright show-trace traces/trace-123456.trace
# 或使用在线 Trace Viewer
# 将追踪文件上传至:https://trace.playwright.dev
```
Trace Viewer 会显示:
- **时间轴**:带截图的逐步操作
- **DOM 快照**:检查每一步中的元素(类似 DevTools)
- **网络**:所有请求的水fall图
- **控制台**:带时间戳的日志消息
- **来源**:触发每个操作的代码
## 控制台监控
查看页面的 JavaScript 控制台输出 —— 对捕获错误至关重要:
```bash
# 显示所有控制台消息
playwright-cli console
# 按严重级别筛选
playwright-cli console error # 仅错误
playwright-cli console warning # 仅警告
playwright-cli console info # 仅信息消息
playwright-cli console log # 仅日志消息
```
### 常见调试模式
```bash
playwright-cli open https://app.example.com
playwright-cli snapshot
playwright-cli click e5 # 发生了意外情况
# 检查是否有 JavaScript 错误
playwright-cli console error
# 输出可能显示:
# [error] TypeError: Cannot read properties of undefined (reading 'map')
# [error] Uncaught ReferenceError: processData is not defined
```
### 交互期间查看控制台
使用 `run-code` 设置持续的控制台监控:
```bash
playwright-cli run-code "async page => {
page.on('console', msg => {
if (msg.type() === 'error' || msg.type() === 'warning') {
console.log(\`[\${msg.type()}] \${msg.text()}\`);
}
});
}"
# 现在开始交互 —— 错误和警告会实时打印
playwright-cli click e5
playwright-cli fill e3 "test"
```
### 捕获未捕获的异常
```bash
playwright-cli run-code "async page => {
page.on('pageerror', error => {
console.log('Uncaught exception:', error.message);
});
}"
```
## 网络监控
查看页面发出的所有网络请求:
```bash
playwright-cli network
```
### 实时查看网络
```bash
playwright-cli run-code "async page => {
page.on('request', request => {
console.log(\`>> \${request.method()} \${request.url()}\`);
});
page.on('response', response => {
console.log(\`<< \${response.status()} \${response.url()}\`);
});
}"
# 现在开始交互 —— 请求会实时打印
playwright-cli click e5
```
### 监控失败的请求
```bash
playwright-cli run-code "async page => {
page.on('requestfailed', request => {
console.log(\`FAILED: \${request.method()} \${request.url()} - \${request.failure()?.errorText}\`);
});
}"
```
### 检查特定响应
```bash
playwright-cli run-code "async page => {
const response = await page.waitForResponse('**/api/users');
return {
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
body: await response.json()
};
}"
```
## 调试策略
### 策略 1:快照前后对比
最简单的调试方法 —— 查看发生了什么变化:
```bash
playwright-cli snapshot --filename=before.yaml
playwright-cli click e5
playwright-cli snapshot --filename=after.yaml
# 比较两个快照,了解变化内容
```
### 策略 2:追踪失败流程
在失败步骤之前开始追踪:
```bash
playwright-cli tracing-start
# 重现问题
playwright-cli goto https://app.example.com/checkout
playwright-cli fill e1 "Jane Doe"
playwright-cli click e5 # 此步骤失败
playwright-cli tracing-stop
# 打开追踪,查看尝试点击时的 DOM 状态
```
### 策略 3:检查元素状态
当交互失败时,检查元素的实际状态:
```bash
# 元素是否可见?
playwright-cli eval "el => window.getComputedStyle(el).display" e5
playwright-cli eval "el => window.getComputedStyle(el).visibility" e5
playwright-cli eval "el => el.getBoundingClientRect()" e5
# 是否被其他元素遮挡?
playwright-cli eval "el => {
const rect = el.getBoundingClientRect();
const topEl = document.elementFromPoint(rect.x + rect.width/2, rect.y + rect.height/2);
return topEl === el ? 'Element is on top' : 'Covered by: ' + topEl?.tagName + '.' + topEl?.className;
}" e5
# 是否被禁用?
playwright-cli eval "el => el.disabled" e5
playwright-cli eval "el => el.getAttribute('aria-disabled')" e5
```
### 策略 4:控制台 + 网络组合
```bash
# 同时监控控制台和网络
playwright-cli run-code "async page => {
page.on('console', msg => {
if (msg.type() === 'error') console.log('[CONSOLE]', msg.text());
});
page.on('requestfailed', req => {
console.log('[NETWORK]', req.method(), req.url(), req.failure()?.errorText);
});
page.on('response', resp => {
if (resp.status() >= 400) {
console.log('[HTTP ERROR]', resp.status(), resp.url());
}
});
}"
# 现在开始交互并查看错误
playwright-cli click e5
playwright-cli fill e3 "test"
```
### 策略 5:等待与重试
如果操作因时序问题失败,可添加显式等待:
```bash
# 等待元素变为可操作状态
playwright-cli run-code "async page => {
await page.locator('#dynamic-button').waitFor({ state: 'visible', timeout: 10000 });
}"
playwright-cli snapshot
playwright-cli click e5
# 等待页面稳定
playwright-cli run-code "async page => {
await page.waitForLoadState('networkidle');
}"
playwright-cli snapshot
```
## 性能分析
### 测量页面加载时间
```bash
playwright-cli run-code "async page => {
const timing = await page.evaluate(() => {
const t = performance.timing;
return {
dns: t.domainLookupEnd - t.domainLookupStart,
tcp: t.connectEnd - t.connectStart,
ttfb: t.responseStart - t.requestStart,
download: t.responseEnd - t.responseStart,
domParsing: t.domInteractive - t.domLoading,
domComplete: t.domComplete - t.domLoading,
total: t.loadEventEnd - t.navigationStart
};
});
return timing;
}"
```
### 使用 Navigation Timing API 分析
```bash
playwright-cli run-code "async page => {
const entries = await page.evaluate(() => {
return performance.getEntriesByType('navigation').map(e => ({
type: e.type,
redirectTime: e.redirectEnd - e.redirectStart,
dnsTime: e.domainLookupEnd - e.domainLookupStart,
connectTime: e.connectEnd - e.connectStart,
tlsTime: e.secureConnectionStart > 0 ? e.connectEnd - e.secureConnectionStart : 0,
requestTime: e.responseStart - e.requestStart,
responseTime: e.responseEnd - e.responseStart,
domProcessing: e.domComplete - e.domInteractive,
loadTime: e.loadEventEnd - e.loadEventStart,
totalTime: e.loadEventEnd - e.startTime
}));
});
return entries;
}"
```
### 列出慢资源
```bash
playwright-cli run-code "async page => {
const resources = await page.evaluate(() => {
return performance.getEntriesByType('resource')
.map(r => ({ name: r.name.split('/').pop(), duration: Math.round(r.duration), size: r.transferSize }))
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
});
return resources;
}"
```
## 追踪 vs 视频 vs 截图
选择合适的捕获方式:
| 功能 | 追踪 | 视频 | 截图 |
| -------------------- | ------------ | ------------------- | -------------- |
| **格式** | `.trace` 文件 | `.webm` 视频 | `.png` 图片 |
| **DOM 检查** | 是 | 否 | 否 |
| **网络详情** | 是 | 否 | 否 |
| **逐步回放** | 是 | 连续播放 | 单帧 |
| **控制台日志** | 是 | 否 | 否 |
| **文件大小** | 中等 | 大 | 小 |
| **最佳用途** | 调试 | 演示、文档 | 快速捕获 |
### 选择指南
- **操作失败且不明原因** → 追踪
- **需要向他人展示流程** → 视频
- **需要验证视觉状态** → 截图
- **需要分析性能** → 追踪(包含网络水fall图)
- **CI 中失败测试的产物** → 追踪 + 截图
## 最佳实践
### 1. 在问题发生前开始追踪
追踪导致失败的整个流程,而不仅仅是失败的步骤:
```bash
playwright-cli tracing-start
# 从头重现完整流程
playwright-cli open https://app.example.com
playwright-cli fill e1 "user@example.com"
playwright-cli click e3
# ... 直至失败的所有步骤 ...
playwright-cli tracing-stop
```
### 2. 主动使用控制台监控
在任何调试会话开始时即启动控制台监控:
```bash
playwright-cli run-code "async page => {
page.on('console', msg => console.log(\`[\${msg.type()}] \${msg.text()}\`));
page.on('pageerror', err => console.log('[EXCEPTION]', err.message));
}"
```
### 3. 清理旧追踪
追踪文件会占用大量磁盘空间:
```bash
# 删除 7 天前的追踪文件
find .playwright-cli/traces -mtime +7 -delete
```
### 4. 组合多种技术
最有效的调试往往结合多种方法:
```bash
playwright-cli tracing-start # 捕获一切
playwright-cli console error # 监控 JS 错误
playwright-cli network # 监控失败的请求
playwright-cli snapshot # 查看当前状态
# ... 交互并调试 ...
playwright-cli tracing-stop # 保存追踪用于详细分析
```