Add Chrome for Testing support for reliable WebDriver automation

- Add setup script (scripts/setup-chrome-for-testing.sh) that downloads
  matching Chrome and ChromeDriver versions from Google's CDN
- Add chrome_binary config option to specify custom Chrome binary path
- Update ChromeDriver to support custom binary via with_port_headless_and_binary()
- Update README with Chrome for Testing setup instructions
- Update config.example.toml with chrome_binary documentation

Chrome for Testing is Google's dedicated browser for automated testing
that guarantees version compatibility with ChromeDriver, avoiding the
common 'version mismatch' errors when Chrome auto-updates.
This commit is contained in:
Dhanji R. Prasanna
2025-12-15 17:02:30 +11:00
parent d142cdfffe
commit 81cba42c8d
6 changed files with 165 additions and 3 deletions

View File

@@ -19,8 +19,18 @@ impl ChromeDriver {
Self::with_port_headless(9515).await
}
/// Create a new ChromeDriver instance with Chrome for Testing binary
pub async fn new_headless_with_binary(chrome_binary: &str) -> Result<Self> {
Self::with_port_headless_and_binary(9515, Some(chrome_binary)).await
}
/// Create a new ChromeDriver instance with a custom port in headless mode
pub async fn with_port_headless(port: u16) -> Result<Self> {
Self::with_port_headless_and_binary(port, None).await
}
/// Create a new ChromeDriver instance with a custom port and optional Chrome binary path
pub async fn with_port_headless_and_binary(port: u16, chrome_binary: Option<&str>) -> Result<Self> {
let url = format!("http://localhost:{}", port);
let mut caps = serde_json::Map::new();
@@ -41,6 +51,12 @@ impl ChromeDriver {
Value::String("--window-size=1920,1080".to_string()),
]),
);
// If a custom Chrome binary is specified, use it
if let Some(binary) = chrome_binary {
chrome_options.insert("binary".to_string(), Value::String(binary.to_string()));
}
caps.insert(
"goog:chromeOptions".to_string(),
Value::Object(chrome_options),

View File

@@ -132,6 +132,10 @@ pub struct WebDriverConfig {
#[serde(default)]
pub chrome_port: u16,
#[serde(default)]
/// Optional path to Chrome binary (e.g., Chrome for Testing)
/// If not set, ChromeDriver will use the default Chrome installation
pub chrome_binary: Option<String>,
#[serde(default)]
pub browser: WebDriverBrowser,
}
@@ -152,6 +156,7 @@ impl Default for WebDriverConfig {
enabled: true,
safari_port: 4444,
chrome_port: 9515,
chrome_binary: None,
browser: WebDriverBrowser::ChromeHeadless,
}
}

View File

@@ -5605,8 +5605,13 @@ impl<W: UiWriter> Agent<W> {
// Wait before each attempt (200ms between retries, total max ~2s)
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
// Try to connect to ChromeDriver in headless mode
match g3_computer_control::ChromeDriver::with_port_headless(port).await {
// Try to connect to ChromeDriver in headless mode (with optional custom binary)
let driver_result = match &self.config.webdriver.chrome_binary {
Some(binary) => g3_computer_control::ChromeDriver::with_port_headless_and_binary(port, Some(binary)).await,
None => g3_computer_control::ChromeDriver::with_port_headless(port).await,
};
match driver_result {
Ok(driver) => {
let session = std::sync::Arc::new(tokio::sync::Mutex::new(WebDriverSession::Chrome(driver)));
*self.webdriver_session.write().await = Some(session);