mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(dsh-plugin-browserskill): stop observation overlay from flashing between frames
Hold the last ready thumbnail until the next one decodes, and swap the img src in place instead of remounting through a placeholder.
This commit is contained in:
@@ -92,6 +92,31 @@ describe("ObservationOverlay", () => {
|
||||
await screen.findByRole("img", { name: "session s1 view" });
|
||||
});
|
||||
|
||||
it("keeps the current frame on stage while the next thumbnail loads", async () => {
|
||||
let resolveNext: ((url: string) => void) | undefined;
|
||||
const h = makeHarness([{ ...BUSY, thumbnailAttachmentId: "att-1" }]);
|
||||
h.loadImage.mockImplementation(async (id: string) => {
|
||||
if (id === "att-2") {
|
||||
return new Promise<string>((resolve) => {
|
||||
resolveNext = resolve;
|
||||
});
|
||||
}
|
||||
return `blob:${id}`;
|
||||
});
|
||||
render(<ObservationOverlay store={h.store} />);
|
||||
const img = await screen.findByRole("img", { name: "session s1 view" });
|
||||
expect(img.getAttribute("src")).toBe("blob:att-1");
|
||||
h.push([{ ...BUSY, thumbnailAttachmentId: "att-2" }]);
|
||||
await waitFor(() => expect(h.loadImage).toHaveBeenCalledWith("att-2"));
|
||||
// Same <img> node, same src — no placeholder flash, no remount/fade.
|
||||
expect(screen.queryByText("waiting for page")).toBeNull();
|
||||
expect(screen.getByRole("img", { name: "session s1 view" })).toBe(img);
|
||||
expect(img.getAttribute("src")).toBe("blob:att-1");
|
||||
resolveNext?.("blob:att-2");
|
||||
await waitFor(() => expect(img.getAttribute("src")).toBe("blob:att-2"));
|
||||
expect(screen.getByRole("img", { name: "session s1 view" })).toBe(img);
|
||||
});
|
||||
|
||||
it("interrupt: disabled while idle, active call posts and shows progress", async () => {
|
||||
const h = makeHarness([{ sessionId: "s1", action: "idle", since: Date.now() }]);
|
||||
render(<ObservationOverlay store={h.store} />);
|
||||
|
||||
@@ -119,7 +119,7 @@ describe("thumbnail blob URL lifecycle", () => {
|
||||
return revoke;
|
||||
}
|
||||
|
||||
it("replacing a frame revokes the old blob URL and drops its entry", async () => {
|
||||
it("holds the last ready frame until the replacement decodes", async () => {
|
||||
const revoke = withRevokeSpy();
|
||||
const { store, esInstances } = harness([{ ...OBS_IDLE, thumbnailAttachmentId: "a1" }]);
|
||||
store.start();
|
||||
@@ -132,11 +132,50 @@ describe("thumbnail blob URL lifecycle", () => {
|
||||
type: "upsert",
|
||||
session: { ...OBS_IDLE, thumbnailAttachmentId: "a2" },
|
||||
});
|
||||
// Old blob stays painted so the overlay does not flash a placeholder.
|
||||
expect(store.getSnapshot().thumbnails["a1"]).toEqual({
|
||||
status: "ready",
|
||||
url: "blob:url-a1",
|
||||
});
|
||||
expect(store.getSnapshot().displayFrames.s1).toEqual({
|
||||
status: "ready",
|
||||
url: "blob:url-a1",
|
||||
});
|
||||
expect(revoke).not.toHaveBeenCalled();
|
||||
store.ensureThumbnail("a2");
|
||||
await vi.waitFor(() =>
|
||||
expect(store.getSnapshot().displayFrames.s1).toEqual({
|
||||
status: "ready",
|
||||
url: "blob:url-a2",
|
||||
}),
|
||||
);
|
||||
expect(store.getSnapshot().thumbnails["a1"]).toBeUndefined();
|
||||
expect(revoke).toHaveBeenCalledWith("blob:url-a1");
|
||||
store.stop();
|
||||
});
|
||||
|
||||
it("keeps the last good frame in displayFrames when the next load fails", async () => {
|
||||
const { store, esInstances, loadImage } = harness([
|
||||
{ ...OBS_IDLE, thumbnailAttachmentId: "a1" },
|
||||
]);
|
||||
store.start();
|
||||
await vi.waitFor(() => expect(store.getSnapshot().sessions).toHaveLength(1));
|
||||
store.ensureThumbnail("a1");
|
||||
await vi.waitFor(() => expect(store.getSnapshot().thumbnails["a1"].status).toBe("ready"));
|
||||
loadImage.mockRejectedValueOnce(new Error("nope"));
|
||||
emit(esInstances[0], {
|
||||
type: "upsert",
|
||||
session: { ...OBS_IDLE, thumbnailAttachmentId: "a2" },
|
||||
});
|
||||
store.ensureThumbnail("a2");
|
||||
await vi.waitFor(() => expect(store.getSnapshot().thumbnails["a2"]?.status).toBe("error"));
|
||||
expect(store.getSnapshot().displayFrames.s1).toEqual({
|
||||
status: "error",
|
||||
url: "blob:url-a1",
|
||||
});
|
||||
store.stop();
|
||||
});
|
||||
|
||||
it("remove and reset revoke the session's tracked frame", async () => {
|
||||
const revoke = withRevokeSpy();
|
||||
const { store, esInstances } = harness([{ ...OBS_IDLE, thumbnailAttachmentId: "a1" }]);
|
||||
|
||||
Reference in New Issue
Block a user