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

Scala Cucumber实现步骤定义完成后网页截图方案问询

刚好我之前也遇到过类似的需求,Cucumber其实提供了步骤级的钩子来解决这个问题,不用手动给几百个步骤加截图代码,下面给你两个实用的方案:


方案一:用@AfterStep钩子实现每个步骤完成后截图

Cucumber除了场景级的@Before/@After钩子,还支持步骤级的@BeforeStep和@AfterStep注解,刚好能满足你“每个步骤定义完成后截图”的需求。

你只需要在现有的Hooks.scala里新增一个@AfterStep方法,复用你已经写好的截图逻辑即可:

// Hooks.scala
@After
def tearDown(result: Scenario){
  if (result.isFailed) {
    ifCurrentDriverTakesSnapshot { takesSnapshot => Snapshotter.takeErrorSnapshot(takesSnapshot, result) }
  }
}

// 新增步骤后截图的钩子
@AfterStep
def afterStep(result: Scenario) {
  // 这里可以选择:所有步骤都截图,或者只在步骤失败时截图,按需调整
  ifCurrentDriverTakesSnapshot { takesSnapshot => 
    Snapshotter.takeStepSnapshot(takesSnapshot, result)
  }
}

然后在Snapshotter.scala里新增一个通用的截图方法(也可以直接复用原方法,加个标识区分场景失败和步骤完成):

// Snapshotter.scala
def takeErrorSnapshot(takesScreenshot: TakesScreenshot, result: Scenario) = {
  takeSnapshot(takesScreenshot, result, "Scenario failed")
}

def takeStepSnapshot(takesScreenshot: TakesScreenshot, result: Scenario) = {
  val stepText = result.getStepMatch.getStepText
  takeSnapshot(takesScreenshot, result, s"Step completed: $stepText")
}

// 通用截图逻辑
private def takeSnapshot(takesScreenshot: TakesScreenshot, result: Scenario, label: String) = {
  try {
    val screenshot = takesScreenshot.getScreenshotAs(OutputType.BYTES)
    result.embed(screenshot, "image/png", label)
  } catch {
    case e: WebDriverException => e.printStackTrace(System.err)
  }
}

这个方案的优势是零侵入现有步骤定义,全局生效,所有步骤执行完都会自动截图。


方案二:监听页面跳转事件自动截图

如果你还需要在“打开新页面”时触发截图,可以结合WebDriver的事件监听机制,监听页面导航行为:

1. 实现WebDriver事件监听器

class PageNavigationScreenshotListener extends WebDriverEventListener {
  // 监听页面跳转完成事件
  override def afterNavigateTo(url: String, driver: WebDriver): Unit = {
    driver match {
      case takesSnapshot: TakesScreenshot =>
        // 需要通过ThreadLocal获取当前执行的Scenario对象(需要提前存)
        ScenarioHolder.get().foreach { scenario =>
          val screenshot = takesSnapshot.getScreenshotAs(OutputType.BYTES)
          scenario.embed(screenshot, "image/png", s"Navigated to: $url")
        }
      case _ => // 忽略不支持截图的驱动
    }
  }

  // 还可以按需重写afterNavigateBack、afterNavigateForward等方法
}

// 用ThreadLocal存储当前Scenario,确保多线程测试安全
object ScenarioHolder {
  private val scenarioThreadLocal = new ThreadLocal[Option[Scenario]]()

  def set(scenario: Scenario): Unit = scenarioThreadLocal.set(Some(scenario))
  def get(): Option[Scenario] = Option(scenarioThreadLocal.get()).flatten
  def clear(): Unit = scenarioThreadLocal.remove()
}

2. 在初始化驱动时注册监听器

// 初始化WebDriver的地方
val chromeOptions = new ChromeOptions()
val originalDriver = new ChromeDriver(chromeOptions)
// 包装成支持事件监听的驱动
val eventFiringDriver = new EventFiringWebDriver(originalDriver)
// 注册截图监听器
eventFiringDriver.register(new PageNavigationScreenshotListener())
// 之后用eventFiringDriver作为你的测试驱动实例

3. 在场景钩子中维护Scenario对象

// Hooks.scala
@Before
def setUp(scenario: Scenario) {
  ScenarioHolder.set(scenario)
  // 其他初始化逻辑
}

@After
def tearDown(result: Scenario){
  ScenarioHolder.clear()
  // 原来的失败截图逻辑
}

注意事项

  • 如果每个步骤都截图,会生成大量图片,可能导致测试报告体积过大,建议通过系统属性控制开关,比如只在调试环境启用:
    @AfterStep
    def afterStep(result: Scenario) {
      if (System.getProperty("cucumber.screenshot.per.step", "false").toBoolean) {
        ifCurrentDriverTakesSnapshot { takesSnapshot => 
          Snapshotter.takeStepSnapshot(takesSnapshot, result)
        }
      }
    }
    
  • 多线程并行测试时,一定要用ThreadLocal存储Scenario和Driver,避免线程安全问题。

内容的提问来源于stack exchange,提问作者ZachH

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:56:53