Skip to content

Commit f3bec8f

Browse files
committed
fix flakiness
Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent 6724a78 commit f3bec8f

File tree

3 files changed

+88
-12
lines changed

3 files changed

+88
-12
lines changed

plotly/src/plot.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,12 +1065,10 @@ mod tests {
10651065
assert!(std::fs::remove_file(&dst).is_ok());
10661066
}
10671067

1068-
#[cfg(feature = "plotly_static")]
10691068
// Helper to generate unique ports for parallel tests
1070-
static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444);
1071-
10721069
#[cfg(feature = "plotly_static")]
10731070
fn get_unique_port() -> u32 {
1071+
static PORT_COUNTER: AtomicU32 = AtomicU32::new(5544);
10741072
PORT_COUNTER.fetch_add(1, Ordering::SeqCst)
10751073
}
10761074

plotly_static/src/lib.rs

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,8 +1047,12 @@ impl AsyncStaticExporter {
10471047

10481048
async fn extract(&mut self, html_content: &str, plot: &PlotData<'_>) -> Result<String> {
10491049
let caps = self.build_webdriver_caps()?;
1050-
debug!("Use WebDriver and headless browser to export static plot");
1050+
debug!(
1051+
"Use WebDriver and headless browser to export static plot (offline_mode={}, port={})",
1052+
self.offline_mode, self.webdriver_port
1053+
);
10511054
let webdriver_url = format!("{}:{}", self.webdriver_url, self.webdriver_port);
1055+
debug!("Connecting to WebDriver at {webdriver_url}");
10521056

10531057
// Reuse existing client or create new one
10541058
let client = if let Some(ref client) = self.webdriver_client {
@@ -1079,6 +1083,70 @@ impl AsyncStaticExporter {
10791083
// Open the HTML
10801084
client.goto(&url).await?;
10811085

1086+
// Ensure DOM is ready and required elements/scripts are available (Windows CI race)
1087+
{
1088+
let start = std::time::Instant::now();
1089+
let timeout = std::time::Duration::from_secs(10);
1090+
loop {
1091+
let state = client
1092+
.execute("return document.readyState;", vec![])
1093+
.await
1094+
.unwrap_or_else(|_| serde_json::Value::Null);
1095+
if state.as_str().map(|s| s == "complete").unwrap_or(false) {
1096+
break;
1097+
}
1098+
if start.elapsed() > timeout {
1099+
return Err(anyhow!(
1100+
"Timeout waiting for document.readyState === 'complete'"
1101+
));
1102+
}
1103+
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1104+
}
1105+
}
1106+
1107+
// Wait for Plotly container element
1108+
{
1109+
let start = std::time::Instant::now();
1110+
let timeout = std::time::Duration::from_secs(10);
1111+
loop {
1112+
let has_el = client
1113+
.execute(
1114+
"return !!document.getElementById('plotly-html-element');",
1115+
vec![],
1116+
)
1117+
.await
1118+
.unwrap_or_else(|_| serde_json::Value::Bool(false));
1119+
if has_el.as_bool().unwrap_or(false) {
1120+
break;
1121+
}
1122+
if start.elapsed() > timeout {
1123+
return Err(anyhow!(
1124+
"Timeout waiting for #plotly-html-element to appear in DOM"
1125+
));
1126+
}
1127+
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1128+
}
1129+
}
1130+
1131+
// In online mode, ensure Plotly is loaded
1132+
if !self.offline_mode {
1133+
let start = std::time::Instant::now();
1134+
let timeout = std::time::Duration::from_secs(15);
1135+
loop {
1136+
let has_plotly = client
1137+
.execute("return !!window.Plotly;", vec![])
1138+
.await
1139+
.unwrap_or_else(|_| serde_json::Value::Bool(false));
1140+
if has_plotly.as_bool().unwrap_or(false) {
1141+
break;
1142+
}
1143+
if start.elapsed() > timeout {
1144+
return Err(anyhow!("Timeout waiting for Plotly library to load"));
1145+
}
1146+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1147+
}
1148+
}
1149+
10821150
let (js_script, args) = match plot.format {
10831151
ImageFormat::PDF => {
10841152
// Always use SVG for PDF export
@@ -1191,20 +1259,30 @@ impl AsyncStaticExporter {
11911259

11921260
#[cfg(test)]
11931261
mod tests {
1194-
use std::path::PathBuf;
1195-
use std::sync::atomic::{AtomicU32, Ordering};
1196-
11971262
use super::*;
1263+
use std::path::PathBuf;
11981264

11991265
fn init() {
12001266
let _ = env_logger::try_init();
12011267
}
12021268

12031269
// Helper to generate unique ports for parallel tests
1204-
static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444);
1205-
12061270
fn get_unique_port() -> u32 {
1207-
PORT_COUNTER.fetch_add(1, Ordering::SeqCst)
1271+
use std::sync::atomic::{AtomicU32, Ordering};
1272+
static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444);
1273+
1274+
// Before we used this counter to generate unique ports.
1275+
// >>> PORT_COUNTER.fetch_add(1, Ordering::SeqCst)
1276+
// However, sometimes the webdriver process is not stopped immediately
1277+
// and we get port conflicts.
1278+
// We try to give some time for other webdriver processes to stop so that we don't
1279+
// get port conflicts.
1280+
loop {
1281+
let p = PORT_COUNTER.fetch_add(1, Ordering::SeqCst);
1282+
if !webdriver::WebDriver::is_webdriver_running(p) {
1283+
return p;
1284+
}
1285+
}
12081286
}
12091287

12101288
fn create_test_plot() -> serde_json::Value {
@@ -1370,7 +1448,7 @@ mod tests {
13701448

13711449
let mut exporter = StaticExporterBuilder::default()
13721450
.spawn_webdriver(true)
1373-
.webdriver_port(get_unique_port())
1451+
.webdriver_port(5444)
13741452
.build_async()
13751453
.unwrap();
13761454

plotly_static/src/webdriver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const WEBDRIVER_BIN: &str = "chromedriver";
3232
/// Default WebDriver port
3333
pub(crate) const WEBDRIVER_PORT: u32 = 4444;
3434
/// Default WebDriver URL
35-
pub(crate) const WEBDRIVER_URL: &str = "http://localhost";
35+
pub(crate) const WEBDRIVER_URL: &str = "http://127.0.0.1";
3636

3737
#[cfg(all(feature = "chromedriver", not(target_os = "windows")))]
3838
pub(crate) fn chrome_default_caps() -> Vec<&'static str> {

0 commit comments

Comments
 (0)