Release exp-v0.52.54: stabilize cross-platform WebUI test suite (#5970) (#5984)

* test: stabilize cross-platform WebUI suite

* Release exp-v0.52.54: stabilize cross-platform WebUI test suite (#5970)

---------

Co-authored-by: Taha Bakhit <git@tahabakhit.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
This commit is contained in:
nesquena-hermes 2026-07-11 23:48:38 -07:00 committed by GitHub
parent 286277ab3d
commit 8f38bc0bf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 45 deletions

View file

@ -13,6 +13,8 @@
### Fixed
- **Cross-platform test suite stabilized.** The profile skill-statistics coverage test now selects a genuinely-incompatible platform per-OS (so the incompatibility assertion holds on macOS as well as Linux), and eight pytest class-scoped instance-fixture deprecation warnings were removed by switching to state-free module fixtures. Test-only; no product code changes. Thanks @tahabakhit. (#5970)
- **The transcript no longer jumps up while you type in a multi-row composer.** When the composer had grown to multiple rows and you were scrolled to the bottom, every keystroke nudged the transcript upward (by roughly the composer's height) and quietly broke stream auto-follow — you'd have to scroll back down. The auto-resize now preserves the scroll position of a bottom-pinned reader, so typing keeps you pinned and streaming keeps following. Readers who deliberately scrolled up are unaffected. Thanks @JeffArcLogic for the report and repro. (#5962, #5514)
- **The reasoning-effort chip is correct right after an empty startup.** On a normal boot with no active session restored, the top-bar reasoning chip wasn't refreshed for the hydrated default model, so it could show a stale value until you changed the model. It now refreshes on empty boot too. Thanks @rodboev. (#5967, #5954)

View file

@ -40,6 +40,11 @@ NODE = shutil.which("node")
pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")
@pytest.fixture(scope="module")
def commands_src() -> str:
return COMMANDS_JS_PATH.read_text(encoding="utf-8")
# ── driver for _findModelInDropdown (ui.js) ─────────────────────────────────
_FIND_DRIVER = r"""
@ -302,12 +307,7 @@ class TestDidYouMeanToastAssembly:
And no_model_match already ends with an opening quote, so cmdModel must close
with `${args}"`, not `"${args}"` (which doubled the quote)."""
@pytest.fixture(scope="class")
def commands_src(self):
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
return (root / "static" / "commands.js").read_text(encoding="utf-8")
# Uses the module-scoped commands_src fixture above.
def test_toast_passes_suggestion_as_t_argument(self, commands_src):
assert "t('model_did_you_mean', suggestion)" in commands_src, (
"cmdModel must pass the suggestion into t() so the template fills in "
@ -342,12 +342,7 @@ class TestSlashQualifiedVersionedNoSnap:
no-match/'did you mean?' toast. The cross-provider direct update is gated on
`!versionedNoSnap` so genuinely-off-catalog providers (no near variant) still work."""
@pytest.fixture(scope="class")
def commands_src(self):
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
return (root / "static" / "commands.js").read_text(encoding="utf-8")
# Uses the module-scoped commands_src fixture above.
def test_cross_provider_direct_update_gated_on_versioned_no_snap(self, commands_src):
# The guard variable is computed from the version check + a near suggestion.
assert "_looksLikeVersionedModel(bare)" in commands_src
@ -470,12 +465,7 @@ class TestCmdModelFullCatalogWiring:
"""Source-level guards: cmdModel must build candidates from the catalog and
inject the option before selecting, so an extras-only winner is honored."""
@pytest.fixture(scope="class")
def commands_src(self):
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
return (root / "static" / "commands.js").read_text(encoding="utf-8")
# Uses the module-scoped commands_src fixture above.
def test_builds_candidates_from_catalog_groups(self, commands_src):
assert "_buildModelCandidates(sel,modelsData&&modelsData.groups)" in commands_src

View file

@ -20,6 +20,37 @@ from tests.conftest import requires_agent_modules
TEST_BASE = f"http://127.0.0.1:{os.environ.get('HERMES_WEBUI_TEST_PORT', '8788')}"
def _read_static_file(name: str) -> str:
return (pathlib.Path(__file__).resolve().parents[1] / "static" / name).read_text(
encoding="utf-8"
)
@pytest.fixture(scope="module")
def commands_js():
return _read_static_file("commands.js")
@pytest.fixture(scope="module")
def messages_js():
return _read_static_file("messages.js")
@pytest.fixture(scope="module")
def index_html():
return _read_static_file("index.html")
@pytest.fixture(scope="module")
def style_css():
return _read_static_file("style.css")
@pytest.fixture(scope="module")
def i18n_js():
return _read_static_file("i18n.js")
def _get(path, expect_ok=True):
import urllib.request, urllib.error
try:
@ -132,11 +163,6 @@ class TestYoloEndpointPost:
class TestYoloCommandRegistration:
"""/yolo slash command should be registered in commands.js."""
@pytest.fixture(scope="class")
def commands_js(self):
with open("static/commands.js", "r", encoding="utf-8") as f:
return f.read()
def test_yolo_command_in_array(self, commands_js):
assert "'yolo'" in commands_js or '"yolo"' in commands_js
@ -153,11 +179,6 @@ class TestYoloCommandRegistration:
class TestYoloBusySendPath:
"""/yolo should be recognized by the busy-send command intercept."""
@pytest.fixture(scope="class")
def messages_js(self):
with open("static/messages.js", "r", encoding="utf-8") as f:
return f.read()
def test_yolo_in_busy_send_allowlist(self, messages_js):
send_idx = messages_js.find("async function send(")
assert send_idx >= 0, "send() not found in messages.js"
@ -186,11 +207,6 @@ class TestYoloBusySendPath:
class TestYoloPillHTML:
"""YOLO pill element should exist in index.html."""
@pytest.fixture(scope="class")
def index_html(self):
with open("static/index.html", "r", encoding="utf-8") as f:
return f.read()
def test_yolo_pill_element_exists(self, index_html):
assert 'id="yoloPill"' in index_html
@ -209,11 +225,6 @@ class TestYoloPillHTML:
class TestYoloCSS:
"""YOLO-related CSS classes should exist."""
@pytest.fixture(scope="class")
def style_css(self):
with open("static/style.css", "r", encoding="utf-8") as f:
return f.read()
def test_yolo_pill_class(self, style_css):
assert ".yolo-pill{" in style_css or ".yolo-pill {" in style_css
@ -240,11 +251,6 @@ class TestYoloI18n:
LOCALES = ["en", "ru", "es", "de", "zh", "ko"]
@pytest.fixture(scope="class")
def i18n_js(self):
with open("static/i18n.js", "r", encoding="utf-8") as f:
return f.read()
@pytest.mark.parametrize("locale", LOCALES)
def test_locale_has_all_yolo_keys(self, i18n_js, locale):
pattern = rf"\s{locale}:\s*\{{"

View file

@ -1,3 +1,4 @@
import sys
from pathlib import Path
import yaml
from api import profiles
@ -25,11 +26,12 @@ def test_get_profile_skills_stats(tmp_path):
# Setup skills directory with:
# 1 compatible & enabled skill ("alpha")
# 1 compatible & disabled skill ("beta")
# 1 incompatible skill ("gamma" - macos only on linux test run)
# 1 incompatible skill ("gamma" is explicitly tagged for another OS)
profile_home = tmp_path / "auditor"
_write_skill(profile_home, "alpha")
_write_skill(profile_home, "beta")
_write_skill(profile_home, "gamma", platforms=["macos"])
incompatible_platform = "linux" if sys.platform == "darwin" else "macos"
_write_skill(profile_home, "gamma", platforms=[incompatible_platform])
_write_config(profile_home, ["beta"])
# Explicitly clear the stats cache to ensure we compute fresh