diff --git a/peekaboo-cli/Sources/peekaboo/ApplicationFinder.swift b/peekaboo-cli/Sources/peekaboo/ApplicationFinder.swift index 22591d4..e7a84c3 100644 --- a/peekaboo-cli/Sources/peekaboo/ApplicationFinder.swift +++ b/peekaboo-cli/Sources/peekaboo/ApplicationFinder.swift @@ -25,7 +25,10 @@ class ApplicationFinder { } // Find all possible matches - let matches = findAllMatches(for: identifier, in: runningApps) + let allMatches = findAllMatches(for: identifier, in: runningApps) + + // Filter out browser helpers for common browser searches + let matches = filterBrowserHelpers(matches: allMatches, identifier: identifier) // Get unique matches let uniqueMatches = removeDuplicateMatches(from: matches) @@ -174,7 +177,15 @@ class ApplicationFinder { runningApps: [NSRunningApplication] ) throws(ApplicationError) -> NSRunningApplication { guard !matches.isEmpty else { - Logger.shared.error("No applications found matching: \(identifier)") + // Provide browser-specific error messages + let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"] + let lowerIdentifier = identifier.lowercased() + + if browserIdentifiers.contains(lowerIdentifier) { + Logger.shared.error("\(identifier.capitalized) browser is not running or not found") + } else { + Logger.shared.error("No applications found matching: \(identifier)") + } // Find similar app names using fuzzy matching let suggestions = findSimilarApplications(identifier: identifier, from: runningApps) @@ -308,6 +319,51 @@ class ApplicationFinder { return count } + + private static func filterBrowserHelpers(matches: [AppMatch], identifier: String) -> [AppMatch] { + // Define common browser identifiers that should filter out helpers + let browserIdentifiers = ["chrome", "safari", "firefox", "edge", "brave", "arc", "opera"] + let lowerIdentifier = identifier.lowercased() + + // Check if the search is for a common browser + guard browserIdentifiers.contains(lowerIdentifier) else { + return matches // No filtering for non-browser searches + } + + Logger.shared.debug("Filtering browser helpers for '\(identifier)' search") + + // Filter out helper processes for browser searches + let filteredMatches = matches.filter { match in + guard let appName = match.app.localizedName?.lowercased() else { return true } + + // Exclude obvious helper processes + let isHelper = appName.contains("helper") || + appName.contains("renderer") || + appName.contains("utility") || + appName.contains("plugin") || + appName.contains("service") || + appName.contains("crashpad") || + appName.contains("gpu") || + appName.contains("background") + + if isHelper { + Logger.shared.debug("Filtering out helper process: \(appName)") + return false + } + + return true + } + + // If we filtered out all matches, return the original matches to avoid "not found" errors + // But log a warning about this case + if filteredMatches.isEmpty && !matches.isEmpty { + Logger.shared.debug("All matches were filtered as helpers, returning original matches to avoid 'not found' error") + return matches + } + + Logger.shared.debug("After browser helper filtering: \(filteredMatches.count) matches remaining") + return filteredMatches + } } enum ApplicationError: Error { diff --git a/peekaboo-cli/Tests/peekabooTests/ApplicationFinderTests.swift b/peekaboo-cli/Tests/peekabooTests/ApplicationFinderTests.swift index 740f1ff..29379c7 100644 --- a/peekaboo-cli/Tests/peekabooTests/ApplicationFinderTests.swift +++ b/peekaboo-cli/Tests/peekabooTests/ApplicationFinderTests.swift @@ -406,4 +406,85 @@ struct ApplicationFinderEdgeCaseTests { } } } + + // MARK: - Browser Helper Filtering Tests + + @Test("Browser helper filtering for Chrome searches", .tags(.browserFiltering)) + func browserHelperFilteringChrome() { + // Test that Chrome helper processes are filtered out when searching for "chrome" + // Note: This test documents expected behavior even when Chrome isn't running + + do { + let result = try ApplicationFinder.findApplication(identifier: "chrome") + // If found, should be the main Chrome app, not a helper + if let appName = result.localizedName?.lowercased() { + #expect(!appName.contains("helper")) + #expect(!appName.contains("renderer")) + #expect(!appName.contains("utility")) + #expect(appName.contains("chrome")) + } + } catch { + // Chrome might not be running, which is okay for this test + // The important thing is that the filtering logic exists + print("Chrome not found, which is acceptable for browser helper filtering test") + } + } + + @Test("Browser helper filtering for Safari searches", .tags(.browserFiltering)) + func browserHelperFilteringSafari() { + // Test that Safari helper processes are filtered out when searching for "safari" + + do { + let result = try ApplicationFinder.findApplication(identifier: "safari") + // If found, should be the main Safari app, not a helper + if let appName = result.localizedName?.lowercased() { + #expect(!appName.contains("helper")) + #expect(!appName.contains("renderer")) + #expect(!appName.contains("utility")) + #expect(appName.contains("safari")) + } + } catch { + // Safari might not be running, which is okay for this test + print("Safari not found, which is acceptable for browser helper filtering test") + } + } + + @Test("Non-browser searches should not filter helpers", .tags(.browserFiltering)) + func nonBrowserSearchesPreserveHelpers() { + // Test that non-browser searches still find helper processes if that's what's being searched for + + // This tests that helper filtering only applies to browser identifiers + let nonBrowserIdentifiers = ["finder", "textedit", "calculator", "activity monitor"] + + for identifier in nonBrowserIdentifiers { + do { + let result = try ApplicationFinder.findApplication(identifier: identifier) + // Should find the app regardless of whether it's a "helper" (for non-browsers) + #expect(result.localizedName != nil) + } catch { + // App might not be running, which is fine for this test + continue + } + } + } + + @Test("Browser error messages are more specific", .tags(.browserFiltering)) + func browserSpecificErrorMessages() { + // Test that browser-specific error messages are provided when browsers aren't found + + let browserIdentifiers = ["chrome", "firefox", "edge"] + + for browser in browserIdentifiers { + do { + _ = try ApplicationFinder.findApplication(identifier: browser) + // If browser is found, test passes + } catch let ApplicationError.notFound(identifier) { + // Should get a not found error with the identifier + #expect(identifier == browser) + // The error logging would contain browser-specific message, but we can't test that here + } catch { + Issue.record("Expected ApplicationError.notFound for browser '\(browser)', got \(error)") + } + } + } } diff --git a/peekaboo-cli/Tests/peekabooTests/TestTags.swift b/peekaboo-cli/Tests/peekabooTests/TestTags.swift index 700ae05..483c043 100644 --- a/peekaboo-cli/Tests/peekabooTests/TestTags.swift +++ b/peekaboo-cli/Tests/peekabooTests/TestTags.swift @@ -14,6 +14,7 @@ extension Tag { @Tag static var performance: Self @Tag static var concurrency: Self @Tag static var memory: Self + @Tag static var browserFiltering: Self // Local-only test tags @Tag static var localOnly: Self