This skill documents the complete workflow for initializing the Playwright trio in a Vue 3 + Vite + TypeScript pnpm monorepo, along with 2 key environment pitfalls discovered during execution.
Applicable scenarios
- First-time Playwright integration in a pnpm monorepo project
- Large-screen/visualization projects that need visual regression testing
- Need to equip AI agents with browser-driving capabilities (MCP + CLI skills)
- Node 22 + Windows environment (pitfalls are related to Node version/OS)
⚠️ Headless browser CPU risk notice (important)
headless is not the same as lightweight. Headless Chromium still runs the full browser engine (Blink layout, V8 JS execution, WebGL/WebGPU rendering pipeline), it just does not display a window. In large-screen 3D/visualization projects , the CPU/GPU consumption of headless mode is almost identical to headed mode:
| Scenario | Risk level | Description |
|---|---|---|
| pw_core MCP persistent connection | 🔴 High | The browser under MCP is along-lived process, it stays alive throughout the AI session and is not destroyed after use. Multiple Agents repeatedly invoking it → process stays resident → memory leak → CPU 100% |
| Large-screen 3D page repeatedly rendering | 🔴 High | ECharts WebGL/3D charts still fully execute the GPU rendering pipeline in headless mode. Every screenshot/snapshot triggers a repaint |
| pw_core + pw_visual running simultaneously | 🟠 Medium-high | Two Chromium processes in parallel, each using 200-500MB of memory; large-screen projects may spike to 1GB+ |
| One-off CI testing | 🟢 Low | The process exits automatically after testing is complete, risk is controllable |
Therefore, projects initialized with this skill must read and follow the constraints in the "Safe Use Guidelines for Headless Browsers" section. Especially on large-screen/visualization/3D-intensive projects, prioritize using pw_visual (headed) to drive the browser, and avoid letting pw_core (headless) stay resident for long periods.
Workflow overview
装三件套 → 端口固定 → 写根级 playwright.config.ts
→ visual-stability.css → .mcp.json + MCP 配置
→ playwright-cli + AI skills 生成
→ package.json scripts (test:e2e + ci)
→ e2e 冒烟测试 → 视觉基线测试
→ AI 记忆三处更新 → 验收
Step 1: Install the trio
pnpm add -D -w @playwright/test @playwright/cli playwright-mcp
pnpm exec playwright install chromium
pnpm exec playwright --version # 验证
Responsibilities of the trio:
| Package | Purpose | When to use |
|---|---|---|
@playwright/test |
Write and run e2e/visual tests | CI stabilization,pnpm test:e2e |
@playwright/cli |
Generate skills for AI agents | playwright-cli install --skills |
playwright-mcp |
AI directly drives the browser via MCP | pw_core (headless, saves tokens but high CPU), pw_visual (headed, visual reasoning) |
Step 2: Fix the dev server port of the monorepo sub-app
For each app that needs testing, in vite.config.ts explicitly set the port to avoid playwright webServer port conflicts:
server: {
port: 5174, // 一期用 5173,二期用 5174,依此类推
host: "127.0.0.1", // 必须 127.0.0.1,不用 0.0.0.0
},
Step 3: Root-level playwright.config.ts
For monorepos, a single root config is recommended, using testDir + testMatch for centralized management, andprojects to distinguish different apps (for later expansion):
import { defineConfig, devices } from "@playwright/test";
const CI = !!process.env.CI;
const baseURL = "http://127.0.0.1:5174";
export default defineConfig({
testDir: "./tests",
testMatch: "**/*.spec.ts", // isolate from vitest *.test.ts
outputDir: "artifacts/test-results",
expect: {
toHaveScreenshot: { maxDiffPixelRatio: 0.001 },
},
fullyParallel: false, // 大屏 3D 资源,并行争抢 GPU
workers: 1,
retries: CI ? 2 : 0,
reporter: [["list"], ["html", { open: "never", outputFolder: "artifacts/playwright-report" }]],
use: {
baseURL,
/** 显式声明 headless,不依赖默认值——确保 CI 和非 CI 行为一致 */
headless: true,
/** 禁用 GPU 及相关加速,降低 headless 模式下的 CPU 消耗 */
launchOptions: {
args: ["--disable-gpu", "--disable-software-rasterizer", "--disable-dev-shm-usage", "--no-sandbox"],
},
timezoneId: "Asia/Shanghai", // 中国大屏用上海时区
viewport: { width: 1440, height: 900 },
stylePath: "tests/visual/visual-stability.css",
screenshot: "only-on-failure",
trace: "retain-on-failure",
},
snapshotPathTemplate: "tests/visual/__screenshots__/{testFilePath}/{arg}{ext}",
projects: [{ name: "chromium-desktop", use: { ...devices["Desktop Chrome"] } }],
webServer: {
command: "pnpm --filter <pkg-name> dev", // 见坑 #2
url: baseURL,
reuseExistingServer: !CI,
timeout: 120_000,
},
});
Pitfall #1: lint-staged prettier --experimental-cli crashes on Node 22
Symptom:lint-staged.config.js in "*": "prettier --experimental-cli --write" triggers in a Node 22 sandbox environment WorkTankWorkerError: Terminated, blocking all git commits.
Root cause: prettier 3.8.1's --experimental-cli depends on the worktank worker pool, which is unstable in some Node 22 environments.
Fix:
// lint-staged.config.js
export default {
/**
* 临时去掉 prettier 的 --experimental-cli 标志。
* prettier 3.8.1 的 --experimental-cli worktank worker
* 在 Node 22 沙箱崩溃,回退到普通 --write,待上游修复后恢复。
*
* 另加 --ignore-unknown:跳过无 parser 的文件(如 .gitignore)
*/
"*": "prettier --write --ignore-unknown",
};
Note:--ignore-unknown is very important—without it, .gitignore and other files without a parser will also be blocked from committing by prettier errors.
Pitfall #2: playwright webServer's pnpm filter swallows --host
Symptom:playwright.config.ts 's webServer.command is set to pnpm dev:<app> --host 127.0.0.1, and after webServer auto-starts it returns 404. In CI mode reuseExistingServer: false will fail directly.
Root cause:pnpm dev:<app> expands to pnpm --filter <pkg> dev, and the following --host 127.0.0.1 is swallowed by the pnpm filter and not passed to vite.
Fix: do not pass --host/--portin webServer.command; rely on the existing server configuration in vite.config.ts:
// playwright.config.ts
webServer: {
// 不在命令行传 --host/--port,依赖 vite.config.ts 的 server 配置
command: "pnpm --filter <pkg> dev",
url: baseURL,
reuseExistingServer: !CI,
timeout: 120_000,
},
General principle:For a vite project's playwright webServer, put all server parameters in vite.config.ts, and only pass dev。
Step 4: Isolate vitest and playwright naming
| Test type | File suffix | Directory | Who runs it |
|---|---|---|---|
| Unit/component/composable | *.test.ts |
**/tests/ |
vitest (pnpm test) |
| e2e/page integration | *.e2e.spec.ts |
tests/e2e/ |
playwright (pnpm test:e2e) |
| Visual regression | *.visual.spec.ts |
tests/visual/ |
playwright (pnpm test:e2e) |
The two configurations are naturally isolated (vitest include uses *.test.ts,playwright testMatch uses *.spec.ts), no need to exclude each other.
Step 5: Baseline screenshot commit strategy
- Commit baseline screenshots to
tests/visual/__screenshots__/(do not add to .gitignore) - Ignore artifacts/, test-results/, playwright-report/
- First run
--update-snapshotsto generate baselines, second runpnpm test:e2eto verify
⚠️ Safe usage guidelines for headless browsers
Core principles
headless Chromium is not a lightweight mode, it runs a full browser engine in the background. The following guidelines help you use Playwright safely in your project and avoid CPU overload incidents.
① Prefer headed mode to drive the browser
Not all scenarios are suitable for headless. Choose by priority:
| Recommended order | Driving method | Applicable scenarios | Reason |
|---|---|---|---|
| ✅ Most recommended | pw_visual(headed) | Daily driving by AI agent | Visual effects are visible, saves CPU and won't "spin idle" |
| ⚠️ As appropriate | pw_core(headless) | Token-sensitive, pure data verification | Saves tokens but not CPU, under large-screen 3D the CPU consumption is almost the same as headed |
| ✅ Recommended | playwright test(CI) | Automated testing, regression verification | Short lifecycle, destroyed after use, risk is controllable |
② MCP browser processes are persistent, their lifecycle must be managed
pw_core and pw_visual are mounted via the MCP protocol; after the AI agent starts, the browser process stays alive.It will not close automatically。
- At the end of the session: the browser must be closed manually. Before the AI agent exits, execute:
// 通过 pw_core 或 pw_visual 的 evaluate_script 关闭页面 await page.close(); // 或在测试脚本中使用 afterAll 钩子 - Long periods of non-use: It is recommended to configure a process timeout or periodic restart in
.mcp.jsonConfigure process timeout or periodic restart - Monitor CPU: For large-screen projects, it is recommended to check Task Manager after the AI agent uses the browser to confirm that the Chromium process has exited
③ Special considerations for large-screen 3D projects
If your project includes ECharts WebGL/3D charts (such as Ring3DChart, 3D maps, etc.), CPU consumption in headless mode will be significantly higher than on pure 2D pages:
- Do not take screenshots frequently: Each
get-screenshotwill trigger a complete WebGL frame render, causing CPU to spike to 100% - Use pw_visual > pw_core: headed mode at least lets you observe the browser's running status
- Reduce meaningless page visits: The AI agent should avoid repeatedly "taking a look" at large-screen pages under headless
- Consider lowering the screenshot resolution: Reduce the viewport or screenshot resolution in non-essential scenarios
④ Add a browser process reclamation reminder in agent-progress.md or AI memory
It is recommended to add the following constraint rules to the project's AI memory file:
### Playwright 浏览器进程使用规则(CPU 安全)
- AI 驱动浏览器默认使用 pw_visual(headed),仅 token 敏感场景用 pw_core
- 每次使用浏览器后检查任务管理器,确认 Chromium 进程已退出
- 禁止在 headless 模式下对 3D 大屏页面做高频截图(每 10 秒 1 次以上)
- 大屏 3D 页面优先用 pw_visual 驱动,避免无头模式下的 GPU 空转
⑤ Perform process cleanup at the end of the session
At the end of the AI session, it is recommended to run the following command to confirm that there are no residual Chromium processes:
# Windows - 检查遗留的 Chromium 进程
tasklist /FI "IMAGENAME eq chrome.exe" 2>NUL | find /I /N "chrome.exe"
# 可选:强制关闭残留(请先确认无其他用户进程)
# taskkill /F /IM chrome.exe 2>NUL
# Linux/macOS
# ps aux | grep -i chromium | grep -v grep
# killall chromium # 谨慎使用
📋 Real incident retrospective: headless browser CPU overload
The following is a complete record of a real incident. This incident directly led to the complete removal of the Playwright toolchain from the project. It is recorded here to help future users identify the same type of risk.
Incident timeline
| Time | Event | Status |
|---|---|---|
| Day 1 | Playwright toolchain initialization completed: pw_core (headless) + pw_visual (headed) MCP, playwright.config.ts, e2e smoke tests, visual baselines, AI memory synchronized in three places | ✅ All 12 acceptance items passed |
| Day 1→3 | The AI agent repeatedly drove the headless browser through pw_core to perform screenshots/snapshots/page validation on large-screen 3D pages | 🔄 Continuously running |
| Day 3 | Host CPU spiked to 100%, and the system became noticeably laggy. Browser processes remained resident for a long time and did not exit | 🔴 Severe |
| Day 3 | The user decided to completely delete the Playwright toolchain, covering 17 items: MCP configuration, test code, configuration files, baseline screenshots, AI skill files, package.json dependencies, AI memory sections | 🗑️ All cleared |
Root cause analysis
初始化 Playwright(pw_core headless MCP)
↓ AI agent 在 3 天内反复通过 pw_core 驱动浏览器 "查看" 大屏 3D 页面
↓ headless Chromium 在后台完整执行 WebGL 渲染管线(3D 环形图、3D 地图等)
↓ 每次 get-screenshot / get-full-snapshot 都触发完整的 GPU 帧渲染
↓ MCP 协议下浏览器是长连接进程,不会用完即销毁
↓ 浏览器进程连续运行 3 天 → 内存持续增长 → CPU 100%
↓ 主机性能严重下降,用户被迫删除 Playwright
Key lessons
- The MCP browser is a long-lived connection, not a one-off call — it will remain alive throughout the entire AI session and will not close automatically after use. This is the biggest difference from
pnpm test:e2eone-off tests. - headless does not save CPU — what headless blocks is the window, not the rendering pipeline. In large-screen 3D projects, the GPU load under headless and headed is almost the same.
- An incident can be caused within 3 days — from initialization to complete deletion, the cycle was only 3 days. Once the AI agent starts calling the browser frequently, the CPU problem will rapidly worsen.
- The habitual "taking a look" is the most dangerous usage — AI agents are naturally inclined to take frequent screenshots/snapshots to confirm page status. In headless mode, this "taking a look" habit will trigger a large amount of useless GPU rendering.
Preventive measures
- Confirm at initialization: Is your project a 3D large-screen type? If so, immediately switch to pw_visual (headed) instead of pw_core
- Set rules before use: In
AGENTS.md, explicitly constrain the frequency and manner of browser calls (refer to the safety rules in the "AI Memory Update Template") - Check immediately after use: At the end of each AI session, run
tasklistto check for residual Chromium processes - Avoid long periods of unattended operation: Do not leave headless browsers running unattended for long periods. If you must use them, it is recommended to set up scheduled restarts or process timeouts
🧰 Troubleshooting Guide
If you encounter the following phenomena while using Playwright, it may indicate that the above incident is recurring.
Symptom Quick Reference Table
| Symptom | Troubleshooting direction | Urgency |
|---|---|---|
| Computer fan spinning wildly, system slowing down | Open Task Manager and check whether there are multiple chrome.exe processes (each using 200-500MB of memory) |
🔴 Handle immediately |
| AI agent gets slower with every reply | Check whether every AI message loop triggers a browser screenshot/snapshot | 🟠 Optimize as soon as possible |
pnpm test:e2e The fan is still spinning after it finishes running |
Check whether there are residual Chromium processes that were not closed by the test framework | 🟠 Fix as soon as possible |
| Browser automatically pops up windows (headed) | Confirm whether pw_core configuration was mixed in but headed mode was mistakenly started | 🟡 Observe and adjust |
Emergency stop-loss steps
If you find that CPU usage has already spiked abnormally:
# 步骤 1:立即检查 Chromium 进程数
tasklist /FI "IMAGENAME eq chrome.exe" 2>NUL
# 步骤 2:如果存在大量 Chromium 进程,强制关闭
taskkill /F /IM chrome.exe 2>NUL
# 步骤 3:关闭重开 AI 会话,确认浏览器工具已不在会话中
# 步骤 4:检查 .mcp.json,临时移除 pw_core/pw_visual 条目
# 步骤 5:复盘分析 — 是哪个 AI agent 行为触发了高频调用
Long-term regression checklist
After each use of Playwright, review the following items:
-
tasklisthas no residualchrome.exe - Task Manager CPU usage has returned to normal levels
- There are no abnormally dense browser call records in the AI session logs
- Each app's vite dev server has been shut down normally
- If it is a CI environment, check the Chromium process exit status code in the CI logs
AI Memory Update Template
Insert in all three places: CLAUDE.md / AGENTS.md / GEMINI.md (<300 words):
## Playwright 工具链
三件套职责:@playwright/test (CI 固化) | @playwright/cli (生成 skills) | playwright-mcp (AI 驱动浏览器)
命名:vitest→*.test.ts,playwright→*.spec.ts
⚠️ 浏览器进程安全规则:
- AI 驱动浏览器默认用 pw_visual(headed),仅 token 敏感场景用 pw_core
- 每次使用浏览器后检查任务管理器,确保 Chromium 进程已退出
- 禁止在 headless 模式下对 3D 大屏页面做高频截图
- 大屏 3D 页面优先用 pw_visual 驱动,避免无头模式 GPU 空转
规则:视觉对齐后必须用 @playwright/test 固化。
Environment notes
- Node 22 + Windows: Add
NODE_OPTIONS=prefix to all node/pnpm commands (clear the problematicNODE_OPTIONS=--use-system-ca) - git commit: Must use the
git-commitskill (check remote commit-types.ts emoji + commitlint pre-validation + -F file method) - pre-commit hook: After modifying lint-staged.config.js, verify once that a commit can pass
Acceptance checklist
pnpm installNo errorspnpm exec playwright --versionOutputs version numberpnpm test:e2eAll 3 tests pass (smoke + visual)pnpm run ciFull process passes (vitest → build → test:e2e).mcp.jsonIncludes pw_core/pw_visual.agents/skills/playwright-cli/Has skill files- The Playwright toolchain sections in the three AI memory locations are consistent
- Sub-app vite.config.ts ports are explicitly fixed
- artifacts/ is gitignored
- ⚠️ Headless browser security specification has been read: confirm the project belongs to the 3D large-screen category, prioritize using pw_visual (headed) rather than pw_core
- ⚠️ Browser process reclamation has been checked:
tasklist /FI "IMAGENAME eq chrome.exe"No residual Chromium processes