mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
test(ui): drop the resurrected duplicate attachment-URL e2e proof (#130343)
#127114 replaced the exact-revoke-count attachment preview test with the invariant-based proof in new-session-page.attachment-url-lifecycle.e2e.test.ts, deleting the old block from new-session-page.prompt-attachments.e2e.test.ts. #124301 branched before that deletion, so its merge resurrected the stale copy and main shipped two tests with the same name. The resurrected copy asserts literal cumulative revokeObjectURL counts. Those counts are not an invariant: on remount the durable IndexedDB draft restore (draft-persistence.ts restoreScope) can re-hydrate the attachment into a fresh id and object URL, then release the copy the in-memory route handoff carried, producing one extra matched createObjectURL/revokeObjectURL pair. Reproduced with stack capture under 6x CDP CPU throttling: revoked reached 3 before the Remove click and 4 after, which is the "expected 4 to be 3" seen in CI. No leak and no double-release: releaseChatAttachmentPayload deletes the payload map entry before revoking, so every URL is created once and revoked once. The surviving lifecycle test asserts exactly that (active URLs, duplicateRevocations and unknownRevocations both zero) and pins the hydration step deterministically, so deleting the duplicate loses no coverage.
This commit is contained in:
committed by
GitHub
parent
057d194372
commit
1932fd66cb
@@ -639,114 +639,6 @@ suite.define(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it("releases pasted image previews after remove, reset, restored removal, and success", async () => {
|
||||
await withNewSessionPage(async (page) => {
|
||||
await page.addInitScript(() => {
|
||||
const createObjectURL = URL.createObjectURL.bind(URL);
|
||||
const revokeObjectURL = URL.revokeObjectURL.bind(URL);
|
||||
const proof = { created: 0, revoked: 0 };
|
||||
(globalThis as unknown as { attachmentUrlProof: typeof proof }).attachmentUrlProof = proof;
|
||||
URL.createObjectURL = (blob: Blob) => {
|
||||
proof.created += 1;
|
||||
return createObjectURL(blob);
|
||||
};
|
||||
URL.revokeObjectURL = (url: string) => {
|
||||
proof.revoked += 1;
|
||||
revokeObjectURL(url);
|
||||
};
|
||||
});
|
||||
await installMockGateway(page, {
|
||||
methodResponses: {
|
||||
"agents.list": {
|
||||
defaultId: "main",
|
||||
mainKey: "main",
|
||||
scope: "agent",
|
||||
agents: [
|
||||
{ id: "main", name: "Main" },
|
||||
{ id: "writer", name: "Writer" },
|
||||
],
|
||||
},
|
||||
"sessions.create": { key: "agent:main:preview-cleanup", runStarted: true },
|
||||
},
|
||||
});
|
||||
const proof = () =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(globalThis as unknown as { attachmentUrlProof: { created: number; revoked: number } })
|
||||
.attachmentUrlProof,
|
||||
);
|
||||
const navigate = (routeId: string, search = "") =>
|
||||
page.evaluate(
|
||||
({ targetRouteId, targetSearch }) => {
|
||||
const app = document.querySelector("openclaw-app") as HTMLElement & {
|
||||
runtime?: {
|
||||
context: {
|
||||
navigate: (routeId: string, options?: { search?: string }) => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!app.runtime) {
|
||||
throw new Error("OpenClaw application runtime is unavailable");
|
||||
}
|
||||
app.runtime.context.navigate(targetRouteId, { search: targetSearch });
|
||||
},
|
||||
{ targetRouteId: routeId, targetSearch: search },
|
||||
);
|
||||
await page.goto(`${suite.server.baseUrl}new`);
|
||||
const composer = page.locator(".new-session-page__message");
|
||||
|
||||
await pastePng(composer);
|
||||
await page.getByRole("img", { name: "pixel.png" }).waitFor();
|
||||
await page.getByRole("button", { name: "Remove attachment" }).click();
|
||||
await expect.poll(async () => (await proof()).revoked).toBe(1);
|
||||
|
||||
await pastePng(composer);
|
||||
await page.getByRole("img", { name: "pixel.png" }).waitFor();
|
||||
const agentDropdown = page.locator(".new-session-page__select--agent wa-dropdown");
|
||||
await page.locator(".new-session-page__select--agent .agent-select__trigger").click();
|
||||
await expect
|
||||
.poll(() =>
|
||||
agentDropdown.evaluate((dropdown) => (dropdown as HTMLElement & { open: boolean }).open),
|
||||
)
|
||||
.toBe(true);
|
||||
await navigate("new-session", "?agent=main&catalog=missing");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
document.querySelector(".new-session-page__select--agent wa-dropdown") as
|
||||
| (HTMLElement & { open: boolean })
|
||||
| null
|
||||
)?.open ?? false,
|
||||
),
|
||||
)
|
||||
.toBe(false);
|
||||
await expect.poll(() => page.locator(".chat-attachment-thumb").count()).toBe(0);
|
||||
await expect.poll(async () => (await proof()).revoked).toBe(2);
|
||||
|
||||
await navigate("new-session");
|
||||
await composer.waitFor();
|
||||
await page.getByRole("img", { name: "pixel.png" }).waitFor();
|
||||
await navigate("chat");
|
||||
await page.waitForURL((url) => url.pathname.endsWith("/chat"));
|
||||
await expect.poll(async () => (await proof()).revoked).toBe(2);
|
||||
|
||||
await navigate("new-session");
|
||||
await composer.waitFor();
|
||||
await expect.poll(() => page.locator(".chat-attachment-thumb").count()).toBe(1);
|
||||
await page.getByRole("button", { name: "Remove attachment" }).click();
|
||||
await expect.poll(async () => (await proof()).revoked).toBe(3);
|
||||
await pastePng(composer);
|
||||
await page.getByRole("img", { name: "pixel.png" }).waitFor();
|
||||
await page.getByRole("button", { name: "Start session" }).click();
|
||||
await page.waitForURL(
|
||||
(url) => url.pathname === controlUiSessionPath("agent:main:preview-cleanup"),
|
||||
);
|
||||
await expect.poll(async () => await proof()).toEqual({ created: 4, revoked: 4 });
|
||||
});
|
||||
});
|
||||
|
||||
it("locks the submitted draft until creation settles and restores it after failure", async () => {
|
||||
await withNewSessionPage(async (page) => {
|
||||
const sessionKey = "agent:main:locked-new-session-draft";
|
||||
|
||||
Reference in New Issue
Block a user