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:
Peter Steinberger
2026-08-26 13:39:23 -07:00
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";