feat(browser): prefer Chrome default + add Brave/Edge fallbacks

Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
Peter Steinberger
2026-01-16 03:24:53 +00:00
parent a0d2a7232e
commit a5d8f89b53
16 changed files with 172 additions and 67 deletions

View File

@@ -5,7 +5,7 @@ import path from "node:path";
import type { ResolvedBrowserConfig } from "./config.js";
export type BrowserExecutable = {
kind: "canary" | "chromium" | "chrome" | "custom";
kind: "brave" | "canary" | "chromium" | "chrome" | "custom" | "edge";
path: string;
};
@@ -28,15 +28,28 @@ function findFirstExecutable(candidates: Array<BrowserExecutable>): BrowserExecu
export function findChromeExecutableMac(): BrowserExecutable | null {
const candidates: Array<BrowserExecutable> = [
{
kind: "canary",
path: "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
kind: "chrome",
path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
{
kind: "canary",
path: path.join(
os.homedir(),
"Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
),
kind: "chrome",
path: path.join(os.homedir(), "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
},
{
kind: "brave",
path: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
},
{
kind: "brave",
path: path.join(os.homedir(), "Applications/Brave Browser.app/Contents/MacOS/Brave Browser"),
},
{
kind: "edge",
path: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
},
{
kind: "edge",
path: path.join(os.homedir(), "Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"),
},
{
kind: "chromium",
@@ -47,12 +60,15 @@ export function findChromeExecutableMac(): BrowserExecutable | null {
path: path.join(os.homedir(), "Applications/Chromium.app/Contents/MacOS/Chromium"),
},
{
kind: "chrome",
path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
kind: "canary",
path: "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
},
{
kind: "chrome",
path: path.join(os.homedir(), "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
kind: "canary",
path: path.join(
os.homedir(),
"Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
),
},
];
@@ -63,10 +79,16 @@ export function findChromeExecutableLinux(): BrowserExecutable | null {
const candidates: Array<BrowserExecutable> = [
{ kind: "chrome", path: "/usr/bin/google-chrome" },
{ kind: "chrome", path: "/usr/bin/google-chrome-stable" },
{ kind: "chrome", path: "/usr/bin/chrome" },
{ kind: "brave", path: "/usr/bin/brave-browser" },
{ kind: "brave", path: "/usr/bin/brave-browser-stable" },
{ kind: "brave", path: "/usr/bin/brave" },
{ kind: "brave", path: "/snap/bin/brave" },
{ kind: "edge", path: "/usr/bin/microsoft-edge" },
{ kind: "edge", path: "/usr/bin/microsoft-edge-stable" },
{ kind: "chromium", path: "/usr/bin/chromium" },
{ kind: "chromium", path: "/usr/bin/chromium-browser" },
{ kind: "chromium", path: "/snap/bin/chromium" },
{ kind: "chrome", path: "/usr/bin/chrome" },
];
return findFirstExecutable(candidates);
@@ -82,20 +104,30 @@ export function findChromeExecutableWindows(): BrowserExecutable | null {
const candidates: Array<BrowserExecutable> = [];
if (localAppData) {
// Chrome Canary (user install)
// Chrome (user install)
candidates.push({
kind: "canary",
path: joinWin(localAppData, "Google", "Chrome SxS", "Application", "chrome.exe"),
kind: "chrome",
path: joinWin(localAppData, "Google", "Chrome", "Application", "chrome.exe"),
});
// Brave (user install)
candidates.push({
kind: "brave",
path: joinWin(localAppData, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
});
// Edge (user install)
candidates.push({
kind: "edge",
path: joinWin(localAppData, "Microsoft", "Edge", "Application", "msedge.exe"),
});
// Chromium (user install)
candidates.push({
kind: "chromium",
path: joinWin(localAppData, "Chromium", "Application", "chrome.exe"),
});
// Chrome (user install)
// Chrome Canary (user install)
candidates.push({
kind: "chrome",
path: joinWin(localAppData, "Google", "Chrome", "Application", "chrome.exe"),
kind: "canary",
path: joinWin(localAppData, "Google", "Chrome SxS", "Application", "chrome.exe"),
});
}
@@ -109,6 +141,26 @@ export function findChromeExecutableWindows(): BrowserExecutable | null {
kind: "chrome",
path: joinWin(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
});
// Brave (system install, 64-bit)
candidates.push({
kind: "brave",
path: joinWin(programFiles, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
});
// Brave (system install, 32-bit on 64-bit Windows)
candidates.push({
kind: "brave",
path: joinWin(programFilesX86, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
});
// Edge (system install, 64-bit)
candidates.push({
kind: "edge",
path: joinWin(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
});
// Edge (system install, 32-bit on 64-bit Windows)
candidates.push({
kind: "edge",
path: joinWin(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
});
return findFirstExecutable(candidates);
}

View File

@@ -128,10 +128,12 @@ describe("browser chrome helpers", () => {
it("picks the first existing Chrome candidate on macOS", () => {
const exists = vi
.spyOn(fs, "existsSync")
.mockImplementation((p) => String(p).includes("Google Chrome Canary"));
.mockImplementation((p) =>
String(p).includes("Google Chrome.app/Contents/MacOS/Google Chrome"),
);
const exe = findChromeExecutableMac();
expect(exe?.kind).toBe("canary");
expect(exe?.path).toMatch(/Google Chrome Canary/);
expect(exe?.kind).toBe("chrome");
expect(exe?.path).toMatch(/Google Chrome\.app/);
exists.mockRestore();
});
@@ -143,12 +145,17 @@ describe("browser chrome helpers", () => {
it("picks the first existing Chrome candidate on Windows", () => {
vi.stubEnv("LOCALAPPDATA", "C:\\Users\\Test\\AppData\\Local");
const exists = vi
.spyOn(fs, "existsSync")
.mockImplementation((p) => String(p).includes("Chrome SxS"));
const exists = vi.spyOn(fs, "existsSync").mockImplementation((p) => {
const pathStr = String(p);
return (
pathStr.includes("Google\\Chrome\\Application\\chrome.exe") ||
pathStr.includes("BraveSoftware\\Brave-Browser\\Application\\brave.exe") ||
pathStr.includes("Microsoft\\Edge\\Application\\msedge.exe")
);
});
const exe = findChromeExecutableWindows();
expect(exe?.kind).toBe("canary");
expect(exe?.path).toMatch(/Chrome SxS/);
expect(exe?.kind).toBe("chrome");
expect(exe?.path).toMatch(/chrome\.exe$/);
exists.mockRestore();
});

View File

@@ -147,7 +147,9 @@ export async function launchClawdChrome(
const exe = resolveBrowserExecutable(resolved);
if (!exe) {
throw new Error("No supported browser found (Chrome/Chromium on macOS, Linux, or Windows).");
throw new Error(
"No supported browser found (Chrome/Brave/Edge/Chromium on macOS, Linux, or Windows).",
);
}
const userDataDir = resolveClawdUserDataDir(profile.name);