fix: restore Python 3.10/3.11 support and report MCP version
Testing / test (3.10) (push) Successful in 48s
Testing / test (3.12) (push) Successful in 47s
Testing / test (3.14) (push) Successful in 1m7s
Testing / remote-protocol-compat (0.15.0) (push) Successful in 6m59s
Testing / remote-protocol-compat (0.16.0) (push) Successful in 6m47s
Testing / test (3.15) (push) Successful in 6m20s

- Drop backslash from f-string expression in markdown/html.py
- That syntax needs 3.12+ (PEP 701) and broke import on 3.10/3.11
- Add Python matrix 3.10/3.12/3.14/3.15 to the Gitea testing workflow
- Pass project_version() to MCPServer; serverInfo.version was empty
- Cover the reported version with an in-memory MCP client test
- Swap deprecated click.get_text_stream for sys.stdin.isatty()
- Add explicit encoding to native host shim and manifest writes
- Add explicit encoding to the registry lock file open
- Add explicit encoding to every read_text/write_text call in tests
- Bump version to 0.16.10 in pyproject.toml and extension/manifest.json
- Move .python-version to 3.15 and refresh uv.lock

Extension code is unchanged; only the manifest version string moved.

Verified with 778 tests on 3.10 through 3.15, plus a strict run using -X warn_default_encoding with EncodingWarning and DeprecationWarning as errors.
This commit is contained in:
2026-10-20 12:41:25 +02:00
parent 7cb034dfc2
commit 69387c1054
19 changed files with 155 additions and 88 deletions
+26 -26
View File
@@ -8,14 +8,14 @@ def read_built_background() -> str:
background_path = ROOT / "extension" / "background.js"
if not background_path.exists():
pytest.skip("extension/background.js is a generated build artifact; run npm run build:extension")
return background_path.read_text()
return background_path.read_text(encoding="utf-8")
def test_extension_retries_error_page_script_injection_before_failing():
# core.ts was split into a core/ subfolder during the structure refactor:
# the URL/error classifiers live in core/errors.ts and the executeScript
# retry wrapper (which calls isTransientScriptError) lives in core/scripting.ts.
errors = (ROOT / "extension" / "src" / "core" / "errors.ts").read_text()
scripting = (ROOT / "extension" / "src" / "core" / "scripting.ts").read_text()
errors = (ROOT / "extension" / "src" / "core" / "errors.ts").read_text(encoding="utf-8")
scripting = (ROOT / "extension" / "src" / "core" / "scripting.ts").read_text(encoding="utf-8")
assert "isBrowserErrorUrl" in errors
assert "isErrorPageScriptError" in errors
@@ -27,7 +27,7 @@ def test_extension_retries_error_page_script_injection_before_failing():
assert "isTransientScriptError(e)" in scripting
def test_read_only_dom_commands_have_error_page_fallbacks():
dom = (ROOT / "extension" / "src" / "commands" / "dom.ts").read_text()
dom = (ROOT / "extension" / "src" / "commands" / "dom.ts").read_text(encoding="utf-8")
assert "fallbackForErrorPageDomOp" in dom
assert 'case "domExists":' in dom
@@ -40,8 +40,8 @@ def test_read_only_dom_commands_have_error_page_fallbacks():
def test_navigation_and_tabs_report_browser_error_pages():
# tabs.watch_url (which reports tab error pages) moved into the read-only
# TabsQueryCommands class in tabs-query.ts during the structure refactor.
tabs = (ROOT / "extension" / "src" / "commands" / "tabs-query.ts").read_text()
navigation = (ROOT / "extension" / "src" / "commands" / "navigation.ts").read_text()
tabs = (ROOT / "extension" / "src" / "commands" / "tabs-query.ts").read_text(encoding="utf-8")
navigation = (ROOT / "extension" / "src" / "commands" / "navigation.ts").read_text(encoding="utf-8")
assert "lastUrl" in tabs
assert "lastStatus" in tabs
@@ -56,10 +56,10 @@ def test_large_extension_operations_yield_between_batches():
# batch loop (cancel-check -> batch call -> progress -> yield) was then
# centralized into the processInBatches() helper in core/throttle.ts, so the
# command modules call that instead of inlining the loop.
core = (ROOT / "extension" / "src" / "core" / "throttle.ts").read_text()
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
groups = (ROOT / "extension" / "src" / "commands" / "groups.ts").read_text()
session = (ROOT / "extension" / "src" / "commands" / "session.ts").read_text()
core = (ROOT / "extension" / "src" / "core" / "throttle.ts").read_text(encoding="utf-8")
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text(encoding="utf-8")
groups = (ROOT / "extension" / "src" / "commands" / "groups.ts").read_text(encoding="utf-8")
session = (ROOT / "extension" / "src" / "commands" / "session.ts").read_text(encoding="utf-8")
assert "yieldForLargeOperation" in core
assert "getLargeOperationThrottle" in core
@@ -105,10 +105,10 @@ def test_large_extension_operations_yield_between_batches():
# and the command registry/groups during the class-based refactor. The
# behavior is identical; assert the defining patterns still exist in their
# new homes.
jobs_module = (ROOT / "extension" / "src" / "classes" / "JobManager.ts").read_text()
connection = (ROOT / "extension" / "src" / "classes" / "NativeConnection.ts").read_text()
perf = (ROOT / "extension" / "src" / "commands" / "perf.ts").read_text()
tabs_module = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
jobs_module = (ROOT / "extension" / "src" / "classes" / "JobManager.ts").read_text(encoding="utf-8")
connection = (ROOT / "extension" / "src" / "classes" / "NativeConnection.ts").read_text(encoding="utf-8")
perf = (ROOT / "extension" / "src" / "commands" / "perf.ts").read_text(encoding="utf-8")
tabs_module = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text(encoding="utf-8")
assert "background: true" in tabs_module # background command flag (was BACKGROUND_COMMANDS)
assert "persistJobs" in jobs_module
@@ -120,8 +120,8 @@ def test_large_extension_operations_yield_between_batches():
assert "__background" in connection
def test_tab_activation_open_and_merge_do_not_steal_audible_video_window():
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text()
navigation = (ROOT / "extension" / "src" / "commands" / "navigation.ts").read_text()
tabs = (ROOT / "extension" / "src" / "commands" / "tabs.ts").read_text(encoding="utf-8")
navigation = (ROOT / "extension" / "src" / "commands" / "navigation.ts").read_text(encoding="utf-8")
assert "await chrome.windows.update(tab.windowId, { focused: true });" not in tabs
assert "active: Boolean(focus) && !background" in navigation
@@ -151,8 +151,8 @@ def test_session_autosave_is_debounced_and_non_overlapping():
# AutoSaveManager (autosave.ts) during the structure refactor; the shared
# snapshot/signature helpers live in session-snapshot.ts. Behavior is
# identical — the defining patterns just live in their new homes.
autosave = (ROOT / "extension" / "src" / "commands" / "autosave.ts").read_text()
snapshot = (ROOT / "extension" / "src" / "commands" / "session-snapshot.ts").read_text()
autosave = (ROOT / "extension" / "src" / "commands" / "autosave.ts").read_text(encoding="utf-8")
snapshot = (ROOT / "extension" / "src" / "commands" / "session-snapshot.ts").read_text(encoding="utf-8")
assert "autoSaveTimer" in autosave
assert "autoSaveInFlight" in autosave
@@ -172,12 +172,12 @@ def test_session_autosave_is_debounced_and_non_overlapping():
assert "clearTimeout(this.autoSaveTimer)" in autosave
def test_cli_and_sdk_expose_gentle_restore_controls():
session_cli = (ROOT / "browser_cli" / "commands" / "session.py").read_text()
tabs_cli = (ROOT / "browser_cli" / "commands" / "tabs.py").read_text()
groups_cli = (ROOT / "browser_cli" / "commands" / "groups.py").read_text()
commands_init = (ROOT / "browser_cli" / "commands" / "__init__.py").read_text()
sdk_session = (ROOT / "browser_cli" / "sdk" / "session.py").read_text()
sdk_perf = (ROOT / "browser_cli" / "sdk" / "perf.py").read_text()
session_cli = (ROOT / "browser_cli" / "commands" / "session.py").read_text(encoding="utf-8")
tabs_cli = (ROOT / "browser_cli" / "commands" / "tabs.py").read_text(encoding="utf-8")
groups_cli = (ROOT / "browser_cli" / "commands" / "groups.py").read_text(encoding="utf-8")
commands_init = (ROOT / "browser_cli" / "commands" / "__init__.py").read_text(encoding="utf-8")
sdk_session = (ROOT / "browser_cli" / "sdk" / "session.py").read_text(encoding="utf-8")
sdk_perf = (ROOT / "browser_cli" / "sdk" / "perf.py").read_text(encoding="utf-8")
# The --gentle-mode flag is defined once via the shared gentle_mode_option helper.
assert '"--gentle-mode"' in commands_init
@@ -200,8 +200,8 @@ def test_cli_and_sdk_expose_gentle_restore_controls():
assert "def status" in sdk_perf
assert "def set_profile" in sdk_perf
perf_cli = (ROOT / "browser_cli" / "commands" / "perf.py").read_text()
root_cli = (ROOT / "browser_cli" / "cli.py").read_text()
perf_cli = (ROOT / "browser_cli" / "commands" / "perf.py").read_text(encoding="utf-8")
root_cli = (ROOT / "browser_cli" / "cli.py").read_text(encoding="utf-8")
assert "perf_group" in perf_cli
assert "perf.status" in perf_cli
assert "perf.set_profile" in perf_cli