Terminal output
By default, cucumber
crate outputs tests result to STDOUT. It provides some CLI options for configuring the output.
Verbosity
By default, cucumber
crate tries to keep the output quite minimal, but its verbosity may be increased with -v
CLI option.
Just specifying -v
makes no difference, as it refers to the default verbosity level (no additional info).
Output World
on failures (-vv
)
Increasing verbosity level with -vv
CLI option, makes the state of the World
being printed at the moment of failure.
extern crate cucumber; extern crate tokio; use std::time::Duration; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(_: &mut AnimalWorld) {} #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .run_and_exit("tests/features/book/output/terminal_verbose.feature") .await; }
This is intended to help debugging failed tests.
Output doc strings (-vvv
)
By default, outputting doc strings of steps is omitted. To include them into the output use -vvv
CLI option:
Feature: Animal feature
Scenario: If we feed a hungry cat it will no longer be hungry
Given a hungry cat
"""
A hungry cat called Felix is rescued from a Whiskas tin in a calamitous
mash-up of cat food brands.
"""
When I feed the cat
Then the cat is not hungry
extern crate cucumber; extern crate tokio; use std::time::Duration; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .run_and_exit("tests/features/book/output/terminal_verbose.feature") .await; }
Coloring
Coloring may be disabled by specifying --color
CLI option:
extern crate cucumber; extern crate tokio; use std::time::Duration; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .run_and_exit("tests/features/book/output/terminal.feature") .await; }
NOTE: By default,
cucumber
crate automatically disables coloring for non-interactive terminals, so there is no need to specify--color
CLI option explicitly on CI.
Debug printing and/or logging
Though cucumber
crate doesn't capture any manual debug printing produced in a step matching function (such as dbg!
or println!
macros), it may be quite misleading to produce and use it for debugging purposes. The reason is simply because cucumber
crate executes scenarios concurrently and normalizes their results before outputting, while any manual print is produced instantly at the moment of its step execution.
WARNING: Moreover, manual printing will very likely interfere with default interactive pretty-printing.
extern crate cucumber; extern crate tokio; use std::time::Duration; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; dbg!("here!"); world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .run_and_exit("tests/features/book/output/terminal.feature") .await; }
To achieve natural output for debugging, the following preparations are required:
- Setting
.max_concurrent_scenarios()
to1
for executing all the scenarios sequentially. - Creating
writer::Basic::raw
withColoring::Never
to avoid interactive pretty-printed output. - Wrapping it into
writer::AssertNormalized
to assurecucumber
about the output being normalized already (due to sequential execution).
extern crate cucumber; extern crate tokio; use std::{io, time::Duration}; use cucumber::{given, then, when, writer, World, WriterExt as _}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; dbg!("here!"); world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .max_concurrent_scenarios(1) .with_writer( writer::Basic::raw(io::stdout(), writer::Coloring::Never, 0) .summarized() .assert_normalized(), ) .run_and_exit("tests/features/book/output/terminal.feature") .await; }
NOTE: The custom print is still output before its step, because is printed during the step execution.
Much better option for debugging would be using tracing
crate integration instead of dbg!
/println!
for doing logs.
extern crate cucumber; extern crate tokio; extern crate tracing; use std::{ sync::atomic::{AtomicUsize, Ordering}, time::Duration, }; use cucumber::{given, then, when, World as _}; use tokio::time; #[derive(cucumber::World, Debug, Default)] struct World; #[given(regex = r"(\d+) secs?")] #[when(regex = r"(\d+) secs?")] #[then(regex = r"(\d+) secs?")] async fn sleep(_: &mut World, secs: u64) { static ID: AtomicUsize = AtomicUsize::new(0); let id = ID.fetch_add(1, Ordering::Relaxed); tracing::info!("before {secs}s sleep: {id}"); time::sleep(Duration::from_secs(secs)).await; tracing::info!("after {secs}s sleep: {id}"); } #[tokio::main] async fn main() { World::cucumber() .init_tracing() .run("tests/features/wait") .await; }
Repeating failed and/or skipped steps
As a number of scenarios grows, it may become quite difficult to find failed/skipped ones in a large output. This issue may be mitigated by duplicating failed and/or skipped steps at the and of output via Cucumber::repeat_failed()
and Cucumber::repeat_skipped()
methods respectively.
extern crate cucumber; extern crate tokio; use std::{time::Duration}; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .repeat_failed() .run_and_exit("tests/features/book/output/terminal_repeat_failed.feature") .await; }
extern crate cucumber; extern crate tokio; use std::{time::Duration}; use cucumber::{given, then, when, World}; use tokio::time::sleep; #[derive(Debug, Default)] struct Animal { pub hungry: bool, } impl Animal { fn feed(&mut self) { self.hungry = false; } } #[derive(Debug, Default, World)] pub struct AnimalWorld { cat: Animal, } #[given(regex = r"^a (hungry|satiated) cat$")] async fn hungry_cat(world: &mut AnimalWorld, state: String) { sleep(Duration::from_secs(2)).await; match state.as_str() { "hungry" => world.cat.hungry = true, "satiated" => world.cat.hungry = false, _ => unreachable!(), } } #[when("I feed the cat")] async fn feed_cat(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; world.cat.feed(); } #[then("the cat is not hungry")] async fn cat_is_fed(world: &mut AnimalWorld) { sleep(Duration::from_secs(2)).await; assert!(!world.cat.hungry); } #[tokio::main] async fn main() { AnimalWorld::cucumber() .repeat_skipped() .run_and_exit("tests/features/book/output/terminal_repeat_skipped.feature") .await; }