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