Files
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

125 lines
3.9 KiB
TypeScript

import type { Page } from "@playwright/test";
/**
* Stub getUserMedia with a canvas-generated stream (animated gradient with a
* moving circle) so the camera phases work headless and deterministically —
* Playwright's bundled headless shell has no media capture support.
*/
export function installFakeCamera(
page: Page,
options: {
cameraDelayMs?: number;
failRequests?: number;
holdCamera?: boolean;
} = {},
) {
return page.addInitScript(
(cameraOptions) => {
const canvas = document.createElement("canvas");
canvas.width = 640;
canvas.height = 480;
const context = canvas.getContext("2d");
let hue = 0;
setInterval(() => {
if (!context) {
return;
}
hue = (hue + 7) % 360;
context.fillStyle = `hsl(${hue} 80% 60%)`;
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#ffffff";
context.beginPath();
context.arc(
canvas.width / 2 + Math.sin(hue / 30) * 60,
canvas.height / 2,
90,
0,
Math.PI * 2,
);
context.fill();
}, 90);
const stream = canvas.captureStream(15);
const mediaDevices = navigator.mediaDevices ?? ({} as MediaDevices);
if (!navigator.mediaDevices) {
Object.defineProperty(navigator, "mediaDevices", {
configurable: true,
value: mediaDevices,
});
}
const devices: MediaDeviceInfo[] = [
{
deviceId: "builtin-camera",
groupId: "mac",
kind: "videoinput",
label: "FaceTime HD Camera",
toJSON() {
return this;
},
} as MediaDeviceInfo,
{
deviceId: "iphone-continuity",
groupId: "iphone",
kind: "videoinput",
label: "Kenny's iPhone Camera",
toJSON() {
return this;
},
} as MediaDeviceInfo,
];
const testWindow = window as Window & {
__BUZZ_E2E_CAMERA_CONSTRAINTS__?: MediaStreamConstraints[];
__BUZZ_E2E_CAMERA_REQUEST_COUNT__?: number;
__BUZZ_E2E_RELEASE_CAMERA__?: () => void;
};
testWindow.__BUZZ_E2E_CAMERA_CONSTRAINTS__ = [];
testWindow.__BUZZ_E2E_CAMERA_REQUEST_COUNT__ = 0;
let releaseCamera: (() => void) | null = null;
testWindow.__BUZZ_E2E_RELEASE_CAMERA__ = () => {
releaseCamera?.();
releaseCamera = null;
};
Object.defineProperty(mediaDevices, "enumerateDevices", {
configurable: true,
value: () => Promise.resolve(devices),
});
Object.defineProperty(mediaDevices, "addEventListener", {
configurable: true,
value: () => {},
});
Object.defineProperty(mediaDevices, "removeEventListener", {
configurable: true,
value: () => {},
});
Object.defineProperty(mediaDevices, "getUserMedia", {
configurable: true,
value: async (constraints: MediaStreamConstraints) => {
testWindow.__BUZZ_E2E_CAMERA_CONSTRAINTS__?.push(constraints);
testWindow.__BUZZ_E2E_CAMERA_REQUEST_COUNT__ =
(testWindow.__BUZZ_E2E_CAMERA_REQUEST_COUNT__ ?? 0) + 1;
if (
testWindow.__BUZZ_E2E_CAMERA_REQUEST_COUNT__ <=
cameraOptions.failRequests
) {
throw new DOMException("Camera access denied", "NotAllowedError");
}
if (cameraOptions.holdCamera) {
await new Promise<void>((resolve) => {
releaseCamera = resolve;
});
} else if (cameraOptions.cameraDelayMs > 0) {
await new Promise((resolve) =>
setTimeout(resolve, cameraOptions.cameraDelayMs),
);
}
return Promise.resolve(stream);
},
});
},
{
cameraDelayMs: options.cameraDelayMs ?? 0,
failRequests: options.failRequests ?? 0,
holdCamera: options.holdCamera ?? false,
},
);
}