perf: slim browser test imports

This commit is contained in:
Peter Steinberger
2026-04-24 15:42:46 +01:00
parent d3d9b5738f
commit a3ae336c51
7 changed files with 101 additions and 25 deletions

View File

@@ -4,9 +4,12 @@ import { type WebSocket, WebSocketServer } from "ws";
import { SsrFBlockedError } from "../infra/net/ssrf.js";
import { rawDataToString } from "../infra/ws.js";
import "../../test-support/browser-security-runtime.mock.js";
import { isDirectCdpWebSocketEndpoint, isWebSocketUrl } from "./cdp.helpers.js";
import {
isDirectCdpWebSocketEndpoint,
isWebSocketUrl,
parseBrowserHttpUrl as parseHttpUrl,
} from "./cdp.helpers.js";
import { createTargetViaCdp, evaluateJavaScript, normalizeCdpWsUrl, snapshotAria } from "./cdp.js";
import { parseHttpUrl } from "./config.js";
import {
BROWSER_ENDPOINT_BLOCKED_MESSAGE,
BROWSER_NAVIGATION_BLOCKED_MESSAGE,

View File

@@ -42,7 +42,12 @@ vi.mock("./runtime-lifecycle.js", () => ({
stopBrowserRuntime: vi.fn(async () => {}),
}));
vi.mock("./server-context.js", () => ({
createBrowserRouteContext: vi.fn(),
}));
const { startBrowserControlServiceFromConfig } = await import("../control-service.js");
vi.doUnmock("./server-context.js");
describe("startBrowserControlServiceFromConfig", () => {
beforeEach(() => {

View File

@@ -3,7 +3,6 @@ import {
matchesHostnameAllowlist,
normalizeHostname,
} from "openclaw/plugin-sdk/browser-security-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { hasProxyEnvConfigured } from "../infra/net/proxy-env.js";
import {
isPrivateNetworkAllowedByPolicy,
@@ -20,6 +19,10 @@ function isAllowedNonNetworkNavigationUrl(parsed: URL): boolean {
return SAFE_NON_NETWORK_URLS.has(parsed.href);
}
function normalizeNavigationUrl(url: string): string {
return url.trim();
}
export class InvalidBrowserNavigationUrlError extends Error {
constructor(message: string) {
super(message);
@@ -85,7 +88,7 @@ export async function assertBrowserNavigationAllowed(
lookupFn?: LookupFn;
} & BrowserNavigationPolicyOptions,
): Promise<void> {
const rawUrl = normalizeOptionalString(opts.url) ?? "";
const rawUrl = normalizeNavigationUrl(opts.url);
if (!rawUrl) {
throw new InvalidBrowserNavigationUrlError("url is required");
}
@@ -150,7 +153,7 @@ export async function assertBrowserNavigationResultAllowed(
lookupFn?: LookupFn;
} & BrowserNavigationPolicyOptions,
): Promise<void> {
const rawUrl = normalizeOptionalString(opts.url) ?? "";
const rawUrl = normalizeNavigationUrl(opts.url);
if (!rawUrl) {
return;
}

View File

@@ -1,5 +1,3 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
const BROWSER_SERVICE_RATE_LIMIT_MESSAGE =
"Browser service rate limit reached. " +
"Wait for the current session to complete, or retry later.";
@@ -17,7 +15,7 @@ function isBrowserbaseUrl(url: string): boolean {
return false;
}
try {
const host = normalizeLowercaseStringOrEmpty(new URL(url).hostname);
const host = new URL(url).hostname.trim().toLowerCase();
return host === "browserbase.com" || host.endsWith(".browserbase.com");
} catch {
return false;

View File

@@ -2,8 +2,11 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { withBrowserFetchPreconnect } from "../../test-fetch.js";
import * as cdpModule from "./cdp.js";
import { BrowserCdpEndpointBlockedError } from "./errors.js";
import { createBrowserRouteContext } from "./server-context.js";
import { makeState, originalFetch } from "./server-context.remote-tab-ops.harness.js";
import {
createTestBrowserRouteContext,
makeState,
originalFetch,
} from "./server-context.remote-tab-ops.harness.js";
afterEach(() => {
globalThis.fetch = originalFetch;
@@ -40,7 +43,7 @@ describe("browser server-context loopback direct WebSocket profiles", () => {
cdpUrl: "ws://127.0.0.1:18800/devtools/browser/SESSION?token=abc",
color: "#FF4500",
};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const opened = await openclaw.openTab("about:blank");
@@ -85,7 +88,7 @@ describe("browser server-context loopback direct WebSocket profiles", () => {
cdpUrl: "ws://127.0.0.1:18800/devtools/browser/SESSION?token=abc",
color: "#FF4500",
};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
await openclaw.focusTab("T1");
@@ -133,7 +136,7 @@ describe("browser server-context loopback direct WebSocket profiles", () => {
cdpUrl: "wss://127.0.0.1:18800/cdp?token=abc",
color: "#FF4500",
};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const tabs = await openclaw.listTabs();
@@ -158,7 +161,7 @@ describe("browser server-context loopback direct WebSocket profiles", () => {
cdpUrl: "ws://10.0.0.42:18800/devtools/browser/SESSION?token=abc",
color: "#FF4500",
};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
await expect(openclaw.listTabs()).rejects.toBeInstanceOf(BrowserCdpEndpointBlockedError);

View File

@@ -1,7 +1,10 @@
import { vi } from "vitest";
import { withBrowserFetchPreconnect } from "../../test-fetch.js";
import type { BrowserServerState } from "./server-context.js";
import { createBrowserRouteContext } from "./server-context.js";
import { resolveCdpControlPolicy } from "./cdp-reachability-policy.js";
import type { ResolvedBrowserProfile } from "./config.js";
import { createProfileSelectionOps } from "./server-context.selection.js";
import { createProfileTabOps } from "./server-context.tab-ops.js";
import type { BrowserServerState, ProfileRuntimeState } from "./server-context.types.js";
export const originalFetch = globalThis.fetch;
@@ -48,11 +51,72 @@ export function makeUnexpectedFetchMock() {
});
}
function resolveProfileForTest(
state: BrowserServerState,
profileName: string,
): ResolvedBrowserProfile {
const rawProfile = state.resolved.profiles[profileName] ?? {};
const cdpPort =
typeof rawProfile.cdpPort === "number"
? rawProfile.cdpPort
: profileName === "remote"
? 9222
: state.resolved.cdpPortRangeStart;
const cdpUrl =
typeof rawProfile.cdpUrl === "string"
? rawProfile.cdpUrl
: `${state.resolved.cdpProtocol}://${state.resolved.cdpHost}:${cdpPort}`;
const parsed = new URL(cdpUrl.replace(/^ws/i, "http"));
const cdpHost = parsed.hostname;
const cdpIsLoopback = cdpHost === "localhost" || cdpHost === "127.0.0.1" || cdpHost === "::1";
return {
name: profileName,
cdpPort,
cdpUrl,
cdpHost,
cdpIsLoopback,
color: rawProfile.color ?? state.resolved.color,
driver: rawProfile.driver === "existing-session" ? "existing-session" : "openclaw",
attachOnly: rawProfile.attachOnly ?? state.resolved.attachOnly,
userDataDir: rawProfile.userDataDir,
};
}
export function createTestBrowserRouteContext(opts: { getState: () => BrowserServerState }) {
const forProfile = (profileName?: string) => {
const state = opts.getState();
const profile = resolveProfileForTest(state, profileName ?? state.resolved.defaultProfile);
const getProfileState = (): ProfileRuntimeState => {
let profileState = state.profiles.get(profile.name);
if (!profileState) {
profileState = { profile, running: null, lastTargetId: null, reconcile: null };
state.profiles.set(profile.name, profileState);
}
return profileState;
};
const tabOps = createProfileTabOps({
profile,
state: () => state,
getProfileState,
});
const selectionOps = createProfileSelectionOps({
profile,
getProfileState,
getCdpControlPolicy: () => resolveCdpControlPolicy(profile, state.resolved.ssrfPolicy),
ensureBrowserAvailable: async () => {},
listTabs: tabOps.listTabs,
openTab: tabOps.openTab,
});
return { profile, ...tabOps, ...selectionOps };
};
return { forProfile };
}
export function createRemoteRouteHarness(fetchMock?: (url: unknown) => Promise<Response>) {
const activeFetchMock = fetchMock ?? makeUnexpectedFetchMock();
global.fetch = withBrowserFetchPreconnect(activeFetchMock);
const state = makeState("remote");
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
return { state, remote: ctx.forProfile("remote"), fetchMock: activeFetchMock };
}

View File

@@ -9,8 +9,8 @@ import "./server-context.chrome-test-harness.js";
import * as cdpHelpersModule from "./cdp.helpers.js";
import * as cdpModule from "./cdp.js";
import { InvalidBrowserNavigationUrlError } from "./navigation-guard.js";
import { createBrowserRouteContext } from "./server-context.js";
import {
createTestBrowserRouteContext,
makeManagedTabsWithNew,
makeState,
originalFetch,
@@ -85,7 +85,7 @@ async function openManagedTabWithRunningProfile(params: {
global.fetch = withBrowserFetchPreconnect(params.fetchMock);
const state = makeState("openclaw");
seedRunningProfileState(state);
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
return await openclaw.openTab(params.url ?? "http://127.0.0.1:3009");
}
@@ -117,7 +117,7 @@ describe("browser server-context tab selection state", () => {
global.fetch = withBrowserFetchPreconnect(fetchMock);
const state = makeState("openclaw");
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const opened = await openclaw.openTab("http://127.0.0.1:8080");
@@ -162,7 +162,7 @@ describe("browser server-context tab selection state", () => {
global.fetch = withBrowserFetchPreconnect(fetchMock);
const state = makeState("openclaw");
state.resolved.ssrfPolicy = {};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const selected = await openclaw.ensureTabAvailable();
@@ -228,7 +228,7 @@ describe("browser server-context tab selection state", () => {
global.fetch = withBrowserFetchPreconnect(fetchMock);
const state = makeState("openclaw");
seedRunningProfileState(state);
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const opened = await openclaw.openTab("http://127.0.0.1:3009");
@@ -248,7 +248,7 @@ describe("browser server-context tab selection state", () => {
global.fetch = withBrowserFetchPreconnect(fetchMock);
const state = makeState("openclaw");
state.resolved.attachOnly = true;
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const opened = await openclaw.openTab("http://127.0.0.1:3009");
@@ -289,7 +289,7 @@ describe("browser server-context tab selection state", () => {
global.fetch = withBrowserFetchPreconnect(fetchMock);
const state = makeState("openclaw");
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
await expect(openclaw.openTab("file:///etc/passwd")).rejects.toBeInstanceOf(
@@ -311,7 +311,7 @@ describe("browser server-context tab selection state", () => {
const state = makeState("openclaw");
state.resolved.ssrfPolicy = {};
const ctx = createBrowserRouteContext({ getState: () => state });
const ctx = createTestBrowserRouteContext({ getState: () => state });
const openclaw = ctx.forProfile("openclaw");
const opened = await openclaw.openTab("https://example.com");