browser_automation_cli/
install.rs1use std::fs;
6use std::io::{self, Write};
7use std::path::{Path, PathBuf};
8
9pub fn get_browsers_dir() -> PathBuf {
13 dirs::home_dir()
14 .unwrap_or_else(|| PathBuf::from("."))
15 .join(".browser-automation-cli")
16 .join("browsers")
17}
18
19pub fn find_installed_chrome() -> Option<PathBuf> {
21 let browsers_dir = get_browsers_dir();
22 let debug = std::env::var("BROWSER_AUTOMATION_CLI_DEBUG").is_ok();
23
24 if debug {
25 let _ = writeln!(
26 io::stderr(),
27 "[chrome-search] home_dir={:?} browsers_dir={}",
28 dirs::home_dir(),
29 browsers_dir.display()
30 );
31 }
32
33 if !browsers_dir.exists() {
34 if debug {
35 let _ = writeln!(io::stderr(), "[chrome-search] browsers_dir does not exist");
36 }
37 return None;
38 }
39
40 let entries = match fs::read_dir(&browsers_dir) {
41 Ok(entries) => entries,
42 Err(e) => {
43 let _ = writeln!(
44 io::stderr(),
45 "Warning: cannot read Chrome cache directory {}: {}",
46 browsers_dir.display(),
47 e
48 );
49 return None;
50 }
51 };
52
53 let mut versions: Vec<_> = entries
54 .filter_map(|e| e.ok())
55 .filter(|e| {
56 let matches = e
57 .file_name()
58 .to_str()
59 .is_some_and(|n| n.starts_with("chrome-"));
60 if debug {
61 let _ = writeln!(
62 io::stderr(),
63 "[chrome-search] entry {:?} matches={}",
64 e.file_name(),
65 matches
66 );
67 }
68 matches
69 })
70 .collect();
71
72 versions.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
73
74 for entry in versions {
75 let dir = entry.path();
76 if let Some(bin) = chrome_binary_in_dir(&dir) {
77 let exists = bin.exists();
78 if debug {
79 let _ = writeln!(
80 io::stderr(),
81 "[chrome-search] candidate {} exists={}",
82 bin.display(),
83 exists
84 );
85 }
86 if exists {
87 return Some(bin);
88 }
89 } else if debug {
90 let _ = writeln!(
91 io::stderr(),
92 "[chrome-search] no binary found in {}",
93 dir.display()
94 );
95 }
96 }
97
98 if debug {
99 let _ = writeln!(io::stderr(), "[chrome-search] no installed Chrome found");
100 }
101 None
102}
103
104fn chrome_binary_in_dir(dir: &Path) -> Option<PathBuf> {
105 #[cfg(target_os = "macos")]
106 {
107 let app =
108 dir.join("Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing");
109 if app.exists() {
110 return Some(app);
111 }
112 let inner = dir.join(
113 "chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing",
114 );
115 if inner.exists() {
116 return Some(inner);
117 }
118 let inner_x64 = dir.join(
119 "chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing",
120 );
121 if inner_x64.exists() {
122 return Some(inner_x64);
123 }
124 None
125 }
126
127 #[cfg(target_os = "linux")]
128 {
129 let bin = dir.join("chrome");
130 if bin.exists() {
131 return Some(bin);
132 }
133 let inner = dir.join("chrome-linux64/chrome");
134 if inner.exists() {
135 return Some(inner);
136 }
137 None
138 }
139
140 #[cfg(target_os = "windows")]
141 {
142 let bin = dir.join("chrome.exe");
143 if bin.exists() {
144 return Some(bin);
145 }
146 let inner = dir.join("chrome-win64/chrome.exe");
147 if inner.exists() {
148 return Some(inner);
149 }
150 None
151 }
152
153 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
154 {
155 let _ = dir;
156 None
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use std::fs;
164
165 #[test]
166 fn get_browsers_dir_ends_with_product_path() {
167 let dir = get_browsers_dir();
168 let s = dir.to_string_lossy();
169 assert!(
170 s.contains("browser-automation-cli") && s.contains("browsers"),
171 "unexpected browsers dir: {s}"
172 );
173 }
174
175 #[test]
176 fn chrome_binary_in_dir_none_for_empty() {
177 let tmp = tempfile::tempdir().unwrap();
178 assert!(chrome_binary_in_dir(tmp.path()).is_none());
179 }
180
181 #[test]
182 #[cfg(target_os = "linux")]
183 fn chrome_binary_in_dir_finds_linux_layout() {
184 let tmp = tempfile::tempdir().unwrap();
185 let nested = tmp.path().join("chrome-linux64");
186 fs::create_dir_all(&nested).unwrap();
187 let bin = nested.join("chrome");
188 fs::write(&bin, b"x").unwrap();
189 assert_eq!(chrome_binary_in_dir(tmp.path()).as_ref(), Some(&bin));
190 }
191}