#![allow(
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::must_use_candidate,
clippy::doc_markdown,
clippy::module_name_repetitions,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::redundant_closure_for_method_calls,
clippy::implicit_clone,
clippy::too_many_lines,
clippy::uninlined_format_args,
clippy::type_complexity,
clippy::unnecessary_map_or,
clippy::match_same_arms,
clippy::should_implement_trait,
clippy::unnecessary_wraps,
clippy::unused_async,
clippy::items_after_statements,
clippy::needless_pass_by_value,
clippy::single_match_else,
clippy::vec_init_then_push,
clippy::from_over_into,
clippy::single_char_pattern,
clippy::ptr_arg,
clippy::unnecessary_sort_by,
clippy::collapsible_match,
clippy::if_same_then_else,
clippy::single_match
)]
#![allow(unused_imports)]
extern crate self as ferridriver_bdd;
pub use ferridriver_bdd_macros::{after, before, given, param_type, step, then, when};
pub use inventory;
pub mod data_table;
pub mod executor;
pub mod expression;
pub mod feature;
pub mod filter;
pub mod hook;
pub mod js;
pub mod param_type;
pub mod registry;
pub mod scenario;
pub mod snippet;
pub mod step;
pub mod steps;
pub mod translate;
pub mod world;
pub mod prelude {
pub use crate::step::{DataTable, StepError, StepParam};
pub use crate::step_err;
pub use crate::world::BrowserWorld;
pub use ferridriver_bdd_macros::{after, before, given, param_type, step, then, when};
pub use ferridriver::Page;
}
#[macro_export]
macro_rules! step_err {
($($arg:tt)*) => {
$crate::step::StepError::from(format!($($arg)*))
};
}
#[macro_export]
macro_rules! bdd_main {
() => {
fn main() {
$crate::run_bdd_harness();
}
};
}
pub fn run_bdd_harness() {
ferridriver_test::logging::init_from_env();
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("failed to build tokio runtime");
let exit_code = rt.block_on(async {
let overrides = ferridriver_test::parse_common_cli_args();
let config = ferridriver_test::config::resolve_config(&overrides).unwrap_or_else(|e| {
eprintln!("config error: {e}");
std::process::exit(1);
});
run_bdd_with(config, overrides).await
});
std::process::exit(exit_code);
}
pub async fn run_bdd_with(
mut config: ferridriver_test::config::TestConfig,
overrides: ferridriver_test::config::CliOverrides,
) -> i32 {
use std::sync::Arc;
let feature_patterns = std::env::var("FERRIDRIVER_FEATURES")
.ok()
.map(|s| s.split(',').map(String::from).collect::<Vec<_>>())
.unwrap_or_else(|| vec!["features/**/*.feature".to_string()]);
if config.features.is_empty() {
config.features = feature_patterns;
}
if let Ok(tags) = std::env::var("FERRIDRIVER_TAGS") {
if config.tags.is_none() {
config.tags = Some(tags);
}
}
if let Some(ref tags) = overrides.bdd_tags {
config.tags = Some(tags.clone());
}
if overrides.bdd_dry_run {
config.dry_run = true;
}
if overrides.bdd_fail_fast {
config.fail_fast = true;
}
if let Some(t) = overrides.bdd_step_timeout {
config.timeout = t;
}
if overrides.bdd_strict {
config.strict = true;
}
if let Some(ref order) = overrides.bdd_order {
config.order = order.clone();
}
if overrides.bdd_language.is_some() {
config.language = overrides.bdd_language.clone();
}
if let Some(s) = overrides.world_parameters.as_deref() {
match serde_json::from_str::<serde_json::Value>(s) {
Ok(v) => config.world_parameters = v,
Err(e) => {
eprintln!("--world-parameters: invalid JSON: {e}");
return 1;
},
}
}
let feature_set = match feature::FeatureSet::discover_and_parse(&config.features, &config.test_ignore) {
Ok(fs) => fs,
Err(e) => {
eprintln!("feature discovery error: {e}");
return 1;
},
};
if feature_set.features.is_empty() {
eprintln!("no feature files found matching: {:?}", config.features);
return 0;
}
let js_globs: Vec<String> = if overrides.bdd_steps.is_empty() {
config.steps.clone()
} else {
overrides.bdd_steps.clone()
};
let extensions: Vec<String> = if overrides.extensions.is_empty() {
std::env::var("FERRIDRIVER_EXTENSIONS")
.ok()
.map(|s| {
s.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
})
.unwrap_or_default()
} else {
overrides.extensions.clone()
};
let plan = if js_globs.is_empty() && extensions.is_empty() {
let registry = Arc::new(registry::StepRegistry::build());
translate::translate_features(&feature_set, registry, &config)
} else {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let bundle = match js::bundle_steps_with(&js_globs, &extensions, &cwd).await {
Ok(b) => b,
Err(e) => {
eprintln!("step bundle error: {e}");
return 1;
},
};
js::translate_features_js(&feature_set, &config, bundle, cwd)
};
if plan.total_tests == 0 {
eprintln!("no scenarios found");
return 0;
}
config.has_bdd = true;
let mut runner = ferridriver_test::runner::TestRunner::new(config, overrides);
runner.run(plan).await
}