mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
perf: slim browser test imports
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user