- Add browser_cli/url_match.py with a canonical URL key for tab matching - Canonicalize scheme, host, default ports, www., trailing slash, query order - Support reuse modes none, exact, similar and origin via normalize_mode() - Pick the closest match when several tabs qualify, preferring identical URLs - Skip the reload when the reused tab already shows the requested page - Expose nav.reusable_tab() so callers can look up a tab without side effects - Accept reuse/reuse_domain/reuse_title in tabs.open(), not just nav.open() - Add --reuse-exact to nav open and nav open-wait; --reuse is now fuzzy - Default MCP tabs_open to reuse="similar" and return a reused flag - Tell MCP agents in the server instructions to reuse before opening - Document the reuse modes in README for MCP, CLI and Python API - Cover url_match, SDK reuse paths and MCP reuse behaviour with new tests - Bump package and extension version to 0.16.9 - Refresh uv.lock for the updated dependency set
85 lines
3.6 KiB
Python
85 lines
3.6 KiB
Python
"""Matching a requested URL against already-open tabs."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from browser_cli.url_match import (
|
|
find_reusable_tab,
|
|
needs_navigation,
|
|
normalize_mode,
|
|
url_key,
|
|
urls_match,
|
|
)
|
|
|
|
def test_canonical_key_ignores_cosmetic_differences():
|
|
assert url_key("https://Example.com:443/app/?b=2&a=1#top") == url_key("https://www.example.com/app?a=1&b=2")
|
|
|
|
def test_canonical_key_keeps_meaningful_differences():
|
|
assert url_key("https://example.com/app") != url_key("http://example.com/app")
|
|
assert url_key("https://example.com/app") != url_key("https://example.com:8080/app")
|
|
assert url_key("https://example.com/a") != url_key("https://example.com/b")
|
|
assert url_key("https://example.com/a?x=1") != url_key("https://example.com/a?x=2")
|
|
|
|
def test_non_urls_have_no_key():
|
|
assert url_key(None) is None
|
|
assert url_key("") is None
|
|
assert url_key("not a url") is None
|
|
|
|
@pytest.mark.parametrize("reuse,reuse_domain,expected", [
|
|
(False, False, "none"),
|
|
(None, False, "none"),
|
|
(True, False, "similar"),
|
|
("exact", False, "exact"),
|
|
("ORIGIN", False, "origin"),
|
|
(False, True, "origin"),
|
|
(True, True, "origin"),
|
|
])
|
|
def test_flags_fold_into_modes(reuse, reuse_domain, expected):
|
|
assert normalize_mode(reuse, reuse_domain) == expected
|
|
|
|
def test_unknown_mode_is_rejected():
|
|
with pytest.raises(ValueError, match="Unknown reuse mode"):
|
|
normalize_mode("fuzzy")
|
|
|
|
def test_exact_mode_only_matches_identical_strings():
|
|
assert urls_match("https://example.com/app", "https://example.com/app", "exact")
|
|
assert not urls_match("https://example.com/app", "https://example.com/app/", "exact")
|
|
|
|
def test_similar_mode_matches_the_same_page():
|
|
assert urls_match("https://example.com/app/", "https://example.com/app#section", "similar")
|
|
assert not urls_match("https://example.com/app", "https://example.com/other", "similar")
|
|
assert not urls_match("https://example.com/app", "https://other.example/app", "similar")
|
|
|
|
def test_similar_mode_treats_a_site_root_request_as_the_site():
|
|
assert urls_match("https://example.com/", "https://example.com/web/streams", "similar")
|
|
assert not urls_match("https://example.com/web", "https://example.com/web/streams", "similar")
|
|
|
|
def test_origin_mode_matches_any_page_of_the_site():
|
|
assert urls_match("https://example.com/web", "https://example.com/other", "origin")
|
|
assert not urls_match("https://example.com/web", "http://example.com/web", "origin")
|
|
|
|
def test_none_mode_never_matches():
|
|
assert not urls_match("https://example.com", "https://example.com", "none")
|
|
assert find_reusable_tab([{"id": 1, "url": "https://example.com"}], "https://example.com", "none") is None
|
|
|
|
def test_closest_tab_wins():
|
|
tabs = [
|
|
{"id": 1, "url": "https://example.com/web/streams/detail"},
|
|
{"id": 2, "url": "https://example.com/web"},
|
|
{"id": 3, "url": "https://example.com/web/?tab=1#x"},
|
|
]
|
|
assert find_reusable_tab(tabs, "https://example.com/web", "similar")["id"] == 2
|
|
assert find_reusable_tab(tabs, "https://example.com/", "similar")["id"] == 2
|
|
|
|
def test_matching_works_on_sdk_tab_objects():
|
|
from browser_cli.models import Tab
|
|
|
|
tabs = [Tab(id=4, window_id=1, active=False, url="https://example.com/app?a=1")]
|
|
assert find_reusable_tab(tabs, "https://example.com/app/#top?", "none") is None
|
|
assert find_reusable_tab(tabs, "https://example.com/app?a=1", "similar").id == 4
|
|
|
|
def test_navigation_is_skipped_only_for_the_same_page():
|
|
assert not needs_navigation("https://example.com/app/", "https://example.com/app#top")
|
|
assert needs_navigation("https://example.com/app", "https://example.com/app/next")
|
|
assert needs_navigation(None, "https://example.com/app")
|