fix(clients): flatten scoped remote output
- Render explicit --remote clients results with profile-only labels. - Suppress remote host group headers for scoped client queries. - Keep global and mixed client listings grouped by host. - Update client and CLI tests for scoped remote rendering. - Bump browser-cli and extension versions to 0.16.5.
This commit is contained in:
@@ -151,21 +151,25 @@ def active_browser_targets(*, include_remotes: bool = True, key=None, suppress_p
|
|||||||
targets.extend(_remote_browser_targets(key=key, suppress_pq_warning=suppress_pq_warning))
|
targets.extend(_remote_browser_targets(key=key, suppress_pq_warning=suppress_pq_warning))
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
def _cached_client_row(target: BrowserTarget) -> dict | None:
|
def _cached_client_row(target: BrowserTarget, *, scoped: bool = False) -> dict | None:
|
||||||
"""Build a clients row from a target's discovery data, skipping a roundtrip.
|
"""Build a clients row from a target's discovery data, skipping a roundtrip.
|
||||||
|
|
||||||
Returns None when the remote didn't advertise its version (older serve), so
|
Returns None when the remote didn't advertise its version (older serve), so
|
||||||
callers fall back to an explicit ``clients.list`` query.
|
callers fall back to an explicit ``clients.list`` query. When *scoped* is
|
||||||
|
true, the caller already selected one remote host, so render profile-only
|
||||||
|
labels instead of adding a host group header.
|
||||||
"""
|
"""
|
||||||
if target.version is None and target.extension_version is None:
|
if target.version is None and target.extension_version is None:
|
||||||
return None
|
return None
|
||||||
return {
|
row = {
|
||||||
"profile": target.display_name,
|
"profile": target.profile if scoped else target.display_name,
|
||||||
"profileGroup": target.display_group,
|
|
||||||
"name": target.browser_name or "",
|
"name": target.browser_name or "",
|
||||||
"version": target.version or "",
|
"version": target.version or "",
|
||||||
"extensionVersion": target.extension_version or "",
|
"extensionVersion": target.extension_version or "",
|
||||||
}
|
}
|
||||||
|
if target.display_group and not scoped:
|
||||||
|
row["profileGroup"] = target.display_group
|
||||||
|
return row
|
||||||
|
|
||||||
def _rows_from_result(result, label: str, profile_group: str | None) -> list[dict]:
|
def _rows_from_result(result, label: str, profile_group: str | None) -> list[dict]:
|
||||||
rows = []
|
rows = []
|
||||||
@@ -249,18 +253,17 @@ def collect_browser_clients(
|
|||||||
if targets:
|
if targets:
|
||||||
uncached = []
|
uncached = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
cached = _cached_client_row(target)
|
cached = _cached_client_row(target, scoped=True)
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
rows.append(cached)
|
rows.append(cached)
|
||||||
else:
|
else:
|
||||||
uncached.append(target)
|
uncached.append(target)
|
||||||
results = _run_concurrent([
|
results = _run_concurrent([
|
||||||
(lambda t=t: _client_rows_async(
|
(lambda t=t: _client_rows_async(
|
||||||
t.display_name,
|
t.profile,
|
||||||
profile=t.profile,
|
profile=t.profile,
|
||||||
remote=remote,
|
remote=remote,
|
||||||
key=key,
|
key=key,
|
||||||
profile_group=t.display_group,
|
|
||||||
))
|
))
|
||||||
for t in uncached
|
for t in uncached
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "browser-cli",
|
"name": "browser-cli",
|
||||||
"version": "0.16.4",
|
"version": "0.16.5",
|
||||||
"description": "Control your browser from the terminal or Python SDK",
|
"description": "Control your browser from the terminal or Python SDK",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "real-browser-cli"
|
name = "real-browser-cli"
|
||||||
version = "0.16.4"
|
version = "0.16.5"
|
||||||
description = "Control your real running browser from the terminal or Python SDK"
|
description = "Control your real running browser from the terminal or Python SDK"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
|
|||||||
+34
-11
@@ -294,29 +294,52 @@ def test_clients_reads_registry_with_trailing_garbage(tmp_path):
|
|||||||
assert "0.8.2" in result.output
|
assert "0.8.2" in result.output
|
||||||
|
|
||||||
def test_clients_remote_uses_remote_endpoint_without_local_registry():
|
def test_clients_remote_uses_remote_endpoint_without_local_registry():
|
||||||
def fake_send_command(command, args=None, profile=None, remote=None, key=None):
|
target = BrowserTarget(
|
||||||
assert command == "clients.list"
|
profile="work",
|
||||||
assert profile is None
|
display_name="127.0.0.1:work",
|
||||||
assert remote == "127.0.0.1:8765"
|
socket_path="",
|
||||||
return [{"name": "Chrome", "version": "1", "extensionVersion": "2.3.4"}]
|
remote="127.0.0.1:8765",
|
||||||
|
browser_name="Chrome",
|
||||||
|
display_group="127.0.0.1",
|
||||||
|
version="1",
|
||||||
|
extension_version="2.3.4",
|
||||||
|
)
|
||||||
|
|
||||||
with patch.dict(os.environ, {}, clear=True), patch(
|
with patch.dict(os.environ, {}, clear=True), patch(
|
||||||
"browser_cli.commands.clients.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json")
|
"browser_cli.commands.clients.REGISTRY_PATH", Path("/nonexistent/browser-cli-registry.json")
|
||||||
), patch("browser_cli.client.core.send_command", side_effect=fake_send_command) as send_command:
|
), patch("browser_cli.client.core.remote_browser_targets", return_value=[target]), patch(
|
||||||
|
"browser_cli.client.core.send_command"
|
||||||
|
) as send_command:
|
||||||
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "clients"])
|
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "clients"])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
send_command.assert_called_once()
|
send_command.assert_not_called()
|
||||||
assert "remote" in result.output
|
assert "work" in result.output
|
||||||
|
assert "127.0.0.1" not in result.output
|
||||||
assert "Chrome" in result.output
|
assert "Chrome" in result.output
|
||||||
assert "2.3.4" in result.output
|
assert "2.3.4" in result.output
|
||||||
|
|
||||||
def test_clients_remote_respects_global_browser_route():
|
def test_clients_remote_respects_global_browser_route():
|
||||||
with patch.dict(os.environ, {}, clear=True), patch("browser_cli.client.core.send_command", return_value=[]) as send_command:
|
target = BrowserTarget(
|
||||||
|
profile="work",
|
||||||
|
display_name="127.0.0.1:work",
|
||||||
|
socket_path="",
|
||||||
|
remote="127.0.0.1:8765",
|
||||||
|
browser_name="Chrome",
|
||||||
|
display_group="127.0.0.1",
|
||||||
|
version="1",
|
||||||
|
extension_version="2.3.4",
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(os.environ, {}, clear=True), patch(
|
||||||
|
"browser_cli.client.core.remote_browser_targets", return_value=[target]
|
||||||
|
), patch("browser_cli.client.core.send_command") as send_command:
|
||||||
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "--browser", "work", "clients"])
|
result = CliRunner().invoke(main, ["--remote", "127.0.0.1:8765", "--browser", "work", "clients"])
|
||||||
|
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 0
|
||||||
send_command.assert_called_once_with("clients.list", profile="work", remote="127.0.0.1:8765", key=None)
|
send_command.assert_not_called()
|
||||||
|
assert "work" in result.output
|
||||||
|
assert "127.0.0.1" not in result.output
|
||||||
|
|
||||||
def test_clients_browser_alias_resolves_to_remote():
|
def test_clients_browser_alias_resolves_to_remote():
|
||||||
"""--browser <host> without --remote resolves the alias, fetches all targets from that remote,
|
"""--browser <host> without --remote resolves the alias, fetches all targets from that remote,
|
||||||
|
|||||||
@@ -681,7 +681,8 @@ def test_collect_browser_clients_with_explicit_remote_lists_all_targets(monkeypa
|
|||||||
|
|
||||||
rows = collect_browser_clients(remote="browser-host.example:8765", registry_path=tmp_path / "missing-registry.json")
|
rows = collect_browser_clients(remote="browser-host.example:8765", registry_path=tmp_path / "missing-registry.json")
|
||||||
|
|
||||||
assert [row["profile"] for row in rows] == ["browser-host.example:main", "browser-host.example:work"]
|
assert [row["profile"] for row in rows] == ["main", "work"]
|
||||||
|
assert [row.get("profileGroup") for row in rows] == [None, None]
|
||||||
assert [row["name"] for row in rows] == ["Chrome", "Firefox"]
|
assert [row["name"] for row in rows] == ["Chrome", "Firefox"]
|
||||||
|
|
||||||
def test_collect_browser_clients_with_explicit_remote_and_browser_filters_target(monkeypatch, tmp_path):
|
def test_collect_browser_clients_with_explicit_remote_and_browser_filters_target(monkeypatch, tmp_path):
|
||||||
@@ -696,7 +697,8 @@ def test_collect_browser_clients_with_explicit_remote_and_browser_filters_target
|
|||||||
|
|
||||||
rows = collect_browser_clients(remote="browser-host.example:8765", browser_alias="work", registry_path=tmp_path / "missing-registry.json")
|
rows = collect_browser_clients(remote="browser-host.example:8765", browser_alias="work", registry_path=tmp_path / "missing-registry.json")
|
||||||
|
|
||||||
assert [row["profile"] for row in rows] == ["browser-host.example:work"]
|
assert [row["profile"] for row in rows] == ["work"]
|
||||||
|
assert rows[0].get("profileGroup") is None
|
||||||
|
|
||||||
def test_collect_browser_clients_falls_back_when_version_unknown(monkeypatch, tmp_path):
|
def test_collect_browser_clients_falls_back_when_version_unknown(monkeypatch, tmp_path):
|
||||||
"""An older remote (no advertised version) still triggers a clients.list query."""
|
"""An older remote (no advertised version) still triggers a clients.list query."""
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "real-browser-cli"
|
name = "real-browser-cli"
|
||||||
version = "0.16.4"
|
version = "0.16.5"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
|||||||
Reference in New Issue
Block a user