Skip to main content

null_e/cleaners/
browsers_test.rs

1//! Testing browser cleanup module
2//!
3//! Handles cleanup of browser binaries used for testing:
4//! - Playwright browsers
5//! - Cypress browser cache
6//! - Puppeteer browsers
7//! - Selenium WebDriver cache
8
9use super::{calculate_dir_size, get_mtime, CleanableItem, SafetyLevel};
10use crate::error::Result;
11use std::path::PathBuf;
12
13/// Testing browsers cleaner
14pub struct TestBrowsersCleaner {
15    home: PathBuf,
16}
17
18impl TestBrowsersCleaner {
19    /// Create a new test browsers cleaner
20    pub fn new() -> Option<Self> {
21        let home = dirs::home_dir()?;
22        Some(Self { home })
23    }
24
25    /// Detect all cleanable items
26    pub fn detect(&self) -> Result<Vec<CleanableItem>> {
27        let mut items = Vec::new();
28
29        // Playwright
30        items.extend(self.detect_playwright()?);
31
32        // Cypress
33        items.extend(self.detect_cypress()?);
34
35        // Puppeteer
36        items.extend(self.detect_puppeteer()?);
37
38        // Selenium
39        items.extend(self.detect_selenium()?);
40
41        // Chrome for Testing
42        items.extend(self.detect_chrome_testing()?);
43
44        Ok(items)
45    }
46
47    /// Detect Playwright browsers
48    fn detect_playwright(&self) -> Result<Vec<CleanableItem>> {
49        let mut items = Vec::new();
50
51        // Playwright stores browsers in different locations per OS
52        let playwright_paths = [
53            // macOS
54            self.home.join("Library/Caches/ms-playwright"),
55            // Linux
56            self.home.join(".cache/ms-playwright"),
57            // Windows
58            self.home.join("AppData/Local/ms-playwright"),
59            // Alternative Linux
60            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            // Check for browser directories
69            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                    // Determine browser type
81                    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 { // Skip small
93                        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    /// Detect Cypress cache
117    fn detect_cypress(&self) -> Result<Vec<CleanableItem>> {
118        let mut items = Vec::new();
119
120        // Cypress cache locations
121        let cypress_paths = [
122            // macOS/Linux
123            self.home.join(".cache/Cypress"),
124            // Windows
125            self.home.join("AppData/Local/Cypress/Cache"),
126            // Alternative
127            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            // Check for version directories
136            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 { // Cypress is usually 400MB+
149                        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    /// Detect Puppeteer browsers
173    fn detect_puppeteer(&self) -> Result<Vec<CleanableItem>> {
174        let mut items = Vec::new();
175
176        // Puppeteer cache locations
177        let puppeteer_paths = [
178            // Default location
179            self.home.join(".cache/puppeteer"),
180            // Windows
181            self.home.join("AppData/Local/puppeteer"),
182            // macOS
183            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    /// Detect Selenium WebDriver cache
215    fn detect_selenium(&self) -> Result<Vec<CleanableItem>> {
216        let mut items = Vec::new();
217
218        // Selenium WebDriver cache locations
219        let selenium_paths = [
220            // selenium-manager cache
221            self.home.join(".cache/selenium"),
222            // WebDriver Manager (Python)
223            self.home.join(".wdm"),
224            // Windows
225            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    /// Detect Chrome for Testing
257    fn detect_chrome_testing(&self) -> Result<Vec<CleanableItem>> {
258        let mut items = Vec::new();
259
260        // Chrome for Testing (new official test binaries)
261        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}