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
相关产品推荐
相关产品推荐

