























@@ -0,0 +1,139 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+3+const { getUnhandledRejectionHandlers, registerUnhandledRejectionHandlerMock, resetHandlers } =
4+vi.hoisted(() => {
5+let handlers: Array<(reason: unknown) => boolean> = [];
6+return {
7+getUnhandledRejectionHandlers: () => handlers,
8+registerUnhandledRejectionHandlerMock: vi.fn((handler: (reason: unknown) => boolean) => {
9+handlers.push(handler);
10+return () => {
11+handlers = handlers.filter((candidate) => candidate !== handler);
12+};
13+}),
14+resetHandlers: () => {
15+handlers = [];
16+},
17+};
18+});
19+20+const {
21+ ensureExtensionRelayForProfilesMock,
22+ getPwAiModuleMock,
23+ isPwAiLoadedMock,
24+ startTrackedBrowserTabCleanupTimerMock,
25+ stopKnownBrowserProfilesMock,
26+ trackedTabCleanupMock,
27+} = vi.hoisted(() => {
28+const trackedTabCleanupMock = vi.fn();
29+return {
30+ensureExtensionRelayForProfilesMock: vi.fn(async () => {}),
31+getPwAiModuleMock: vi.fn(),
32+isPwAiLoadedMock: vi.fn(() => false),
33+startTrackedBrowserTabCleanupTimerMock: vi.fn(() => trackedTabCleanupMock),
34+stopKnownBrowserProfilesMock: vi.fn(async () => {}),
35+ trackedTabCleanupMock,
36+};
37+});
38+39+vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
40+registerUnhandledRejectionHandler: registerUnhandledRejectionHandlerMock,
41+}));
42+43+vi.mock("./server-lifecycle.js", () => ({
44+ensureExtensionRelayForProfiles: ensureExtensionRelayForProfilesMock,
45+stopKnownBrowserProfiles: stopKnownBrowserProfilesMock,
46+}));
47+48+vi.mock("./session-tab-cleanup.js", () => ({
49+startTrackedBrowserTabCleanupTimer: startTrackedBrowserTabCleanupTimerMock,
50+}));
51+52+vi.mock("./pw-ai-state.js", () => ({
53+isPwAiLoaded: isPwAiLoadedMock,
54+}));
55+56+vi.mock("./pw-ai-module.js", () => ({
57+getPwAiModule: getPwAiModuleMock,
58+}));
59+60+const { createBrowserRuntimeState, stopBrowserRuntime } = await import("./runtime-lifecycle.js");
61+const { isPlaywrightDialogRaceUnhandledRejection } = await import("./unhandled-rejections.js");
62+63+beforeEach(() => {
64+resetHandlers();
65+registerUnhandledRejectionHandlerMock.mockClear();
66+ensureExtensionRelayForProfilesMock.mockClear();
67+getPwAiModuleMock.mockClear();
68+isPwAiLoadedMock.mockReset().mockReturnValue(false);
69+startTrackedBrowserTabCleanupTimerMock.mockClear();
70+stopKnownBrowserProfilesMock.mockClear();
71+trackedTabCleanupMock.mockClear();
72+});
73+74+describe("browser unhandled rejection lifecycle", () => {
75+it("matches direct and nested Playwright dialog-race protocol errors", () => {
76+const direct = Object.assign(
77+new Error("Protocol error (Page.handleJavaScriptDialog): No dialog is showing"),
78+{ method: "Page.handleJavaScriptDialog" },
79+);
80+const nested = new Error("browser action failed", {
81+cause: Object.assign(new Error("No dialog is showing"), {
82+method: "Page.handleJavaScriptDialog",
83+}),
84+});
85+const wrapped = {
86+error: new Error("Protocol error (Dialog.handleJavaScriptDialog): No dialog is showing"),
87+};
88+89+expect(isPlaywrightDialogRaceUnhandledRejection(direct)).toBe(true);
90+expect(isPlaywrightDialogRaceUnhandledRejection(nested)).toBe(true);
91+expect(isPlaywrightDialogRaceUnhandledRejection(wrapped)).toBe(true);
92+});
93+94+it("keeps non-dialog and non-race Playwright errors unhandled", () => {
95+expect(
96+isPlaywrightDialogRaceUnhandledRejection(
97+Object.assign(new Error("No dialog is showing"), { method: "Page.navigate" }),
98+),
99+).toBe(false);
100+expect(
101+isPlaywrightDialogRaceUnhandledRejection(
102+new Error("Protocol error (Page.handleJavaScriptDialog): Target closed"),
103+),
104+).toBe(false);
105+expect(isPlaywrightDialogRaceUnhandledRejection(new Error("No dialog is showing"))).toBe(false);
106+});
107+108+it("registers during startup and unregisters during shutdown", async () => {
109+stopKnownBrowserProfilesMock.mockImplementationOnce(async () => {
110+expect(getUnhandledRejectionHandlers()).toHaveLength(1);
111+});
112+const state = await createBrowserRuntimeState({
113+resolved: { profiles: {} } as never,
114+port: 18791,
115+onWarn: vi.fn(),
116+});
117+118+expect(registerUnhandledRejectionHandlerMock).toHaveBeenCalledTimes(1);
119+expect(getUnhandledRejectionHandlers()).toHaveLength(1);
120+expect(
121+getUnhandledRejectionHandlers()[0]?.(
122+new Error("Protocol error (Page.handleJavaScriptDialog): No dialog is showing"),
123+),
124+).toBe(true);
125+126+const clearState = vi.fn();
127+await stopBrowserRuntime({
128+current: state,
129+getState: () => state,
130+ clearState,
131+onWarn: vi.fn(),
132+});
133+134+expect(trackedTabCleanupMock).toHaveBeenCalledTimes(1);
135+expect(stopKnownBrowserProfilesMock).toHaveBeenCalledTimes(1);
136+expect(clearState).toHaveBeenCalledTimes(1);
137+expect(getUnhandledRejectionHandlers()).toEqual([]);
138+});
139+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。