firefox_webdriver/driver/
builder.rs1use std::path::PathBuf;
25
26use crate::error::{Error, Result};
27
28use super::core::Driver;
29use super::profile::ExtensionSource;
30
31#[derive(Debug, Default, Clone)]
39pub struct DriverBuilder {
40 binary: Option<PathBuf>,
42 extension: Option<ExtensionSource>,
44}
45
46impl DriverBuilder {
51 #[inline]
53 #[must_use]
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 #[inline]
64 #[must_use]
65 pub fn binary(mut self, path: impl Into<PathBuf>) -> Self {
66 self.binary = Some(path.into());
67 self
68 }
69
70 #[inline]
79 #[must_use]
80 pub fn extension(mut self, path: impl Into<PathBuf>) -> Self {
81 let path = path.into();
82 self.extension = Some(ExtensionSource::from(path));
83 self
84 }
85
86 #[inline]
94 #[must_use]
95 pub fn extension_base64(mut self, data: impl Into<String>) -> Self {
96 self.extension = Some(ExtensionSource::base64(data));
97 self
98 }
99
100 #[inline]
106 #[must_use]
107 pub fn extension_source(mut self, source: ExtensionSource) -> Self {
108 self.extension = Some(source);
109 self
110 }
111
112 pub async fn build(self) -> Result<Driver> {
123 let binary = self.validate_binary()?;
124 let extension = self.validate_extension()?;
125
126 Driver::new(binary, extension).await
127 }
128}
129
130impl DriverBuilder {
135 fn validate_binary(&self) -> Result<PathBuf> {
137 let binary = self.binary.clone().ok_or_else(|| {
138 Error::config(
139 "Firefox binary path is required. Use .binary() to set it.\n\
140 Example: Driver::builder().binary(\"/usr/bin/firefox\")",
141 )
142 })?;
143
144 if !binary.exists() {
145 return Err(Error::firefox_not_found(&binary));
146 }
147
148 Ok(binary)
149 }
150
151 fn validate_extension(&self) -> Result<ExtensionSource> {
153 let extension = self.extension.clone().ok_or_else(|| {
154 Error::config(
155 "Extension is required. Use .extension() or .extension_base64() to set it.\n\
156 Example: Driver::builder().extension(\"./extension\")",
157 )
158 })?;
159
160 if let Some(path) = extension.path()
162 && !path.exists()
163 {
164 return Err(Error::config(format!(
165 "Extension not found at: {}\n\
166 Ensure the extension directory or .xpi file exists.",
167 path.display()
168 )));
169 }
170
171 Ok(extension)
172 }
173}
174
175#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_new_creates_empty_builder() {
185 let builder = DriverBuilder::new();
186 assert!(builder.binary.is_none());
187 assert!(builder.extension.is_none());
188 }
189
190 #[test]
191 fn test_default_creates_empty_builder() {
192 let builder = DriverBuilder::default();
193 assert!(builder.binary.is_none());
194 assert!(builder.extension.is_none());
195 }
196
197 #[test]
198 fn test_binary_sets_path() {
199 let builder = DriverBuilder::new().binary("/usr/bin/firefox");
200 assert_eq!(builder.binary, Some(PathBuf::from("/usr/bin/firefox")));
201 }
202
203 #[test]
204 fn test_extension_sets_source() {
205 let builder = DriverBuilder::new().extension("./extension");
206 assert!(builder.extension.is_some());
207 }
208
209 #[test]
210 fn test_extension_base64_sets_source() {
211 let builder = DriverBuilder::new().extension_base64("UEsDBBQ...");
212 assert!(builder.extension.is_some());
213
214 if let Some(ExtensionSource::Base64(data)) = builder.extension {
215 assert_eq!(data, "UEsDBBQ...");
216 } else {
217 panic!("Expected Base64 extension source");
218 }
219 }
220
221 #[test]
222 fn test_extension_source_sets_directly() {
223 let source = ExtensionSource::packed("./ext.xpi");
224 let builder = DriverBuilder::new().extension_source(source.clone());
225 assert_eq!(builder.extension, Some(source));
226 }
227
228 #[test]
229 fn test_build_fails_without_binary() {
230 let rt = tokio::runtime::Runtime::new().unwrap();
231 let result = rt.block_on(DriverBuilder::new().extension("./extension").build());
232 assert!(result.is_err());
233
234 let err = result.unwrap_err();
235 assert!(err.to_string().contains("binary"));
236 }
237
238 #[test]
239 fn test_build_fails_without_extension() {
240 let rt = tokio::runtime::Runtime::new().unwrap();
241 let result = rt.block_on(DriverBuilder::new().binary("/bin/sh").build());
242 assert!(result.is_err());
243
244 let err = result.unwrap_err();
245 assert!(err.to_string().contains("Extension"));
246 }
247
248 #[test]
249 fn test_build_fails_with_nonexistent_binary() {
250 let rt = tokio::runtime::Runtime::new().unwrap();
251 let result = rt.block_on(
252 DriverBuilder::new()
253 .binary("/nonexistent/firefox")
254 .extension_base64("data")
255 .build(),
256 );
257
258 assert!(result.is_err());
259 }
260
261 #[test]
262 fn test_builder_is_clone() {
263 let builder = DriverBuilder::new().binary("/usr/bin/firefox");
264 let cloned = builder.clone();
265 assert_eq!(builder.binary, cloned.binary);
266 }
267}