use std::path::PathBuf;
use std::sync::Arc;
use ferridriver_bdd::feature::FeatureSet;
use ferridriver_bdd::filter::{TagExpression, filter_scenarios};
use ferridriver_bdd::js::{JsBddSession, JsScenarioResult, JsStepStatus};
use ferridriver_bdd::scenario::expand_feature;
use ferridriver_bdd::world::BrowserWorld;
async fn build_world() -> BrowserWorld {
let browser = Arc::new(
ferridriver::chromium()
.launch(ferridriver::options::LaunchOptions {
headless: Some(true),
..Default::default()
})
.await
.expect("launch chromium"),
);
let context = Arc::new(browser.new_context(None));
let page = context.new_page().await.expect("new page");
let request = Arc::new(ferridriver::http_client::HttpClient::new(Default::default()));
let fixtures = ferridriver_test::model::TestFixtures {
browser,
page,
context,
request,
test_info: Arc::new(ferridriver_test::model::TestInfo::new_anonymous()),
modifiers: Arc::new(ferridriver_test::model::TestModifiers::default()),
browser_config: ferridriver_test::config::BrowserConfig::default(),
bdd_args: None,
bdd_data_table: None,
bdd_doc_string: None,
};
BrowserWorld::new(fixtures)
}
#[tokio::test]
async fn js_steps_pass_fail_and_tag_filter() {
let fixtures_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let feature_src = std::fs::read_to_string(fixtures_dir.join("cukes.feature")).expect("read feature");
let session = JsBddSession::from_globs(&["cukes.steps.js".to_string()], &fixtures_dir)
.await
.expect("load js bdd session");
let feature_set = FeatureSet::parse_text(&feature_src).expect("parse feature");
let parsed = &feature_set.features[0];
let mut scenarios = expand_feature(parsed);
let tag_expr = TagExpression::parse("@smoke and not @wip").expect("tag expr");
filter_scenarios(&mut scenarios, &tag_expr);
let mut world = build_world().await;
let mut results: Vec<JsScenarioResult> = Vec::new();
for scenario in &scenarios {
world.reset_scenario_state();
results.push(session.run_scenario(scenario, &mut world).await);
}
session.after_all().await.expect("afterAll");
eprintln!("\n=== JS step results ===");
for r in &results {
eprintln!("Scenario: {} [{}]", r.name, if r.passed { "PASS" } else { "FAIL" });
for s in &r.steps {
eprintln!(" {} {} -> {:?}", s.keyword, s.text, s.status);
}
}
assert!(
!results.iter().any(|r| r.name.contains("excluded")),
"tag filter must exclude @wip scenario"
);
assert_eq!(results.len(), 5, "expected 5 @smoke scenarios, got {}", results.len());
let pass = |name: &str| {
results
.iter()
.find(|r| r.name == name || r.name.starts_with(name))
.unwrap_or_else(|| panic!("scenario {name} missing"))
};
assert!(pass("eat some cukes").passed, "eat some cukes should pass");
assert!(pass("data table sum").passed, "data table scenario should pass");
let outline: Vec<&JsScenarioResult> = results.iter().filter(|r| r.name.starts_with("outline math")).collect();
assert_eq!(outline.len(), 2, "outline expands to 2 rows");
assert!(outline.iter().all(|r| r.passed), "both outline rows should pass");
let failing = pass("deliberately failing");
assert!(!failing.passed, "failing scenario must fail");
let failed_step = failing
.steps
.iter()
.find(|s| matches!(s.status, JsStepStatus::Failed(_)))
.expect("a failed step");
let JsStepStatus::Failed(msg) = &failed_step.status else {
unreachable!()
};
assert!(msg.contains("boom from js step"), "JS throw message propagated: {msg}");
assert!(
msg.contains("cukes.steps.js"),
"failure remaps to the original .js source via source map: {msg}"
);
}