Files
overlord/Overlord-Server/test/e2e/remotedesktop-canvas-presentation.e2e.ts
T
2026-07-30 15:12:16 -05:00

122 lines
3.9 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { login } from "./helpers";
test("Canvas presents only the latest decoded video frame per browser refresh", async ({ page }) => {
await login(page);
await page.addInitScript(() => {
const counters = { decoded: 0, drawn: 0, closed: 0 };
Object.defineProperty(window, "__rdPresentationCounters", { value: counters, configurable: true });
class FakeWebSocket extends EventTarget {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
readonly CONNECTING = 0;
readonly OPEN = 1;
readonly CLOSING = 2;
readonly CLOSED = 3;
readyState = FakeWebSocket.OPEN;
bufferedAmount = 0;
binaryType: BinaryType = "blob";
constructor(url: string | URL) {
super();
const isRemoteDesktopControl = new URL(String(url), window.location.href).pathname.endsWith("/rd/ws");
if (isRemoteDesktopControl) {
Object.defineProperty(window, "__rdSocket", { value: this, configurable: true });
}
queueMicrotask(() => this.dispatchEvent(new Event("open")));
}
send() {}
close() {
this.readyState = FakeWebSocket.CLOSED;
this.dispatchEvent(new CloseEvent("close"));
}
}
class FakeVideoDecoder extends EventTarget {
static async isConfigSupported(config: VideoDecoderConfig) {
return { supported: true, config };
}
decodeQueueSize = 0;
private output: (frame: VideoFrame) => void;
constructor(init: VideoDecoderInit) {
super();
this.output = init.output;
}
configure() {}
decode() {
counters.decoded += 1;
const frame = {
displayWidth: 1920,
displayHeight: 1080,
codedWidth: 1920,
codedHeight: 1080,
close() {
counters.closed += 1;
},
} as unknown as VideoFrame;
this.output(frame);
}
close() {}
}
class FakeEncodedVideoChunk {
constructor(_init: EncodedVideoChunkInit) {}
}
const originalGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function (contextId: string, ...args: unknown[]) {
if (this.id === "frameCanvas" && contextId === "2d") {
return {
drawImage() {
counters.drawn += 1;
},
putImageData() {},
} as unknown as CanvasRenderingContext2D;
}
return originalGetContext.call(this, contextId as "2d", ...args as []) as CanvasRenderingContext2D;
} as typeof HTMLCanvasElement.prototype.getContext;
Object.defineProperty(window, "WebSocket", { value: FakeWebSocket, configurable: true });
Object.defineProperty(window, "VideoDecoder", { value: FakeVideoDecoder, configurable: true });
Object.defineProperty(window, "EncodedVideoChunk", { value: FakeEncodedVideoChunk, configurable: true });
});
await page.goto("/remotedesktop?clientId=canvas-presentation-test");
await page.evaluate(async () => {
const packet = new Uint8Array(18);
packet.set([0x46, 0x52, 0x4d, 2, 0, 240, 5, 0]);
new DataView(packet.buffer).setUint16(8, 1920, true);
new DataView(packet.buffer).setUint16(10, 1080, true);
packet.set([0, 0, 0, 1, 38, 1], 12);
const socket = (window as typeof window & { __rdSocket: EventTarget }).__rdSocket;
for (let i = 0; i < 4; i++) {
socket.dispatchEvent(new MessageEvent("message", { data: packet.buffer.slice(0) }));
await Promise.resolve();
await Promise.resolve();
}
});
await expect.poll(() => page.evaluate(() => (
window as typeof window & {
__rdPresentationCounters: { decoded: number; drawn: number; closed: number };
}
).__rdPresentationCounters)).toEqual({
decoded: 4,
drawn: 1,
closed: 4,
});
await expect(page.locator("#diagDropped")).toHaveText("0 / 3");
});