Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/gemini-web/browserSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ export async function openGeminiBrowserSession(
input: OpenGeminiBrowserSessionInput,
): Promise<GeminiBrowserSession> {
const { browserConfig, keepBrowserDefault, purpose, log } = input;
const profileDir =
browserConfig?.manualLoginProfileDir ?? path.join(os.homedir(), ".oracle", "browser-profile");
await mkdir(profileDir, { recursive: true });

const resolvedConfig = resolveBrowserConfig({
...browserConfig,
manualLogin: true,
manualLoginProfileDir: profileDir,
keepBrowser: browserConfig?.keepBrowser ?? keepBrowserDefault,
});
const profileDir = resolveGeminiProfileDir(
resolvedConfig.manualLoginProfileDir,
browserConfig?.manualLoginProfileDir,
process.env.ORACLE_BROWSER_PROFILE_DIR,
);
await mkdir(profileDir, { recursive: true });
const keepBrowser = Boolean(resolvedConfig.keepBrowser);

let port = await readDevToolsPort(profileDir);
Expand Down Expand Up @@ -112,3 +113,16 @@ export async function openGeminiBrowserSession(
close,
};
}

function resolveGeminiProfileDir(
...candidates: Array<string | null | undefined>
): string {
for (const candidate of candidates) {
if (typeof candidate !== "string") continue;
const normalized = candidate.trim();
if (normalized) {
return normalized;
}
}
return path.join(os.homedir(), ".oracle", "browser-profile");
}
158 changes: 158 additions & 0 deletions tests/gemini-web/browserSessionManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

const {
launchChrome,
connectWithNewTab,
closeTab,
readDevToolsPort,
writeDevToolsActivePort,
writeChromePid,
cleanupStaleProfileState,
verifyDevToolsReachable,
} = vi.hoisted(() => ({
launchChrome: vi.fn(),
connectWithNewTab: vi.fn(),
closeTab: vi.fn(async () => undefined),
readDevToolsPort: vi.fn(async () => null),
writeDevToolsActivePort: vi.fn(async () => undefined),
writeChromePid: vi.fn(async () => undefined),
cleanupStaleProfileState: vi.fn(async () => undefined),
verifyDevToolsReachable: vi.fn(async () => ({ ok: false, error: "unreachable" })),
}));

vi.mock("../../src/browser/chromeLifecycle.js", () => ({
launchChrome,
connectWithNewTab,
closeTab,
}));

vi.mock("../../src/browser/profileState.js", () => ({
readDevToolsPort,
writeDevToolsActivePort,
writeChromePid,
cleanupStaleProfileState,
verifyDevToolsReachable,
}));

describe("openGeminiBrowserSession", () => {
const tempDirs: string[] = [];

beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();

launchChrome.mockResolvedValue({
port: 9222,
pid: 12345,
kill: vi.fn(),
});
connectWithNewTab.mockResolvedValue({
targetId: "target-1",
client: {
close: vi.fn(async () => undefined),
},
});
readDevToolsPort.mockResolvedValue(null);
verifyDevToolsReachable.mockResolvedValue({ ok: false, error: "unreachable" });
delete process.env.ORACLE_BROWSER_PROFILE_DIR;
});

afterEach(async () => {
delete process.env.ORACLE_BROWSER_PROFILE_DIR;
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
await rm(dir, { recursive: true, force: true });
}
}
});

it("prefers explicit manualLoginProfileDir over ORACLE_BROWSER_PROFILE_DIR", async () => {
const explicitDir = await mkdtemp(path.join(os.tmpdir(), "oracle-gemini-explicit-"));
const envDir = await mkdtemp(path.join(os.tmpdir(), "oracle-gemini-env-"));
tempDirs.push(explicitDir, envDir);
process.env.ORACLE_BROWSER_PROFILE_DIR = envDir;

const { openGeminiBrowserSession } = await import(
"../../src/gemini-web/browserSessionManager.js"
);

const session = await openGeminiBrowserSession({
browserConfig: { manualLoginProfileDir: explicitDir },
keepBrowserDefault: false,
purpose: "test explicit profile",
log: () => {},
});

expect(session.profileDir).toBe(explicitDir);
expect(launchChrome).toHaveBeenCalledWith(
expect.objectContaining({
manualLogin: true,
manualLoginProfileDir: explicitDir,
}),
explicitDir,
expect.any(Function),
);

await session.close();
});

it("uses ORACLE_BROWSER_PROFILE_DIR when manualLoginProfileDir is not set", async () => {
const envDir = await mkdtemp(path.join(os.tmpdir(), "oracle-gemini-env-"));
tempDirs.push(envDir);
process.env.ORACLE_BROWSER_PROFILE_DIR = envDir;

const { openGeminiBrowserSession } = await import(
"../../src/gemini-web/browserSessionManager.js"
);

const session = await openGeminiBrowserSession({
browserConfig: {},
keepBrowserDefault: false,
purpose: "test env profile",
log: () => {},
});

expect(session.profileDir).toBe(envDir);
expect(launchChrome).toHaveBeenCalledWith(
expect.objectContaining({
manualLogin: true,
manualLoginProfileDir: envDir,
}),
envDir,
expect.any(Function),
);

await session.close();
});

it("ignores blank ORACLE_BROWSER_PROFILE_DIR values", async () => {
process.env.ORACLE_BROWSER_PROFILE_DIR = " ";

const { openGeminiBrowserSession } = await import(
"../../src/gemini-web/browserSessionManager.js"
);

const session = await openGeminiBrowserSession({
browserConfig: {},
keepBrowserDefault: false,
purpose: "test blank env profile",
log: () => {},
});

const expectedDefault = path.join(os.homedir(), ".oracle", "browser-profile");
expect(session.profileDir).toBe(expectedDefault);
expect(launchChrome).toHaveBeenCalledWith(
expect.objectContaining({
manualLogin: true,
}),
expectedDefault,
expect.any(Function),
);

await session.close();
});
});