1. Playwright自动化脚本内存溢出问题深度解析
最近在团队内部主导的Web自动化测试项目中,我们全面采用了Playwright作为核心测试框架。在实际运行过程中,当测试用例数量超过200个时,频繁出现内存溢出(OOM)问题,导致CI/CD流水线多次中断。经过两周的排查和优化,最终将内存占用稳定控制在1.5GB以内。以下是完整的解决方案和实战经验。
关键发现:Playwright默认配置下每个浏览器实例会保留约50MB内存,当并行执行时未正确关闭实例会导致内存累积
1.1 典型内存溢出场景还原
我们最初的项目配置如下:
- Playwright 1.40.0 + Python 3.10
- 在Pytest中通过fixture启动浏览器
- 使用4个worker并行执行测试
- 每个测试用例平均运行时长45秒
当执行到第187个用例时,监控显示:
bash复制Memory usage:
初始: 1.2GB
峰值: 4.3GB (触发OOM)
通过pyrasite工具注入分析发现:
python复制# 内存中残留的BrowserContext对象
[<BrowserContext page=3> × 42,
<BrowserContext page=1> × 15,
<Page url="https://..."> × 87]
1.2 根本原因定位
经过堆内存分析,主要问题集中在三个层面:
- 上下文泄漏:
python复制@pytest.fixture
def page():
browser = playwright.chromium.launch() # 每次都会创建新实例
yield browser.new_page()
# 缺少browser.close()
- 并行执行策略:
bash复制pytest -n 4 # 未限制单个worker内存
- 默认资源限制:
javascript复制// Playwright默认配置
const browser = await chromium.launch({
headless: true,
// 未设置以下参数
args: ['--disable-dev-shm-usage']
});
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统化解决方案
2.1 基础内存管理方案
方案一:强制回收机制
python复制@pytest.fixture(scope="function")
def page(playwright):
browser = playwright.chromium.launch(
args=["--disable-dev-shm-usage"]
)
context = browser.new_context()
page = context.new_page()
yield page
# 关键清理顺序
page.close()
context.close()
browser.close()
方案二:全局实例管理
python复制class BrowserPool:
_instance = None
@classmethod
def get_browser(cls):
if cls._instance is None:
cls._instance = playwright.chromium.launch(
headless=True,
args=[
'--single-process',
'--no-zygote',
'--no-sandbox',
'--disable-setuid-sandbox'
]
)
return cls._instance
@pytest.fixture(scope="session")
def browser():
yield BrowserPool.get_browser()
BrowserPool.cleanup() # 测试结束后统一清理
2.2 高级调优策略
内存限制参数对比
| 参数 | 默认值 | 推荐值 | 效果 |
|---|---|---|---|
| --disable-dev-shm-usage | 未设置 | 必选 | 减少/dev/shm内存占用30% |
| --single-process | false | true | 降低多进程开销 |
| --max-old-space-size | 未限制 | 2048 | 限制Node内存 |
| PWDEBUG | false | console | 输出内存警告 |
Gradle项目特殊配置
对于使用Gradle构建的Java项目:
gradle复制test {
maxHeapSize = "2g"
jvmArgs = [
"-XX:+HeapDumpOnOutOfMemoryError",
"-XX:MaxMetaspaceSize=512m"
]
}
3. 实战优化案例
3.1 电商平台测试优化
原始状态:
- 300个测试用例
- 平均内存占用:3.8GB
- 执行时间:42分钟
优化后:
python复制# conftest.py
def pytest_configure(config):
# 限制Playwright缓存
os.environ["PLAYWRIGHT_DISABLE_CACHE"] = "1"
os.environ["PLAYWRIGHT_SLOW_MO"] = "0"
@pytest.fixture(scope="class")
def chrome_context(browser):
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
# 禁用非必要功能
java_script_enabled=False,
has_touch=False
)
yield context
context.clear_cookies()
context.clear_permissions()
最终效果:
- 内存峰值:1.2GB
- 执行时间:38分钟
- 稳定性:连续运行20次无OOM
3.2 持续集成环境配置
GitLab CI示例:
yaml复制test:e2e:
image: mcr.microsoft.com/playwright:v1.40.0
variables:
NODE_OPTIONS: "--max-old-space-size=2048"
script:
- export PLAYWRIGHT_BROWSERS_PATH=/tmp/pw-browsers
- pytest tests/ -n 4 --max-worker-restart=0
artifacts:
paths:
- playwright-report/
when: always
4. 深度排查工具箱
4.1 内存分析工具链
- Python内存分析:
bash复制pip install memray
memray run -o profile.bin test_module.py
memray stats profile.bin
- Chromium内存统计:
python复制from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
print(page.evaluate("""
() => {
return {
jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit,
usedJSHeapSize: window.performance.memory.usedJSHeapSize
}
}
"""))
4.2 典型问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 内存线性增长 | 未关闭page/context | 严格遵循close顺序 |
| 突然OOM | 大文件下载 | 设置downloads_path |
| 重复登录失败 | cookie未清理 | context.clear_cookies() |
| 截图内存高 | 全页截图 | 改用clip参数 |
5. 长效预防机制
5.1 自动化监控方案
在pytest中添加内存钩子:
python复制# conftest.py
import psutil
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
process = psutil.Process()
start_mem = process.memory_info().rss / 1024 / 1024
yield
end_mem = process.memory_info().rss / 1024 / 1024
if end_mem - start_mem > 50: # MB
pytest.fail(f"内存泄漏警告: 单用例增长{end_mem-start_mem:.2f}MB")
5.2 架构级优化
推荐采用微服务化测试架构:
code复制测试调度服务 (限制内存)
├── 用例执行器1 (1GB上限)
├── 用例执行器2 (1GB上限)
└── 结果聚合服务
通过Docker限制单容器内存:
dockerfile复制FROM mcr.microsoft.com/playwright
ENV NODE_OPTIONS="--max-old-space-size=1024"
CMD ["pytest", "-n", "2", "--max-memory-per-worker=500MB"]
