多环境下使用Scenario Outline运行Karate测试遇空值错误求助
问题描述
我编写了一个简单的UI测试,模拟2名用户登录应用完成支付操作,需要在dev和recipe环境分别针对每位用户执行测试。为此准备了commons-data-recipe.json和commons-data-dev.json两个JSON数据文件,可通过自定义runner或Maven命令行启动测试。但运行时出现错误:result is neither list nor function: [type: NULL, value: null],该错误出现在测试的最后一行| karate.setup().profile |。
测试代码
@UI Feature: do some stuff. @setup Scenario: * def configuration = call read('classpath:configuration.feature') * def profile = read('this:commons-data-' + configuration.env + '.json') Scenario Outline: Pay the product # connection Given driver loginUrl When input(commons.connection.login, '<login>') And input(commons.connection.password, '<password>') And click(commons.connection.connectionBtn) Then waitForUrl(homeUrl) # Add payment methods When input(quotationPage.paymentConditionSelect, '<paymentConditionSelect>') And waitFor(quotationPage.paymentConditionSelectOption).input(Key.ENTER) And input(quotationPage.paymentModeSelect, '<paymentModeSelect>') And waitFor(quotationPage.paymentModeSelectOption).input(Key.ENTER) Then match cleanGrossMargin contains '<cleanGrossMargin4>' # some other stuff... Examples: | karate.setup().profile | login | password | | karate.setup().profile | toto | password1 | | karate.setup().profile | titi | password2 |
Runner代码
@Karate.Test Karate myTest() { return Karate.run("classpath:ui/blabla/umyTest.feature").relativeTo(getClass()); }
错误原因
问题出在Scenario Outline的Examples表格中:
karate.setup().profile的用法不符合Karate规则,@setup场景中定义的变量无法通过karate.setup()直接在Examples里引用,最终返回null导致解析失败。- 表格第一列重复使用
karate.setup().profile作为列名和取值,Karate无法识别这种无效的列定义,进而触发错误。
解决方案
方案1:直接引用setup中的profile变量
修改Examples表格,去掉无效的karate.setup().profile列,使用Karate的#{变量名}语法直接引用@setup中定义的profile字段:
@UI Feature: do some stuff. @setup Scenario: * def configuration = call read('classpath:configuration.feature') * def profile = read('this:commons-data-' + configuration.env + '.json') Scenario Outline: Pay the product # connection Given driver loginUrl When input(commons.connection.login, '<login>') And input(commons.connection.password, '<password>') And click(commons.connection.connectionBtn) Then waitForUrl(homeUrl) # Add payment methods When input(quotationPage.paymentConditionSelect, '<paymentConditionSelect>') And waitFor(quotationPage.paymentConditionSelectOption).input(Key.ENTER) And input(quotationPage.paymentModeSelect, '<paymentModeSelect>') And waitFor(quotationPage.paymentModeSelectOption).input(Key.ENTER) Then match cleanGrossMargin contains '<cleanGrossMargin4>' # some other stuff... Examples: | login | password | paymentConditionSelect | paymentModeSelect | cleanGrossMargin4 | | toto | password1 | #{profile.paymentConditionSelect} | #{profile.paymentModeSelect} | #{profile.cleanGrossMargin4} | | titi | password2 | #{profile.paymentConditionSelect} | #{profile.paymentModeSelect} | #{profile.cleanGrossMargin4} |
方案2:合并用户数据与profile
如果需要给不同用户配置不同的profile字段,可以在@setup中合并数据,再传入Examples:
@UI Feature: do some stuff. @setup Scenario: * def configuration = call read('classpath:configuration.feature') * def baseProfile = read('this:commons-data-' + configuration.env + '.json') # 定义用户专属数据,可覆盖baseProfile字段 * def users = [ { login: 'toto', password: 'password1', cleanGrossMargin4: 'margin-1' }, { login: 'titi', password: 'password2', cleanGrossMargin4: 'margin-2' } ] # 合并基础profile与用户数据 * def testData = users.map(u => karate.merge(baseProfile, u)) Scenario Outline: Pay the product # connection Given driver loginUrl When input(commons.connection.login, '<login>') And input(commons.connection.password, '<password>') And click(commons.connection.connectionBtn) Then waitForUrl(homeUrl) # Add payment methods When input(quotationPage.paymentConditionSelect, '<paymentConditionSelect>') And waitFor(quotationPage.paymentConditionSelectOption).input(Key.ENTER) And input(quotationPage.paymentModeSelect, '<paymentModeSelect>') And waitFor(quotationPage.paymentModeSelectOption).input(Key.ENTER) Then match cleanGrossMargin contains '<cleanGrossMargin4>' # some other stuff... Examples: | testData |
方案3:修复Examples列定义
如果需要显式传递profile作为参数,修改列名为有效名称,并用#{profile}传入取值:
@UI Feature: do some stuff. @setup Scenario: * def configuration = call read('classpath:configuration.feature') * def profile = read('this:commons-data-' + configuration.env + '.json') Scenario Outline: Pay the product # connection Given driver loginUrl When input(commons.connection.login, '<login>') And input(commons.connection.password, '<password>') And click(commons.connection.connectionBtn) Then waitForUrl(homeUrl) # Add payment methods When input(quotationPage.paymentConditionSelect, '<profileData.paymentConditionSelect>') And waitFor(quotationPage.paymentConditionSelectOption).input(Key.ENTER) And input(quotationPage.paymentModeSelect, '<profileData.paymentModeSelect>') And waitFor(quotationPage.paymentModeSelectOption).input(Key.ENTER) Then match cleanGrossMargin contains '<profileData.cleanGrossMargin4>' # some other stuff... Examples: | profileData | login | password | | #{profile} | toto | password1 | | #{profile} | titi | password2 |
内容的提问来源于stack exchange,提问作者Olivier
相关产品推荐
相关产品推荐

