119 lines
3.8 KiB
TypeScript
119 lines
3.8 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();
|
|
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");
|
|
});
|