viewpoint_test/lib.rs
1//! # Viewpoint Test - Browser Testing Framework
2//!
3//! Test framework for `Viewpoint` browser automation, providing assertion APIs,
4//! test harness setup, and convenient test macros for browser-based E2E tests.
5//!
6//! ## Features
7//!
8//! - **Test Harness**: Automatic browser, context, and page setup/teardown
9//! - **Locator Assertions**: Wait-based assertions for elements (`expect(locator)`)
10//! - **Page Assertions**: Assertions for page state (`expect_page(page)`)
11//! - **Soft Assertions**: Collect multiple failures without stopping the test
12//! - **Fixture Scoping**: Reuse browser/context across tests for performance
13//! - **Test Macro**: Convenient `#[viewpoint::test]` attribute for test setup
14//!
15//! ## Quick Start
16//!
17//! The easiest way to write tests is using the `TestHarness`:
18//!
19//! ```no_run
20//! use viewpoint_test::{TestHarness, expect};
21//!
22//! #[tokio::test]
23//! async fn my_test() -> Result<(), Box<dyn std::error::Error>> {
24//! let harness = TestHarness::new().await?;
25//! let page = harness.page();
26//!
27//! page.goto("https://example.com").goto().await?;
28//!
29//! // Assert element is visible
30//! expect(page.locator("h1")).to_be_visible().await?;
31//!
32//! // Assert element has text
33//! expect(page.locator("h1")).to_have_text("Example Domain").await?;
34//!
35//! Ok(()) // harness drops and cleans up automatically
36//! }
37//! ```
38//!
39//! ## Using the Test Macro
40//!
41//! For even more convenience, use the `#[viewpoint::test]` attribute:
42//!
43//! ```text
44//! use viewpoint_test::test;
45//! use viewpoint_core::Page;
46//!
47//! #[viewpoint_test::test]
48//! async fn my_test(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
49//! page.goto("https://example.com").goto().await?;
50//! // page is automatically set up and cleaned up
51//! Ok(())
52//! }
53//! ```
54//!
55//! ## Locator Assertions
56//!
57//! The [`expect()`] function creates assertions for locators that automatically wait:
58//!
59//! ```ignore
60//! use viewpoint_test::{TestHarness, expect};
61//!
62//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
63//! # let harness = TestHarness::new().await?;
64//! # let page = harness.page();
65//! // Visibility assertions
66//! expect(page.locator("button")).to_be_visible().await?;
67//! expect(page.locator(".hidden")).to_be_hidden().await?;
68//!
69//! // Text content assertions
70//! expect(page.locator("h1")).to_have_text("Welcome").await?;
71//! expect(page.locator("p")).to_contain_text("Hello").await?;
72//!
73//! // Input value assertions
74//! expect(page.locator("input")).to_have_value("initial value").await?;
75//! expect(page.locator("input")).to_be_empty().await?;
76//!
77//! // State assertions
78//! expect(page.locator("button")).to_be_enabled().await?;
79//! expect(page.locator("input")).to_be_disabled().await?;
80//! expect(page.locator("input[type=checkbox]")).to_be_checked().await?;
81//!
82//! // Attribute assertions
83//! expect(page.locator("a")).to_have_attribute("href", "/about").await?;
84//!
85//! // CSS assertions
86//! expect(page.locator("div")).to_have_css("display", "flex").await?;
87//!
88//! // Count assertions
89//! expect(page.locator("li")).to_have_count(5).await?;
90//!
91//! // Focus assertions
92//! expect(page.locator("input")).to_be_focused().await?;
93//!
94//! // Negation with `.not()`
95//! expect(page.locator("button")).not().to_be_disabled().await?;
96//! # Ok(())
97//! # }
98//! ```
99//!
100//! ## Page Assertions
101//!
102//! The [`expect_page`] function creates assertions for page state:
103//!
104//! ```ignore
105//! use viewpoint_test::{TestHarness, expect_page};
106//!
107//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
108//! # let harness = TestHarness::new().await?;
109//! # let page = harness.page();
110//! // URL assertions
111//! expect_page(page).to_have_url("https://example.com/").await?;
112//! expect_page(page).to_have_url_matching(r"example\.com").await?;
113//!
114//! // Title assertions
115//! expect_page(page).to_have_title("Example Domain").await?;
116//! expect_page(page).to_have_title_matching(r"Example.*").await?;
117//!
118//! // Negation
119//! expect_page(page).not().to_have_url("https://wrong.com/").await?;
120//! # Ok(())
121//! # }
122//! ```
123//!
124//! ## Soft Assertions
125//!
126//! Soft assertions collect failures without stopping the test, useful for checking
127//! multiple conditions:
128//!
129//! ```ignore
130//! use viewpoint_test::{TestHarness, SoftAssertions};
131//!
132//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
133//! # let harness = TestHarness::new().await?;
134//! # let page = harness.page();
135//! let soft = SoftAssertions::new();
136//!
137//! // These won't fail immediately
138//! soft.expect(page.locator("h1")).to_have_text("Welcome").await;
139//! soft.expect(page.locator("nav")).to_be_visible().await;
140//! soft.expect(page.locator("footer")).to_be_visible().await;
141//!
142//! // Assert all at once - fails if any assertion failed
143//! soft.assert_all()?;
144//! # Ok(())
145//! # }
146//! ```
147//!
148//! ## Fixture Scoping
149//!
150//! The harness supports different scoping levels for performance optimization:
151//!
152//! ```no_run
153//! use viewpoint_test::TestHarness;
154//! use viewpoint_core::Browser;
155//!
156//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
157//! // Test-scoped (default): new browser per test
158//! let harness = TestHarness::new().await?;
159//!
160//! // Module-scoped: reuse browser, fresh context/page per test
161//! # let shared_browser = Browser::launch().headless(true).launch().await?;
162//! let harness = TestHarness::from_browser(&shared_browser).await?;
163//!
164//! // Context-scoped: reuse context, fresh page per test
165//! # let context = shared_browser.new_context().await?;
166//! let harness = TestHarness::from_context(&context).await?;
167//! # Ok(())
168//! # }
169//! ```
170//!
171//! ## Test Configuration
172//!
173//! Configure tests with [`TestConfig`]:
174//!
175//! ```no_run
176//! use viewpoint_test::{TestHarness, TestConfig};
177//! use std::time::Duration;
178//!
179//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
180//! let config = TestConfig::builder()
181//! .headless(true)
182//! .timeout(Duration::from_secs(60))
183//! .build();
184//!
185//! let harness = TestHarness::with_config(config).await?;
186//! # Ok(())
187//! # }
188//! ```
189//!
190//! ## Error Handling
191//!
192//! Assertions return [`AssertionError`] on failure with detailed messages:
193//!
194//! ```ignore
195//! use viewpoint_test::{TestHarness, expect, AssertionError};
196//!
197//! # async fn example() -> Result<(), AssertionError> {
198//! # let harness = TestHarness::new().await.unwrap();
199//! # let page = harness.page();
200//! // This will fail with a descriptive message if the element doesn't have the text
201//! expect(page.locator("h1"))
202//! .to_have_text("Expected Title")
203//! .await?;
204//! # Ok(())
205//! # }
206//! ```
207
208mod config;
209mod error;
210pub mod expect;
211mod harness;
212
213pub use config::{TestConfig, TestConfigBuilder};
214pub use error::{AssertionError, TestError};
215pub use expect::{
216 Expectable, LocatorAssertions, PageAssertions, SoftAssertionError, SoftAssertions,
217 SoftLocatorAssertions, SoftPageAssertions, expect, expect_page,
218};
219pub use harness::TestHarness;
220
221// Re-export the test macro for convenience
222pub use viewpoint_test_macros::test;
223
224// Re-export core types for convenience
225pub use viewpoint_core::{Browser, BrowserContext, CoreError, DocumentLoadState, Page};