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