test: expand browser command coverage
- Add mocked Click CLI tests across DOM, cookies, page, storage, perf, navigation, tabs, groups, sessions, and windows commands. - Cover integration paths for cookies, page info, performance profiles, and web storage commands. - Extend DOM integration coverage for eval, scrolling, waiting, focus, hover, typing, selection, keyboard, and checkbox interactions. - Add native host unit coverage for message framing, socket helpers, paging guards, timeouts, and profile alias resolution.
This commit is contained in:
@@ -0,0 +1,993 @@
|
||||
"""
|
||||
Unit tests for CLI command modules.
|
||||
All send_command calls are mocked — no live browser required.
|
||||
Uses Click's CliRunner to exercise the CLI output formatting paths.
|
||||
"""
|
||||
from unittest.mock import patch, MagicMock
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _run(group, args, return_value):
|
||||
"""Invoke a Click group/command with a mocked send_command return value."""
|
||||
with patch("browser_cli.commands.send_command", return_value=return_value):
|
||||
result = CliRunner().invoke(group, args)
|
||||
return result
|
||||
|
||||
def _run_error(group, args, exc):
|
||||
"""Invoke a Click group/command where send_command raises exc."""
|
||||
with patch("browser_cli.commands.send_command", side_effect=exc):
|
||||
result = CliRunner().invoke(group, args)
|
||||
return result
|
||||
|
||||
def _run_no_multi(group, args, return_value, module_path=None):
|
||||
"""Invoke a command patching send_command AND _multi_browser_targets → single-browser mode."""
|
||||
ctx1 = patch("browser_cli.commands.send_command", return_value=return_value)
|
||||
if module_path:
|
||||
ctx2 = patch(f"{module_path}._multi_browser_targets", return_value=[])
|
||||
with ctx1, ctx2:
|
||||
return CliRunner().invoke(group, args)
|
||||
else:
|
||||
with ctx1:
|
||||
return CliRunner().invoke(group, args)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# dom commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.dom import dom_group
|
||||
|
||||
def test_cli_dom_query_no_results():
|
||||
result = _run(dom_group, ["query", "span.nothing"], [])
|
||||
assert result.exit_code == 0
|
||||
assert "No elements found" in result.output
|
||||
|
||||
def test_cli_dom_query_with_results():
|
||||
elements = [{"tag": "div", "text": "hello", "attrs": {"class": "foo"}}]
|
||||
result = _run(dom_group, ["query", "div"], elements)
|
||||
assert result.exit_code == 0
|
||||
assert "div" in result.output
|
||||
|
||||
def test_cli_dom_click():
|
||||
result = _run(dom_group, ["click", "button#submit"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Clicked" in result.output
|
||||
|
||||
def test_cli_dom_type():
|
||||
result = _run(dom_group, ["type", "input#name", "pytest"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Typed into" in result.output
|
||||
|
||||
def test_cli_dom_attr():
|
||||
result = _run(dom_group, ["attr", "a", "href"], ["https://example.com"])
|
||||
assert result.exit_code == 0
|
||||
assert "https://example.com" in result.output
|
||||
|
||||
def test_cli_dom_attr_empty():
|
||||
result = _run(dom_group, ["attr", "span", "href"], [])
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_dom_text():
|
||||
result = _run(dom_group, ["text", "h1"], ["Example Domain"])
|
||||
assert result.exit_code == 0
|
||||
assert "Example Domain" in result.output
|
||||
|
||||
def test_cli_dom_text_empty():
|
||||
result = _run(dom_group, ["text", "h9"], [])
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_dom_exists_true():
|
||||
result = _run(dom_group, ["exists", "html"], True)
|
||||
assert result.exit_code == 0
|
||||
assert "exists" in result.output
|
||||
|
||||
def test_cli_dom_exists_false():
|
||||
result = _run(dom_group, ["exists", "#no-such"], False)
|
||||
assert result.exit_code != 0
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_cli_dom_scroll_coord():
|
||||
result = _run(dom_group, ["scroll", "--x", "0", "--y", "500"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Scrolled" in result.output
|
||||
|
||||
def test_cli_dom_scroll_selector():
|
||||
result = _run(dom_group, ["scroll", "footer"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Scrolled" in result.output
|
||||
|
||||
def test_cli_dom_eval_string():
|
||||
result = _run(dom_group, ["eval", "document.title"], "My Page")
|
||||
assert result.exit_code == 0
|
||||
assert "My Page" in result.output
|
||||
|
||||
def test_cli_dom_eval_null():
|
||||
result = _run(dom_group, ["eval", "void 0"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "null" in result.output
|
||||
|
||||
def test_cli_dom_eval_dict():
|
||||
result = _run(dom_group, ["eval", "({a:1})"], {"a": 1})
|
||||
assert result.exit_code == 0
|
||||
assert '"a"' in result.output
|
||||
|
||||
def test_cli_dom_wait_for():
|
||||
result = _run(dom_group, ["wait-for", "html"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Ready" in result.output
|
||||
|
||||
def test_cli_dom_wait_for_visible():
|
||||
result = _run(dom_group, ["wait-for", "html", "--visible"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "visible" in result.output
|
||||
|
||||
def test_cli_dom_wait_for_hidden():
|
||||
result = _run(dom_group, ["wait-for", "#spinner", "--hidden"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "hidden" in result.output
|
||||
|
||||
def test_cli_dom_key():
|
||||
result = _run(dom_group, ["key", "Enter"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Key 'Enter'" in result.output
|
||||
|
||||
def test_cli_dom_key_with_selector():
|
||||
result = _run(dom_group, ["key", "Tab", "--selector", "input"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "input" in result.output
|
||||
|
||||
def test_cli_dom_hover():
|
||||
result = _run(dom_group, ["hover", ".menu"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Hovered" in result.output
|
||||
|
||||
def test_cli_dom_check():
|
||||
result = _run(dom_group, ["check", "#accept"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Checked" in result.output
|
||||
|
||||
def test_cli_dom_uncheck():
|
||||
result = _run(dom_group, ["uncheck", "#accept"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Unchecked" in result.output
|
||||
|
||||
def test_cli_dom_clear():
|
||||
result = _run(dom_group, ["clear", "input#q"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Cleared" in result.output
|
||||
|
||||
def test_cli_dom_focus():
|
||||
result = _run(dom_group, ["focus", "input#q"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Focused" in result.output
|
||||
|
||||
def test_cli_dom_submit():
|
||||
result = _run(dom_group, ["submit", "form"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Submitted" in result.output
|
||||
|
||||
def test_cli_dom_select():
|
||||
result = _run(dom_group, ["select", "#lang", "en"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Selected" in result.output
|
||||
|
||||
def test_cli_dom_poll():
|
||||
result = _run(dom_group, ["poll", "#status", "ready"], {"value": "ready"})
|
||||
assert result.exit_code == 0
|
||||
assert "Matched" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# cookies commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.cookies import cookies_group
|
||||
|
||||
def test_cli_cookies_list_empty():
|
||||
result = _run(cookies_group, ["list"], [])
|
||||
assert result.exit_code == 0
|
||||
assert "No cookies found" in result.output
|
||||
|
||||
def test_cli_cookies_list_with_cookies():
|
||||
cookies = [{"name": "session", "value": "abc123", "domain": "example.com",
|
||||
"path": "/", "secure": True, "httpOnly": False}]
|
||||
result = _run(cookies_group, ["list"], cookies)
|
||||
assert result.exit_code == 0
|
||||
assert "session" in result.output
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_cookies_list_filter_url():
|
||||
cookies = [{"name": "x", "value": "y", "domain": "example.com",
|
||||
"path": "/", "secure": False, "httpOnly": False}]
|
||||
result = _run(cookies_group, ["list", "--url", "https://example.com"], cookies)
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_cookies_list_filter_domain():
|
||||
cookies = [{"name": "x", "value": "y", "domain": "example.com",
|
||||
"path": "/", "secure": False, "httpOnly": False}]
|
||||
result = _run(cookies_group, ["list", "--domain", "example.com"], cookies)
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_cookies_list_filter_name():
|
||||
cookies = [{"name": "session", "value": "abc", "domain": "example.com",
|
||||
"path": "/", "secure": False, "httpOnly": True}]
|
||||
result = _run(cookies_group, ["list", "--name", "session"], cookies)
|
||||
assert result.exit_code == 0
|
||||
assert "session" in result.output
|
||||
|
||||
def test_cli_cookies_get_found():
|
||||
cookie = {"name": "tok", "value": "secret123", "domain": "example.com", "path": "/"}
|
||||
result = _run(cookies_group, ["get", "https://example.com", "tok"], cookie)
|
||||
assert result.exit_code == 0
|
||||
assert "secret123" in result.output
|
||||
|
||||
def test_cli_cookies_get_not_found():
|
||||
result = _run(cookies_group, ["get", "https://example.com", "missing"], None)
|
||||
assert result.exit_code != 0
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_cli_cookies_set():
|
||||
result = _run(cookies_group, ["set", "https://example.com", "tok", "val"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Set cookie" in result.output
|
||||
|
||||
def test_cli_cookies_set_with_options():
|
||||
result = _run(cookies_group, [
|
||||
"set", "https://example.com", "tok", "val",
|
||||
"--secure", "--http-only", "--path", "/app",
|
||||
"--same-site", "lax",
|
||||
], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Set cookie" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# page commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.page import page_group
|
||||
|
||||
def test_cli_page_info_basic():
|
||||
info = {"title": "Example", "url": "https://example.com", "readyState": "complete", "lang": "en", "meta": {}}
|
||||
result = _run(page_group, ["info"], info)
|
||||
assert result.exit_code == 0
|
||||
assert "Example" in result.output
|
||||
assert "https://example.com" in result.output
|
||||
|
||||
def test_cli_page_info_with_meta():
|
||||
info = {"title": "My Page", "url": "https://example.com", "readyState": "complete", "lang": "de",
|
||||
"meta": {"description": "A test page", "author": "Tester"}}
|
||||
result = _run(page_group, ["info"], info)
|
||||
assert result.exit_code == 0
|
||||
assert "description" in result.output
|
||||
assert "A test page" in result.output
|
||||
|
||||
def test_cli_page_info_empty_response():
|
||||
result = _run(page_group, ["info"], {})
|
||||
assert result.exit_code == 0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# storage commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.storage import storage_group
|
||||
|
||||
def test_cli_storage_get_null():
|
||||
result = _run(storage_group, ["get", "mykey"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "null" in result.output
|
||||
|
||||
def test_cli_storage_get_string():
|
||||
result = _run(storage_group, ["get", "mykey"], "hello-world")
|
||||
assert result.exit_code == 0
|
||||
assert "hello-world" in result.output
|
||||
|
||||
def test_cli_storage_get_dict():
|
||||
result = _run(storage_group, ["get"], {"k1": "v1", "k2": "v2"})
|
||||
assert result.exit_code == 0
|
||||
assert "k1" in result.output
|
||||
|
||||
def test_cli_storage_get_session():
|
||||
result = _run(storage_group, ["get", "key", "--type", "session"], "ses-val")
|
||||
assert result.exit_code == 0
|
||||
assert "ses-val" in result.output
|
||||
|
||||
def test_cli_storage_set():
|
||||
result = _run(storage_group, ["set", "mykey", "myvalue"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Set" in result.output
|
||||
assert "mykey" in result.output
|
||||
|
||||
def test_cli_storage_set_session():
|
||||
result = _run(storage_group, ["set", "sk", "sv", "--type", "session"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "session" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# perf commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.perf import perf_group
|
||||
|
||||
def test_cli_perf_status_no_jobs():
|
||||
status = {"performanceProfile": "auto", "audible": False,
|
||||
"throttle": {"batchSize": 10, "pauseMs": 50, "mode": "auto"}, "jobs": []}
|
||||
result = _run(perf_group, ["status"], status)
|
||||
assert result.exit_code == 0
|
||||
assert "auto" in result.output
|
||||
assert "No running jobs" in result.output
|
||||
|
||||
def test_cli_perf_status_with_jobs():
|
||||
status = {
|
||||
"performanceProfile": "gentle",
|
||||
"audible": True,
|
||||
"throttle": {"batchSize": 5, "pauseMs": 200, "mode": "gentle"},
|
||||
"jobs": [
|
||||
{"id": "j1", "command": "session.load", "status": "running",
|
||||
"current": 3, "total": 10, "percent": 30, "phase": "open_tabs"},
|
||||
],
|
||||
}
|
||||
result = _run(perf_group, ["status"], status)
|
||||
assert result.exit_code == 0
|
||||
assert "j1" in result.output
|
||||
assert "session.load" in result.output
|
||||
assert "30%" in result.output
|
||||
|
||||
def test_cli_perf_profile_set():
|
||||
result = _run(perf_group, ["profile", "normal"], {"performanceProfile": "normal"})
|
||||
assert result.exit_code == 0
|
||||
assert "normal" in result.output
|
||||
|
||||
def test_cli_perf_profile_gentle():
|
||||
result = _run(perf_group, ["profile", "gentle"], {"performanceProfile": "gentle"})
|
||||
assert result.exit_code == 0
|
||||
assert "gentle" in result.output
|
||||
|
||||
def test_cli_perf_profile_ultra():
|
||||
result = _run(perf_group, ["profile", "ultra"], {"performanceProfile": "ultra"})
|
||||
assert result.exit_code == 0
|
||||
assert "ultra" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# navigate commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.navigate import nav_group
|
||||
|
||||
def test_cli_nav_open():
|
||||
result = _run(nav_group, ["open", "https://example.com"], {"id": 42, "url": "https://example.com"})
|
||||
assert result.exit_code == 0
|
||||
assert "Opened" in result.output
|
||||
|
||||
def test_cli_nav_open_bg():
|
||||
result = _run(nav_group, ["open", "https://example.com", "--bg"], {"id": 42})
|
||||
assert result.exit_code == 0
|
||||
assert "Opened" in result.output
|
||||
|
||||
def test_cli_nav_open_with_group():
|
||||
result = _run(nav_group, ["open", "https://example.com", "--group", "work"], {"id": 42})
|
||||
assert result.exit_code == 0
|
||||
assert "work" in result.output
|
||||
|
||||
def test_cli_nav_open_with_window():
|
||||
result = _run(nav_group, ["open", "https://example.com", "--window", "main"], {"id": 42})
|
||||
assert result.exit_code == 0
|
||||
assert "main" in result.output
|
||||
|
||||
def test_cli_nav_reload():
|
||||
result = _run(nav_group, ["reload"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Reloaded" in result.output
|
||||
|
||||
def test_cli_nav_hard_reload():
|
||||
result = _run(nav_group, ["hard-reload"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "Hard reloaded" in result.output
|
||||
|
||||
def test_cli_nav_back():
|
||||
result = _run(nav_group, ["back"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "back" in result.output.lower()
|
||||
|
||||
def test_cli_nav_forward():
|
||||
result = _run(nav_group, ["forward"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "forward" in result.output.lower()
|
||||
|
||||
def test_cli_nav_focus_found():
|
||||
result = _run(nav_group, ["focus", "example.com"], {"url": "https://example.com"})
|
||||
assert result.exit_code == 0
|
||||
assert "Focused" in result.output
|
||||
|
||||
def test_cli_nav_focus_not_found():
|
||||
result = _run(nav_group, ["focus", "no-match"], None)
|
||||
assert result.exit_code == 0
|
||||
assert "No tab found" in result.output
|
||||
|
||||
def test_cli_nav_open_wait():
|
||||
result = _run(nav_group, ["open-wait", "https://example.com"],
|
||||
{"id": 1, "title": "Example Domain", "url": "https://example.com"})
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded" in result.output
|
||||
assert "Example Domain" in result.output
|
||||
|
||||
def test_cli_nav_open_wait_no_title():
|
||||
result = _run(nav_group, ["open-wait", "https://example.com"], {"id": 1})
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded" in result.output
|
||||
|
||||
def test_cli_nav_wait():
|
||||
result = _run(nav_group, ["wait"], {"url": "https://example.com", "title": "Example"})
|
||||
assert result.exit_code == 0
|
||||
assert "Ready" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# navigate commands — with tab_id argument
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cli_nav_reload_with_tab_id():
|
||||
result = _run(nav_group, ["reload", "42"], None)
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_nav_back_with_tab_id():
|
||||
result = _run(nav_group, ["back", "42"], None)
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_nav_forward_with_tab_id():
|
||||
result = _run(nav_group, ["forward", "42"], None)
|
||||
assert result.exit_code == 0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# tabs commands (CLI module) — single-browser mode
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.tabs import tabs_group
|
||||
|
||||
_TABS_MOD = "browser_cli.commands.tabs"
|
||||
|
||||
_SAMPLE_TAB = {"id": 1, "windowId": 1, "active": True, "muted": False,
|
||||
"title": "Example", "url": "https://example.com", "groupId": -1}
|
||||
|
||||
def test_cli_tabs_list_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(tabs_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "No tabs found" in result.output
|
||||
|
||||
def test_cli_tabs_list_with_tabs():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_TAB]), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(tabs_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_tabs_close_by_id():
|
||||
with patch("browser_cli.commands.send_command", return_value={"closed": 1}):
|
||||
result = CliRunner().invoke(tabs_group, ["close", "42"])
|
||||
assert result.exit_code == 0
|
||||
assert "Closed 1" in result.output
|
||||
|
||||
def test_cli_tabs_close_inactive():
|
||||
with patch("browser_cli.commands.send_command", return_value={"closed": 3}):
|
||||
result = CliRunner().invoke(tabs_group, ["close", "--inactive"])
|
||||
assert result.exit_code == 0
|
||||
assert "Closed 3" in result.output
|
||||
|
||||
def test_cli_tabs_close_duplicates():
|
||||
with patch("browser_cli.commands.send_command", return_value={"closed": 2}):
|
||||
result = CliRunner().invoke(tabs_group, ["close", "--duplicates"])
|
||||
assert result.exit_code == 0
|
||||
assert "Closed 2" in result.output
|
||||
|
||||
def test_cli_tabs_move_forward():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["move", "42", "--forward"])
|
||||
assert result.exit_code == 0
|
||||
assert "Tab moved" in result.output
|
||||
|
||||
def test_cli_tabs_move_to_window():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["move", "42", "--window", "2"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_tabs_active():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["active", "42"])
|
||||
assert result.exit_code == 0
|
||||
assert "42" in result.output
|
||||
|
||||
def test_cli_tabs_status():
|
||||
tab = {"id": 5, "windowId": 1, "active": True, "muted": False,
|
||||
"title": "MyPage", "url": "https://my.page"}
|
||||
with patch("browser_cli.commands.send_command", return_value=tab):
|
||||
result = CliRunner().invoke(tabs_group, ["status"])
|
||||
assert result.exit_code == 0
|
||||
assert "MyPage" in result.output
|
||||
|
||||
def test_cli_tabs_filter():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_TAB]):
|
||||
result = CliRunner().invoke(tabs_group, ["filter", "example"])
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_tabs_filter_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]):
|
||||
result = CliRunner().invoke(tabs_group, ["filter", "nope"])
|
||||
assert result.exit_code == 0
|
||||
assert "No tabs" in result.output
|
||||
|
||||
def test_cli_tabs_count():
|
||||
with patch("browser_cli.commands.send_command", return_value=7), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(tabs_group, ["count"])
|
||||
assert result.exit_code == 0
|
||||
assert "7" in result.output
|
||||
|
||||
def test_cli_tabs_count_with_pattern():
|
||||
with patch("browser_cli.commands.send_command", return_value=3), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(tabs_group, ["count", "http"])
|
||||
assert result.exit_code == 0
|
||||
assert "3" in result.output
|
||||
assert "http" in result.output
|
||||
|
||||
def test_cli_tabs_query():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_TAB]):
|
||||
result = CliRunner().invoke(tabs_group, ["query", "example"])
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_tabs_html():
|
||||
with patch("browser_cli.commands.send_command", return_value="<html><body>hello</body></html>"):
|
||||
result = CliRunner().invoke(tabs_group, ["html"])
|
||||
assert result.exit_code == 0
|
||||
assert "hello" in result.output
|
||||
|
||||
def test_cli_tabs_dedupe():
|
||||
with patch("browser_cli.commands.send_command", return_value={"closed": 4}):
|
||||
result = CliRunner().invoke(tabs_group, ["dedupe"])
|
||||
assert result.exit_code == 0
|
||||
assert "4 duplicate" in result.output
|
||||
|
||||
def test_cli_tabs_sort():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["sort", "--by", "title"])
|
||||
assert result.exit_code == 0
|
||||
assert "title" in result.output
|
||||
|
||||
def test_cli_tabs_merge_windows():
|
||||
with patch("browser_cli.commands.send_command", return_value={"moved": 2}):
|
||||
result = CliRunner().invoke(tabs_group, ["merge-windows"])
|
||||
assert result.exit_code == 0
|
||||
assert "2" in result.output
|
||||
|
||||
def test_cli_tabs_mute():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 5}):
|
||||
result = CliRunner().invoke(tabs_group, ["mute", "5"])
|
||||
assert result.exit_code == 0
|
||||
assert "Muted tab 5" in result.output
|
||||
|
||||
def test_cli_tabs_unmute():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 5}):
|
||||
result = CliRunner().invoke(tabs_group, ["unmute", "5"])
|
||||
assert result.exit_code == 0
|
||||
assert "Unmuted tab 5" in result.output
|
||||
|
||||
def test_cli_tabs_pin():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 5}):
|
||||
result = CliRunner().invoke(tabs_group, ["pin", "5"])
|
||||
assert result.exit_code == 0
|
||||
assert "Pinned tab 5" in result.output
|
||||
|
||||
def test_cli_tabs_unpin():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 5}):
|
||||
result = CliRunner().invoke(tabs_group, ["unpin", "5"])
|
||||
assert result.exit_code == 0
|
||||
assert "Unpinned tab 5" in result.output
|
||||
|
||||
def test_cli_tabs_watch_url():
|
||||
with patch("browser_cli.commands.send_command", return_value={"url": "https://done.com"}):
|
||||
result = CliRunner().invoke(tabs_group, ["watch-url", "done\\.com"])
|
||||
assert result.exit_code == 0
|
||||
assert "done.com" in result.output
|
||||
|
||||
def test_cli_tabs_screenshot_stdout():
|
||||
with patch("browser_cli.commands.send_command", return_value={"dataUrl": "data:image/png;base64,abc"}):
|
||||
result = CliRunner().invoke(tabs_group, ["screenshot"])
|
||||
assert result.exit_code == 0
|
||||
assert "data:image/png" in result.output
|
||||
|
||||
def test_cli_tabs_screenshot_to_file(tmp_path):
|
||||
import base64
|
||||
png_bytes = b"\x89PNG\r\n\x1a\n" + b"\x00" * 8 # minimal header
|
||||
data_url = "data:image/png;base64," + base64.b64encode(png_bytes).decode()
|
||||
out = tmp_path / "shot.png"
|
||||
with patch("browser_cli.commands.send_command", return_value={"dataUrl": data_url}):
|
||||
result = CliRunner().invoke(tabs_group, ["screenshot", str(out)])
|
||||
assert result.exit_code == 0
|
||||
assert "saved" in result.output.lower()
|
||||
assert out.exists()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# groups commands (CLI module)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.groups import group_group
|
||||
|
||||
_GROUPS_MOD = "browser_cli.commands.groups"
|
||||
|
||||
_SAMPLE_GROUP = {"id": 10, "title": "Work", "color": "blue", "collapsed": False, "tabCount": 3}
|
||||
|
||||
def test_cli_groups_list_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]), \
|
||||
patch(f"{_GROUPS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(group_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "No groups found" in result.output
|
||||
|
||||
def test_cli_groups_list_with_groups():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_GROUP]), \
|
||||
patch(f"{_GROUPS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(group_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "Work" in result.output
|
||||
|
||||
def test_cli_groups_tabs():
|
||||
tabs = [_SAMPLE_TAB]
|
||||
with patch("browser_cli.commands.send_command", return_value=tabs):
|
||||
result = CliRunner().invoke(group_group, ["tabs", "10"])
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
def test_cli_groups_tabs_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]):
|
||||
result = CliRunner().invoke(group_group, ["tabs", "10"])
|
||||
assert result.exit_code == 0
|
||||
assert "No tabs" in result.output
|
||||
|
||||
def test_cli_groups_count():
|
||||
with patch("browser_cli.commands.send_command", return_value=5), \
|
||||
patch(f"{_GROUPS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(group_group, ["count"])
|
||||
assert result.exit_code == 0
|
||||
assert "5" in result.output
|
||||
|
||||
def test_cli_groups_query_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]):
|
||||
result = CliRunner().invoke(group_group, ["query", "nothing"])
|
||||
assert result.exit_code == 0
|
||||
assert "No groups" in result.output
|
||||
|
||||
def test_cli_groups_query_found():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_GROUP]):
|
||||
result = CliRunner().invoke(group_group, ["query", "Work"])
|
||||
assert result.exit_code == 0
|
||||
assert "Work" in result.output
|
||||
|
||||
def test_cli_groups_close():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(group_group, ["close", "10"])
|
||||
assert result.exit_code == 0
|
||||
assert "10" in result.output
|
||||
|
||||
def test_cli_groups_create():
|
||||
with patch("browser_cli.commands.send_command", return_value={"id": 42}):
|
||||
result = CliRunner().invoke(group_group, ["create", "Research"])
|
||||
assert result.exit_code == 0
|
||||
assert "Research" in result.output
|
||||
assert "42" in result.output
|
||||
|
||||
def test_cli_groups_add_tab_no_url():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 7}):
|
||||
result = CliRunner().invoke(group_group, ["add-tab", "Work"])
|
||||
assert result.exit_code == 0
|
||||
assert "Work" in result.output
|
||||
|
||||
def test_cli_groups_add_tab_with_url():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabId": 9}):
|
||||
result = CliRunner().invoke(group_group, ["add-tab", "Work", "https://docs.example.com"])
|
||||
assert result.exit_code == 0
|
||||
assert "docs.example.com" in result.output
|
||||
|
||||
def test_cli_groups_move_forward():
|
||||
with patch("browser_cli.commands.send_command", return_value={"moved": True}):
|
||||
result = CliRunner().invoke(group_group, ["move", "10", "--forward"])
|
||||
assert result.exit_code == 0
|
||||
assert "forward" in result.output
|
||||
|
||||
def test_cli_groups_move_backward():
|
||||
with patch("browser_cli.commands.send_command", return_value={"moved": True}):
|
||||
result = CliRunner().invoke(group_group, ["move", "10", "--backward"])
|
||||
assert result.exit_code == 0
|
||||
assert "backward" in result.output
|
||||
|
||||
def test_cli_groups_move_no_direction():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(group_group, ["move", "10"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_groups_move_already_at_end():
|
||||
with patch("browser_cli.commands.send_command", return_value={"moved": False}):
|
||||
result = CliRunner().invoke(group_group, ["move", "10", "--forward"])
|
||||
assert result.exit_code == 0
|
||||
assert "already at" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# session commands (CLI module)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.session import session_group
|
||||
|
||||
_SESSION_MOD = "browser_cli.commands.session"
|
||||
|
||||
def test_cli_session_save():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabs": 5}):
|
||||
result = CliRunner().invoke(session_group, ["save", "work"])
|
||||
assert result.exit_code == 0
|
||||
assert "work" in result.output
|
||||
assert "5" in result.output
|
||||
|
||||
def test_cli_session_load():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabs": 8}):
|
||||
result = CliRunner().invoke(session_group, ["load", "work"])
|
||||
assert result.exit_code == 0
|
||||
assert "work" in result.output
|
||||
assert "8" in result.output
|
||||
|
||||
def test_cli_session_load_background():
|
||||
with patch("browser_cli.commands.send_command", return_value={"jobId": "j-abc", "status": "running"}):
|
||||
result = CliRunner().invoke(session_group, ["load", "work", "--background"])
|
||||
assert result.exit_code == 0
|
||||
assert "j-abc" in result.output
|
||||
|
||||
def test_cli_session_load_lazy():
|
||||
with patch("browser_cli.commands.send_command", return_value={"tabs": 20}):
|
||||
result = CliRunner().invoke(session_group, ["load", "work", "--lazy", "--eager-tabs", "5"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_cli_session_diff_has_changes():
|
||||
diff = {"added": ["https://new.com"], "removed": ["https://old.com"]}
|
||||
with patch("browser_cli.commands.send_command", return_value=diff):
|
||||
result = CliRunner().invoke(session_group, ["diff", "a", "b"])
|
||||
assert result.exit_code == 0
|
||||
assert "new.com" in result.output
|
||||
assert "old.com" in result.output
|
||||
|
||||
def test_cli_session_diff_identical():
|
||||
with patch("browser_cli.commands.send_command", return_value={"added": [], "removed": []}):
|
||||
result = CliRunner().invoke(session_group, ["diff", "a", "b"])
|
||||
assert result.exit_code == 0
|
||||
assert "identical" in result.output
|
||||
|
||||
def test_cli_session_diff_no_data():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["diff", "a", "b"])
|
||||
assert result.exit_code == 0
|
||||
assert "No diff" in result.output
|
||||
|
||||
def test_cli_session_list_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]), \
|
||||
patch(f"{_SESSION_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(session_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "No saved sessions" in result.output
|
||||
|
||||
def test_cli_session_list_with_sessions():
|
||||
sessions = [{"name": "work", "tabs": 3, "savedAt": 1700000000000}]
|
||||
with patch("browser_cli.commands.send_command", return_value=sessions), \
|
||||
patch(f"{_SESSION_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(session_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "work" in result.output
|
||||
|
||||
def test_cli_session_remove():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["remove", "old-session"])
|
||||
assert result.exit_code == 0
|
||||
assert "old-session" in result.output
|
||||
|
||||
def test_cli_session_job_status_running():
|
||||
with patch("browser_cli.commands.send_command", return_value={"status": "running", "percent": 42}):
|
||||
result = CliRunner().invoke(session_group, ["job-status", "j-xyz"])
|
||||
assert result.exit_code == 0
|
||||
assert "running" in result.output
|
||||
|
||||
def test_cli_session_job_status_with_error():
|
||||
with patch("browser_cli.commands.send_command", return_value={"status": "failed", "error": "something broke"}):
|
||||
result = CliRunner().invoke(session_group, ["job-status", "j-bad"])
|
||||
assert result.exit_code == 0
|
||||
assert "something broke" in result.output
|
||||
|
||||
def test_cli_session_job_status_with_result():
|
||||
with patch("browser_cli.commands.send_command", return_value={"status": "done", "result": "Opened 5 tabs"}):
|
||||
result = CliRunner().invoke(session_group, ["job-status", "j-done"])
|
||||
assert result.exit_code == 0
|
||||
assert "Opened 5 tabs" in result.output
|
||||
|
||||
def test_cli_session_job_cancel():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["job-cancel", "j-running"])
|
||||
assert result.exit_code == 0
|
||||
assert "j-running" in result.output
|
||||
|
||||
def test_cli_session_auto_save_on():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["auto-save", "on"])
|
||||
assert result.exit_code == 0
|
||||
assert "on" in result.output
|
||||
|
||||
def test_cli_session_auto_save_off():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["auto-save", "off"])
|
||||
assert result.exit_code == 0
|
||||
assert "off" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# multi-browser error paths (None targets → "Cannot resolve" exit)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.client import BrowserTarget
|
||||
|
||||
def _fake_target(name="browser-a"):
|
||||
return BrowserTarget(profile=name, display_name=name, socket_path="/tmp/fake.sock")
|
||||
|
||||
def test_cli_tabs_list_multi_browser_all_none():
|
||||
"""If every multi-browser target returns None, show error and exit 1."""
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_TABS_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["list"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_tabs_count_multi_browser_all_none():
|
||||
"""If every multi-browser count returns None, show error and exit 1."""
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_TABS_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_TABS_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(tabs_group, ["count"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_groups_list_multi_browser_all_none():
|
||||
"""If every multi-browser group list returns None, exit 1."""
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_GROUPS_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_GROUPS_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(group_group, ["list"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_groups_count_multi_browser_all_none():
|
||||
"""If every multi-browser group count returns None, exit 1."""
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_GROUPS_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_GROUPS_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(group_group, ["count"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_session_list_multi_browser_all_none():
|
||||
"""If every multi-browser session list returns None, exit 1."""
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_SESSION_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_SESSION_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(session_group, ["list"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# tabs screenshot error paths
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cli_tabs_screenshot_bad_dataurl(tmp_path):
|
||||
"""Screenshot command exits non-zero when dataUrl has wrong format."""
|
||||
out = tmp_path / "bad.png"
|
||||
with patch("browser_cli.commands.send_command", return_value={"dataUrl": "not-a-dataurl"}):
|
||||
result = CliRunner().invoke(tabs_group, ["screenshot", str(out)])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_tabs_screenshot_bad_base64(tmp_path):
|
||||
"""Screenshot command exits non-zero when base64 data is corrupt."""
|
||||
out = tmp_path / "bad.png"
|
||||
with patch("browser_cli.commands.send_command", return_value={"dataUrl": "data:image/png;base64,!!!NOT_VALID_BASE64!!!"}):
|
||||
result = CliRunner().invoke(tabs_group, ["screenshot", str(out)])
|
||||
assert result.exit_code != 0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# windows commands (CLI module)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands.windows import windows_group
|
||||
|
||||
_WINDOWS_MOD = "browser_cli.commands.windows"
|
||||
|
||||
_SAMPLE_WINDOW = {"id": 1, "alias": "main", "tabCount": 5, "state": "normal"}
|
||||
|
||||
def test_cli_windows_list_empty():
|
||||
with patch("browser_cli.commands.send_command", return_value=[]), \
|
||||
patch(f"{_WINDOWS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(windows_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "No windows found" in result.output
|
||||
|
||||
def test_cli_windows_list_with_windows():
|
||||
with patch("browser_cli.commands.send_command", return_value=[_SAMPLE_WINDOW]), \
|
||||
patch(f"{_WINDOWS_MOD}._multi_browser_targets", return_value=[]):
|
||||
result = CliRunner().invoke(windows_group, ["list"])
|
||||
assert result.exit_code == 0
|
||||
assert "main" in result.output
|
||||
|
||||
def test_cli_windows_list_multi_all_none():
|
||||
target = _fake_target()
|
||||
with patch("browser_cli.commands.send_command", return_value=None), \
|
||||
patch(f"{_WINDOWS_MOD}._multi_browser_targets", return_value=[target]), \
|
||||
patch(f"{_WINDOWS_MOD}._handle_multi", return_value=None):
|
||||
result = CliRunner().invoke(windows_group, ["list"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_cli_windows_rename():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(windows_group, ["rename", "1", "work"])
|
||||
assert result.exit_code == 0
|
||||
assert "work" in result.output
|
||||
assert "1" in result.output
|
||||
|
||||
def test_cli_windows_close():
|
||||
with patch("browser_cli.commands.send_command", return_value=None):
|
||||
result = CliRunner().invoke(windows_group, ["close", "2"])
|
||||
assert result.exit_code == 0
|
||||
assert "2" in result.output
|
||||
|
||||
def test_cli_windows_open_no_url():
|
||||
with patch("browser_cli.commands.send_command", return_value={"id": 5}):
|
||||
result = CliRunner().invoke(windows_group, ["open"])
|
||||
assert result.exit_code == 0
|
||||
assert "5" in result.output
|
||||
|
||||
def test_cli_windows_open_with_url():
|
||||
with patch("browser_cli.commands.send_command", return_value={"id": 6}):
|
||||
result = CliRunner().invoke(windows_group, ["open", "https://example.com"])
|
||||
assert result.exit_code == 0
|
||||
assert "example.com" in result.output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# commands/__init__.py error paths
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from browser_cli.commands import _handle, _handle_multi
|
||||
from browser_cli.client import BrowserNotConnected
|
||||
|
||||
def test_handle_raises_system_exit_on_browser_not_connected():
|
||||
"""_handle converts BrowserNotConnected into SystemExit(1)."""
|
||||
with patch("browser_cli.commands.send_command", side_effect=BrowserNotConnected("no socket")):
|
||||
with pytest.raises(SystemExit):
|
||||
_handle("tabs.list")
|
||||
|
||||
def test_handle_raises_system_exit_on_runtime_error():
|
||||
"""_handle converts RuntimeError into SystemExit(1)."""
|
||||
with patch("browser_cli.commands.send_command", side_effect=RuntimeError("browser blew up")):
|
||||
with pytest.raises(SystemExit):
|
||||
_handle("tabs.list")
|
||||
|
||||
def test_handle_multi_returns_none_on_error():
|
||||
"""_handle_multi silently returns None on BrowserNotConnected."""
|
||||
with patch("browser_cli.commands.send_command", side_effect=BrowserNotConnected("gone")):
|
||||
result = _handle_multi("tabs.list")
|
||||
assert result is None
|
||||
|
||||
def test_handle_multi_returns_none_on_runtime_error():
|
||||
"""_handle_multi silently returns None on RuntimeError."""
|
||||
with patch("browser_cli.commands.send_command", side_effect=RuntimeError("oops")):
|
||||
result = _handle_multi("tabs.list")
|
||||
assert result is None
|
||||
|
||||
def test_handle_multi_with_remote():
|
||||
"""_handle_multi routes through remote when remote arg is set."""
|
||||
with patch("browser_cli.commands.send_command", return_value={"ok": True}) as mock_send:
|
||||
result = _handle_multi("tabs.list", profile="brave", remote="host:8765")
|
||||
assert result == {"ok": True}
|
||||
mock_send.assert_called_once_with("tabs.list", {}, profile="brave", remote="host:8765")
|
||||
Reference in New Issue
Block a user