5959//!
6060//! ## Features and Dependencies
6161//!
62- //! ### Required Features
62+ //! ### Driver Features
6363//!
64- //! You must enable one of the following features:
64+ //! For compile time only, no specific feature must be provided. However, to use
65+ //! static export at runtime, one of the below features must be enabled.
6566//!
6667//! - `chromedriver`: Use Chrome/Chromium for rendering
6768//! - `geckodriver`: Use Firefox for rendering
@@ -294,6 +295,7 @@ use fantoccini::{wd::Capabilities, Client, ClientBuilder};
294295#[ cfg( not( any( test, feature = "debug" ) ) ) ]
295296use log:: { debug, error, warn} ;
296297use serde:: Serialize ;
298+ #[ cfg( any( feature = "chromedriver" , feature = "geckodriver" ) ) ]
297299use serde_json:: map:: Map as JsonMap ;
298300use urlencoding:: encode;
299301use webdriver:: WebDriver ;
@@ -303,6 +305,9 @@ use crate::template::{image_export_js_script, pdf_export_js_script};
303305mod template;
304306mod webdriver;
305307
308+ #[ cfg( not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ) ]
309+ const DRIVER_FEATURE_REQUIRED : & str = "Static image export at runtime requires enabling either the 'chromedriver' or 'geckodriver' feature." ;
310+
306311/// Supported image formats for static image export.
307312///
308313/// This enum defines all the image formats that can be exported from Plotly
@@ -648,6 +653,7 @@ impl StaticExporterBuilder {
648653 }
649654
650655 /// Create a new WebDriver instance based on the spawn_webdriver flag
656+ #[ cfg( any( feature = "chromedriver" , feature = "geckodriver" ) ) ]
651657 fn create_webdriver ( & self ) -> Result < WebDriver > {
652658 let port = self . webdriver_port ;
653659 let in_async = tokio:: runtime:: Handle :: try_current ( ) . is_ok ( ) ;
@@ -688,16 +694,22 @@ impl StaticExporterBuilder {
688694 /// .expect("Failed to build AsyncStaticExporter");
689695 /// ```
690696 pub fn build_async ( & self ) -> Result < AsyncStaticExporter > {
691- let wd = self . create_webdriver ( ) ?;
692- Ok ( AsyncStaticExporter {
693- webdriver_port : self . webdriver_port ,
694- webdriver_url : self . webdriver_url . clone ( ) ,
695- webdriver : wd,
696- offline_mode : self . offline_mode ,
697- pdf_export_timeout : self . pdf_export_timeout ,
698- webdriver_browser_caps : self . webdriver_browser_caps . clone ( ) ,
699- webdriver_client : None ,
700- } )
697+ #[ cfg( not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ) ]
698+ return Err ( anyhow ! ( DRIVER_FEATURE_REQUIRED ) ) ;
699+
700+ #[ cfg( any( feature = "chromedriver" , feature = "geckodriver" ) ) ]
701+ {
702+ let wd = self . create_webdriver ( ) ?;
703+ Ok ( AsyncStaticExporter {
704+ webdriver_port : self . webdriver_port ,
705+ webdriver_url : self . webdriver_url . clone ( ) ,
706+ webdriver : wd,
707+ offline_mode : self . offline_mode ,
708+ pdf_export_timeout : self . pdf_export_timeout ,
709+ webdriver_browser_caps : self . webdriver_browser_caps . clone ( ) ,
710+ webdriver_client : None ,
711+ } )
712+ }
701713 }
702714}
703715
@@ -927,6 +939,10 @@ pub struct AsyncStaticExporter {
927939 pdf_export_timeout : u32 ,
928940
929941 /// Browser command-line flags (e.g., "--headless", "--no-sandbox")
942+ #[ cfg_attr(
943+ not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ,
944+ allow( dead_code)
945+ ) ]
930946 webdriver_browser_caps : Vec < String > ,
931947
932948 /// Cached WebDriver client for session reuse
@@ -1121,46 +1137,53 @@ impl AsyncStaticExporter {
11211137 }
11221138
11231139 fn build_webdriver_caps ( & self ) -> Result < Capabilities > {
1124- // Define browser capabilities (copied to avoid reordering existing code)
1125- let mut caps = JsonMap :: new ( ) ;
1126- let mut browser_opts = JsonMap :: new ( ) ;
1127- let browser_args = self . webdriver_browser_caps . clone ( ) ;
1128-
1129- browser_opts. insert ( "args" . to_string ( ) , serde_json:: json!( browser_args) ) ;
1130-
1131- // Add Chrome binary capability if BROWSER_PATH is set
1132- #[ cfg( feature = "chromedriver" ) ]
1133- if let Ok ( chrome_path) = std:: env:: var ( "BROWSER_PATH" ) {
1134- browser_opts. insert ( "binary" . to_string ( ) , serde_json:: json!( chrome_path) ) ;
1135- debug ! ( "Added Chrome binary capability: {chrome_path}" ) ;
1136- }
1137- // Add Firefox binary capability if BROWSER_PATH is set
1138- #[ cfg( feature = "geckodriver" ) ]
1139- if let Ok ( firefox_path) = std:: env:: var ( "BROWSER_PATH" ) {
1140- browser_opts. insert ( "binary" . to_string ( ) , serde_json:: json!( firefox_path) ) ;
1141- debug ! ( "Added Firefox binary capability: {firefox_path}" ) ;
1142- }
1143-
1144- // Add Firefox-specific preferences for CI environments
1145- #[ cfg( feature = "geckodriver" ) ]
1140+ #[ cfg( not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ) ]
11461141 {
1147- let prefs = common:: get_firefox_ci_preferences ( ) ;
1148- browser_opts. insert ( "prefs" . to_string ( ) , serde_json:: json!( prefs) ) ;
1149- debug ! ( "Added Firefox preferences for CI compatibility" ) ;
1142+ Err ( anyhow ! ( DRIVER_FEATURE_REQUIRED ) )
11501143 }
1144+ #[ cfg( any( feature = "chromedriver" , feature = "geckodriver" ) ) ]
1145+ {
1146+ // Define browser capabilities (copied to avoid reordering existing code)
1147+ let mut caps = JsonMap :: new ( ) ;
1148+ let mut browser_opts = JsonMap :: new ( ) ;
1149+ let browser_args = self . webdriver_browser_caps . clone ( ) ;
1150+
1151+ browser_opts. insert ( "args" . to_string ( ) , serde_json:: json!( browser_args) ) ;
1152+
1153+ // Add Chrome binary capability if BROWSER_PATH is set
1154+ #[ cfg( feature = "chromedriver" ) ]
1155+ if let Ok ( chrome_path) = std:: env:: var ( "BROWSER_PATH" ) {
1156+ browser_opts. insert ( "binary" . to_string ( ) , serde_json:: json!( chrome_path) ) ;
1157+ debug ! ( "Added Chrome binary capability: {chrome_path}" ) ;
1158+ }
1159+ // Add Firefox binary capability if BROWSER_PATH is set
1160+ #[ cfg( feature = "geckodriver" ) ]
1161+ if let Ok ( firefox_path) = std:: env:: var ( "BROWSER_PATH" ) {
1162+ browser_opts. insert ( "binary" . to_string ( ) , serde_json:: json!( firefox_path) ) ;
1163+ debug ! ( "Added Firefox binary capability: {firefox_path}" ) ;
1164+ }
11511165
1152- caps. insert (
1153- "browserName" . to_string ( ) ,
1154- serde_json:: json!( get_browser_name( ) ) ,
1155- ) ;
1156- caps. insert (
1157- get_options_key ( ) . to_string ( ) ,
1158- serde_json:: json!( browser_opts) ,
1159- ) ;
1166+ // Add Firefox-specific preferences for CI environments
1167+ #[ cfg( feature = "geckodriver" ) ]
1168+ {
1169+ let prefs = common:: get_firefox_ci_preferences ( ) ;
1170+ browser_opts. insert ( "prefs" . to_string ( ) , serde_json:: json!( prefs) ) ;
1171+ debug ! ( "Added Firefox preferences for CI compatibility" ) ;
1172+ }
1173+
1174+ caps. insert (
1175+ "browserName" . to_string ( ) ,
1176+ serde_json:: json!( get_browser_name( ) ) ,
1177+ ) ;
1178+ caps. insert (
1179+ get_options_key ( ) . to_string ( ) ,
1180+ serde_json:: json!( browser_opts) ,
1181+ ) ;
11601182
1161- debug ! ( "WebDriver capabilities: {caps:?}" ) ;
1183+ debug ! ( "WebDriver capabilities: {caps:?}" ) ;
11621184
1163- Ok ( caps)
1185+ Ok ( caps)
1186+ }
11641187 }
11651188
11661189 #[ cfg( target_os = "windows" ) ]
@@ -1334,6 +1357,15 @@ mod tests {
13341357 let _ = env_logger:: try_init ( ) ;
13351358 }
13361359
1360+ #[ test]
1361+ #[ cfg( not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ) ]
1362+ fn build_without_driver_feature_returns_error ( ) {
1363+ match StaticExporterBuilder :: default ( ) . build_async ( ) {
1364+ Err ( e) => assert_eq ! ( e. to_string( ) , DRIVER_FEATURE_REQUIRED ) ,
1365+ Ok ( _) => panic ! ( "expected build to fail without a driver feature" ) ,
1366+ }
1367+ }
1368+
13371369 // Helper to generate unique ports for parallel tests
13381370 #[ cfg( not( feature = "debug" ) ) ]
13391371 fn get_unique_port ( ) -> u32 {
0 commit comments