@@ -0,0 +1,18 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("keeps the primary workspace inside a narrow viewport", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/deep/nested/device/");
|
||||
await expect(page.locator("main").first()).toBeVisible();
|
||||
await expect(
|
||||
page.locator("main .loading, main .workbench-loading"),
|
||||
).toHaveCount(0);
|
||||
|
||||
const widths = await page.evaluate(() => ({
|
||||
content: document.documentElement.scrollWidth,
|
||||
viewport: document.documentElement.clientWidth,
|
||||
}));
|
||||
expect(widths.viewport).toBeLessThanOrEqual(430);
|
||||
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
|
||||
});
|
||||
@@ -2,8 +2,12 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
collectPassiveCapabilities,
|
||||
createRedactedReport,
|
||||
evaluateRequirementProfile,
|
||||
exportMediaCapabilityResult,
|
||||
exportReportCsv,
|
||||
exportReportJson,
|
||||
parseRequirementProfile,
|
||||
probeMediaCapabilities,
|
||||
runProbe,
|
||||
type Capability,
|
||||
type DeviceEnvironment,
|
||||
@@ -21,6 +25,7 @@ function environment(
|
||||
visualViewport: { width: 1200, height: 700 },
|
||||
matchMedia: (query: string) => ({ matches: query.includes("fine") }),
|
||||
WebGLRenderingContext: function WebGLRenderingContext() {},
|
||||
Worker: function Worker() {},
|
||||
indexedDB: {},
|
||||
caches: {},
|
||||
} as unknown as Window;
|
||||
@@ -81,6 +86,61 @@ describe("passive device inventory", () => {
|
||||
expect(capabilities.find((item) => item.id === "camera-api")?.state).toBe(
|
||||
"unavailable",
|
||||
);
|
||||
expect(
|
||||
capabilities.find((item) => item.id === "camera-api")?.availability,
|
||||
).toBe("missing-api");
|
||||
});
|
||||
|
||||
it("distinguishes insecure context and Permissions Policy failures", async () => {
|
||||
const insecure = environment();
|
||||
insecure.secureContext = false;
|
||||
expect(
|
||||
collectPassiveCapabilities(insecure).find(
|
||||
(item) => item.id === "camera-api",
|
||||
),
|
||||
).toMatchObject({ availability: "insecure-context" });
|
||||
expect(await runProbe("camera", insecure)).toMatchObject({
|
||||
state: "unsupported",
|
||||
availability: "insecure-context",
|
||||
});
|
||||
|
||||
const blocked = environment({
|
||||
mediaDevices: { getUserMedia: vi.fn() },
|
||||
});
|
||||
Object.assign(blocked.document, {
|
||||
permissionsPolicy: {
|
||||
allowsFeature: (feature: string) => feature !== "camera",
|
||||
},
|
||||
});
|
||||
expect(await runProbe("camera", blocked)).toMatchObject({
|
||||
state: "denied",
|
||||
availability: "permissions-policy",
|
||||
});
|
||||
});
|
||||
|
||||
it("distinguishes missing devices from denied permission", async () => {
|
||||
const missing = await runProbe(
|
||||
"camera",
|
||||
environment({
|
||||
mediaDevices: {
|
||||
getUserMedia: async () => {
|
||||
throw new DOMException("No camera", "NotFoundError");
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(missing).toMatchObject({ availability: "device-or-os" });
|
||||
const denied = await runProbe(
|
||||
"camera",
|
||||
environment({
|
||||
mediaDevices: {
|
||||
getUserMedia: async () => {
|
||||
throw new DOMException("Denied", "NotAllowedError");
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(denied).toMatchObject({ availability: "user-permission" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -180,3 +240,108 @@ describe("redacted exports", () => {
|
||||
expect(report.policy.join(" ")).toContain("No stable identifier");
|
||||
});
|
||||
});
|
||||
|
||||
describe("application requirement profiles", () => {
|
||||
it("combines legacy requirements with required and optional capabilities", () => {
|
||||
const profile = parseRequirementProfile({
|
||||
id: "de.example.tool",
|
||||
name: "Example",
|
||||
requirements: { secureContext: true, workers: true },
|
||||
capabilities: {
|
||||
required: ["file-system", "workers"],
|
||||
optional: ["webgpu-api", "workers"],
|
||||
},
|
||||
});
|
||||
expect(profile.required).toEqual([
|
||||
"file-system",
|
||||
"secure-context",
|
||||
"workers",
|
||||
]);
|
||||
expect(profile.optional).toEqual(["webgpu-api"]);
|
||||
|
||||
const evaluation = evaluateRequirementProfile(
|
||||
profile,
|
||||
collectPassiveCapabilities(environment()),
|
||||
);
|
||||
expect(evaluation.status).toBe("blocked");
|
||||
expect(
|
||||
evaluation.requirements.find((item) => item.id === "workers")?.state,
|
||||
).toBe("available");
|
||||
expect(
|
||||
evaluation.requirements.find((item) => item.id === "file-system")
|
||||
?.remediation,
|
||||
).toContain("browser/version");
|
||||
});
|
||||
|
||||
it("reports unknown required identifiers and rejects malformed profiles", () => {
|
||||
const profile = parseRequirementProfile(
|
||||
JSON.stringify({ capabilities: { required: ["future-api"] } }),
|
||||
);
|
||||
const result = evaluateRequirementProfile(profile, []);
|
||||
expect(result.status).toBe("blocked");
|
||||
expect(result.requirements[0]).toMatchObject({ state: "unknown" });
|
||||
expect(() =>
|
||||
parseRequirementProfile({ capabilities: { required: ["Bad ID"] } }),
|
||||
).toThrow(/capability ID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MediaCapabilities lab", () => {
|
||||
it("runs only the requested configuration and exports bucketed numbers", async () => {
|
||||
const decodingInfo = vi.fn(async () => ({
|
||||
supported: true,
|
||||
smooth: false,
|
||||
powerEfficient: true,
|
||||
}));
|
||||
const result = await probeMediaCapabilities(
|
||||
{
|
||||
kind: "video",
|
||||
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
bitrate: 8_765_432,
|
||||
framerate: 30,
|
||||
},
|
||||
environment({ mediaCapabilities: { decodingInfo } }),
|
||||
);
|
||||
expect(decodingInfo).toHaveBeenCalledOnce();
|
||||
expect(result).toMatchObject({
|
||||
state: "complete",
|
||||
supported: true,
|
||||
smooth: false,
|
||||
powerEfficient: true,
|
||||
});
|
||||
const exported = exportMediaCapabilityResult(result);
|
||||
expect(exported).toContain("Full-HD");
|
||||
expect(exported).toContain("2-to-10-Mbit/s");
|
||||
expect(exported).not.toContain("8765432");
|
||||
expect(exported).not.toContain("1920");
|
||||
});
|
||||
|
||||
it("handles unsupported browsers and validates dangerous input", async () => {
|
||||
const unsupported = await probeMediaCapabilities(
|
||||
{
|
||||
kind: "audio",
|
||||
contentType: 'audio/webm; codecs="opus"',
|
||||
channels: "2",
|
||||
bitrate: 192_000,
|
||||
samplerate: 48_000,
|
||||
},
|
||||
environment(),
|
||||
);
|
||||
expect(unsupported.state).toBe("unsupported");
|
||||
await expect(
|
||||
probeMediaCapabilities(
|
||||
{
|
||||
kind: "video",
|
||||
contentType: "audio/mp4\ninvalid",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
bitrate: 8_000_000,
|
||||
framerate: 30,
|
||||
},
|
||||
environment(),
|
||||
),
|
||||
).rejects.toThrow(/video MIME type/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user