@@ -1080,6 +1080,59 @@ impl StaticExporter {
10801080 data. split_once ( "," ) . map ( |d| d. 1 . to_string ( ) )
10811081 }
10821082
1083+ /// Get Firefox preferences optimized for CI environments.
1084+ ///
1085+ /// These preferences force software rendering and enable WebGL in headless
1086+ /// mode to work around graphics/WebGL issues in CI environments.
1087+ #[ cfg( feature = "geckodriver" ) ]
1088+ fn get_firefox_ci_preferences ( ) -> serde_json:: Map < String , serde_json:: Value > {
1089+ let mut prefs = serde_json:: Map :: new ( ) ;
1090+
1091+ // Force software rendering and enable WebGL in headless mode
1092+ prefs. insert (
1093+ "layers.acceleration.disabled" . to_string ( ) ,
1094+ serde_json:: json!( true ) ,
1095+ ) ;
1096+ prefs. insert ( "gfx.webrender.all" . to_string ( ) , serde_json:: json!( false ) ) ;
1097+ prefs. insert (
1098+ "gfx.webrender.software" . to_string ( ) ,
1099+ serde_json:: json!( true ) ,
1100+ ) ;
1101+ prefs. insert ( "webgl.disabled" . to_string ( ) , serde_json:: json!( false ) ) ;
1102+ prefs. insert ( "webgl.force-enabled" . to_string ( ) , serde_json:: json!( true ) ) ;
1103+ prefs. insert ( "webgl.enable-webgl2" . to_string ( ) , serde_json:: json!( true ) ) ;
1104+
1105+ // Force software WebGL implementation
1106+ prefs. insert (
1107+ "webgl.software-rendering" . to_string ( ) ,
1108+ serde_json:: json!( true ) ,
1109+ ) ;
1110+ prefs. insert (
1111+ "webgl.software-rendering.force" . to_string ( ) ,
1112+ serde_json:: json!( true ) ,
1113+ ) ;
1114+
1115+ // Disable hardware acceleration completely
1116+ prefs. insert (
1117+ "gfx.canvas.azure.accelerated" . to_string ( ) ,
1118+ serde_json:: json!( false ) ,
1119+ ) ;
1120+ prefs. insert (
1121+ "gfx.canvas.azure.accelerated-layers" . to_string ( ) ,
1122+ serde_json:: json!( false ) ,
1123+ ) ;
1124+ prefs. insert (
1125+ "gfx.content.azure.backends" . to_string ( ) ,
1126+ serde_json:: json!( "cairo" ) ,
1127+ ) ;
1128+
1129+ // Force software rendering for all graphics
1130+ prefs. insert ( "gfx.2d.force-enabled" . to_string ( ) , serde_json:: json!( true ) ) ;
1131+ prefs. insert ( "gfx.2d.force-software" . to_string ( ) , serde_json:: json!( true ) ) ;
1132+
1133+ prefs
1134+ }
1135+
10831136 fn build_webdriver_caps ( & self ) -> Result < Capabilities > {
10841137 // Define browser capabilities
10851138 let mut caps = JsonMap :: new ( ) ;
@@ -1101,6 +1154,14 @@ impl StaticExporter {
11011154 debug ! ( "Added Firefox binary capability: {firefox_path}" ) ;
11021155 }
11031156
1157+ // Add Firefox-specific preferences for CI environments
1158+ #[ cfg( feature = "geckodriver" ) ]
1159+ {
1160+ let prefs = Self :: get_firefox_ci_preferences ( ) ;
1161+ browser_opts. insert ( "prefs" . to_string ( ) , serde_json:: json!( prefs) ) ;
1162+ debug ! ( "Added Firefox preferences for CI compatibility" ) ;
1163+ }
1164+
11041165 caps. insert (
11051166 "browserName" . to_string ( ) ,
11061167 serde_json:: json!( get_browser_name( ) ) ,
@@ -1355,6 +1416,46 @@ mod tests {
13551416 assert ! ( std:: fs:: remove_file( dst. as_path( ) ) . is_ok( ) ) ;
13561417 }
13571418
1419+ /// Adding the test that fails in the CI workflow to get logs about it...
1420+ #[ test]
1421+ fn save_surface_to_png ( ) {
1422+ use crate :: Surface ;
1423+ let mut plot = Plot :: new ( ) ;
1424+ let z_matrix = vec ! [
1425+ vec![ 1.0 , 2.0 , 3.0 ] ,
1426+ vec![ 4.0 , 5.0 , 6.0 ] ,
1427+ vec![ 7.0 , 8.0 , 9.0 ] ,
1428+ ] ;
1429+ let x_unique = vec ! [ 1.0 , 2.0 , 3.0 ] ;
1430+ let y_unique = vec ! [ 4.0 , 5.0 , 6.0 ] ;
1431+ let surface = Surface :: new ( z_matrix)
1432+ . x ( x_unique)
1433+ . y ( y_unique)
1434+ . name ( "Surface" ) ;
1435+
1436+ plot. add_trace ( surface) ;
1437+ let dst = PathBuf :: from ( "example.png" ) ;
1438+ let mut exporter = plotly_static:: StaticExporterBuilder :: default ( )
1439+ . webdriver_port ( get_unique_port ( ) )
1440+ . build ( )
1441+ . unwrap ( ) ;
1442+
1443+ assert ! ( !plot
1444+ . to_base64_with_exporter( & mut exporter, ImageFormat :: PNG , 1024 , 680 , 1.0 )
1445+ . unwrap( )
1446+ . is_empty( ) ) ;
1447+
1448+ plot. write_image_with_exporter ( & mut exporter, & dst, ImageFormat :: PNG , 800 , 600 , 1.0 )
1449+ . unwrap ( ) ;
1450+ assert ! ( dst. exists( ) ) ;
1451+
1452+ let metadata = std:: fs:: metadata ( & dst) . expect ( "Could not retrieve file metadata" ) ;
1453+ let file_size = metadata. len ( ) ;
1454+ assert ! ( file_size > 0 , ) ;
1455+ // assert!(std::fs::remove_file(&dst).is_ok());
1456+ // assert!(!dst.exists());
1457+ }
1458+
13581459 #[ test]
13591460 #[ cfg( feature = "chromedriver" ) ]
13601461 // Skip this test for geckodriver as it doesn't support multiple concurrent
0 commit comments