mirror of https://github.com/openclaw/openclaw.git
test: cover browser cleanup for cron and subagents (#60146) (thanks @BrianWang1990)
This commit is contained in:
parent
e697838899
commit
3b09b58c5d
|
|
@ -153,6 +153,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Browser/host inspection: keep static Chrome inspection helpers out of the activated browser runtime so `openclaw doctor browser` and related checks do not eagerly load the bundled browser plugin. (#59471) Thanks @vincentkoc.
|
||||
- Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like `ws://localhost.:...` rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.
|
||||
- Browser/attach-only profiles: disconnect cached Playwright CDP sessions when stopping attach-only or remote CDP profiles, while still reporting never-started local managed profiles as not stopped. (#60097) Thanks @pedh.
|
||||
- Browser/task cleanup: close tracked browser tabs and best-effort browser processes when cron-isolated agents and subagents finish, so background browser runs stop leaking orphaned sessions. (#60146) Thanks @BrianWang1990.
|
||||
- Agents/output sanitization: strip namespaced `antml:thinking` blocks from user-visible text so Anthropic-style internal monologue tags do not leak into replies. (#59550) Thanks @obviyus.
|
||||
- Kimi Coding/tools: normalize Anthropic tool payloads into the OpenAI-compatible function shape Kimi Coding expects so tool calls stop losing required arguments. (#59440) Thanks @obviyus.
|
||||
- Image tool/paths: resolve relative local media paths against the agent `workspaceDir` instead of `process.cwd()` so inputs like `inbox/receipt.png` pass the local-path allowlist reliably. (#57222) Thanks Priyansh Gupta.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ const lifecycleEventMocks = vi.hoisted(() => ({
|
|||
emitSessionLifecycleEvent: vi.fn(),
|
||||
}));
|
||||
|
||||
const browserMaintenanceMocks = vi.hoisted(() => ({
|
||||
closeTrackedBrowserTabsForSessions: vi.fn(async () => 0),
|
||||
}));
|
||||
|
||||
vi.mock("../tasks/task-executor.js", () => ({
|
||||
completeTaskRunByRunId: taskExecutorMocks.completeTaskRunByRunId,
|
||||
failTaskRunByRunId: taskExecutorMocks.failTaskRunByRunId,
|
||||
|
|
@ -27,6 +31,10 @@ vi.mock("../sessions/session-lifecycle-events.js", () => ({
|
|||
emitSessionLifecycleEvent: lifecycleEventMocks.emitSessionLifecycleEvent,
|
||||
}));
|
||||
|
||||
vi.mock("../plugin-sdk/browser-maintenance.js", () => ({
|
||||
closeTrackedBrowserTabsForSessions: browserMaintenanceMocks.closeTrackedBrowserTabsForSessions,
|
||||
}));
|
||||
|
||||
vi.mock("./subagent-registry-helpers.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./subagent-registry-helpers.js")>(
|
||||
"./subagent-registry-helpers.js",
|
||||
|
|
@ -58,6 +66,7 @@ describe("subagent registry lifecycle hardening", () => {
|
|||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
browserMaintenanceMocks.closeTrackedBrowserTabsForSessions.mockClear();
|
||||
mod = await import("./subagent-registry-lifecycle.js");
|
||||
});
|
||||
|
||||
|
|
@ -164,4 +173,86 @@ describe("subagent registry lifecycle hardening", () => {
|
|||
expect(entry.cleanupCompletedAt).toBeTypeOf("number");
|
||||
expect(persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cleans up tracked browser sessions before subagent cleanup flow", async () => {
|
||||
const persist = vi.fn();
|
||||
const entry = createRunEntry({
|
||||
expectsCompletionMessage: false,
|
||||
});
|
||||
const runSubagentAnnounceFlow = vi.fn(async () => true);
|
||||
|
||||
const controller = mod.createSubagentRegistryLifecycleController({
|
||||
runs: new Map([[entry.runId, entry]]),
|
||||
resumedRuns: new Set(),
|
||||
subagentAnnounceTimeoutMs: 1_000,
|
||||
persist,
|
||||
clearPendingLifecycleError: vi.fn(),
|
||||
countPendingDescendantRuns: () => 0,
|
||||
suppressAnnounceForSteerRestart: () => false,
|
||||
shouldEmitEndedHookForRun: () => false,
|
||||
emitSubagentEndedHookForRun: vi.fn(async () => {}),
|
||||
notifyContextEngineSubagentEnded: vi.fn(async () => {}),
|
||||
resumeSubagentRun: vi.fn(),
|
||||
captureSubagentCompletionReply: vi.fn(async () => "final completion reply"),
|
||||
runSubagentAnnounceFlow,
|
||||
warn: vi.fn(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
controller.completeSubagentRun({
|
||||
runId: entry.runId,
|
||||
endedAt: 4_000,
|
||||
outcome: { status: "ok" },
|
||||
reason: SUBAGENT_ENDED_REASON_COMPLETE,
|
||||
triggerCleanup: true,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(browserMaintenanceMocks.closeTrackedBrowserTabsForSessions).toHaveBeenCalledWith({
|
||||
sessionKeys: [entry.childSessionKey],
|
||||
onWarn: expect.any(Function),
|
||||
});
|
||||
expect(runSubagentAnnounceFlow).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
childSessionKey: entry.childSessionKey,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips browser cleanup when steer restart suppresses cleanup flow", async () => {
|
||||
const entry = createRunEntry({
|
||||
expectsCompletionMessage: false,
|
||||
});
|
||||
const runSubagentAnnounceFlow = vi.fn(async () => true);
|
||||
|
||||
const controller = mod.createSubagentRegistryLifecycleController({
|
||||
runs: new Map([[entry.runId, entry]]),
|
||||
resumedRuns: new Set(),
|
||||
subagentAnnounceTimeoutMs: 1_000,
|
||||
persist: vi.fn(),
|
||||
clearPendingLifecycleError: vi.fn(),
|
||||
countPendingDescendantRuns: () => 0,
|
||||
suppressAnnounceForSteerRestart: () => true,
|
||||
shouldEmitEndedHookForRun: () => false,
|
||||
emitSubagentEndedHookForRun: vi.fn(async () => {}),
|
||||
notifyContextEngineSubagentEnded: vi.fn(async () => {}),
|
||||
resumeSubagentRun: vi.fn(),
|
||||
captureSubagentCompletionReply: vi.fn(async () => "final completion reply"),
|
||||
runSubagentAnnounceFlow,
|
||||
warn: vi.fn(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
controller.completeSubagentRun({
|
||||
runId: entry.runId,
|
||||
endedAt: 4_000,
|
||||
outcome: { status: "ok" },
|
||||
reason: SUBAGENT_ENDED_REASON_COMPLETE,
|
||||
triggerCleanup: true,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(browserMaintenanceMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();
|
||||
expect(runSubagentAnnounceFlow).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ const {
|
|||
loadConfigMock,
|
||||
fetchWithSsrFGuardMock,
|
||||
runCronIsolatedAgentTurnMock,
|
||||
closeTrackedBrowserTabsForSessionsMock,
|
||||
} = vi.hoisted(() => ({
|
||||
enqueueSystemEventMock: vi.fn(),
|
||||
requestHeartbeatNowMock: vi.fn(),
|
||||
loadConfigMock: vi.fn(),
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
runCronIsolatedAgentTurnMock: vi.fn(async () => ({ status: "ok" as const, summary: "ok" })),
|
||||
closeTrackedBrowserTabsForSessionsMock: vi.fn(async () => 0),
|
||||
}));
|
||||
|
||||
function enqueueSystemEvent(...args: unknown[]) {
|
||||
|
|
@ -59,6 +61,10 @@ vi.mock("../cron/isolated-agent.js", () => ({
|
|||
runCronIsolatedAgentTurn: runCronIsolatedAgentTurnMock,
|
||||
}));
|
||||
|
||||
vi.mock("../plugin-sdk/browser-maintenance.js", () => ({
|
||||
closeTrackedBrowserTabsForSessions: closeTrackedBrowserTabsForSessionsMock,
|
||||
}));
|
||||
|
||||
import { buildGatewayCronService } from "./server-cron.js";
|
||||
|
||||
function createCronConfig(name: string): OpenClawConfig {
|
||||
|
|
@ -80,6 +86,7 @@ describe("buildGatewayCronService", () => {
|
|||
loadConfigMock.mockClear();
|
||||
fetchWithSsrFGuardMock.mockClear();
|
||||
runCronIsolatedAgentTurnMock.mockClear();
|
||||
closeTrackedBrowserTabsForSessionsMock.mockClear();
|
||||
});
|
||||
|
||||
it("routes main-target jobs to the scoped session for enqueue + wake", async () => {
|
||||
|
|
@ -200,6 +207,10 @@ describe("buildGatewayCronService", () => {
|
|||
sessionKey: "project-alpha-monitor",
|
||||
}),
|
||||
);
|
||||
expect(closeTrackedBrowserTabsForSessionsMock).toHaveBeenCalledWith({
|
||||
sessionKeys: ["project-alpha-monitor"],
|
||||
onWarn: expect.any(Function),
|
||||
});
|
||||
} finally {
|
||||
state.cron.stop();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue