change that tab open change url inplace and get active tab from window id
This commit is contained in:
@@ -117,6 +117,10 @@ class BrowserCLI:
|
|||||||
def focus_url(self, pattern: str) -> None:
|
def focus_url(self, pattern: str) -> None:
|
||||||
self._cmd("navigate.focus", {"pattern": pattern})
|
self._cmd("navigate.focus", {"pattern": pattern})
|
||||||
|
|
||||||
|
def navigate_tab(self, tab_id: int, url: str) -> None:
|
||||||
|
"""Navigate a specific tab to *url*."""
|
||||||
|
self._cmd("navigate.to", {"tabId": tab_id, "url": url})
|
||||||
|
|
||||||
# ── Search ────────────────────────────────────────────────────────────
|
# ── Search ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
@@ -168,6 +172,13 @@ class BrowserCLI:
|
|||||||
"""Switch browser focus to a tab by ID."""
|
"""Switch browser focus to a tab by ID."""
|
||||||
self._cmd("tabs.active", {"tabId": tab_id})
|
self._cmd("tabs.active", {"tabId": tab_id})
|
||||||
|
|
||||||
|
def window_active_tab(self, window_id: int) -> Tab:
|
||||||
|
"""Return active tab for a specific browser window."""
|
||||||
|
data = self._cmd("tabs.active_in_window", {"windowId": window_id})
|
||||||
|
if not isinstance(data, dict) or "id" not in data:
|
||||||
|
raise RuntimeError(f"No active tab found for window {window_id}")
|
||||||
|
return self._make_tab(data)
|
||||||
|
|
||||||
def tabs_filter(self, pattern_or_filter: str | Callable[[Tab], bool] | Callable[[list[Tab]], Iterable[Tab]]) -> list[Tab]:
|
def tabs_filter(self, pattern_or_filter: str | Callable[[Tab], bool] | Callable[[list[Tab]], Iterable[Tab]]) -> list[Tab]:
|
||||||
"""Return tabs filtered by pattern or a Python callable."""
|
"""Return tabs filtered by pattern or a Python callable."""
|
||||||
if isinstance(pattern_or_filter, str):
|
if isinstance(pattern_or_filter, str):
|
||||||
|
|||||||
@@ -87,11 +87,8 @@ class Tab:
|
|||||||
return self._b()._cmd("tabs.html", {"tabId": self.id})
|
return self._b()._cmd("tabs.html", {"tabId": self.id})
|
||||||
|
|
||||||
def open(self, url: str, *, background: bool = False) -> None:
|
def open(self, url: str, *, background: bool = False) -> None:
|
||||||
"""Navigate this tab to *url*."""
|
"""Navigate this tab to *url* in place."""
|
||||||
# Re-uses navigate.open which opens a new tab; for in-place navigation
|
self._b().navigate_tab(self.id, url)
|
||||||
# we target by tabId via the focus then navigate approach. For now we
|
|
||||||
# open a new tab in the same window as a convenience.
|
|
||||||
self._b()._cmd("navigate.open", {"url": url, "background": background})
|
|
||||||
|
|
||||||
|
|
||||||
# ── Group ─────────────────────────────────────────────────────────────────────
|
# ── Group ─────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+20
-2
@@ -121,6 +121,7 @@ async function dispatch(command, args) {
|
|||||||
switch (command) {
|
switch (command) {
|
||||||
// ── Navigation ────────────────────────────────────────────────────────
|
// ── Navigation ────────────────────────────────────────────────────────
|
||||||
case "navigate.open": return navOpen(args);
|
case "navigate.open": return navOpen(args);
|
||||||
|
case "navigate.to": return navTo(args);
|
||||||
case "navigate.reload": return navReload(args, false);
|
case "navigate.reload": return navReload(args, false);
|
||||||
case "navigate.hard_reload": return navReload(args, true);
|
case "navigate.hard_reload": return navReload(args, true);
|
||||||
case "navigate.back": return navBack(args);
|
case "navigate.back": return navBack(args);
|
||||||
@@ -132,6 +133,7 @@ async function dispatch(command, args) {
|
|||||||
case "tabs.close": return tabsClose(args);
|
case "tabs.close": return tabsClose(args);
|
||||||
case "tabs.move": return tabsMove(args);
|
case "tabs.move": return tabsMove(args);
|
||||||
case "tabs.active": return tabsActive(args);
|
case "tabs.active": return tabsActive(args);
|
||||||
|
case "tabs.active_in_window": return tabsActiveInWindow(args);
|
||||||
case "tabs.filter": return tabsFilter(args);
|
case "tabs.filter": return tabsFilter(args);
|
||||||
case "tabs.count": return tabsCount(args);
|
case "tabs.count": return tabsCount(args);
|
||||||
case "tabs.query": return tabsQuery(args);
|
case "tabs.query": return tabsQuery(args);
|
||||||
@@ -191,9 +193,11 @@ async function dispatch(command, args) {
|
|||||||
|
|
||||||
// ── Navigation ────────────────────────────────────────────────────────────────
|
// ── Navigation ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function navOpen({ url, background, window: windowName, group: groupNameOrId }) {
|
async function navOpen({ url, background, window: windowName, windowId: explicitWindowId, group: groupNameOrId }) {
|
||||||
let windowId;
|
let windowId;
|
||||||
if (windowName) {
|
if (explicitWindowId != null) {
|
||||||
|
windowId = explicitWindowId;
|
||||||
|
} else if (windowName) {
|
||||||
const aliases = await getAliases();
|
const aliases = await getAliases();
|
||||||
const entry = Object.entries(aliases).find(([, v]) => v === windowName);
|
const entry = Object.entries(aliases).find(([, v]) => v === windowName);
|
||||||
if (entry) windowId = parseInt(entry[0]);
|
if (entry) windowId = parseInt(entry[0]);
|
||||||
@@ -221,6 +225,11 @@ async function navOpen({ url, background, window: windowName, group: groupNameOr
|
|||||||
return { id: tab.id, url: tab.url };
|
return { id: tab.id, url: tab.url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function navTo({ tabId, url }) {
|
||||||
|
const tab = await chrome.tabs.update(tabId, { url });
|
||||||
|
return { id: tab.id, url: tab.url || url };
|
||||||
|
}
|
||||||
|
|
||||||
async function navReload({ tabId }, bypassCache) {
|
async function navReload({ tabId }, bypassCache) {
|
||||||
const tab = tabId ? { id: tabId } : await getActiveTab();
|
const tab = tabId ? { id: tabId } : await getActiveTab();
|
||||||
await chrome.tabs.reload(tab.id, { bypassCache });
|
await chrome.tabs.reload(tab.id, { bypassCache });
|
||||||
@@ -326,6 +335,15 @@ async function tabsActive({ tabId }) {
|
|||||||
return { tabId };
|
return { tabId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function tabsActiveInWindow({ windowId }) {
|
||||||
|
const activeTabs = await chrome.tabs.query({ windowId, active: true });
|
||||||
|
const tab = activeTabs[0];
|
||||||
|
if (!tab) {
|
||||||
|
throw new Error(`No active tab found for window ${windowId}`);
|
||||||
|
}
|
||||||
|
return tabInfo(tab);
|
||||||
|
}
|
||||||
|
|
||||||
async function tabsFilter({ pattern }) {
|
async function tabsFilter({ pattern }) {
|
||||||
const all = await chrome.tabs.query({});
|
const all = await chrome.tabs.query({});
|
||||||
return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo);
|
return all.filter(t => t.url && t.url.includes(pattern)).map(tabInfo);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "browser-cli",
|
"name": "browser-cli",
|
||||||
"version": "0.5.7",
|
"version": "0.5.8",
|
||||||
"description": "Control your browser from the terminal via browser-cli",
|
"description": "Control your browser from the terminal via browser-cli",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"tabs",
|
"tabs",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "browser-cli"
|
name = "browser-cli"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
description = "Control your real running browser from the terminal via a Chrome extension"
|
description = "Control your real running browser from the terminal via a Chrome extension"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
+27
-1
@@ -154,6 +154,12 @@ class TestNavigation:
|
|||||||
b.focus_url("github.com")
|
b.focus_url("github.com")
|
||||||
mock_send.assert_called_once_with("navigate.focus", {"pattern": "github.com"}, profile=None)
|
mock_send.assert_called_once_with("navigate.focus", {"pattern": "github.com"}, profile=None)
|
||||||
|
|
||||||
|
def test_navigate_tab(self, b, mock_send):
|
||||||
|
b.navigate_tab(5, "https://example.com")
|
||||||
|
mock_send.assert_called_once_with(
|
||||||
|
"navigate.to", {"tabId": 5, "url": "https://example.com"}, profile=None
|
||||||
|
)
|
||||||
|
|
||||||
def test_profile_forwarded(self, b_profile, mock_send):
|
def test_profile_forwarded(self, b_profile, mock_send):
|
||||||
b_profile.reload()
|
b_profile.reload()
|
||||||
mock_send.assert_called_once_with("navigate.reload", {"tabId": None}, profile="brave")
|
mock_send.assert_called_once_with("navigate.reload", {"tabId": None}, profile="brave")
|
||||||
@@ -244,6 +250,18 @@ class TestTabs:
|
|||||||
b.tabs_active(10)
|
b.tabs_active(10)
|
||||||
mock_send.assert_called_once_with("tabs.active", {"tabId": 10}, profile=None)
|
mock_send.assert_called_once_with("tabs.active", {"tabId": 10}, profile=None)
|
||||||
|
|
||||||
|
def test_window_active_tab(self, b, mock_send):
|
||||||
|
mock_send.return_value = TAB_DATA
|
||||||
|
tab = b.window_active_tab(1)
|
||||||
|
assert isinstance(tab, Tab)
|
||||||
|
assert tab.id == 10
|
||||||
|
mock_send.assert_called_once_with("tabs.active_in_window", {"windowId": 1}, profile=None)
|
||||||
|
|
||||||
|
def test_window_active_tab_missing_raises(self, b, mock_send):
|
||||||
|
mock_send.return_value = None
|
||||||
|
with pytest.raises(RuntimeError, match="No active tab found for window 1"):
|
||||||
|
b.window_active_tab(1)
|
||||||
|
|
||||||
def test_tabs_filter(self, b, mock_send):
|
def test_tabs_filter(self, b, mock_send):
|
||||||
mock_send.return_value = [TAB_DATA]
|
mock_send.return_value = [TAB_DATA]
|
||||||
tabs = b.tabs_filter("example")
|
tabs = b.tabs_filter("example")
|
||||||
@@ -564,7 +582,15 @@ class TestTabModel:
|
|||||||
def test_open(self, tab, mock_send):
|
def test_open(self, tab, mock_send):
|
||||||
tab.open("https://new.example.com")
|
tab.open("https://new.example.com")
|
||||||
mock_send.assert_called_once_with(
|
mock_send.assert_called_once_with(
|
||||||
"navigate.open", {"url": "https://new.example.com", "background": False}, profile=None
|
"navigate.to", {"tabId": 10, "url": "https://new.example.com"}, profile=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_open_background_changes_same_tab(self, tab, mock_send):
|
||||||
|
tab.open("https://new.example.com", background=True)
|
||||||
|
mock_send.assert_called_once_with(
|
||||||
|
"navigate.to",
|
||||||
|
{"tabId": 10, "url": "https://new.example.com"},
|
||||||
|
profile=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_unbound_raises(self):
|
def test_unbound_raises(self):
|
||||||
|
|||||||
@@ -81,3 +81,24 @@ def test_nav_open_in_background(browser):
|
|||||||
assert not new_tab.get("active"), "background tab should not be active"
|
assert not new_tab.get("active"), "background tab should not be active"
|
||||||
finally:
|
finally:
|
||||||
browser("tabs.close", {"tabId": new_id})
|
browser("tabs.close", {"tabId": new_id})
|
||||||
|
|
||||||
|
|
||||||
|
def test_nav_to_updates_existing_tab(browser):
|
||||||
|
result = browser("navigate.open", {"url": "https://example.com", "background": True})
|
||||||
|
tab_id = result["id"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
before_ids = {t["id"] for t in browser("tabs.list")}
|
||||||
|
updated = browser("navigate.to", {"tabId": tab_id, "url": "https://example.org"})
|
||||||
|
assert updated["id"] == tab_id
|
||||||
|
|
||||||
|
tabs = browser("tabs.list")
|
||||||
|
after_ids = {t["id"] for t in tabs}
|
||||||
|
assert after_ids == before_ids
|
||||||
|
tab = next(t for t in tabs if t["id"] == tab_id)
|
||||||
|
assert "example.org" in (tab.get("url") or "")
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
browser("tabs.close", {"tabId": tab_id})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ def test_tabs_active_exists(browser):
|
|||||||
assert len(active) >= 1, "Expected at least one active tab"
|
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_html(browser, http_tab):
|
def test_tabs_html(browser, http_tab):
|
||||||
html = browser("tabs.html", {"tabId": http_tab["id"]})
|
html = browser("tabs.html", {"tabId": http_tab["id"]})
|
||||||
assert isinstance(html, str)
|
assert isinstance(html, str)
|
||||||
|
|||||||
Reference in New Issue
Block a user