

















@@ -1,5 +1,6 @@
11import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
22import "../../test-support/browser-security-runtime.mock.js";
3+import type { OpenClawConfig } from "../config/config.js";
34import type { BrowserDispatchResponse } from "./routes/dispatcher.js";
4556vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => {
@@ -28,7 +29,7 @@ function okDispatchResponse(): BrowserDispatchResponse {
2829}
29303031const mocks = vi.hoisted(() => ({
31-loadConfig: vi.fn(() => ({
32+loadConfig: vi.fn<() => OpenClawConfig>(() => ({
3233gateway: {
3334auth: {
3435token: "loopback-token",
@@ -215,6 +216,202 @@ describe("fetchBrowserJson loopback auth", () => {
215216});
216217});
217218219+it("avoids restart-gateway guidance for attachOnly dispatcher timeouts", async () => {
220+mocks.loadConfig.mockReturnValue({
221+browser: {
222+attachOnly: true,
223+defaultProfile: "manual",
224+profiles: {
225+manual: {
226+cdpUrl: "http://127.0.0.1:9222",
227+attachOnly: true,
228+color: "#00AA00",
229+},
230+},
231+},
232+});
233+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP handshake timeout"));
234+235+await expectThrownBrowserFetchError(
236+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=manual"),
237+{
238+contains: [
239+"Chrome CDP handshake timeout",
240+"browser profile is external to OpenClaw",
241+"Restarting the OpenClaw gateway will not launch it",
242+],
243+omits: ["Restart the OpenClaw gateway", "Do NOT retry the browser tool"],
244+},
245+);
246+});
247+248+it("avoids restart-gateway guidance for existing-session dispatcher timeouts", async () => {
249+mocks.loadConfig.mockReturnValue({
250+browser: {
251+defaultProfile: "user",
252+profiles: {
253+user: {
254+driver: "existing-session",
255+attachOnly: true,
256+color: "#00AA00",
257+},
258+},
259+},
260+});
261+mocks.dispatch.mockRejectedValueOnce(new DOMException("operation aborted", "AbortError"));
262+263+await expectThrownBrowserFetchError(() => fetchBrowserJson<{ ok: boolean }>("/tabs"), {
264+contains: [
265+"operation aborted",
266+"browser profile is external to OpenClaw",
267+"Restarting the OpenClaw gateway will not launch it",
268+],
269+omits: ["Restart the OpenClaw gateway", "Do NOT retry the browser tool"],
270+});
271+});
272+273+it("avoids restart-gateway guidance for remote CDP dispatcher timeouts", async () => {
274+mocks.loadConfig.mockReturnValue({
275+browser: {
276+defaultProfile: "remote",
277+profiles: {
278+remote: {
279+cdpUrl: "https://browserless.example/chrome?token=test",
280+color: "#00AA00",
281+},
282+},
283+},
284+});
285+mocks.dispatch.mockRejectedValueOnce(new Error("timed out"));
286+287+await expectThrownBrowserFetchError(
288+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=remote"),
289+{
290+contains: [
291+"timed out",
292+"browser profile is external to OpenClaw",
293+"Restarting the OpenClaw gateway will not launch it",
294+],
295+omits: ["Restart the OpenClaw gateway", "Do NOT retry the browser tool"],
296+},
297+);
298+});
299+300+it("keeps restart-gateway guidance for managed local dispatcher timeouts", async () => {
301+mocks.loadConfig.mockReturnValue({
302+browser: {
303+defaultProfile: "openclaw",
304+profiles: {
305+openclaw: {
306+cdpPort: 18800,
307+color: "#FF4500",
308+},
309+},
310+},
311+});
312+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP handshake timeout"));
313+314+await expectThrownBrowserFetchError(
315+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=openclaw"),
316+{
317+contains: ["Chrome CDP handshake timeout", "Restart the OpenClaw gateway"],
318+omits: ["browser profile is external to OpenClaw", "Do NOT retry the browser tool"],
319+},
320+);
321+});
322+323+it("keeps restart-gateway guidance when dispatcher profile resolution fails", async () => {
324+mocks.loadConfig.mockImplementation(() => {
325+throw new Error("config unavailable");
326+});
327+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP handshake timeout"));
328+329+await expectThrownBrowserFetchError(
330+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=manual"),
331+{
332+contains: ["Chrome CDP handshake timeout", "Restart the OpenClaw gateway"],
333+omits: ["browser profile is external to OpenClaw", "Do NOT retry the browser tool"],
334+},
335+);
336+});
337+338+it("keeps restart-gateway guidance for unknown dispatcher profiles", async () => {
339+mocks.loadConfig.mockReturnValue({
340+browser: {
341+defaultProfile: "openclaw",
342+profiles: {
343+openclaw: {
344+cdpPort: 18800,
345+color: "#FF4500",
346+},
347+},
348+},
349+});
350+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP handshake timeout"));
351+352+await expectThrownBrowserFetchError(
353+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=missing"),
354+{
355+contains: ["Chrome CDP handshake timeout", "Restart the OpenClaw gateway"],
356+omits: ["browser profile is external to OpenClaw", "Do NOT retry the browser tool"],
357+},
358+);
359+});
360+361+it("uses the default external profile when dispatcher request omits profile", async () => {
362+mocks.loadConfig.mockReturnValue({
363+browser: {
364+defaultProfile: "manual",
365+profiles: {
366+manual: {
367+cdpUrl: "http://127.0.0.1:9222",
368+attachOnly: true,
369+color: "#00AA00",
370+},
371+},
372+},
373+});
374+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP handshake timeout"));
375+376+await expectThrownBrowserFetchError(() => fetchBrowserJson<{ ok: boolean }>("/tabs"), {
377+contains: [
378+"Chrome CDP handshake timeout",
379+"browser profile is external to OpenClaw",
380+"Restarting the OpenClaw gateway will not launch it",
381+],
382+omits: ["Restart the OpenClaw gateway", "Do NOT retry the browser tool"],
383+});
384+});
385+386+it("keeps no-retry hint but not restart guidance for persistent external profile failures", async () => {
387+mocks.loadConfig.mockReturnValue({
388+browser: {
389+attachOnly: true,
390+defaultProfile: "manual",
391+profiles: {
392+manual: {
393+cdpUrl: "http://127.0.0.1:9222",
394+attachOnly: true,
395+color: "#00AA00",
396+},
397+},
398+},
399+});
400+mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP connection refused"));
401+402+await expectThrownBrowserFetchError(
403+() => fetchBrowserJson<{ ok: boolean }>("/tabs?profile=manual"),
404+{
405+contains: [
406+"Chrome CDP connection refused",
407+"browser profile is external to OpenClaw",
408+"Do NOT retry the browser tool",
409+],
410+omits: ["Restart the OpenClaw gateway"],
411+},
412+);
413+});
414+218415it("keeps no-retry hint for persistent dispatcher failures", async () => {
219416mocks.dispatch.mockRejectedValueOnce(new Error("Chrome CDP connection refused"));
220417此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。