memlab 测试场景(IScenario)接口完全指南:编写 E2E 内存泄漏检测脚本的 11 个回调
开发工具测试CLIMCP 服务【免费下载链接】memlabA framework for finding JavaScript memory leaks and analyzing heap snapshots项目地址https://gitcode.com/gh_mirrors/me/memlab点击查看免费下载导读IScenario是 memlab 框架中定义如何让一次 E2E 测试与浏览器交互的核心接口。本文以 Types.ts 中的接口定义为主线逐一拆解url、action、back、repeat、leakFilter等全部回调的语义、参数与真实代码示例并结合 CLI 的memlab run --scenario与memlab/api的run({scenario})两种使用方式以及 BaseSynthesizer.ts 的源码级访问计划生成逻辑帮助你写出可复用、可精确控制泄漏判定标准的测试场景文件。一、什么是 IScenario测试场景在 memlab 中的定位IScenario定义于 Types.ts描述了 memlab 应该如何与一个 Web 应用交互以完成内存泄漏检测。一个测试场景既可以保存为.js文件通过命令行传入也可以直接作为对象字面量传给memlab/api的runAPI// save as test.js and use in terminal: // $ memlab run --scenario test.js module.exports { url: () https://www.npmjs.com/, action: async () ... , back: async () ... , cookies: () ... , // optional repeat: () ... , // optional ... };const {run} require(memlab/api); (async function () { const scenario { url: () https://www.facebook.com, action: async () ... , back: async () ... , cookies: () ... , // optional repeat: () ... , // optional ... }; const leaks await run({scenario}); })();1.1 两条使用路径的底层一致性从源码可以确认两条路径最终殊途同归CLI 路径memlab run --scenario 文件在 ScenarioFileOption.ts 中通过utils.loadScenario(scenarioFile)读取文件实现在 Utils.ts会校验文件存在性并处理非法文件报错把结果挂到config.scenario上API 路径run({scenario})见 API.ts直接把场景对象传给内部流程。从 API 实现看run等价于预热warmup→ 浏览器交互并采集快照takeSnapshots→ 泄漏分析findLeaks三步的组合。无论哪条路径场景对象最终都会进入 TestPlanner.ts 的prepareSynthesizer由 BaseSynthesizer.ts 的synthesisForScenario把回调编排成一个带步骤顺序的访问计划visit plan。1.2 场景被展开成怎样的执行序列synthesisForScenario的源码逻辑清晰展示了action/back/repeat如何决定浏览器访问顺序只有url无action生成单步page-load作为target步骤执行——即仅检测初始页面加载引入的泄漏有action时先访问page-loadbaseline然后按repeat()的次数循环插入action-on-page与revert回退步骤最后执行一次action-on-pagetarget与一次revertfinal。其中repeat在 BaseSynthesizer.ts 中的默认取值为scenario.repeat ? scenario.repeat() : 0即不声明repeat就只做一轮action - back。而revert步骤只有在声明了scenario.back时才使用自定义回退回调否则回落到内置的通用回退步骤SynthesisUtils.revertStep。二、必选回调url定义位置Types.tsurl是唯一必选回调返回页面初始 URL 的字符串值const scenario { url: () https://www.npmjs.com/, }; module.exports scenario;关键语义如果场景只提供url没有actionmemlab 会尝试直接从初始页面加载中检测内存泄漏——初始加载分配的所有 JS 对象都会成为泄漏过滤的候选对象。这一点与 BaseSynthesizer.ts 中if (!scenario.action)分支的处理完全对应。三、核心交互对action 与 back3.1 action触发泄漏的交互定义位置Types.ts类型为InteractionsCallbackaction定义了初始页面加载之后、用于触发内存泄漏的浏览器交互。action回调触发过程中浏览器里分配的所有 JS 对象都会被纳入泄漏过滤的候选集合。参数page——puppeteer 的Page对象提供与浏览器交互的 API示例const scenario { url: () https://www.npmjs.com/, action: async (page) { await page.click(a[href/link]); }, back: async (page) { await page.click(a[href/back]); }, } module.exports scenario;务必注意释放 puppeteer 元素句柄。官方文档与源码都强调从page.$x或page.$取到的元素句柄element handle必须显式释放否则会在浏览器上下文里造成额外内存泄漏干扰检测结果。使用element.dispose()或Promise.all(elements.map(e e.dispose()))完成清理const scenario { url: () https://www.npmjs.com/, action: async (page) { const elements await page.$x(//button[contains(., Text in Button)]); const [button] elements; if (button) { await button.click(); } // dispose external references to JS objects in browser context await Promise.all(elements.map(e e.dispose())); }, back: async (page) ... , } module.exports scenario;仓库自带的示例场景 detached-dom.js 完整演示了这一最佳实践先用$x按 XPath 找到按钮点击后立刻Promise.all(elements.map(e e.dispose()))清理句柄。3.2 backaction 的撤销操作定义位置Types.tsback指定 memlab 如何回退/撤销action造成的状态变化本质是一个 undo 操作const scenario { url: () https://www.npmjs.com/, action: async (page) { await page.click(a[href/link]); }, back: async (page) { await page.click(a[href/back]); }, }memlab 需要撤销action的原因是其差分对比式的泄漏检测原理对比执行交互前后的堆快照找出被action分配、但back之后仍未释放的对象——这正是leakFilter与leakedNodeIds的数据来源。在 detached-dom.js 中back通过点击a[href/]返回首页来复位页面状态。四、交互次数控制repeat定义位置Types.tsrepeat返回一个数字表示 memlab 额外执行多少轮actionbackmodule.exports { url: () ... , action: async (page) ... , back: async (page) ... , // browser interaction: two additional [ action - back ] // init-load - action - back - action - back - action - back repeat: () 2, };注意repeat的值是额外轮数。repeat: () 2得到的序列是init-load - action - back - action - back - action - back即总共 3 次action、前 2 次各跟一次back最后一次actiontarget 快照点之后跟final回退。这与 BaseSynthesizer.ts 的循环展开逻辑一致。多轮往返交互有助于放大每次交互都残留一点内存这类渐进式泄漏的检测信号。五、生命周期钩子beforeInitialPageLoad 与 setup5.1 beforeInitialPageLoad初始加载前的准备定义位置Types.ts在初始页面加载之前仅调用一次常用于设置 HTTP 头或准备数据const scenario { url: () https://www.npmjs.com/, beforeInitialPageLoad: async (page) { // before the initial page load }, action: async (page) { await page.click(a[href/link]); }, back: async (page) { await page.click(a[href/back]); }, } module.exports scenario;源码印证在 E2EInteractionManager.ts 中只有当tabInfo.idx 1第一个步骤时才会调用opArgs.scenario?.beforeInitialPageLoad。5.2 setup初始加载后的数据准备定义位置Types.tssetup在初始页面加载之后仅调用一次可用来登录官方推荐优先用cookies或在action之前准备数据const scenario { url: () https://www.npmjs.com/, setup: async (page) { // log in or prepare data for the interaction }, action: async (page) { await page.click(a[href/link]); }, back: async (page) { await page.click(a[href/back]); }, } module.exports scenario;同样在 E2EInteractionManager.tssetup只在首个步骤执行完成后被调用。六、认证与 Cookiecookies定义位置Types.ts如果被测页面需要认证或特定 Cookie可以以name, value, domain元组列表的形式传入const scenario { url: () https://www.facebook.com/, cookies: () [ {name:cm_j, value: none, domain: .facebook.com}, {name:datr, value: yJvIY..., domain: .facebook.com}, {name:c_user, value: 8917..., domain: .facebook.com}, {name:xs, value: 95:9WQ..., domain: .facebook.com}, // ... ], }; module.exports scenario;domain 自动填充规则请务必提供正确的domain字段若缺失memlab 会基于url回调推断 domain。源码实现在 TestPlanner.ts通过new URL(scenario.url())解析主机名去掉最左侧子域后拼上.前缀例如https://www.facebook.com/会推导出.facebook.com。cookies的返回类型是Cookies即Cookie数组单个Cookie的name、value必填其余字段可选。七、页面加载判定isPageLoaded定义位置Types.ts类型为CheckPageLoadCallback该回调用于判定初始加载及后续交互中页面是否加载完成返回Promiseboolean返回truememlab 认为导航完成进入下一步返回falsememlab 会持续调用该回调直到返回true可await异步逻辑解析后再返回true未提供时的默认行为memlab 认为连续至少 500ms 没有网络连接即视为导航结束。module.exports { url: () ... , action: async (page) ... , back: async (page) ... , isPageLoaded: async (page) { await page.waitForNavigation({ // consider navigation to be finished when there are // no more than 2 network connections for at least 500 ms. waitUntil: networkidle2, // Maximum navigation time in milliseconds timeout: 5000, }); return true; }, };从 BaseSynthesizer.ts 可以看到访问计划中的isPageLoaded字段优先取scenario.isPageLoaded否则回落给合成器的默认检查器getPageLoadChecker内部通过waitFor等待delayWhenNoPageLoadCheck配置时长。八、泄漏判定三件套beforeLeakFilter、leakFilter、retainerReferenceFiltermemlab 的默认泄漏过滤器把**游离的 DOM 元素detached DOM elements和从 React Fiber 树上脱离的未挂载 Fiber 节点unmounted Fiber nodes**视为内存泄漏。当默认规则不满足被测应用时可以用下面三个回调自定义判定逻辑。它们在 Config.ts 中被统一收集为externalLeakFilter并接入分析流程见 HeapAnalyzer.ts 中对beforeLeakFilter的调用。8.1 beforeLeakFilter过滤前的初始化钩子定义位置Types.ts类型为InitLeakFilterCallback在所有leakFilter调用之前仅执行一次适合初始化数据存储或做一次性预处理module.exports { url: () ... , action: async (page) ... , back: async (page) ... , beforeLeakFilter: (snapshot, leakedNodeIds) { // initialize some data stores }, };snapshot所有浏览器交互完成后采集的最终堆快照IHeapSnapshot可查询堆快照数据leakedNodeIdsSetnumber——由action分配、但back之后未释放的所有 JS 堆对象的 id 集合。8.2 leakFilter自定义泄漏判定算法定义位置Types.ts类型为LeakFilterCallbackleakFilter对每个被action分配、但back后未释放的节点逐一调用返回boolean决定该节点是否算泄漏。这些未释放对象可能是有意驻留内存的缓存也可能真的是内存泄漏——回调让你针对被测 JS 程序择优挑出真正的泄漏module.exports { url: () ... , action: async (page) ... , back: async (page) ... , leakFilter(node, snapshot, leakedNodeIds) { // any unreleased node (JS heap object) with 1MB // retained size is considered a memory leak return node.retainedSize 1000000; }, };nodeIHeapNode——堆快照中分配但未释放的对象snapshot最终堆快照leakedNodeIds泄漏候选节点 id 集合。仓库还提供了一个按保留大小过滤的参考实现filter-by-retained-size.js可作为自定义leakFilter的起点。8.3 retainerReferenceFilter控制保留追踪retainer trace的边定义位置Types.ts类型为ReferenceFilterCallback该回调对堆快照中的**每一条引用edge**调用决定该引用是否应纳入保留追踪retainer trace计算edgeIHeapEdge——被评估是否纳入 retainer trace 的引用边snapshot最终堆快照isReferenceUsedByDefaultboolean——memlab 默认逻辑是否使用该引用true表示默认会纳入计算。返回值语义需要特别注意返回true只表示该引用成为 retainer trace 的候选最终可能进也可能不进返回false则必然排除。而且如果排除的是某个对象的支配引用dominator reference即从 GC 根到达该堆对象必须经过的边该对象会在堆图中被视为不可达从而整个引用和对象都不会进入 retainer trace 检测与保留大小计算。// save as leak-filter.js module.exports { retainerReferenceFilter(edge, _snapshot, _leakedNodeIds) { // exclude react fiber references if (edge.name_or_index.toString().startsWith(__reactFiber$)) { return false; } return true; } };九、IScenario 属性速查表属性类型必选调用时机核心作用url() string✅规划阶段返回初始页面 URL仅此一项时检测初始加载泄漏actionInteractionsCallback否初始加载后每轮执行触发泄漏的交互其分配对象进入候选集backInteractionsCallback否每次 action 之后撤销 action 的状态变更undorepeat() number否规划阶段额外执行多少轮action - back默认 0beforeInitialPageLoadInteractionsCallback否初始加载前仅一次设置 HTTP 头、准备数据setupInteractionsCallback否初始加载后仅一次登录推荐用 cookies或准备数据cookies() Cookies否规划阶段认证 Cookie 列表domain 可自动推断isPageLoadedCheckPageLoadCallback否每次导航后自定义页面加载完成判定默认 500ms 无网络连接beforeLeakFilterInitLeakFilterCallback否leakFilter 之前仅一次初始化数据存储等一次性预处理leakFilterLeakFilterCallback否每个候选节点自定义泄漏判定默认检测游离 DOM 与未挂载 FiberretainerReferenceFilterReferenceFilterCallback否每条引用边控制引用是否纳入 retainer trace 计算十、最小可用场景与进阶组合示例最小场景仅检测初始加载泄漏module.exports { url: () https://example.com, };综合示例含认证、重复交互与自定义泄漏阈值module.exports { url: () https://example.com/, cookies: () [ {name: session, value: abc123, domain: .example.com}, ], isPageLoaded: async (page) { await page.waitForNavigation({waitUntil: networkidle2, timeout: 5000}); return true; }, action: async (page) { const elements await page.$x(//button[contains(., Load more)]); const [button] elements; if (button) { await button.click(); } await Promise.all(elements.map(e e.dispose())); }, back: async (page) { await page.click(a[href/]); }, repeat: () 3, leakFilter(node) { return node.retainedSize 1000000; // 1MB }, };十一、进一步阅读接口完整定义Types.ts场景如何生成访问计划BaseSynthesizer.tsCLI 的--scenario解析ScenarioFileOption.tsAPI 的run({scenario})实现API.ts官方示例场景detached-dom.js官方示例泄漏过滤器filter-by-retained-size.js配套类型InteractionsCallback、LeakFilterCallback、ReferenceFilterCallback、Page。赞分享开发工具测试CLIMCP 服务【免费下载链接】memlabA framework for finding JavaScript memory leaks and analyzing heap snapshots项目地址https://gitcode.com/gh_mirrors/me/memlab点击查看免费下载相关推荐MemLab 内存泄漏检测实战指南从 Puppeteer 场景编写到堆快照分析的完整实践MemLab 内存泄漏检测实战指南从 Puppeteer 场景编写到堆快照分析的完整实践 MemLab 是 Meta 开源的端到端 JavaScript 内存开发工具测试CLIMCP 服务Babylon.js 内存泄漏 CI 测试实战基于 memlab 的场景化内存泄漏运行器Babylon.js 内存泄漏 CI 测试实战基于 memlab 的场景化内存泄漏运行器 本文以 Babylon.js 仓库内 packages/tools/图形学游戏开发3D渲染memlab 内存泄漏检测实战从浏览器 E2E 场景到堆快照分析与内存断言memlab 内存泄漏检测实战从浏览器 E2E 场景到堆快照分析与内存断言 memlab 是一个端到端E2E的 JavaScript 内存泄漏测试与分析框开发工具测试CLIMCP 服务上一篇ESP32音频开发中的MCLK配置终极指南深度解析与实战应用下一篇Filterizr 源码架构解析核心类设计与 6 大布局算法实现原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

社区发现经典算法GN原理与Brandes加速的Python实现

社区发现经典算法GN原理与Brandes加速的Python实现

在社交网络分析里,社区发现这个话题总结起来就是:把一群看起来“抱团”的节点找出来。GN算法(Girvan-Newman)大概是这类算法里最常被拿来当入门教材的经典,它用“边介数(edge betweenness)”作为…

2026/10/14 19:21:24 阅读更多 →
t3code:三层代码资产管理方案,让复用与追溯成为工程习惯

t3code:三层代码资产管理方案,让复用与追溯成为工程习惯

说实话,“t3code”这个名字我盯着看了好一会儿,第一反应是它可能是一个充满未知的项目代号。但真正动手去梳理之后,我发现这个带着“第三层级”意味的词汇,更像是一个值得认真对待的工程化思路:把我们每天都在写、却不…

2026/10/14 17:56:43 阅读更多 →
Java SSL证书校验绕过原理与安全替代方案

Java SSL证书校验绕过原理与安全替代方案

1. 这个操作到底在绕过什么——SSL握手过程中的信任链真相“忽略SSL证书校验”这八个字,在Java开发者的日常里出现频率高得有点反常。它常被写在测试环境的配置注释里,藏在某段临时调试代码的catch块之后,甚至作为“快速上线”的口头禅在组会…

2026/10/14 19:21:21 阅读更多 →

最新新闻

pstack:面向 Cursor 编辑器的调用链可观测性工具

pstack:面向 Cursor 编辑器的调用链可观测性工具

1. 这不是另一个“进程快照工具”:pstack 的真实定位与设计哲学第一次看到“pstack”这个名字,很多人会下意识联想到 Linux 系统自带的pstack命令——那个用来打印运行中进程调用栈的轻量级诊断工具。但 Lauren Tan 在 Cursor 生态里推出的这个 pstack&a…

2026/10/14 23:54:27 阅读更多 →
IDEA报错无效目标发行版本?Java编译器版本与SDK配置排查指南

IDEA报错无效目标发行版本?Java编译器版本与SDK配置排查指南

1. 从一次编译中断说起:这个报错到底在说什么如果你在 IntelliJ IDEA 里点下运行或构建按钮,控制台突然甩出一行红字,大意是“无效的目标发行版本”,然后整个编译流程直接中断,那你来对地方了。这个报错在 Java 开发圈…

2026/10/14 23:54:27 阅读更多 →
Claude Code三层记忆与Hook事件驱动实战指南

Claude Code三层记忆与Hook事件驱动实战指南

用过 Claude Code 的朋友应该都有过这种体验:同一个项目开了十几个会话,每次新开会话都要把项目背景、技术栈、代码规范重新交代一遍,稍微少说两句,AI 给出的方案就跑偏。一开始我以为是自己提示词写得不好,后来把它的…

2026/10/14 23:54:27 阅读更多 →
Java面试Redis与MQ:从原理到实战的深度解析

Java面试Redis与MQ:从原理到实战的深度解析

1. 面试官到底想从消息中间件里听到什么面到消息中间件这块,很多人第一反应就是背“解耦、异步、削峰”这六个字。我面过不少人,也被人面过不少次,说实话,这三个词本身没错,但如果你只会说这三个词,面试官基…

2026/10/14 23:54:27 阅读更多 →
多Agent管线安全防护:三道闸门拦住Prompt注入与敏感泄漏

多Agent管线安全防护:三道闸门拦住Prompt注入与敏感泄漏

多Agent管线现在几乎是LLM应用团队的标配——主控Agent负责拆任务,几个子Agent分头干活,中间再串一串工具调用。听起来很顺,但你有没有想过:一旦某个Agent被注入、某个工具参数传错、或者输出里带出了不该出现的信息,整…

2026/10/14 23:54:27 阅读更多 →
C#调用C++结构体跨语言内存对齐实战指南

C#调用C++结构体跨语言内存对齐实战指南

简介:本资源是一套完整的C#调用C结构体混合编程实战项目,面向Windows平台下具备基础C#和C语法能力的中高级开发者,解决跨语言数据结构互通与DLL互操作这一高频工程难题。项目包含C动态链接库(MyLib.dll)源码、C#调用端…

2026/10/14 23:53:24 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →