""" 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="hello"): 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")