phase: 06_sharer_quality_presets
Build and Push Containers / test (push) Successful in 1m12s
Build and Push Containers / build-and-push-app (push) Successful in 9s

**Phase 06 — final verification pass: 100% green.**

- **Verified (all 6 tasks complete in working tree):** `#quality` select + `PRESETS` table; preset-driven capture request + `contentHint`; per-leg `legCap()`/`degradation` at both call sites; live `applyQuality()` with Overconstrained fallback + republish; `q` key cycle; `#stat-quality`/`#stat-codec` readouts; full e2e matrix (requests, committed params ×3, zero-cap, mid-share switch, unrescalable device, late-joiner first-offer, readout, `q`); realdisplay delivered-pixels `light` case. `test_sender_fidelity.py` **untouched and green** (regression canary).
- **Gates:** `uv run ruff check . && uv run ruff format --check .` → clean (32 files); `uv run pyright` → 0 errors; `uv run pytest` → **284 passed, 5 skipped (realdisplay-gated), coverage 95%** (93s); `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → **4 passed, 1 skipped** (no audio source on host — documented platform condition).
- **Completion criteria:** all 11 ✓ — sharp default reproduces old request; light ≤960×600@15fps/motion/round(cap×0.35)/maintain-framerate with moving viewer frames; mid-share re-prices with `peers.size` unchanged, no renegotiation, roster matches `getSettings()`; refused-rescale toasts "next share"; `q` cycles + toasts, text-field guard holds; readout exact + codec shape, both reset to `—` idle; realdisplay: light decoded ≤990px (measured 320×200), sharp clears `SHARP_FLOOR_PX`; no new env/wire/Python/asset; suite 100% green, coverage >90%.
- **Docs:** README.md reviewed in full against code — Quality table, `q` key, env-var table, routes (`/share`), kiosk commands, key table, doc paths all consistent; **no fixes needed**. AGENTS.md — verified, no stale statements, no changes. PLAN.md untouched (diff = 0).
- **Reported (per phase, not edited):** PLAN.md §6 line 204 still says "per-leg `maxBitrate` cap (default **12 Mbit/s**)" — code has defaulted to **25 Mbit/s** since phase 02; needs a user-approved one-line amendment.
- **Next pending phase:** none — 06 is the last phase in `todo/`; the roadmap is complete.
This commit is contained in:
2026-11-02 22:07:17 -05:00
parent f51d0fe9ae
commit 3f93481c54
44 changed files with 2107 additions and 54 deletions
+11 -3
View File
@@ -13,7 +13,7 @@ import socket
import subprocess
import sys
import time
from collections.abc import Iterator, Sequence
from collections.abc import Iterator, Mapping, Sequence
from pathlib import Path
import pytest
@@ -45,8 +45,15 @@ def _free_port() -> int:
return probe.getsockname()[1]
def _start(extra_args: Sequence[str] = ()) -> tuple[str, subprocess.Popen[str]]:
"""Spawn a real `mirror serve` on a free port and wait for it to answer."""
def _start(
extra_args: Sequence[str] = (),
extra_env: Mapping[str, str] = {},
) -> tuple[str, subprocess.Popen[str]]:
"""Spawn a real `mirror serve` on a free port and wait for it to answer.
`extra_env` overrides inherited environment variables (it wins over
`os.environ`, so a developer's `MIRROR_*` cannot leak into a test that
names the opposite value)."""
import httpx
port = _free_port()
@@ -74,6 +81,7 @@ def _start(extra_args: Sequence[str] = ()) -> tuple[str, subprocess.Popen[str]]:
"PYTHONUNBUFFERED": "1",
# A stray DEBUGPY in the developer's shell would stall the server.
"DEBUGPY": "0",
**extra_env,
},
)
url = f"http://127.0.0.1:{port}"
+619
View File
@@ -0,0 +1,619 @@
"""End-to-end: the capture request and the per-leg encoder parameters follow
the selected quality preset.
`pickSurface()` asks the capture device for the *selected* preset's pixel
budget and frame rate instead of one hardcoded 4K/30 request, and `start()`
records which preset the live capture belongs to. The capture assertions read
the page's own request back through `window.__probe` (AGENTS.md §6):
`lastRequested` is the size the request named, `qualityKey` the preset it
came from.
The per-leg assertions read the *committed* sender parameters back from the
sharer's real RTCPeerConnection legs through the same `__probe` handle:
each leg encodes under `round(config.maxBitrateBps * preset.factor)` and the
preset's `degradationPreference`, read at call time — so a preset chosen
before a viewer joins lands on that leg's first offer. Media rules still
govern: pixel assertions are made on the *viewer's* `<video>`.
Delivered *capture* pixels are deliberately not asserted here: with
`--use-fake-device-for-media-stream` the synthetic device is a fixed
1280x720 and its clamping is not the behaviour under test (PLAN §5). Where
a real capturer runs (`MIRROR_REALDISPLAY=1`, the `realdisplay` suite) the
delivered pixels are asserted instead.
The late-joining-viewer case proves the *first offer* of a leg that comes
up under a preset chosen before that viewer joined carries the preset's cap:
a new `RTCPeerConnection` is wrapped on the sharer page (the same pattern the
other e2e suites use for their `RTCPeerConnection` probes) and the parameters
are read back at `setLocalDescription` — before `negotiate()`'s
post-negotiation re-apply has run, so the only code that could have written
them is `addPeer()`'s `sendEncodings`.
"""
from __future__ import annotations
import re
import time
import pytest
from playwright.sync_api import expect
from .conftest import _start, _stop, frames_matching, start_sharing, stop_sharing
pytestmark = [pytest.mark.e2e, pytest.mark.timeout(180)]
@pytest.fixture
def device(base_url, browser, request):
"""Open a page as its own device (isolated context, fresh peer id).
`url` targets a different server than the session's (the zero-cap
fixture); the default is the session server, which is what the capture-
request tests use.
"""
opened = []
def _open(path: str = "/", errors: list | None = None, url: str | None = None):
context = browser.new_context(viewport={"width": 1100, "height": 720})
opened.append(context)
request.addfinalizer(context.close)
page = context.new_page()
if errors is not None:
page.on("pageerror", lambda error: errors.append(f"pageerror: {error}"))
page.goto(f"{url or base_url}{path}", wait_until="domcontentloaded")
expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=10_000)
return page
return _open
@pytest.fixture
def zero_cap_url():
"""A real `mirror serve` with `MIRROR_MAX_BITRATE=0` — the "uncapped"
setting of the WebRTC API, which must not turn into a 0 bps leg."""
url, process = _start(extra_env={"MIRROR_MAX_BITRATE": "0"})
yield url
_stop(process)
# Reads the video sender's committed parameters back from the sharer's live
# legs (copied per suite from test_sender_fidelity.py — one copy per suite is
# the convention). `state` is the leg's connectionState; `maxBitrate` is the
# cap the leg is actually encoding under (undefined when none was written —
# it reads back as null — which is exactly the "uncapped" state). If the
# server had written a 0 bps cap it would read back as 0, and the zero-cap
# test's `is None` assertion is what keeps the two apart.
SENDER_PROBE = """() => {
const probe = window.__probe;
if (!probe) return null;
return [...probe.peers.values()].map((peer) => {
const sender = peer.pc.getSenders().find(
(s) => s.track && s.track.kind === "video"
);
if (!sender) return null;
const params = sender.getParameters();
const encodings = params.encodings || [];
return {
state: peer.pc.connectionState,
degradationPreference: params.degradationPreference,
maxBitrate: encodings.length ? encodings[0].maxBitrate : null,
};
});
}"""
def _wait_for(sharer, want: int, pred, timeout: float = 45.0) -> list:
"""Poll the sharer's legs until `want` of them satisfy `pred` (or give up)."""
legs: list = []
deadline = time.time() + timeout
while time.time() < deadline:
legs = sharer.evaluate(SENDER_PROBE) or []
if len(legs) == want and all(leg and pred(leg) for leg in legs):
return legs
time.sleep(0.5)
return legs
# The page's own request, the preset it came from, the track's contentHint
# (a pure read-back of what start() set — not a delivered pixel), and the
# inputs the page used: screen.width / devicePixelRatio are read *from the
# page* — under Playwright `screen` is the emulated viewport, so hardcoding
# a display size would make this environment-dependent.
REQUEST_PROBE = """() => {
const probe = window.__probe;
if (!probe) return null;
const stream = document.querySelector('#preview').srcObject;
const track = stream && stream.getVideoTracks()[0];
return {
requested: probe.lastRequested,
qualityKey: probe.qualityKey,
contentHint: track ? track.contentHint : null,
screenWidth: screen.width,
screenHeight: screen.height,
dpr: window.devicePixelRatio || 1,
};
}"""
# The pixel ceilings and the per-preset contentHint, the mirror of `PRESETS`
# in share.js: the request is min(screen * dpr, cap). This suite asserts the
# *request* the page built; the table itself is guarded by test_app.py's
# three-preset agreement test.
PRESETS_UNDER_TEST = {
"sharp": {"cap": (3840, 2160), "contentHint": "detail"},
"balanced": {"cap": (1920, 1200), "contentHint": "detail"},
"light": {"cap": (960, 600), "contentHint": "motion"},
}
def _bitrate_format(bps: float) -> str:
"""The mirror of `bitrateFormat` in net.js — the panel's own renderer,
so the test can recompute the exact line the panel must print."""
if not bps or bps < 0:
return "—"
if bps >= 1e6:
return f"{bps / 1e6:.1f} Mbps"
return f"{round(bps / 1e3)} kbps"
class TestQualityPresets:
def test_each_preset_asks_the_capture_for_its_own_budget(self, device):
"""Each preset drives the capture request: `sharp` is the old
hardcoded 4K cap (the no-regression argument), `balanced` 1080p-class,
`light` a small and cheap budget — and the page records which preset
the live capture belongs to."""
errors: list = []
for key, preset in PRESETS_UNDER_TEST.items():
cap_w, cap_h = preset["cap"]
sharer = device("/mirror", errors)
sharer.select_option("#quality", key)
start_sharing(sharer)
result = sharer.evaluate(REQUEST_PROBE)
assert result, "the __probe handle is missing"
assert result["qualityKey"] == key, result
# The page's own arithmetic, recomputed from the page's own inputs:
# min(round(screen * dpr), cap).
expected_w = min(round(result["screenWidth"] * result["dpr"]), cap_w)
expected_h = min(round(result["screenHeight"] * result["dpr"]), cap_h)
assert result["requested"]["width"] == expected_w, result
assert result["requested"]["height"] == expected_h, result
# Text-first presets take `detail`; `light` is glanceable, so
# motion wins over glyph crispness.
assert result["contentHint"] == preset["contentHint"], result
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_light_leg_encodes_at_the_presets_share(self, device, base_url):
"""A leg that comes up under `light` encodes at the preset's share
of the server's ceiling and with the preset's degradation preference —
both read from the preset *at call time*: `light` was selected before
sharing started, so the viewer's leg must carry them from its first
offer, and the wall must still be moving (a cheaper picture must not
be a dead one)."""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
errors: list = []
sharer = device("/mirror", errors)
sharer.select_option("#quality", "light")
start_sharing(sharer)
viewer = device("/watch", errors)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
assert legs[0]["maxBitrate"] == round(cap * 0.35), legs
assert legs[0]["degradationPreference"] == "maintain-framerate", legs
# The cheaper picture still has to be a live one.
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"a light leg froze: {frames}"
assert errors == [], f"uncaught browser errors: {errors}"
def test_each_preset_commits_its_sender_parameters(self, device, base_url):
"""The full committed-parameter matrix, `sharp` first so the
no-regression leg is the first thing it proves: each preset's live
leg commits `round(cap * factor)` and the preset's
`degradationPreference` — read at call time, so the preset selected
before sharing lands on the leg from its first offer — and the wall
stays moving on the viewer in every case (a cheaper picture must not
be a dead one). `sharp` is phase 02's behaviour verbatim: full cap,
`maintain-resolution`.
"""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
# The committed-parameter mirror of PRESETS_UNDER_TEST: the factor the
# server's ceiling is scaled by and the degradation preference the
# encoder commits to, per preset. `sharp` first: full cap and
# maintain-resolution is the unchanged phase 02 behaviour.
committed = {
"sharp": (1.0, "maintain-resolution"),
"balanced": (0.6, "balanced"),
"light": (0.35, "maintain-framerate"),
}
for key, (factor, degradation) in committed.items():
errors: list = []
sharer = device("/mirror", errors)
sharer.select_option("#quality", key)
start_sharing(sharer)
viewer = device("/watch", errors)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", (key, legs)
assert legs[0]["maxBitrate"] == round(cap * factor), (key, legs)
assert legs[0]["degradationPreference"] == degradation, (key, legs)
# The picture still has to be a live one in every preset.
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"the {key} leg froze: {frames}"
assert errors == [], f"uncaught browser errors on {key}: {errors}"
# Tear the whole iteration down before the next preset: the
# contexts outlive the iteration, and the wall auto-subscribes
# *every live viewer* to *every new mirror* — an uncleaned
# viewer would stack a second (third, ...) leg on the next
# preset's sharer and break the one-leg expectation. Stop the
# share first, so the viewer's tile (and its peer connection)
# is torn down before its page goes away.
stop_sharing(sharer)
viewer.close()
sharer.close()
def test_a_zero_cap_stays_uncapped_under_light(self, device, zero_cap_url):
"""`MIRROR_MAX_BITRATE=0` is a real setting — "uncapped" in the
WebRTC API. `legCap()` must return 0 and no `maxBitrate` may be
written: an uncapped leg must not become a 0 bps leg. The preset's
degradation preference still lands, and the picture still moves.
"""
import httpx
config = httpx.get(f"{zero_cap_url}/api/config", timeout=5).json()
assert config["maxBitrateBps"] == 0, config
errors: list = []
sharer = device("/mirror", errors, url=zero_cap_url)
sharer.select_option("#quality", "light")
start_sharing(sharer)
viewer = device("/watch", errors, url=zero_cap_url)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
# No maxBitrate written at all: an uncapped leg, not a 0 bps one.
assert legs[0]["maxBitrate"] is None, legs
assert legs[0]["degradationPreference"] == "maintain-framerate", legs
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"the uncapped leg froze: {frames}"
assert errors == [], f"uncaught browser errors: {errors}"
def test_changing_quality_mid_share_re_prices_the_live_leg(self, device, base_url):
"""A preset change made while a leg is live re-prices that leg —
``setParameters`` commits without an offer, so the CPU/egress
change is instant and no second RTCPeerConnection is needed for it —
and the viewer keeps decoding moving frames across the switch. The
roster must tell the truth in *both* outcomes: its width/height
equal the track's own ``getSettings()``, whatever the engine
settled on (the synthetic device clamps; delivered pixels are the
``realdisplay`` suite's, task 06)."""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
errors: list = []
sharer = device("/mirror", errors)
start_sharing(sharer) # the default preset: sharp
viewer = device("/watch", errors)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
# The switch, mid-share, through the control the user has.
sharer.select_option("#quality", "light")
# The single live leg re-prices in place: ``setParameters`` commits
# within ~5 s of the change, and the connection state returns to /
# stays ``connected`` (a leg that comes up fresh for the new size
# reads ``legCap()`` at its first offer, so the cap lands either way).
def repriced(leg):
return leg["state"] == "connected" and leg["maxBitrate"] == round(cap * 0.35)
re = _wait_for(sharer, 1, repriced, timeout=5.0)
# No second RTCPeerConnection: the roster of live legs is still the
# one the viewer was subscribed on.
assert sharer.evaluate("() => window.__probe.peers.size") == 1
assert re and re[0]["maxBitrate"] == round(cap * 0.35), re
assert re[0]["state"] == "connected", re
# The switch did not freeze anybody.
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, frames
# The roster tells the truth: whatever the engine settled on, the
# published width/height equal the track's own settings. Do not
# assert a *delivered* size here — the synthetic device clamps, and
# task 06 owns delivered pixels on a real desktop.
def track_settings():
return sharer.evaluate(
"() => { const s = document.querySelector('#preview').srcObject;"
" const t = s && s.getVideoTracks()[0];"
" return t ? t.getSettings() : null; }"
)
def roster_agrees():
settings = track_settings()
if not settings or not settings.get("width"):
return False, None, settings
mirror = httpx.get(f"{base_url}/api/streams", timeout=5).json()["mirrors"]
assert len(mirror) == 1, mirror
mirror = mirror[0]
agrees = mirror["width"] == settings["width"] and mirror["height"] == settings["height"]
return agrees, mirror, settings
deadline = time.time() + 10.0
agrees, mirror, settings = roster_agrees()
while not agrees and time.time() < deadline:
time.sleep(0.5)
agrees, mirror, settings = roster_agrees()
assert agrees, f"roster does not match the track's own settings: {mirror!r} vs {settings!r}"
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_device_that_cannot_rescale_still_changes_rate(self, device, base_url):
"""A display that cannot rescale answers the full constraint with the
named ``OverconstrainedError``: the retry applies the frame rate
alone, the leg is still re-priced, the viewer still moves, the
toast says the size lands on the next share (it does not pretend
the capture rescaled), and the roster stays truthful."""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
errors: list = []
sharer = device("/mirror", errors)
start_sharing(sharer)
viewer = device("/watch", errors)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
# Simulate the display: a constraint that carries a size fails with
# the named error; the frame-rate-only retry passes. (This is the
# real-world behaviour the retry exists for — a capturer that holds
# its size — and nothing else in the path is stubbed.)
sharer.evaluate(
"() => { const s = document.querySelector('#preview').srcObject;"
" const t = s.getVideoTracks()[0];"
" const orig = t.applyConstraints.bind(t);"
" t.applyConstraints = (c) => (c && c.width)"
" ? Promise.reject(new DOMException('size cannot be applied', 'OverconstrainedError'))"
" : orig(c); }"
)
sharer.select_option("#quality", "light")
# The toast lands with the settle (before the leg is measured
# again), so it is read first: the preset is named, and because
# the size was never applied the line says it lands next share.
toast = sharer.locator("#toast")
expect(toast).to_contain_text("Light", timeout=5_000)
text = toast.text_content() or ""
assert "next share" in text, text
# The leg is still re-priced, and the switch did not freeze anybody.
def repriced(leg):
return leg["state"] == "connected" and leg["maxBitrate"] == round(cap * 0.35)
re = _wait_for(sharer, 1, repriced, timeout=5.0)
assert re and re[0]["maxBitrate"] == round(cap * 0.35), re
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, frames
# The roster tells the truth about whatever the device delivered.
def track_settings():
return sharer.evaluate(
"() => { const s = document.querySelector('#preview').srcObject;"
" const t = s && s.getVideoTracks()[0];"
" return t ? t.getSettings() : null; }"
)
def roster_agrees():
settings = track_settings()
if not settings or not settings.get("width"):
return False, None, settings
mirror = httpx.get(f"{base_url}/api/streams", timeout=5).json()["mirrors"]
assert len(mirror) == 1, mirror
mirror = mirror[0]
agrees = mirror["width"] == settings["width"] and mirror["height"] == settings["height"]
return agrees, mirror, settings
deadline = time.time() + 10.0
agrees, mirror, settings = roster_agrees()
while not agrees and time.time() < deadline:
time.sleep(0.5)
agrees, mirror, settings = roster_agrees()
assert agrees, f"roster does not match the track's own settings: {mirror!r} vs {settings!r}"
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_viewer_who_joins_after_a_switch_gets_the_new_cap(self, device, base_url):
"""The `addPeer` path, not just the re-apply path.
A preset change mid-share re-prices the *existing* leg (task 04's
re-apply). This proves the other half: a viewer who subscribes
*after* the switch gets a leg whose **first offer** already carries
the new cap — `legCap()` is read at call time in `addPeer()` and
written into `sendEncodings` before any negotiation exists. The
sharer's `RTCPeerConnection` is wrapped (the same pattern the other
e2e suites use for their peer-connection probes) so the parameters
are read back at `setLocalDescription`, i.e. before `negotiate()`'s
post-negotiation re-apply has run: the only code that could have
written them is `addPeer()`'s `sendEncodings`.
"""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
errors: list = []
sharer = device("/mirror", errors)
start_sharing(sharer) # the default preset: sharp
first = device("/watch", errors)
expect(first.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
assert legs[0]["maxBitrate"] == cap, legs
# The switch, mid-share: the existing leg re-prices via the re-apply.
sharer.select_option("#quality", "light")
def repriced(leg):
return leg["state"] == "connected" and leg["maxBitrate"] == round(cap * 0.35)
re = _wait_for(sharer, 1, repriced, timeout=5.0)
assert re and re[0]["maxBitrate"] == round(cap * 0.35), re
# From here on, every new RTCPeerConnection on the sharer page is
# the late joiner's leg: the wrapper records the committed parameters
# at setLocalDescription time, before negotiate()'s re-apply.
assert sharer.evaluate(
"""() => {
window.__firstOffers = [];
const RealPC = window.RTCPeerConnection;
window.RTCPeerConnection = function (config) {
const pc = new RealPC(config);
const orig = pc.setLocalDescription.bind(pc);
pc.setLocalDescription = (d) => orig(d).then(() => {
const sender = pc.getSenders().find(
(s) => s.track && s.track.kind === 'video'
);
const params = sender ? sender.getParameters() : {};
const enc = (params.encodings || [])[0] || {};
window.__firstOffers.push({
maxBitrate: 'maxBitrate' in enc ? enc.maxBitrate : null,
degradationPreference: params.degradationPreference || null,
});
return pc;
});
return pc;
};
window.RTCPeerConnection.prototype = RealPC.prototype;
window.RTCPeerConnection.static = RealPC.static;
return true;
}"""
)
late = device("/watch", errors)
expect(late.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
# Both legs are now live under the switched preset.
legs = _wait_for(sharer, 2, lambda leg: leg["state"] == "connected")
assert legs and all(leg["state"] == "connected" for leg in legs), legs
offers = sharer.evaluate("() => window.__firstOffers") or []
assert offers, "no first offer was captured from the late joiner's leg"
# The first offer carried the new cap: addPeer's sendEncodings, not
# the post-negotiation re-apply, which ran later on the same leg.
assert offers[-1]["maxBitrate"] == round(cap * 0.35), offers
# And the settled leg is still capped — nothing drifted after.
assert all(leg["maxBitrate"] == round(cap * 0.35) for leg in legs), legs
assert sharer.evaluate("() => window.__probe.peers.size") == 2
# The late joiner's picture is a live one, and the sharer agrees
# that two viewers are watching.
frames = frames_matching(late, ".tile video")
assert frames["distinct"] >= 2, f"the late joiner froze: {frames}"
expect(sharer.locator("#viewer-count")).to_have_text("2", timeout=10_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_the_panel_shows_the_budget_and_the_codec(self, device, base_url):
"""The panel says what the share is actually costing (phase 06 task
05): the quality budget in force — the selected preset, its capture
ceiling, its frame rate and its per-leg share of the server's cap —
and the codec the leg actually negotiated, resolved in the same
`getStats()` read the 1 s tick already makes. The codec is `—` where
the engine does not report it: the panel must never guess. Both
lines reset to `—` when the share stops."""
import httpx
cap = httpx.get(f"{base_url}/api/config", timeout=5).json()["maxBitrateBps"]
assert cap > 0
errors: list = []
sharer = device("/mirror", errors)
sharer.select_option("#quality", "balanced")
start_sharing(sharer)
# The budget line is written by start() from state: an exact match,
# not a substring — the panel must not misprice the share. The cap
# is read live from /api/config (never hardcoded) and scaled by the
# preset's factor, exactly as legCap() does.
expected = (
f"balanced · ≤1920×1200 @24fps · " # noqa: RUF001 (the panel line itself carries the multiplication sign)
f"{_bitrate_format(round(cap * 0.6))} /viewer"
)
expect(sharer.locator("#stat-quality")).to_have_text(expected, timeout=10_000)
viewer = device("/watch", errors)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
legs = _wait_for(sharer, 1, lambda leg: leg["state"] == "connected")
assert legs and legs[0]["state"] == "connected", legs
# The codec line is written once per 1 s stats tick (no extra
# getStats() call): let a tick run after the leg is up, then read it.
time.sleep(2.5)
codec = (sharer.locator("#stat-codec").text_content() or "").strip()
# The shape, not the codec: which one the engine negotiates is not a
# contract. `—` is the honest floor where the engine does not expose
# the outbound codec; a name must be the short form
# (`video/VP8` → `VP8`), never a guess.
assert codec == "—" or re.fullmatch(r"[A-Z0-9/]{2,16}", codec), codec
# The share is still a live one while the panel reports on it.
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, frames
# Stopping resets both lines: `—`, not a stale reading.
stop_sharing(sharer)
expect(sharer.locator("#stat-quality")).to_have_text("—", timeout=10_000)
expect(sharer.locator("#stat-codec")).to_have_text("—", timeout=10_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_q_cycles_the_preset_and_says_so(self, device):
"""``q`` cycles the preset on the share page (sharp → balanced →
light → sharp) and toasts the new one, naming it and its budget;
a ``q`` typed into ``#name`` is text, not a command, and never
changes the select (the router's text-field guard)."""
errors: list = []
page = device("/mirror", errors)
toast = page.locator("#toast")
page.keyboard.press("q")
expect(page.locator("#quality")).to_have_value("balanced", timeout=5_000)
assert page.evaluate("() => window.__probe.qualityKey") == "balanced"
expect(toast).to_contain_text("Balanced", timeout=5_000)
text = toast.text_content() or ""
assert "≤1920×1200" in text, text # noqa: RUF001 (the toast itself carries the multiplication sign)
# Nothing is shared yet: the capture size lands on the next share,
# and the line says so rather than pretending it rescaled.
assert "next share" in text, text
page.keyboard.press("q")
expect(page.locator("#quality")).to_have_value("light")
expect(toast).to_contain_text("Light", timeout=5_000)
page.keyboard.press("q")
expect(page.locator("#quality")).to_have_value("sharp")
expect(toast).to_contain_text("Sharp", timeout=5_000)
# A ``q`` typed into #name is text, not a command: the select stays.
page.locator("#name").click()
page.keyboard.type("q")
expect(page.locator("#quality")).to_have_value("sharp")
assert errors == [], f"uncaught browser errors: {errors}"
+99
View File
@@ -443,6 +443,105 @@ class TestRealX11Capture:
expect(share_page.locator("#viewer-count")).to_have_text("1", timeout=20_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_light_preset_delivers_a_small_picture(self, share_page, watch_page):
"""The delivered-pixel proof that the preset reaches the *real*
capturer, not just the request object.
``#quality = light`` is selected **before** the share starts, so the
request — under Playwright ``screen`` is the emulated viewport, so
the 1200x760 share window asks ``min(viewport, 960x600)`` =
960x600 — goes to Chromium's real X11 backend. The sharer's track
settings must honour the budget (at least 80% of the 960 request
wide, never above it): that is the proof the request reached the
real capturer and was not clamped away to the desktop or the
window. The viewer's decoded width must be at or under the cap —
the phase's delivered-pixels criterion — *and below the sharp
path's floor*: if a ``light`` share delivered a sharp-size picture,
the budget never reached the wire. The aspect guard keeps proving
the picture is *our* desktop, not somebody else's window.
The sharp path's floor (``SHARP_FLOOR_PX``) is deliberately
**not** asserted here: it stays the sharp test's, and a small
picture is exactly what a ``light`` share should deliver — the
small picture that is still the whole desktop.
Measured on this host (Xvfb 1280x800x24, phase 06): the capturer
delivered 960x600 (track settings, ``frameRate`` 15), and the
viewer decoded 320x200 — VP8 honouring ``light``'s
``maintain-framerate`` at ``round(25 Mbit/s x 0.35)`` = 8.75
Mbit/s, i.e. the encoder's resolution-degradation policy (the same
leg encodes 960x600 under ``maintain-resolution``); the encoder
may sacrifice resolution, which is the preset's contract, so the
decoded-width floor below guards a *collapsed* picture (>= a
quarter of the request), not the encoder's honest ladder.
"""
errors: list[str] = []
share_page.on("pageerror", lambda error: errors.append(str(error)))
share_page.select_option("#quality", "light")
share_page.locator("#start").click()
expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000)
# The preset is what the live capture belongs to (a pure parameter
# read, not a pixel claim — the pixels are the viewer's, below).
assert share_page.evaluate("() => window.__probe.qualityKey") == "light"
# The request reached the *real* capturer and was not clamped away:
# the track the X11 backend settled on honours the light budget —
# at least 80% of the 960 request wide (the capturer did not drop
# the size), never above it (the capturer did not ignore it and
# deliver the 1200x750 window-clamped desktop).
settings = share_page.evaluate(
"""() => {
const track = document.querySelector('#preview').srcObject.getVideoTracks()[0];
const s = track.getSettings();
return {width: s.width, height: s.height, surface: s.displaySurface};
}"""
)
assert settings["surface"] == "monitor", f"not a monitor capture: {settings}"
assert settings["width"] <= 990, f"the capturer ignored the light budget: {settings}"
assert settings["width"] >= 768, f"the capturer did not honour the request: {settings}"
expect(watch_page.locator(".tile[data-state='live']")).to_have_count(1, timeout=60_000)
# Start measuring on the watcher, then paint: cycling the captured
# machine's background is what a real presenter's screen does constantly.
assert watch_page.evaluate(PROBE) == "started"
for colour in ("#c81e1e", "#0b0d10", "#1e6ec8", "#0b0d10", "#1ec85a"):
share_page.evaluate("(colour) => { document.body.style.background = colour; }", colour)
time.sleep(0.28)
result = watch_page.evaluate("() => window.__probe")
# Delivered pixels follow the preset: the decoded picture is at or
# under the light cap, and below the sharp path's floor (a
# sharp-size picture from a light share would mean the budget
# never reached the wire). Measured 320x200 on this host — the
# encoder's maintain-framerate degradation, see the docstring.
assert result["width"] <= 990, (
f"the watcher decoded a {result['width']}px-wide picture: the light budget is "
f"<= 960px, so a wider decode means the budget did not reach the wire: {result}"
)
assert result["width"] < SHARP_FLOOR_PX, (
f"the watcher decoded a {result['width']}px-wide picture: that is at or above "
f"the sharp floor ({SHARP_FLOOR_PX}px), so a light share delivered a sharp-size "
f"picture: {result}"
)
assert result["width"] >= 240, (
f"the watcher decoded a {result['width']}px-wide picture: a quarter of the 960px "
f"request must arrive at least, or the picture collapsed: {result}"
)
assert abs(result["width"] / result["height"] - XVFB_ASPECT) < 0.06, (
f"{result} does not match the {XVFB_GEOMETRY} desktop aspect "
f"({XVFB_ASPECT:.3f}); this is not our Xvfb screen"
)
assert result["distinct"] >= 2, (
f"the watcher received a frozen frame: {result} — a small-but-still picture "
f"must not pass a delivered-pixel test"
)
expect(share_page.locator("#viewer-count")).to_have_text("1", timeout=20_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_real_desktop_shares_audio_when_the_host_offers_any(self, base_url, browsers):
"""The realdisplay counterpart of the audio-arrival assertion.
+38 -6
View File
@@ -177,15 +177,47 @@ class TestClientScripts:
"a different bitrate silently"
)
def test_the_quality_options_are_the_three_presets(self):
"""`#quality` (mirror.html) and `PRESETS` (share.js) are one list in two places.
If the option values drift from the table keys, a preset the panel offers has
no row (or an orphan row the panel can never select), and `currentPreset()`
falls back to `sharp` instead of noticing: the sharer gets a wall they did not
ask for and nothing fails. One list, two places, one test that notices (the
same shape as the bitrate-default test)."""
html = (STATIC / "mirror.html").read_text()
select = re.search(r'<select id="quality"[^>]*>.*?</select>', html, re.S)
assert select, "mirror.html must render a #quality select"
options = set(re.findall(r'value="([A-Za-z0-9_-]+)"', select.group(0)))
source = (STATIC / "share.js").read_text()
table = re.search(r"const PRESETS = Object\.freeze\(\{(.*?)\}\);", source, re.S)
assert table, "share.js must define the PRESETS table"
# Top-level keys sit two spaces in; the fields inside a preset sit four
# spaces in, so anchoring on exactly two spaces selects the keys only.
keys = set(re.findall(r"^ ([a-z]+): \{$", table.group(1), re.M))
expected = {"sharp", "balanced", "light"}
assert options == expected, f"#quality offers {sorted(options)}, not {sorted(expected)}"
assert keys == expected, f"PRESETS keys are {sorted(keys)}, not {sorted(expected)}"
assert options == keys, "the select offers presets the table cannot resolve"
def test_sender_quality_is_reapplied_on_negotiation_and_connection(self):
"""`setParameters` is per-negotiation: a re-offer (a surface switch,
a viewer that re-joined) resets the encodings. The cap must therefore
be re-applied after `setLocalDescription` *and* when the leg becomes
`connected`. Both call sites are asserted so a future edit cannot drop
one and silently soften the wall."""
a viewer that re-joined) resets the encodings. The preset's cap and
degradation preference must therefore be re-applied after
`setLocalDescription` *and* when the leg becomes `connected`. Both
call sites are asserted so a future edit cannot drop one and silently
soften the wall. The preference itself stays a single, named
decision — the PRESETS table's `degradation` field, read at call
time — so a hardcoded literal at the call site would be a second,
drifting decision."""
source = (STATIC / "share.js").read_text()
assert source.count('"maintain-resolution"') == 1, (
"the degradation preference must be a single, named decision"
assert re.search(r'degradationPreference\s*=\s*"', source) is None, (
"a hardcoded degradation literal at a call site is a second decision"
)
assert "params.degradationPreference = PRESETS[currentPreset()].degradation" in source, (
"the preference is not read from the PRESETS table at call time"
)
assert "async function applySenderQuality" in source