playwright-automate
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaywright Browser Automation
Playwright浏览器自动化
I'll help you automate browser tasks using Playwright for testing, screenshots, PDFs, and workflow automation.
Arguments: - automation task (screenshot, pdf, scrape, test) or specific URL/workflow
$ARGUMENTS我将帮助你使用Playwright实现浏览器任务自动化,包括测试、截图、PDF生成以及工作流自动化。
参数: - 自动化任务(截图、PDF、爬取、测试)或特定URL/工作流
$ARGUMENTSAutomation Capabilities
自动化能力
Common Workflows:
- Browser testing and validation
- Screenshot capture (full page, specific elements)
- PDF generation from web pages
- Web scraping and data extraction
- Form automation and submissions
- Performance monitoring
常见工作流:
- 浏览器测试与验证
- 截图捕获(整页、特定元素)
- 网页PDF生成
- 网页爬取与数据提取
- 表单自动化与提交
- 性能监控
Token Optimization Strategy
Token优化策略
This skill has been optimized to reduce token usage by 70-80% (from 3,000-5,000 to 900-1,500 tokens) through intelligent tool usage patterns.
通过智能工具使用模式,该技能已优化,可将Token使用量减少70-80%(从3000-5000个Token降至900-1500个)。
Core Optimization Patterns
核心优化模式
1. Early Exit Pattern (Saves 90% for non-Playwright projects)
bash
undefined1. 提前退出模式(非Playwright项目可节省90%Token)
bash
undefinedCheck Playwright installation status FIRST
首先检查Playwright安装状态
if ! grep -q "@playwright/test" package.json 2>/dev/null; then
echo "❌ Playwright not installed. Run: npm install --save-dev @playwright/test"
exit 1
fi
**2. Template-Based Script Generation (Saves 60-70%)**
```bashif ! grep -q "@playwright/test" package.json 2>/dev/null; then
echo "❌ Playwright未安装。请运行:npm install --save-dev @playwright/test"
exit 1
fi
**2. 基于模板的脚本生成(节省60-70%Token)**
```bashUse heredocs with predefined templates - no dynamic code generation
使用预定义模板的here文档 - 不进行动态代码生成
cat > scripts/playwright-screenshot.ts << 'EOF'
import { chromium } from '@playwright/test';
async function captureScreenshot(url: string, output: string) {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot({ path: output, fullPage: true });
await browser.close();
}
captureScreenshot(process.argv[2], process.argv[3] || 'screenshot.png');
EOF
**3. Focus Area Flags (Saves 50-70%)**
```bashcat > scripts/playwright-screenshot.ts << 'EOF'
import { chromium } from '@playwright/test';
async function captureScreenshot(url: string, output: string) {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot({ path: output, fullPage: true });
await browser.close();
}
captureScreenshot(process.argv[2], process.argv[3] || 'screenshot.png');
EOF
**3. 聚焦区域标记(节省50-70%Token)**
```bashProcess only requested automation type
仅处理请求的自动化类型
case "$TASK" in
screenshot) generate_screenshot_script ;;
pdf) generate_pdf_script ;;
scrape) generate_scrape_script ;;
test) generate_test_script ;;
form) generate_form_script ;;
performance) generate_performance_script ;;
esac
**4. Bash-Based Detection (Saves 80-90%)**
```bashcase "$TASK" in
screenshot) generate_screenshot_script ;;
pdf) generate_pdf_script ;;
scrape) generate_scrape_script ;;
test) generate_test_script ;;
form) generate_form_script ;;
performance) generate_performance_script ;;
esac
**4. 基于Bash的检测(节省80-90%Token)**
```bashUse Bash for all detection - never Read files
使用Bash进行所有检测 - 绝不读取文件
command -v playwright &> /dev/null && echo "✓ Playwright CLI available"
grep -q "playwright" "$HOME/.claude/config.json" 2>/dev/null && echo "✓ MCP configured"
**5. Incremental Script Generation (Saves 60%)**
```bashcommand -v playwright &> /dev/null && echo "✓ Playwright CLI可用"
grep -q "playwright" "$HOME/.claude/config.json" 2>/dev/null && echo "✓ MCP已配置"
**5. 增量脚本生成(节省60%Token)**
```bashGenerate ONE script at a time based on request
根据请求仅生成一个脚本
generate_only_requested_automation_type() {
local task="$1"
Generate ONLY the specific script needed
case "$task" in
screenshot) cat > scripts/playwright-screenshot.ts << 'EOF'
generate_only_requested_automation_type() {
local task="$1"
仅生成所需的特定脚本
case "$task" in
screenshot) cat > scripts/playwright-screenshot.ts << 'EOF'
... only screenshot template ...
... 仅截图模板 ...
EOF
;;
esac
}
**6. Selector Strategy Caching (Saves 40-50%)**
```bashEOF
;;
esac
}
**6. 选择器策略缓存(节省40-50%Token)**
```bashCache common selector patterns
缓存常见选择器模式
CACHE_FILE=".claude/cache/playwright-automate/selector-patterns.json"
if [ -f "$CACHE_FILE" ]; then
Reuse cached selectors for common elements
echo "Using cached selector strategies"
fi
**7. Git Diff for Changed Pages (Saves 70-80%)**
```bashCACHE_FILE=".claude/cache/playwright-automate/selector-patterns.json"
if [ -f "$CACHE_FILE" ]; then
重用常见元素的缓存选择器
echo "使用缓存的选择器策略"
fi
**7. 针对变更页面的Git Diff(节省70-80%Token)**
```bashOnly regenerate scripts for changed pages/components
仅为变更的页面/组件重新生成脚本
changed_files=$(git diff --name-only HEAD~1 HEAD | grep -E '.(tsx?|jsx?)$')
if [ -z "$changed_files" ]; then
echo "✓ No page changes detected, using existing automation scripts"
exit 0
fi
**8. MCP Integration (Zero Claude Tokens)**
```bashchanged_files=$(git diff --name-only HEAD~1 HEAD | grep -E '.(tsx?|jsx?)$')
if [ -z "$changed_files" ]; then
echo "✓ 未检测到页面变更,使用现有自动化脚本"
exit 0
fi
**8. MCP集成(零Claude Token消耗)**
```bashUse MCP server for Playwright execution - no Claude API calls
使用MCP服务器执行Playwright - 无需调用Claude API
if mcp_server_available "playwright"; then
MCP handles execution completely
echo "Using Playwright MCP server for automation"
exit 0
fi
undefinedif mcp_server_available "playwright"; then
MCP完全处理执行过程
echo "使用Playwright MCP服务器进行自动化"
exit 0
fi
undefinedToken Usage Comparison
Token使用量对比
Unoptimized Approach (3,000-5,000 tokens):
- Read all page files to understand structure (1,000 tokens)
- Read existing automation scripts (500 tokens)
- Generate all automation types dynamically (1,500 tokens)
- Analyze selectors from DOM inspection (1,000 tokens)
Optimized Approach (900-1,500 tokens):
- Bash-based Playwright detection (50 tokens)
- Early exit if not installed (100 tokens)
- Template-based script for requested type only (400-800 tokens)
- Cached selector patterns (50 tokens)
- Git diff for changed pages only (100 tokens)
- Focus area flag processing (200-400 tokens)
未优化方案(3000-5000个Token):
- 读取所有页面文件以理解结构(1000个Token)
- 读取现有自动化脚本(500个Token)
- 动态生成所有自动化类型(1500个Token)
- 通过DOM检查分析选择器(1000个Token)
优化方案(900-1500个Token):
- 基于Bash的Playwright检测(50个Token)
- 若未安装则提前退出(100个Token)
- 仅为请求类型生成基于模板的脚本(400-800个Token)
- 缓存的选择器模式(50个Token)
- 仅针对变更页面的Git Diff(100个Token)
- 聚焦区域标记处理(200-400个Token)
Usage Pattern Impact
使用模式影响
Simple Screenshot (300-600 tokens):
bash
playwright-automate screenshot https://example.com简单截图(300-600个Token):
bash
playwright-automate screenshot https://example.comEarly exit check (50) + Template generation (250-500) + Execution (50)
提前退出检查(50)+ 模板生成(250-500)+ 执行(50)
**PDF Generation (300-600 tokens):**
```bash
playwright-automate pdf https://example.com/docs
**PDF生成(300-600个Token):**
```bash
playwright-automate pdf https://example.com/docsEarly exit check (50) + Template generation (250-500) + Execution (50)
提前退出检查(50)+ 模板生成(250-500)+ 执行(50)
**Web Scraping (500-900 tokens):**
```bash
playwright-automate scrape https://example.com
**网页爬取(500-900个Token):**
```bash
playwright-automate scrape https://example.comEarly exit check (50) + Selector strategy (200) + Template generation (250-500) + Execution (50)
提前退出检查(50)+ 选择器策略(200)+ 模板生成(250-500)+ 执行(50)
**E2E Test Generation (600-1,000 tokens):**
```bash
playwright-automate test --flow login
**E2E测试生成(600-1000个Token):**
```bash
playwright-automate test --flow loginEarly exit check (50) + Flow analysis (200-300) + Template generation (300-500) + Execution (50)
提前退出检查(50)+ 流程分析(200-300)+ 模板生成(300-500)+ 执行(50)
**No Playwright Installed (100 tokens):**
```bash
**未安装Playwright(100个Token):**
```bashEarly exit immediately - 90% savings
立即提前退出 - 节省90%Token
playwright-automate screenshot https://example.com
playwright-automate screenshot https://example.com
Output: "❌ Playwright not installed" (100 tokens vs. 3,000+)
输出:"❌ Playwright未安装"(100个Token vs 3000+个Token)
undefinedundefinedCaching Strategy
缓存策略
Cache Location:
.claude/cache/playwright-automate/Cached Data:
- Playwright installation status (valid until package.json changes)
- MCP configuration (valid until ~/.claude/config.json changes)
- Common selector patterns (button, input, form, navigation)
- Generated automation scripts (valid until page structure changes)
- Performance baseline metrics
Cache Invalidation:
bash
undefined缓存位置:
.claude/cache/playwright-automate/缓存数据:
- Playwright安装状态(在package.json变更前有效)
- MCP配置(在~/.claude/config.json变更前有效)
- 常见选择器模式(按钮、输入框、表单、导航)
- 生成的自动化脚本(在页面结构变更前有效)
- 性能基准指标
缓存失效:
bash
undefinedAutomatic invalidation triggers
自动失效触发条件
watch_files=(
"package.json"
"~/.claude/config.json"
"src/**/*.{tsx,jsx,html}"
"playwright.config.ts"
)
undefinedwatch_files=(
"package.json"
"~/.claude/config.json"
"src/**/*.{tsx,jsx,html}"
"playwright.config.ts"
)
undefinedBest Practices for Token Efficiency
Token效率最佳实践
DO:
- ✅ Use early exit for non-Playwright projects
- ✅ Generate only requested automation type
- ✅ Leverage template-based scripts with heredocs
- ✅ Cache selector patterns for common elements
- ✅ Use git diff to detect changed pages
- ✅ Prefer MCP integration when available
- ✅ Use focus area flags (--screenshot, --pdf, --scrape)
DON'T:
- ❌ Read all page files to understand structure
- ❌ Generate all automation types speculatively
- ❌ Analyze selectors dynamically from DOM
- ❌ Regenerate scripts for unchanged pages
- ❌ Use WebFetch or Read for Playwright detection
建议:
- ✅ 对非Playwright项目使用提前退出
- ✅ 仅生成请求的自动化类型
- ✅ 利用here文档的基于模板的脚本
- ✅ 缓存常见元素的选择器模式
- ✅ 使用git diff检测变更页面
- ✅ 若可用则优先使用MCP集成
- ✅ 使用聚焦区域标记(--screenshot、--pdf、--scrape)
禁止:
- ❌ 读取所有页面文件以理解结构
- ❌ 推测性地生成所有自动化类型
- ❌ 从DOM动态分析选择器
- ❌ 为未变更页面重新生成脚本
- ❌ 使用WebFetch或Read进行Playwright检测
Integration with Other Skills
与其他技能的集成
Shared caching with:
- - E2E test script templates
/e2e-generate - - MCP configuration status
/mcp-setup - - External tool integration patterns
/tool-connect
Optimization patterns borrowed from:
- - Template-based script generation
/test - - Configuration detection and caching
/ci-setup - - Early exit and focus area patterns
/security-scan
Caching Behavior:
- Cache location:
.claude/cache/playwright-automate/ - Caches: Playwright installation status, MCP configuration, common workflows
- Cache validity: Until package.json or MCP config changes
- Shared with: ,
/mcp-setup,/e2e-generateskills/tool-connect
Usage:
- - Screenshot (300-600 tokens)
playwright-automate screenshot https://example.com - - PDF generation (300-600 tokens)
playwright-automate pdf https://example.com - - Generate test script (600-1,000 tokens)
playwright-automate test - - Scraping script (500-900 tokens)
playwright-automate scrape https://example.com
共享缓存的技能:
- - E2E测试脚本模板
/e2e-generate - - MCP配置状态
/mcp-setup - - 外部工具集成模式
/tool-connect
借鉴的优化模式来自:
- - 基于模板的脚本生成
/test - - 配置检测与缓存
/ci-setup - - 提前退出与聚焦区域模式
/security-scan
缓存行为:
- 缓存位置:
.claude/cache/playwright-automate/ - 缓存内容:Playwright安装状态、MCP配置、常见工作流
- 缓存有效期:直到package.json或MCP配置变更
- 共享对象:、
/mcp-setup、/e2e-generate技能/tool-connect
使用方法:
- - 截图(300-600个Token)
playwright-automate screenshot https://example.com - - PDF生成(300-600个Token)
playwright-automate pdf https://example.com - - 生成测试脚本(600-1000个Token)
playwright-automate test - - 爬取脚本(500-900个Token)
playwright-automate scrape https://example.com
Phase 1: Prerequisites Check
阶段1:前置条件检查
bash
#!/bin/bashbash
#!/bin/bashCheck Playwright installation and setup
检查Playwright安装与设置
check_playwright() {
echo "=== Playwright Setup Check ==="
echo ""
# Check for Playwright installation
if [ -f "package.json" ]; then
if grep -q "@playwright/test" package.json; then
echo "✓ Playwright detected in package.json"
PLAYWRIGHT_INSTALLED=true
else
echo "⚠️ Playwright not found in package.json"
PLAYWRIGHT_INSTALLED=false
fi
else
echo "⚠️ No package.json found"
PLAYWRIGHT_INSTALLED=false
fi
# Check if Playwright CLI is available
if command -v playwright &> /dev/null; then
echo "✓ Playwright CLI available"
playwright --version
else
echo "⚠️ Playwright CLI not in PATH"
fi
# Check for Playwright MCP server
if [ -f "$HOME/.claude/config.json" ]; then
if grep -q "playwright" "$HOME/.claude/config.json"; then
echo "✓ Playwright MCP server configured"
MCP_CONFIGURED=true
else
echo "⚠️ Playwright MCP server not configured"
MCP_CONFIGURED=false
fi
fi
echo ""
# Offer installation if needed
if [ "$PLAYWRIGHT_INSTALLED" = false ]; then
echo "Would you like to install Playwright?"
echo " 1. npm install --save-dev @playwright/test"
echo " 2. npx playwright install"
echo ""
fi
if [ "$MCP_CONFIGURED" = false ]; then
echo "For MCP integration, run: /mcp-setup playwright"
echo ""
fi}
check_playwright
undefinedcheck_playwright() {
echo "=== Playwright设置检查 ==="
echo ""
# 检查Playwright安装
if [ -f "package.json" ]; then
if grep -q "@playwright/test" package.json; then
echo "✓ 在package.json中检测到Playwright"
PLAYWRIGHT_INSTALLED=true
else
echo "⚠️ 在package.json中未找到Playwright"
PLAYWRIGHT_INSTALLED=false
fi
else
echo "⚠️ 未找到package.json"
PLAYWRIGHT_INSTALLED=false
fi
# 检查Playwright CLI是否可用
if command -v playwright &> /dev/null; then
echo "✓ Playwright CLI可用"
playwright --version
else
echo "⚠️ Playwright CLI不在PATH中"
fi
# 检查Playwright MCP服务器
if [ -f "$HOME/.claude/config.json" ]; then
if grep -q "playwright" "$HOME/.claude/config.json"; then
echo "✓ Playwright MCP服务器已配置"
MCP_CONFIGURED=true
else
echo "⚠️ Playwright MCP服务器未配置"
MCP_CONFIGURED=false
fi
fi
echo ""
# 若需要则提供安装选项
if [ "$PLAYWRIGHT_INSTALLED" = false ]; then
echo "你是否要安装Playwright?"
echo " 1. npm install --save-dev @playwright/test"
echo " 2. npx playwright install"
echo ""
fi
if [ "$MCP_CONFIGURED" = false ]; then
echo "如需MCP集成,请运行:/mcp-setup playwright"
echo ""
fi}
check_playwright
undefinedPhase 2: Automation Type Detection
阶段2:自动化类型检测
Based on your request, I'll determine the automation workflow:
bash
#!/bin/bash根据你的请求,我将确定自动化工作流:
bash
#!/bin/bashParse automation request
解析自动化请求
parse_automation_request() {
local args="$1"
case "$args" in
*screenshot*|*capture*|*snap*)
TASK="screenshot"
;;
*pdf*|*print*|*save*)
TASK="pdf"
;;
*scrape*|*extract*|*data*)
TASK="scrape"
;;
*test*|*e2e*|*verify*)
TASK="test"
;;
*form*|*submit*|*fill*)
TASK="form"
;;
*monitor*|*performance*|*perf*)
TASK="performance"
;;
*)
TASK="interactive"
;;
esac
echo "Automation task: $TASK"}
parse_automation_request "$ARGUMENTS"
undefinedparse_automation_request() {
local args="$1"
case "$args" in
*screenshot*|*capture*|*snap*)
TASK="screenshot"
;;
*pdf*|*print*|*save*)
TASK="pdf"
;;
*scrape*|*extract*|*data*)
TASK="scrape"
;;
*test*|*e2e*|*verify*)
TASK="test"
;;
*form*|*submit*|*fill*)
TASK="form"
;;
*monitor*|*performance*|*perf*)
TASK="performance"
;;
*)
TASK="interactive"
;;
esac
echo "自动化任务:$TASK"}
parse_automation_request "$ARGUMENTS"
undefinedPhase 3: Screenshot Automation
阶段3:截图自动化
Generate automated screenshot workflows:
typescript
// scripts/playwright-screenshot.ts
import { chromium, FullConfig } from '@playwright/test';
async function captureScreenshots(config: ScreenshotConfig) {
const browser = await chromium.launch({
headless: true
});
const context = await browser.newContext({
viewport: config.viewport || { width: 1920, height: 1080 }
});
const page = await context.newPage();
try {
// Navigate to URL
await page.goto(config.url, { waitUntil: 'networkidle' });
// Wait for specific element if provided
if (config.waitForSelector) {
await page.waitForSelector(config.waitForSelector, { timeout: 10000 });
}
// Capture full page screenshot
if (config.fullPage) {
await page.screenshot({
path: config.outputPath || 'screenshot-full.png',
fullPage: true
});
console.log(`✓ Full page screenshot saved: ${config.outputPath}`);
}
// Capture specific element
if (config.elementSelector) {
const element = page.locator(config.elementSelector);
await element.screenshot({
path: config.elementOutputPath || 'screenshot-element.png'
});
console.log(`✓ Element screenshot saved: ${config.elementOutputPath}`);
}
// Capture multiple viewports (responsive)
if (config.responsive) {
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1920, height: 1080 }
];
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.screenshot({
path: `screenshot-${viewport.name}.png`
});
console.log(`✓ ${viewport.name} screenshot saved`);
}
}
} finally {
await context.close();
await browser.close();
}
}
// Configuration interface
interface ScreenshotConfig {
url: string;
viewport?: { width: number; height: number };
waitForSelector?: string;
fullPage?: boolean;
elementSelector?: string;
outputPath?: string;
elementOutputPath?: string;
responsive?: boolean;
}
// Example usage
const config: ScreenshotConfig = {
url: process.env.URL || 'http://localhost:3000',
fullPage: true,
responsive: true,
outputPath: 'screenshots/homepage.png'
};
captureScreenshots(config).catch(console.error);Bash wrapper for easy execution:
bash
#!/bin/bash生成自动化截图工作流:
typescript
// scripts/playwright-screenshot.ts
import { chromium, FullConfig } from '@playwright/test';
async function captureScreenshots(config: ScreenshotConfig) {
const browser = await chromium.launch({
headless: true
});
const context = await browser.newContext({
viewport: config.viewport || { width: 1920, height: 1080 }
});
const page = await context.newPage();
try {
// 导航到URL
await page.goto(config.url, { waitUntil: 'networkidle' });
// 若提供则等待特定元素
if (config.waitForSelector) {
await page.waitForSelector(config.waitForSelector, { timeout: 10000 });
}
// 捕获整页截图
if (config.fullPage) {
await page.screenshot({
path: config.outputPath || 'screenshot-full.png',
fullPage: true
});
console.log(`✓ 整页截图已保存:${config.outputPath}`);
}
// 捕获特定元素
if (config.elementSelector) {
const element = page.locator(config.elementSelector);
await element.screenshot({
path: config.elementOutputPath || 'screenshot-element.png'
});
console.log(`✓ 元素截图已保存:${config.elementOutputPath}`);
}
// 捕获多视口截图(响应式)
if (config.responsive) {
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1920, height: 1080 }
];
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.screenshot({
path: `screenshot-${viewport.name}.png`
});
console.log(`✓ ${viewport.name}截图已保存`);
}
}
} finally {
await context.close();
await browser.close();
}
}
// 配置接口
interface ScreenshotConfig {
url: string;
viewport?: { width: number; height: number };
waitForSelector?: string;
fullPage?: boolean;
elementSelector?: string;
outputPath?: string;
elementOutputPath?: string;
responsive?: boolean;
}
// 示例用法
const config: ScreenshotConfig = {
url: process.env.URL || 'http://localhost:3000',
fullPage: true,
responsive: true,
outputPath: 'screenshots/homepage.png'
};
captureScreenshots(config).catch(console.error);便于执行的Bash包装器:
bash
#!/bin/bashscreenshot-automation.sh
screenshot-automation.sh
screenshot_page() {
local url="$1"
local output="${2:-screenshot.png}"
echo "Capturing screenshot of: $url"
npx playwright screenshot \
--browser chromium \
--full-page \
--output "$output" \
"$url"
if [ $? -eq 0 ]; then
echo "✓ Screenshot saved: $output"
echo " Size: $(du -h "$output" | cut -f1)"
else
echo "❌ Screenshot failed"
exit 1
fi}
screenshot_page() {
local url="$1"
local output="${2:-screenshot.png}"
echo "正在捕获截图:$url"
npx playwright screenshot \
--browser chromium \
--full-page \
--output "$output" \
"$url"
if [ $? -eq 0 ]; then
echo "✓ 截图已保存:$output"
echo " 大小:$(du -h "$output" | cut -f1)"
else
echo "❌ 截图失败"
exit 1
fi}
Multi-viewport capture
多视口捕获
screenshot_responsive() {
local url="$1"
local base_name="${2:-screenshot}"
echo "Capturing responsive screenshots..."
# Mobile
npx playwright screenshot \
--browser chromium \
--viewport-size 375,667 \
--output "${base_name}-mobile.png" \
"$url"
# Tablet
npx playwright screenshot \
--browser chromium \
--viewport-size 768,1024 \
--output "${base_name}-tablet.png" \
"$url"
# Desktop
npx playwright screenshot \
--browser chromium \
--viewport-size 1920,1080 \
--full-page \
--output "${base_name}-desktop.png" \
"$url"
echo "✓ Responsive screenshots complete"
ls -lh ${base_name}-*.png}
screenshot_responsive() {
local url="$1"
local base_name="${2:-screenshot}"
echo "正在捕获响应式截图..."
# 移动端
npx playwright screenshot \
--browser chromium \
--viewport-size 375,667 \
--output "${base_name}-mobile.png" \
"$url"
# 平板端
npx playwright screenshot \
--browser chromium \
--viewport-size 768,1024 \
--output "${base_name}-tablet.png" \
"$url"
# 桌面端
npx playwright screenshot \
--browser chromium \
--viewport-size 1920,1080 \
--full-page \
--output "${base_name}-desktop.png" \
"$url"
echo "✓ 响应式截图完成"
ls -lh ${base_name}-*.png}
Execute based on arguments
根据参数执行
case "$1" in
single)
screenshot_page "$2" "$3"
;;
responsive)
screenshot_responsive "$2" "$3"
;;
*)
echo "Usage: $0 {single|responsive} <url> [output]"
;;
esac
undefinedcase "$1" in
single)
screenshot_page "$2" "$3"
;;
responsive)
screenshot_responsive "$2" "$3"
;;
*)
echo "用法:$0 {single|responsive} <url> [output]"
;;
esac
undefinedPhase 4: PDF Generation
阶段4:PDF生成
Generate PDFs from web pages:
typescript
// scripts/playwright-pdf.ts
import { chromium } from '@playwright/test';
async function generatePDF(config: PDFConfig) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// Wait for dynamic content
if (config.waitForSelector) {
await page.waitForSelector(config.waitForSelector);
}
// Add delay for full rendering
if (config.delayMs) {
await page.waitForTimeout(config.delayMs);
}
// Generate PDF
await page.pdf({
path: config.outputPath || 'output.pdf',
format: config.format || 'A4',
printBackground: true,
margin: config.margin || {
top: '20px',
right: '20px',
bottom: '20px',
left: '20px'
},
displayHeaderFooter: config.headerFooter || false,
headerTemplate: config.headerTemplate,
footerTemplate: config.footerTemplate
});
console.log(`✓ PDF generated: ${config.outputPath}`);
} finally {
await context.close();
await browser.close();
}
}
interface PDFConfig {
url: string;
outputPath?: string;
format?: 'Letter' | 'Legal' | 'A4' | 'A3';
waitForSelector?: string;
delayMs?: number;
margin?: { top: string; right: string; bottom: string; left: string };
headerFooter?: boolean;
headerTemplate?: string;
footerTemplate?: string;
}
// CLI execution
const url = process.argv[2] || 'http://localhost:3000';
const output = process.argv[3] || 'output.pdf';
generatePDF({ url, outputPath: output }).catch(console.error);从网页生成PDF:
typescript
// scripts/playwright-pdf.ts
import { chromium } from '@playwright/test';
async function generatePDF(config: PDFConfig) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// 等待动态内容
if (config.waitForSelector) {
await page.waitForSelector(config.waitForSelector);
}
// 添加延迟以完成渲染
if (config.delayMs) {
await page.waitForTimeout(config.delayMs);
}
// 生成PDF
await page.pdf({
path: config.outputPath || 'output.pdf',
format: config.format || 'A4',
printBackground: true,
margin: config.margin || {
top: '20px',
right: '20px',
bottom: '20px',
left: '20px'
},
displayHeaderFooter: config.headerFooter || false,
headerTemplate: config.headerTemplate,
footerTemplate: config.footerTemplate
});
console.log(`✓ PDF已生成:${config.outputPath}`);
} finally {
await context.close();
await browser.close();
}
}
interface PDFConfig {
url: string;
outputPath?: string;
format?: 'Letter' | 'Legal' | 'A4' | 'A3';
waitForSelector?: string;
delayMs?: number;
margin?: { top: string; right: string; bottom: string; left: string };
headerFooter?: boolean;
headerTemplate?: string;
footerTemplate?: string;
}
// CLI执行
const url = process.argv[2] || 'http://localhost:3000';
const output = process.argv[3] || 'output.pdf';
generatePDF({ url, outputPath: output }).catch(console.error);Phase 5: Web Scraping
阶段5:网页爬取
Automated data extraction:
typescript
// scripts/playwright-scrape.ts
import { chromium } from '@playwright/test';
import * as fs from 'fs';
async function scrapeData(config: ScrapeConfig) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// Wait for content
await page.waitForSelector(config.contentSelector, { timeout: 10000 });
// Extract data
const data = await page.evaluate((selectors) => {
const results: any = {};
Object.entries(selectors).forEach(([key, selector]) => {
const elements = document.querySelectorAll(selector as string);
results[key] = Array.from(elements).map(el => ({
text: el.textContent?.trim(),
html: el.innerHTML,
attributes: Array.from(el.attributes).reduce((acc, attr) => {
acc[attr.name] = attr.value;
return acc;
}, {} as Record<string, string>)
}));
});
return results;
}, config.selectors);
// Save results
const output = {
url: config.url,
timestamp: new Date().toISOString(),
data
};
fs.writeFileSync(
config.outputPath || 'scraped-data.json',
JSON.stringify(output, null, 2)
);
console.log(`✓ Data scraped and saved: ${config.outputPath}`);
console.log(` Extracted ${Object.keys(data).length} data sets`);
} finally {
await context.close();
await browser.close();
}
}
interface ScrapeConfig {
url: string;
contentSelector: string;
selectors: Record<string, string>;
outputPath?: string;
}
// Example usage
const config: ScrapeConfig = {
url: process.argv[2] || 'http://localhost:3000',
contentSelector: 'main',
selectors: {
titles: 'h1, h2, h3',
paragraphs: 'p',
links: 'a[href]',
images: 'img[src]'
},
outputPath: 'scraped-data.json'
};
scrapeData(config).catch(console.error);自动化数据提取:
typescript
// scripts/playwright-scrape.ts
import { chromium } from '@playwright/test';
import * as fs from 'fs';
async function scrapeData(config: ScrapeConfig) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// 等待内容加载
await page.waitForSelector(config.contentSelector, { timeout: 10000 });
// 提取数据
const data = await page.evaluate((selectors) => {
const results: any = {};
Object.entries(selectors).forEach(([key, selector]) => {
const elements = document.querySelectorAll(selector as string);
results[key] = Array.from(elements).map(el => ({
text: el.textContent?.trim(),
html: el.innerHTML,
attributes: Array.from(el.attributes).reduce((acc, attr) => {
acc[attr.name] = attr.value;
return acc;
}, {} as Record<string, string>)
}));
});
return results;
}, config.selectors);
// 保存结果
const output = {
url: config.url,
timestamp: new Date().toISOString(),
data
};
fs.writeFileSync(
config.outputPath || 'scraped-data.json',
JSON.stringify(output, null, 2)
);
console.log(`✓ 数据已爬取并保存:${config.outputPath}`);
console.log(` 提取了${Object.keys(data).length}组数据`);
} finally {
await context.close();
await browser.close();
}
}
interface ScrapeConfig {
url: string;
contentSelector: string;
selectors: Record<string, string>;
outputPath?: string;
}
// 示例用法
const config: ScrapeConfig = {
url: process.argv[2] || 'http://localhost:3000',
contentSelector: 'main',
selectors: {
titles: 'h1, h2, h3',
paragraphs: 'p',
links: 'a[href]',
images: 'img[src]'
},
outputPath: 'scraped-data.json'
};
scrapeData(config).catch(console.error);Phase 6: Form Automation
阶段6:表单自动化
Automated form filling and submission:
typescript
// scripts/playwright-form.ts
import { chromium } from '@playwright/test';
async function automateForm(config: FormConfig) {
const browser = await chromium.launch({
headless: config.headless !== false
});
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// Fill form fields
for (const [selector, value] of Object.entries(config.fields)) {
await page.fill(selector, String(value));
console.log(`✓ Filled field: ${selector}`);
}
// Handle checkboxes
if (config.checkboxes) {
for (const selector of config.checkboxes) {
await page.check(selector);
console.log(`✓ Checked: ${selector}`);
}
}
// Handle radio buttons
if (config.radioButtons) {
for (const selector of config.radioButtons) {
await page.check(selector);
console.log(`✓ Selected radio: ${selector}`);
}
}
// Handle dropdowns
if (config.selects) {
for (const [selector, value] of Object.entries(config.selects)) {
await page.selectOption(selector, value);
console.log(`✓ Selected option: ${selector} = ${value}`);
}
}
// Take screenshot before submit
if (config.screenshotBeforeSubmit) {
await page.screenshot({ path: 'form-before-submit.png' });
}
// Submit form
if (config.submitSelector) {
await page.click(config.submitSelector);
await page.waitForLoadState('networkidle');
console.log('✓ Form submitted');
// Take screenshot after submit
if (config.screenshotAfterSubmit) {
await page.screenshot({ path: 'form-after-submit.png' });
}
// Verify success
if (config.successSelector) {
const success = await page.locator(config.successSelector).isVisible();
if (success) {
console.log('✓ Form submission successful');
} else {
console.log('⚠️ Success indicator not found');
}
}
}
} finally {
await context.close();
await browser.close();
}
}
interface FormConfig {
url: string;
fields: Record<string, string | number>;
checkboxes?: string[];
radioButtons?: string[];
selects?: Record<string, string>;
submitSelector?: string;
successSelector?: string;
screenshotBeforeSubmit?: boolean;
screenshotAfterSubmit?: boolean;
headless?: boolean;
}
// Example
const config: FormConfig = {
url: 'http://localhost:3000/contact',
fields: {
'input[name="name"]': 'Test User',
'input[name="email"]': 'test@example.com',
'textarea[name="message"]': 'This is an automated test message'
},
checkboxes: ['input[name="newsletter"]'],
submitSelector: 'button[type="submit"]',
successSelector: '.success-message',
screenshotAfterSubmit: true
};
automateForm(config).catch(console.error);自动化表单填写与提交:
typescript
// scripts/playwright-form.ts
import { chromium } from '@playwright/test';
async function automateForm(config: FormConfig) {
const browser = await chromium.launch({
headless: config.headless !== false
});
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(config.url, { waitUntil: 'networkidle' });
// 填写表单字段
for (const [selector, value] of Object.entries(config.fields)) {
await page.fill(selector, String(value));
console.log(`✓ 已填写字段:${selector}`);
}
// 处理复选框
if (config.checkboxes) {
for (const selector of config.checkboxes) {
await page.check(selector);
console.log(`✓ 已勾选:${selector}`);
}
}
// 处理单选按钮
if (config.radioButtons) {
for (const selector of config.radioButtons) {
await page.check(selector);
console.log(`✓ 已选择单选按钮:${selector}`);
}
}
// 处理下拉菜单
if (config.selects) {
for (const [selector, value] of Object.entries(config.selects)) {
await page.selectOption(selector, value);
console.log(`✓ 已选择选项:${selector} = ${value}`);
}
}
// 提交前截图
if (config.screenshotBeforeSubmit) {
await page.screenshot({ path: 'form-before-submit.png' });
}
// 提交表单
if (config.submitSelector) {
await page.click(config.submitSelector);
await page.waitForLoadState('networkidle');
console.log('✓ 表单已提交');
// 提交后截图
if (config.screenshotAfterSubmit) {
await page.screenshot({ path: 'form-after-submit.png' });
}
// 验证提交成功
if (config.successSelector) {
const success = await page.locator(config.successSelector).isVisible();
if (success) {
console.log('✓ 表单提交成功');
} else {
console.log('⚠️ 未找到成功提示');
}
}
}
} finally {
await context.close();
await browser.close();
}
}
interface FormConfig {
url: string;
fields: Record<string, string | number>;
checkboxes?: string[];
radioButtons?: string[];
selects?: Record<string, string>;
submitSelector?: string;
successSelector?: string;
screenshotBeforeSubmit?: boolean;
screenshotAfterSubmit?: boolean;
headless?: boolean;
}
// 示例
const config: FormConfig = {
url: 'http://localhost:3000/contact',
fields: {
'input[name="name"]': 'Test User',
'input[name="email"]': 'test@example.com',
'textarea[name="message"]': '这是一条自动化测试消息'
},
checkboxes: ['input[name="newsletter"]'],
submitSelector: 'button[type="submit"]',
successSelector: '.success-message',
screenshotAfterSubmit: true
};
automateForm(config).catch(console.error);Phase 7: Performance Monitoring
阶段7:性能监控
Monitor page performance metrics:
typescript
// scripts/playwright-performance.ts
import { chromium, devices } from '@playwright/test';
async function measurePerformance(url: string) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext(devices['Desktop Chrome']);
const page = await context.newPage();
try {
// Start performance monitoring
await page.goto(url, { waitUntil: 'networkidle' });
// Collect performance metrics
const metrics = await page.evaluate(() => {
const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const paintMetrics = performance.getEntriesByType('paint');
return {
dns: perfData.domainLookupEnd - perfData.domainLookupStart,
tcp: perfData.connectEnd - perfData.connectStart,
request: perfData.responseStart - perfData.requestStart,
response: perfData.responseEnd - perfData.responseStart,
dom: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
load: perfData.loadEventEnd - perfData.loadEventStart,
total: perfData.loadEventEnd - perfData.fetchStart,
firstPaint: paintMetrics.find(m => m.name === 'first-paint')?.startTime || 0,
firstContentfulPaint: paintMetrics.find(m => m.name === 'first-contentful-paint')?.startTime || 0
};
});
// Web Vitals
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
let lcp = 0;
let fid = 0;
let cls = 0;
// Largest Contentful Paint
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1] as any;
lcp = lastEntry.renderTime || lastEntry.loadTime;
}).observe({ entryTypes: ['largest-contentful-paint'] });
// Cumulative Layout Shift
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(entry as any).hadRecentInput) {
cls += (entry as any).value;
}
}
}).observe({ entryTypes: ['layout-shift'] });
setTimeout(() => {
resolve({ lcp, fid, cls });
}, 5000);
});
});
console.log('=== Performance Metrics ===');
console.log('');
console.log('Navigation Timing:');
console.log(` DNS Lookup: ${metrics.dns.toFixed(2)}ms`);
console.log(` TCP Connect: ${metrics.tcp.toFixed(2)}ms`);
console.log(` Request: ${metrics.request.toFixed(2)}ms`);
console.log(` Response: ${metrics.response.toFixed(2)}ms`);
console.log(` DOM Processing: ${metrics.dom.toFixed(2)}ms`);
console.log(` Load Event: ${metrics.load.toFixed(2)}ms`);
console.log(` Total: ${metrics.total.toFixed(2)}ms`);
console.log('');
console.log('Paint Metrics:');
console.log(` First Paint: ${metrics.firstPaint.toFixed(2)}ms`);
console.log(` First Contentful Paint: ${metrics.firstContentfulPaint.toFixed(2)}ms`);
console.log('');
console.log('Web Vitals:');
console.log(` LCP (Largest Contentful Paint): ${(vitals as any).lcp.toFixed(2)}ms`);
console.log(` CLS (Cumulative Layout Shift): ${(vitals as any).cls.toFixed(4)}`);
// Performance assessment
console.log('');
console.log('Assessment:');
if (metrics.total < 3000) {
console.log(' ✓ Excellent load time');
} else if (metrics.total < 5000) {
console.log(' ⚠️ Good load time, room for improvement');
} else {
console.log(' ❌ Slow load time, optimization needed');
}
} finally {
await context.close();
await browser.close();
}
}
const url = process.argv[2] || 'http://localhost:3000';
measurePerformance(url).catch(console.error);监控页面性能指标:
typescript
// scripts/playwright-performance.ts
import { chromium, devices } from '@playwright/test';
async function measurePerformance(url: string) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext(devices['Desktop Chrome']);
const page = await context.newPage();
try {
// 开始性能监控
await page.goto(url, { waitUntil: 'networkidle' });
// 收集性能指标
const metrics = await page.evaluate(() => {
const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const paintMetrics = performance.getEntriesByType('paint');
return {
dns: perfData.domainLookupEnd - perfData.domainLookupStart,
tcp: perfData.connectEnd - perfData.connectStart,
request: perfData.responseStart - perfData.requestStart,
response: perfData.responseEnd - perfData.responseStart,
dom: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
load: perfData.loadEventEnd - perfData.loadEventStart,
total: perfData.loadEventEnd - perfData.fetchStart,
firstPaint: paintMetrics.find(m => m.name === 'first-paint')?.startTime || 0,
firstContentfulPaint: paintMetrics.find(m => m.name === 'first-contentful-paint')?.startTime || 0
};
});
// Web Vitals指标
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
let lcp = 0;
let fid = 0;
let cls = 0;
// 最大内容绘制
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1] as any;
lcp = lastEntry.renderTime || lastEntry.loadTime;
}).observe({ entryTypes: ['largest-contentful-paint'] });
// 累积布局偏移
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(entry as any).hadRecentInput) {
cls += (entry as any).value;
}
}
}).observe({ entryTypes: ['layout-shift'] });
setTimeout(() => {
resolve({ lcp, fid, cls });
}, 5000);
});
});
console.log('=== 性能指标 ===');
console.log('');
console.log('导航计时:');
console.log(` DNS查询: ${metrics.dns.toFixed(2)}ms`);
console.log(` TCP连接: ${metrics.tcp.toFixed(2)}ms`);
console.log(` 请求发送: ${metrics.request.toFixed(2)}ms`);
console.log(` 响应接收: ${metrics.response.toFixed(2)}ms`);
console.log(` DOM处理: ${metrics.dom.toFixed(2)}ms`);
console.log(` 加载事件: ${metrics.load.toFixed(2)}ms`);
console.log(` 总耗时: ${metrics.total.toFixed(2)}ms`);
console.log('');
console.log('绘制指标:');
console.log(` 首次绘制: ${metrics.firstPaint.toFixed(2)}ms`);
console.log(` 首次内容绘制: ${metrics.firstContentfulPaint.toFixed(2)}ms`);
console.log('');
console.log('Web Vitals:');
console.log(` LCP(最大内容绘制): ${(vitals as any).lcp.toFixed(2)}ms`);
console.log(` CLS(累积布局偏移): ${(vitals as any).cls.toFixed(4)}`);
// 性能评估
console.log('');
console.log('评估结果:');
if (metrics.total < 3000) {
console.log(' ✓ 加载速度极佳');
} else if (metrics.total < 5000) {
console.log(' ⚠️ 加载速度良好,仍有优化空间');
} else {
console.log(' ❌ 加载速度缓慢,需要优化');
}
} finally {
await context.close();
await browser.close();
}
}
const url = process.argv[2] || 'http://localhost:3000';
measurePerformance(url).catch(console.error);Phase 8: Package Scripts
阶段8:包脚本
Add automation scripts to package.json:
bash
#!/bin/bash将自动化脚本添加到package.json:
bash
#!/bin/bashAdd Playwright automation scripts to package.json
将Playwright自动化脚本添加到package.json
add_automation_scripts() {
echo "Add these scripts to your package.json:"
echo ""
cat << 'EOF'
"scripts": {
"playwright:screenshot": "ts-node scripts/playwright-screenshot.ts",
"playwright:pdf": "ts-node scripts/playwright-pdf.ts",
"playwright:scrape": "ts-node scripts/playwright-scrape.ts",
"playwright:form": "ts-node scripts/playwright-form.ts",
"playwright:perf": "ts-node scripts/playwright-performance.ts"
}
EOF
echo ""
}
add_automation_scripts
undefinedadd_automation_scripts() {
echo "将以下脚本添加到你的package.json:"
echo ""
cat << 'EOF'
"scripts": {
"playwright:screenshot": "ts-node scripts/playwright-screenshot.ts",
"playwright:pdf": "ts-node scripts/playwright-pdf.ts",
"playwright:scrape": "ts-node scripts/playwright-scrape.ts",
"playwright:form": "ts-node scripts/playwright-form.ts",
"playwright:perf": "ts-node scripts/playwright-performance.ts"
}
EOF
echo ""
}
add_automation_scripts
undefinedPractical Examples
实际示例
Screenshot capture:
bash
/playwright-automate screenshot https://example.com
/playwright-automate screenshot --responsive --output screenshots/PDF generation:
bash
/playwright-automate pdf https://example.com/docs
/playwright-automate pdf --format A4 --output documentation.pdfWeb scraping:
bash
/playwright-automate scrape https://example.com
/playwright-automate scrape --selector ".products" --output data.jsonForm automation:
bash
/playwright-automate form contact-form.config.json
/playwright-automate test-submissionPerformance monitoring:
bash
/playwright-automate performance https://example.com截图捕获:
bash
/playwright-automate screenshot https://example.com
/playwright-automate screenshot --responsive --output screenshots/PDF生成:
bash
/playwright-automate pdf https://example.com/docs
/playwright-automate pdf --format A4 --output documentation.pdf网页爬取:
bash
/playwright-automate scrape https://example.com
/playwright-automate scrape --selector ".products" --output data.json表单自动化:
bash
/playwright-automate form contact-form.config.json
/playwright-automate test-submission性能监控:
bash
/playwright-automate performance https://example.comBest Practices
最佳实践
Automation Guidelines:
- ✅ Use explicit waits (waitForSelector, waitForLoadState)
- ✅ Handle errors gracefully with try-catch
- ✅ Set reasonable timeouts
- ✅ Clean up browser instances
- ✅ Respect robots.txt and rate limits (for scraping)
Anti-Patterns:
- ❌ Hard-coded delays (use waitFor instead)
- ❌ No error handling
- ❌ Ignoring GDPR/privacy concerns
- ❌ Excessive scraping requests
自动化指南:
- ✅ 使用显式等待(waitForSelector、waitForLoadState)
- ✅ 使用try-catch优雅处理错误
- ✅ 设置合理的超时时间
- ✅ 清理浏览器实例
- ✅ 遵守robots.txt和速率限制(爬取时)
反模式:
- ❌ 硬编码延迟(改用waitFor)
- ❌ 无错误处理
- ❌ 忽略GDPR/隐私问题
- ❌ 过度爬取请求
Integration Points
集成点
- - Generate E2E tests with Playwright
/e2e-generate - - Run Playwright tests alongside unit tests
/test - - Configure Playwright MCP server
/mcp-setup - - Add Playwright automation to CI pipeline
/ci-setup
- - 使用Playwright生成E2E测试
/e2e-generate - - 与单元测试一起运行Playwright测试
/test - - 配置Playwright MCP服务器
/mcp-setup - - 将Playwright自动化添加到CI流水线
/ci-setup
What I'll Actually Do
我实际会执行的操作
- Detect setup - Check Playwright installation
- Understand task - Determine automation type
- Generate scripts - Create TypeScript automation code
- Execute safely - Run with proper error handling
- Document results - Provide clear output and logs
Important: I will NEVER:
- Scrape sites without respecting robots.txt
- Automate without rate limiting
- Ignore privacy and security concerns
- Add AI attribution
All browser automation will be safe, ethical, and well-documented.
Credits: Based on Playwright browser automation best practices and MCP Playwright server integration patterns.
- 检测设置 - 检查Playwright安装情况
- 理解任务 - 确定自动化类型
- 生成脚本 - 创建TypeScript自动化代码
- 安全执行 - 带适当错误处理的运行
- 记录结果 - 提供清晰的输出和日志
重要提示: 我绝不会:
- 在不遵守robots.txt的情况下爬取网站
- 不进行速率限制的自动化
- 忽略隐私和安全问题
- 添加AI署名
所有浏览器自动化都将是安全、合规且有完善文档的。
致谢: 基于Playwright浏览器自动化最佳实践和MCP Playwright服务器集成模式。