You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多环境下使用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表格中:

  1. karate.setup().profile的用法不符合Karate规则,@setup场景中定义的变量无法通过karate.setup()直接在Examples里引用,最终返回null导致解析失败。
  2. 表格第一列重复使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:25:16