Puppeteer 是一个 Node 库,提供高级 API 来控制 Chrome 或 Chromium,非常适合用于自动化检测前端内存泄漏问题。下面我将详细介绍如何使用 Puppeteer 编写内存泄漏检测脚本。
基础环境设置
首先安装 Puppeteer:
bash
npm install puppeteer
基本内存检测脚本
javascript
const puppeteer = require('puppeteer');
async function detectMemoryLeaks(url) {
const browser = await puppeteer.launch({
headless: false, // 设置为 true 在生产环境
devtools: true // 打开开发者工具
});
const page = await browser.newPage();
// 启用性能监控
await page.goto(url, { waitUntil: 'networkidle2' });
// 获取初始内存使用情况
const initialMemory = await page.metrics();
console.log('初始内存:', initialMemory.JSHeapUsedSize / 1024 / 1024, 'MB');
// 执行可能引起内存泄漏的操作
for (let i = 0; i < 10; i++) {
await page.click('#some-button'); // 替换为你的交互操作
await page.waitForTimeout(1000);
// 获取当前内存使用
const currentMemory = await page.metrics();
const usedMB = currentMemory.JSHeapUsedSize / 1024 / 1024;
console.log(`操作 ${i+1} 后内存:`, usedMB, 'MB');
// 检查内存增长是否异常
if (usedMB > initialMemory.JSHeapUsedSize / 1024 / 1024 * 2) {
console.warn('⚠️ 检测到可能的内存泄漏!');
}
}
await browser.close();
}
// 测试你的页面
detectMemoryLeaks('http://localhost:3000');
高级内存分析脚本
javascript
const puppeteer = require('puppeteer');
const fs = require('fs');
async function advancedMemoryAnalysis(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 1. 获取初始堆快照
await page.goto(url);
const client = await page.target().createCDPSession();
await client.send('HeapProfiler.enable');
// 保存初始堆快照
const initialSnapshot = await client.send('HeapProfiler.takeHeapSnapshot', {
reportProgress: false
});
fs.writeFileSync('initial.heapsnapshot', JSON.stringify(initialSnapshot));
// 2. 执行测试操作
for (let i = 0; i < 5; i++) {
await page.click('#leaky-button');
await page.waitForTimeout(1000);
}
// 3. 获取操作后堆快照
const finalSnapshot = await client.send('HeapProfiler.takeHeapSnapshot', {
reportProgress: false
});
fs.writeFileSync('final.heapsnapshot', JSON.stringify(finalSnapshot));
// 4. 比较内存变化
const initialMetrics = await page.metrics();
const finalMetrics = await page.metrics();
console.log('内存变化分析:');
console.log('JS堆初始大小:', initialMetrics.JSHeapUsedSize / 1024 / 1024, 'MB');
console.log('JS堆最终大小:', finalMetrics.JSHeapUsedSize / 1024 / 1024, 'MB');
console.log('DOM节点数变化:', finalMetrics.Nodes - initialMetrics.Nodes);
await browser.close();
console.log('分析完成,可以使用Chrome开发者工具加载 .heapsnapshot 文件进行详细比较');
}
advancedMemoryAnalysis('http://localhost:3000');
定时内存监控脚本
javascript
const puppeteer = require('puppeteer');
const { setInterval, clearInterval } = require('timers');
async function monitorMemory(url, duration = 60000) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
const memoryData = [];
const startTime = Date.now();
const interval = setInterval(async () => {
const metrics = await page.metrics();
const timeElapsed = (Date.now() - startTime) / 1000;
const memoryUsage = metrics.JSHeapUsedSize / 1024 / 1024;
memoryData.push({
time: timeElapsed,
memory: memoryUsage
});
console.log(`[${timeElapsed.toFixed(1)}s] 内存使用: ${memoryUsage.toFixed(2)} MB`);
if (Date.now() - startTime >= duration) {
clearInterval(interval);
await browser.close();
// 生成简单报告
const initial = memoryData[0].memory;
const peak = Math.max(...memoryData.map(d => d.memory));
const growth = ((peak - initial) / initial * 100).toFixed(2);
console.log('\n内存监控报告:');
console.log(`初始内存: ${initial.toFixed(2)} MB`);
console.log(`峰值内存: ${peak.toFixed(2)} MB`);
console.log(`内存增长率: ${growth}%`);
if (growth > 50) {
console.warn('⚠️ 检测到显著内存增长,可能存在内存泄漏');
}
}
}, 5000);
}
monitorMemory('http://localhost:3000', 300000); // 监控5分钟
实战技巧
-
模拟用户操作:
javascript// 模拟一系列用户操作 async function simulateUserFlow(page) { await page.type('#username', 'testuser'); await page.type('#password', 'password123'); await page.click('#login-btn'); await page.waitForNavigation(); await page.click('#open-modal'); await page.waitForSelector('.modal'); await page.click('.close-modal'); }
-
检测DOM节点泄漏:
javascriptasync function checkDOMLeaks(page) { const initialCount = (await page.evaluate(() => document.querySelectorAll('*').length)); // 执行操作 for (let i = 0; i < 10; i++) { await page.click('#add-element'); await page.click('#remove-element'); } const finalCount = (await page.evaluate(() => document.querySelectorAll('*').length)); if (finalCount > initialCount * 1.2) { console.warn('DOM节点数异常增长:', initialCount, '->', finalCount); } }
-
检测事件监听器泄漏:
javascriptasync function checkEventListenerLeaks(page) { const initialListeners = await page.evaluate(() => { return Array.from(document.querySelectorAll('*')).reduce((count, el) => { return count + (el._events ? Object.keys(el._events).length : 0); }, 0); }); // 执行操作 await page.click('#add-listeners'); await page.click('#remove-listeners'); const finalListeners = await page.evaluate(() => { return Array.from(document.querySelectorAll('*')).reduce((count, el) => { return count + (el._events ? Object.keys(el._events).length : 0); }, 0); }); if (finalListeners > initialListeners) { console.warn('事件监听器泄漏:', initialListeners, '->', finalListeners); } }
集成到CI/CD流程
javascript
const puppeteer = require('puppeteer');
const assert = require('assert');
describe('内存泄漏测试', () => {
let browser;
let page;
before(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
await page.goto('http://localhost:3000');
});
after(async () => {
await browser.close();
});
it('不应有显著内存增长', async () => {
const initial = await page.metrics();
// 执行测试操作
for (let i = 0; i < 10; i++) {
await page.click('#leaky-component');
await page.waitForTimeout(500);
}
const final = await page.metrics();
const growth = (final.JSHeapUsedSize - initial.JSHeapUsedSize) / initial.JSHeapUsedSize;
assert.ok(growth < 0.5, `内存增长超过50% (${(growth*100).toFixed(2)}%)`);
});
it('DOM节点数应保持稳定', async () => {
const initial = await page.evaluate(() => document.querySelectorAll('*').length);
// 执行测试操作
await page.click('#render-list');
await page.click('#clear-list');
const final = await page.evaluate(() => document.querySelectorAll('*').length);
const diff = final - initial;
assert.ok(diff <= 5, `DOM节点数差异过大 (${diff}个)`);
});
});
分析建议
-
结合Chrome开发者工具:
- 将生成的堆快照(.heapsnapshot)加载到Chrome开发者工具的Memory面板中
- 比较不同时间点的快照,找出内存增长的对象
-
重点关注:
- 分离的DOM树(Detached DOM tree)
- 持续增长的数组或对象
- 未被释放的闭包
-
常见模式检查:
- 循环引用
- 未清理的定时器
- 全局变量积累
- 未取消的事件监听器
通过以上Puppeteer脚本,你可以自动化地检测前端应用中的内存泄漏问题,并将其集成到开发流程中,提前发现和解决内存问题。