使用jasmine-browser-runner与Ext.js3.2.1时Firefox WebDriver报循环对象值错误
问题描述
jasmine-browser-runner在Chrome中运行正常,但在Firefox中执行含Ext.js 3.2.1的测试时,直接抛出JavascriptError: Cyclic object value错误,导致测试完全无法进行。
环境信息
- jasmine-browser-runner:最新版本
- Firefox:多版本测试均复现
- Chrome:相同代码/测试无问题
- selenium-webdriver:jasmine-browser-runner默认依赖
- Ext.js:3.2.1版本(核心问题来源)
- 操作系统:全平台(Linux已验证)
最小复现步骤
- 配置jasmine运行文件:
// jasmine-config.js module.exports = { srcFiles: [ 'path/to/ext-js-3.2.1/ext-base-debug.js', // 包含问题方法createInterceptor 'path/to/ext-js-3.2.1/ext-all-debug.js' // 大量调用该方法 ], specFiles: [ 'test/simple.spec.js' ] };
- 编写最简测试用例:
// test/simple.spec.js describe('Simple Test with Ext.js', function() { it('should pass', function() { expect(true).toBe(true); }); });
- Chrome运行(成功):
npx jasmine-browser-runner runSpecs --config=jasmine-config.js --browser=chrome # 测试正常通过
- Firefox运行(失败):
npx jasmine-browser-runner runSpecs --config=jasmine-config.js --browser=firefox # 抛出错误:JavascriptError: Cyclic object value
错误详情
JavascriptError: Cyclic object value: function(fcn, scope){ var method = this; return !Ext.isFunction(fcn) ? this : function() { var me = this, args = arguments; fcn.target = me; // Ext.js创建的循环引用 fcn.method = method; // Ext.js创建的循环引用 return (fcn.apply(scope || me || window, args) !== false) ? method.apply(me || window, args) : null; }; } at Object.throwDecodedError (selenium-webdriver/lib/error.js:523:15) at parseHttpResponse (selenium-webdriver/lib/http.js:524:13) RemoteStacktrace: cloneObject@chrome://remote/content/marionette/json.sys.mjs:46:11 cloneJSON@chrome://remote/content/marionette/json.sys.mjs:207:12 json.clone@chrome://remote/content/marionette/json.sys.mjs:212:22 receiveMessage@chrome://remote/content/marionette/actors/MarionetteCommandsChild.sys.mjs:289:19 request@http://localhost:8890/__src__/path/to/ext-js/ext-and-extensions-debug.js:84625:22
根因分析
- Ext.js 3.2.1扩展了
Function.prototype,其中createInterceptor等方法会生成循环引用:
// 来自ext-base-debug.js约863行 Function.prototype.createInterceptor = function(fcn, scope){ var method = this; // 形成循环引用 return function() { fcn.target = this; // 给传入函数添加指向当前上下文的引用 fcn.method = method; // 给传入函数添加指向原方法的引用 }; };
- Chrome的DevTools Protocol在序列化对象时能自动处理循环引用;而Firefox的Marionette Protocol使用严格JSON序列化,遇到循环引用直接抛出错误。
- jasmine-browser-runner在提取测试结果时,需要序列化整个JavaScript执行上下文,Firefox的Marionette无法处理Ext.js留下的循环引用,导致失败。
解决方案
方案1:修补Ext.js的循环引用问题
直接修改Ext.js的createInterceptor方法,避免给函数实例添加循环引用属性:
// 在引入Ext.js后立即执行以下代码,覆盖原方法 Function.prototype.createInterceptor = function(fcn, scope){ var method = this; return !Ext.isFunction(fcn) ? this : function() { var me = this, args = arguments; // 移除循环引用的赋值操作,改用局部变量传递上下文 var result = fcn.apply(scope || me || window, args); return result !== false ? method.apply(me || window, args) : null; }; };
如果无法修改原Ext.js文件,可以在测试启动前注入这段代码,覆盖原方法。
方案2:配置jasmine-browser-runner,限制序列化范围
jasmine-browser-runner默认会序列化整个执行上下文,我们可以自定义测试结果提取逻辑,只获取必要的测试结果,避免序列化包含循环引用的对象。
修改jasmine配置,添加自定义修复脚本:
// jasmine-config.js module.exports = { srcFiles: [ 'path/to/ext-js-3.2.1/ext-base-debug.js', 'path/to/ext-js-3.2.1/ext-all-debug.js', 'path/to/custom-jasmine-fix.js' // 自定义修复脚本 ], specFiles: [ 'test/simple.spec.js' ] };
// custom-jasmine-fix.js // 重写jasmine的结果报告逻辑,只返回必要数据 jasmine.getEnv().addReporter({ jasmineDone: function(result) { // 只提取测试结果的核心数据,避免序列化整个上下文 var simplifiedResult = { totalSpecsDefined: result.totalSpecsDefined, failedCount: result.failedCount, passedCount: result.totalSpecsDefined - result.failedCount }; // 将简化后的结果传递给runner,避免循环引用问题 window.__jasmine_test_results__ = simplifiedResult; } });
同时需要调整jasmine-browser-runner的结果提取逻辑,读取window.__jasmine_test_results__的值而非默认的全上下文数据。
方案3:使用Firefox的Chrome DevTools Protocol(实验性)
Firefox 79+支持Chrome DevTools Protocol(CDP),可以强制jasmine-browser-runner使用CDP而非Marionette:
# 启动Firefox时启用CDP npx jasmine-browser-runner runSpecs --config=jasmine-config.js --browser=firefox --firefox-options='{"args": ["-remote-debugging-port=9222"]}'
注意:该方式可能存在兼容性问题,需根据项目情况测试验证。
针对疑问的解答
jasmine-browser-runner是否可配置结果提取逻辑?
可以。通过自定义jasmine reporter,只提取测试结果的核心数据,避免序列化包含循环引用的执行上下文。如方案2所示,重写报告逻辑,将简化后的结果存储在全局变量中,再由runner读取。Marionette是否应像CDP一样处理循环引用?
目前Marionette的设计严格遵循JSON规范,不支持循环引用序列化。虽然针对遗留框架可考虑优化,但短期内官方不会改变这一行为,建议通过代码修补或自定义逻辑解决问题。是否可让jasmine-browser-runner为Firefox使用其他协议?
可以尝试强制使用Chrome DevTools Protocol(方案3),但该方式属于实验性用法,部分功能可能受限,需根据项目情况测试验证。
内容的提问来源于stack exchange,提问作者Tanishq Bhadouria

