Karate嵌套feature调用出现跨上下文JS函数传递错误如何解决
问题描述
我正在使用Karate进行UI自动化测试,其中一个场景需要轮询Oracle Sales Cloud应用UI的账户搜索功能,通过多次搜索弥补与其他应用集成产生的延迟。
为实现轮询功能,我们设计了如下流程:
- 单次搜索场景:封装单次搜索的UI操作逻辑
- 轮询场景:通过
karate.repeat()多次调用单次搜索场景,直到找到目标账户。该场景还包含业务报告所需的其他校验和打印逻辑,无法直接删除,且为避免重复代码,我们将其作为公共组件供多个场景复用。
最终的流程结构大致如下:
@searchAcctOnce Scenario: 执行单次账户搜索 - (搜索相关UI步骤) @pollAccount Scenario: 轮询账户 - ... - def searchAcct = function(i) { karate.call(@searchAcctOnce) } - karate.repeat(10, searchAcct) - ... Scenario: 查询账户后执行后续操作 - call read (@pollAccount) - ... - ...
上述流程本身运行正常,但当我们使用karate-config.js中通过callSingle定义的通用函数时出现问题:调用完嵌套的@searchAcctOnce并返回@pollAccount作用域后,上下文似乎发生了变更,抛出如下错误:
The value 'DynamicObject<JSFunction>@16e99306' cannot be passed from one context to another. The current context is 0x6f9ab79d and the argument value originates from context 0x54af3cb9.
复现说明
项目执行流程为:nestedCalls.feature -> search.feature@pollAccount -> search.feature@searchOnce -> 返回到search.feature@pollAccount并调用util.printSomething()时抛出错误,替换karate.repeat()为单次调用也可复现。
各文件代码如下:
- karate-config.js
function fn() { var config = {}; //var driverConfig={ type: 'chrome', showDriverLog: false, start: true, addOptions:['--disable-extensions','--disable-infobar', '--incognito'] }; var driverConfig={ type: 'msedgedriver', executable: "./msedgedriver.exe", webDriverSession: { capabilities: { browserName: 'edge' } } } karate.configure('driver', driverConfig); karate.configure('report', {showLog: true, showAllSteps: false, showRequest: true, showResponse: true}); config.util = karate.callSingle('util.feature'); return config; }
- nestedCalls.feature
Feature: web-browser automation Background: * configure driver = { type: 'chrome' } Scenario: Test nested calls context change * driver 'https://www.youtube.com/' * def search = call read("search.feature@pollAccount")
- search.feature
Feature: @pollAccount Scenario: Poll Account * def karateVideo = "//*[text()='Shotokan Karate Follow Along Class - 9th Kyu White Belt - Class #1']" * def searchOnce = function(i) { karate.call('search.feature@searchOnce') } * karate.repeat(5, searchOnce) * if(exists(karateVideo)) util.printSomething() @searchOnce Scenario: Search Once * def karateVideo = "//*[text()='Shotokan Karate Follow Along Class - 9th Kyu White Belt - Class #1']" * delay(5000) * if(!exists(karateVideo)) input('/html/body/ytd-app/div/div/ytd-masthead/div[3]/div[2]/ytd-searchbox/form/div[1]/div[1]/input', "karate") * if(!exists(karateVideo)) delay(5000) * if(!exists(karateVideo)) click('/html/body/ytd-app/div/div/ytd-masthead/div[3]/div[2]/ytd-searchbox/button')
- util.feature
Feature: Utilities Scenario: Test nested calls * def printSomething = function() {karate.log("something")}
问题原因与解决方案
核心原因
该报错属于Karate的设计特性,不是bug。callSingle返回的对象中的JS函数会绑定全局初始化时的上下文,嵌套调用子场景时会创建独立的执行上下文,返回父场景后上下文发生切换,原绑定旧上下文的JS函数无法跨上下文调用。
可行解决方案
以下方案按优先级排序:
- 方案1:放弃用
callSingle加载工具函数,改为在使用场景主动调用加载
删掉karate-config.js里的config.util = karate.callSingle('util.feature');,在需要用到util的场景头部添加* def util = call read('util.feature'),每次调用都会生成绑定当前上下文的函数实例,不会出现跨上下文问题。 - 方案2:工具逻辑改用Karate场景封装,而非JS函数
把printSomething这类通用逻辑做成带标签的独立Scenario,需要用时直接call read('util.feature@printSomething'),不需要依赖全局上下文绑定的函数。 - 方案3:全局函数直接挂载到karate全局对象
在karate-config.js里直接把通用函数挂载到karate对象,比如:
调用时直接用karate.printSomething = function() { karate.log("something") }karate.printSomething(),不需要通过config.util调用,不受上下文切换影响。 - 方案4:嵌套调用时手动传递util对象
调用子场景时把util作为参数传入,让函数和子场景上下文绑定:* def searchOnce = function(i) { karate.call('search.feature@searchOnce', { util: util }) }
内容的提问来源于stack exchange,提问作者Victor Dantas
相关产品推荐
相关产品推荐

