Skip to main content

ferridriver_test/
lib.rs

1#![allow(
2  clippy::missing_errors_doc,
3  clippy::missing_panics_doc,
4  clippy::must_use_candidate,
5  clippy::must_use_unit,
6  clippy::return_self_not_must_use,
7  clippy::doc_markdown,
8  clippy::doc_link_with_quotes,
9  clippy::module_name_repetitions,
10  clippy::cast_possible_truncation,
11  clippy::cast_precision_loss,
12  clippy::redundant_closure_for_method_calls,
13  clippy::implicit_clone,
14  clippy::struct_excessive_bools,
15  clippy::large_enum_variant,
16  clippy::needless_raw_string_hashes,
17  clippy::should_implement_trait,
18  clippy::match_same_arms,
19  clippy::uninlined_format_args,
20  clippy::single_char_pattern,
21  clippy::unused_self,
22  clippy::unused_async,
23  clippy::bool_to_int_with_if,
24  clippy::manual_let_else,
25  clippy::too_many_lines,
26  clippy::impl_trait_in_params,
27  clippy::needless_pass_by_value,
28  clippy::match_wildcard_for_single_variants,
29  clippy::manual_string_new,
30  clippy::format_push_string,
31  clippy::trivially_copy_pass_by_ref,
32  clippy::unnecessary_wraps,
33  clippy::default_trait_access,
34  clippy::wildcard_imports,
35  clippy::items_after_statements,
36  clippy::field_reassign_with_default,
37  clippy::map_unwrap_or,
38  clippy::iter_on_single_items,
39  clippy::similar_names,
40  clippy::semicolon_if_nothing_returned,
41  clippy::inconsistent_struct_constructor,
42  clippy::derivable_impls,
43  clippy::used_underscore_items,
44  clippy::explicit_iter_loop,
45  clippy::iter_on_empty_collections,
46  clippy::wrong_self_convention,
47  clippy::unnecessary_sort_by,
48  clippy::iter_over_hash_type,
49  clippy::manual_assert,
50  clippy::explicit_deref_methods,
51  clippy::option_if_let_else,
52  clippy::match_bool,
53  clippy::ref_option,
54  clippy::needless_lifetimes,
55  clippy::type_complexity,
56  clippy::expect_used,
57  clippy::duration_subsec,
58  clippy::verbose_file_reads,
59  clippy::if_not_else,
60  clippy::implicit_hasher,
61  clippy::stable_sort_primitive
62)]
63//! ferridriver-test -- High-performance E2E test runner for browser automation.
64//!
65//! Provides a Playwright Test-compatible API for writing and running browser tests
66//! with automatic fixture injection, parallel execution, and rich reporting.
67//!
68//! # Quick Start (Rust)
69//!
70//! ```ignore
71//! use ferridriver_test::prelude::*;
72//!
73//! #[ferritest]
74//! async fn basic_navigation(ctx: TestContext) {
75//!     let page = ctx.page().await?;
76//!     page.goto("https://example.com", None).await?;
77//!     expect(&*page).to_have_title("Example Domain").await?;
78//! }
79//!
80//! #[ferritest(retries = 2, tag = "smoke")]
81//! async fn login_test(ctx: TestContext) {
82//!     let page = ctx.page().await?;
83//!     page.goto("https://app.example.com/login", None).await?;
84//!     page.locator("#email").fill("user@example.com").await?;
85//!     page.locator("#password").fill("password").await?;
86//!     page.locator("button[type=submit]").click().await?;
87//!     expect(&*page).to_have_url("https://app.example.com/dashboard").await?;
88//! }
89//! ```
90
91// -- Core modules --
92pub mod config;
93pub mod context;
94pub mod ct;
95pub mod discovery;
96pub mod dispatcher;
97pub mod expect;
98pub mod fixture;
99pub mod git_info;
100pub mod interactive;
101pub mod logging;
102pub mod model;
103pub mod reporter;
104pub mod retry;
105pub mod runner;
106pub mod server;
107pub mod shard;
108pub mod snapshot;
109pub mod tracing;
110pub mod tui;
111pub mod tui_reporter;
112pub mod watch;
113pub mod worker;
114
115// -- Re-exports --
116pub use config::{CliOverrides, TestConfig, parse_common_cli_args};
117pub use context::TestContext;
118pub use discovery::{HookKindTag, HookRegistration as InventoryHookRegistration, TestRegistration};
119pub use expect::{ToPassOptions, expect, expect_configured, expect_poll, to_pass, to_pass_with_options};
120pub use fixture::FixturePool;
121pub use model::{
122  HookDef, HookKind, HookOwner, HookPhase, HookRegistration, HookScope, SuiteDef, SuiteMode, TestAnnotation, TestCase,
123  TestFailure, TestFixtures, TestFn, TestId, TestInfo, TestModifiers, TestOutcome, TestPlan, TestPlanBuilder,
124  TestStatus, TestStep,
125};
126pub use reporter::{EventBus, EventBusBuilder, Reporter, ReporterDriver, ReporterEvent, ReporterSet, Subscription};
127pub use runner::TestRunner;
128
129// Re-export proc macros.
130pub use ferridriver_test_macros::{after_all, after_each, before_all, before_each, ferritest, ferritest_each};
131
132// Re-export inventory for the proc macro expansion.
133pub use inventory;
134
135/// Run all `#[ferritest]` tests in this binary.
136///
137/// Reads config from `ferridriver.config.toml` (auto-discovered),
138/// applies CLI args (`-- --headed --backend webkit --workers 1`),
139/// and runs all registered tests through the parallel runner.
140///
141/// ```ignore
142/// use ferridriver_test::prelude::*;
143///
144/// #[ferritest]
145/// async fn my_test(page: Page) {
146///     page.goto("https://example.com", None).await.unwrap();
147/// }
148///
149/// ferridriver_test::main!();
150/// ```
151#[macro_export]
152macro_rules! main {
153  () => {
154    fn main() {
155      $crate::run_harness();
156    }
157  };
158}
159
160/// Entry point called by `main!()`. Parses CLI args, loads config,
161/// discovers tests, and runs them.
162pub fn run_harness() {
163  logging::init_from_env();
164
165  let rt = tokio::runtime::Builder::new_multi_thread()
166    .enable_all()
167    .build()
168    .expect("failed to build tokio runtime");
169
170  let exit_code = rt.block_on(async {
171    let overrides = config::parse_common_cli_args();
172    let config = config::resolve_config(&overrides).unwrap_or_else(|e| {
173      eprintln!("config error: {e}");
174      std::process::exit(1);
175    });
176    let plan = discovery::collect_rust_tests(&config);
177    let mut runner = runner::TestRunner::new(config, overrides);
178    Box::pin(runner.run(plan)).await
179  });
180
181  // Drain any still-running tasks so that child processes spawned via
182  // `tokio::process::Command::kill_on_drop(true)` actually get their `Drop`
183  // impls run — `std::process::exit` below would otherwise abort the process
184  // without destructors, leaving browser zombies.
185  rt.shutdown_timeout(std::time::Duration::from_secs(5));
186
187  std::process::exit(exit_code);
188}
189
190/// Prelude for convenient imports in test files.
191pub mod prelude {
192  pub use ferridriver::{Browser, ContextRef as BrowserContext, Locator, Page};
193
194  pub use crate::context::TestContext;
195  pub use crate::expect::{expect, expect_configured, expect_poll, to_pass};
196  pub use crate::fixture::FixturePool;
197  pub use crate::model::{TestFailure, TestInfo};
198  pub use ferridriver_test_macros::{after_all, after_each, before_all, before_each, ferritest, ferritest_each};
199}