null_e/cleaners/
browsers_test.rs1use super::{calculate_dir_size, get_mtime, CleanableItem, SafetyLevel};
10use crate::error::Result;
11use std::path::PathBuf;
12
13pub struct TestBrowsersCleaner {
15 home: PathBuf,
16}
17
18impl TestBrowsersCleaner {
19 pub fn new() -> Option<Self> {
21 let home = dirs::home_dir()?;
22 Some(Self { home })
23 }
24
25 pub fn detect(&self) -> Result<Vec<CleanableItem>> {
27 let mut items = Vec::new();
28
29 items.extend(self.detect_playwright()?);
31
32 items.extend(self.detect_cypress()?);
34
35 items.extend(self.detect_puppeteer()?);
37
38 items.extend(self.detect_selenium()?);
40
41 items.extend(self.detect_chrome_testing()?);
43
44 Ok(items)
45 }
46
47 fn detect_playwright(&self) -> Result<Vec<CleanableItem>> {
49 let mut items = Vec::new();
50
51 let playwright_paths = [
53 self.home.join("Library/Caches/ms-playwright"),
55 self.home.join(".cache/ms-playwright"),
57 self.home.join("AppData/Local/ms-playwright"),
59 self.home.join(".local/share/ms-playwright"),
61 ];
62
63 for pw_path in playwright_paths {
64 if !pw_path.exists() {
65 continue;
66 }
67
68 if let Ok(entries) = std::fs::read_dir(&pw_path) {
70 for entry in entries.filter_map(|e| e.ok()) {
71 let path = entry.path();
72 if !path.is_dir() {
73 continue;
74 }
75
76 let name = path.file_name()
77 .map(|n| n.to_string_lossy().to_string())
78 .unwrap_or_else(|| "Unknown".to_string());
79
80 let (browser_name, icon) = if name.contains("chromium") {
82 ("Chromium", "🌐")
83 } else if name.contains("firefox") {
84 ("Firefox", "🦊")
85 } else if name.contains("webkit") {
86 ("WebKit", "🧭")
87 } else {
88 continue;
89 };
90
91 let (size, file_count) = calculate_dir_size(&path)?;
92 if size < 50_000_000 { continue;
94 }
95
96 items.push(CleanableItem {
97 name: format!("Playwright {}", browser_name),
98 category: "Testing".to_string(),
99 subcategory: "Playwright".to_string(),
100 icon,
101 path,
102 size,
103 file_count: Some(file_count),
104 last_modified: get_mtime(&entry.path()),
105 description: "Browser binary for Playwright testing. Will be re-downloaded.",
106 safe_to_delete: SafetyLevel::SafeWithCost,
107 clean_command: Some("npx playwright install --clean".to_string()),
108 });
109 }
110 }
111 }
112
113 Ok(items)
114 }
115
116 fn detect_cypress(&self) -> Result<Vec<CleanableItem>> {
118 let mut items = Vec::new();
119
120 let cypress_paths = [
122 self.home.join(".cache/Cypress"),
124 self.home.join("AppData/Local/Cypress/Cache"),
126 self.home.join("Library/Caches/Cypress"),
128 ];
129
130 for cypress_path in cypress_paths {
131 if !cypress_path.exists() {
132 continue;
133 }
134
135 if let Ok(entries) = std::fs::read_dir(&cypress_path) {
137 for entry in entries.filter_map(|e| e.ok()) {
138 let path = entry.path();
139 if !path.is_dir() {
140 continue;
141 }
142
143 let version = path.file_name()
144 .map(|n| n.to_string_lossy().to_string())
145 .unwrap_or_else(|| "Unknown".to_string());
146
147 let (size, file_count) = calculate_dir_size(&path)?;
148 if size < 100_000_000 { continue;
150 }
151
152 items.push(CleanableItem {
153 name: format!("Cypress v{}", version),
154 category: "Testing".to_string(),
155 subcategory: "Cypress".to_string(),
156 icon: "🌲",
157 path,
158 size,
159 file_count: Some(file_count),
160 last_modified: get_mtime(&entry.path()),
161 description: "Cypress test runner. Will be re-downloaded when needed.",
162 safe_to_delete: SafetyLevel::SafeWithCost,
163 clean_command: Some("npx cypress cache clear".to_string()),
164 });
165 }
166 }
167 }
168
169 Ok(items)
170 }
171
172 fn detect_puppeteer(&self) -> Result<Vec<CleanableItem>> {
174 let mut items = Vec::new();
175
176 let puppeteer_paths = [
178 self.home.join(".cache/puppeteer"),
180 self.home.join("AppData/Local/puppeteer"),
182 self.home.join("Library/Caches/puppeteer"),
184 ];
185
186 for pup_path in puppeteer_paths {
187 if !pup_path.exists() {
188 continue;
189 }
190
191 let (size, file_count) = calculate_dir_size(&pup_path)?;
192 if size < 100_000_000 {
193 continue;
194 }
195
196 items.push(CleanableItem {
197 name: "Puppeteer Browsers".to_string(),
198 category: "Testing".to_string(),
199 subcategory: "Puppeteer".to_string(),
200 icon: "🎭",
201 path: pup_path,
202 size,
203 file_count: Some(file_count),
204 last_modified: None,
205 description: "Puppeteer Chrome/Chromium binary. Will be re-downloaded.",
206 safe_to_delete: SafetyLevel::SafeWithCost,
207 clean_command: None,
208 });
209 }
210
211 Ok(items)
212 }
213
214 fn detect_selenium(&self) -> Result<Vec<CleanableItem>> {
216 let mut items = Vec::new();
217
218 let selenium_paths = [
220 self.home.join(".cache/selenium"),
222 self.home.join(".wdm"),
224 self.home.join("AppData/Local/selenium"),
226 ];
227
228 for sel_path in selenium_paths {
229 if !sel_path.exists() {
230 continue;
231 }
232
233 let (size, file_count) = calculate_dir_size(&sel_path)?;
234 if size < 50_000_000 {
235 continue;
236 }
237
238 items.push(CleanableItem {
239 name: "Selenium WebDriver".to_string(),
240 category: "Testing".to_string(),
241 subcategory: "Selenium".to_string(),
242 icon: "🔧",
243 path: sel_path,
244 size,
245 file_count: Some(file_count),
246 last_modified: None,
247 description: "Selenium browser drivers. Will be re-downloaded.",
248 safe_to_delete: SafetyLevel::SafeWithCost,
249 clean_command: None,
250 });
251 }
252
253 Ok(items)
254 }
255
256 fn detect_chrome_testing(&self) -> Result<Vec<CleanableItem>> {
258 let mut items = Vec::new();
259
260 let chrome_paths = [
262 self.home.join(".cache/chrome-for-testing"),
263 self.home.join("AppData/Local/chrome-for-testing"),
264 ];
265
266 for chrome_path in chrome_paths {
267 if !chrome_path.exists() {
268 continue;
269 }
270
271 let (size, file_count) = calculate_dir_size(&chrome_path)?;
272 if size < 100_000_000 {
273 continue;
274 }
275
276 items.push(CleanableItem {
277 name: "Chrome for Testing".to_string(),
278 category: "Testing".to_string(),
279 subcategory: "Chrome".to_string(),
280 icon: "🌐",
281 path: chrome_path,
282 size,
283 file_count: Some(file_count),
284 last_modified: None,
285 description: "Chrome for Testing binaries. Will be re-downloaded.",
286 safe_to_delete: SafetyLevel::SafeWithCost,
287 clean_command: None,
288 });
289 }
290
291 Ok(items)
292 }
293}