fd5447cbb9
Testing / remote-protocol-compat (0.9.3) (push) Successful in 42s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 44s
Package Extension / package-extension (push) Successful in 43s
Build & Publish Package / publish (push) Successful in 43s
Testing / test (push) Successful in 45s
Restructure the Python API and internals around composable namespaces and a standalone transport/endpoint layer. Bump to 0.12.0. Python API: - Replace flat methods (b.tabs_list(), b.group_list()) with namespaces: b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage, b.cookies, b.session, b.perf, b.extension. - Shrink browser_cli/__init__.py to a thin composition root; move all behaviour into browser_cli/sdk/ (one module per namespace + factories, base, routing). Internals: - Add browser_cli/transport.py and remote_transport.py to isolate IPC from command logic; client.py now delegates instead of owning transport. - Add browser_cli/endpoints.py for endpoint resolution and browser_cli/errors.py for shared error types. - Extract markdown rendering into browser_cli/markdown.py (out of extract). - Add USER_AGENT to version_manager. Tooling & tests: - Add justfile with common dev tasks. - Update CLI commands and demo to the namespaced API. - Rework tests for the new layout; add test_transport.py and test_refactor_boundaries.py to lock in module boundaries. BREAKING CHANGE: flat API methods are removed in favour of namespaces (e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
149 lines
5.3 KiB
Python
149 lines
5.3 KiB
Python
"""Tests for tabs.* commands."""
|
|
import time
|
|
import pytest
|
|
|
|
def test_tabs_list(browser):
|
|
tabs = browser("tabs.list")
|
|
assert isinstance(tabs, list)
|
|
assert len(tabs) > 0
|
|
first = tabs[0]
|
|
assert "id" in first
|
|
assert "windowId" in first
|
|
assert "url" in first
|
|
assert "title" in first
|
|
assert "muted" in first
|
|
assert "groupId" in first
|
|
|
|
def test_tabs_count(browser):
|
|
count = browser("tabs.count", {})
|
|
tabs = browser("tabs.list")
|
|
assert count == len(tabs)
|
|
|
|
def test_tabs_count_with_pattern(browser):
|
|
count = browser("tabs.count", {"pattern": "http"})
|
|
assert isinstance(count, int)
|
|
assert count >= 0
|
|
|
|
def test_tabs_filter(browser):
|
|
result = browser("tabs.filter", {"pattern": "http"})
|
|
assert isinstance(result, list)
|
|
for tab in result:
|
|
assert "http" in tab.get("url", "")
|
|
|
|
def test_tabs_query(browser):
|
|
result = browser("tabs.query", {"search": "a"})
|
|
assert isinstance(result, list)
|
|
|
|
def test_tabs_active_exists(browser):
|
|
tabs = browser("tabs.list")
|
|
active = [t for t in tabs if t.get("active")]
|
|
assert len(active) >= 1, "Expected at least one active tab"
|
|
|
|
def test_tabs_active_in_window(browser):
|
|
active = next(t for t in browser("tabs.list") if t.get("active"))
|
|
result = browser("tabs.active_in_window", {"windowId": active["windowId"]})
|
|
assert result["id"] == active["id"]
|
|
assert result["windowId"] == active["windowId"]
|
|
|
|
def test_tabs_status(browser):
|
|
result = browser("tabs.status", {})
|
|
assert isinstance(result, dict)
|
|
assert "id" in result
|
|
assert "muted" in result
|
|
|
|
def test_tabs_html(browser, http_tab):
|
|
html = browser("tabs.html", {"tabId": http_tab["id"]})
|
|
assert isinstance(html, str)
|
|
assert len(html) > 0
|
|
assert "<html" in html.lower() or "<!doctype" in html.lower()
|
|
|
|
def test_tabs_close_by_id(browser):
|
|
result = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
tab_id = result["id"]
|
|
|
|
browser("tabs.close", {"tabId": tab_id})
|
|
|
|
tabs = browser("tabs.list")
|
|
assert tab_id not in [t["id"] for t in tabs]
|
|
|
|
def test_tabs_dedupe(browser):
|
|
# Open the same URL twice
|
|
r1 = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
r2 = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
id1, id2 = r1["id"], r2["id"]
|
|
|
|
try:
|
|
# Dedupe keys on tab.url and intentionally skips tabs that haven't
|
|
# resolved a URL yet, so wait for both tabs to finish loading first.
|
|
for _ in range(30):
|
|
tabs = {t["id"]: t for t in browser("tabs.list")}
|
|
if all(tabs.get(tid, {}).get("url", "").startswith("http") for tid in (id1, id2)):
|
|
break
|
|
time.sleep(0.1)
|
|
else:
|
|
pytest.skip("Duplicate test tabs did not finish loading")
|
|
|
|
result = browser("tabs.dedupe")
|
|
assert isinstance(result, dict)
|
|
assert result.get("closed", 0) >= 0
|
|
# At least one of the two duplicates should be gone
|
|
remaining = browser("tabs.list")
|
|
remaining_ids = {t["id"] for t in remaining}
|
|
assert not (id1 in remaining_ids and id2 in remaining_ids), \
|
|
"Both duplicate tabs still open after dedupe"
|
|
finally:
|
|
for tid in (id1, id2):
|
|
try:
|
|
browser("tabs.close", {"tabId": tid})
|
|
except Exception:
|
|
pass
|
|
|
|
def test_tabs_sort(browser):
|
|
result = browser("tabs.sort", {"by": "domain"})
|
|
# No error and at least returns something (None or dict)
|
|
assert result is None or isinstance(result, dict)
|
|
|
|
def test_tabs_move_forward(browser):
|
|
r1 = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
r2 = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
id1, id2 = r1["id"], r2["id"]
|
|
|
|
try:
|
|
# Move id1 forward — just verify no error is raised
|
|
browser("tabs.move", {"tabId": id1, "forward": True})
|
|
finally:
|
|
browser("tabs.close", {"tabId": id1})
|
|
browser("tabs.close", {"tabId": id2})
|
|
|
|
def test_tabs_merge_windows_no_crash(browser):
|
|
result = browser("tabs.merge_windows")
|
|
assert isinstance(result, dict)
|
|
assert "moved" in result
|
|
|
|
def test_tabs_mute_and_unmute(browser, http_tab):
|
|
muted = browser("tabs.mute", {"tabId": http_tab["id"]})
|
|
assert isinstance(muted, dict)
|
|
assert muted["tabId"] == http_tab["id"]
|
|
assert muted["muted"] is True
|
|
listed = browser("tabs.list")
|
|
listed_tab = next(t for t in listed if t["id"] == http_tab["id"])
|
|
assert listed_tab["muted"] is True
|
|
|
|
unmuted = browser("tabs.unmute", {"tabId": http_tab["id"]})
|
|
assert isinstance(unmuted, dict)
|
|
assert unmuted["tabId"] == http_tab["id"]
|
|
assert unmuted["muted"] is False
|
|
listed = browser("tabs.list")
|
|
listed_tab = next(t for t in listed if t["id"] == http_tab["id"])
|
|
assert listed_tab["muted"] is False
|
|
status = browser("tabs.status", {"tabId": http_tab["id"]})
|
|
assert status["muted"] is False
|
|
|
|
def test_tabs_mute_requires_explicit_tab_when_multiple_tabs_open(browser):
|
|
opened = browser("navigate.open", {"url": "https://example.com", "background": True})
|
|
try:
|
|
with pytest.raises(RuntimeError, match="Refusing to mute without explicit tab ID"):
|
|
browser("tabs.mute", {})
|
|
finally:
|
|
browser("tabs.close", {"tabId": opened["id"]})
|