merge(chv-muse-round12): 合入 P4 浏览器完全对齐 (1c8fc3f)

冲突按并集解:build.sh 保留 P3 authd 检查/拷入/runcmd(含
start chv-authd)+ P4 三件套检查/拷入(P4 的重复 runcmd 行
删掉——同一文件写两次会盖掉 authd 启动行)+ P4 的
cdp-broker.service/tmpfiles(含 ask-queue)+ P3 叶子模块条件
拷入;buildinfo.py 两边内容项并集;chv-browser.py 取 P4 重构
版(broker 客户端);production-readiness.md P4 的 §15 顺延为
§16。P2 的 BrowserTest 改走假 CDP broker(断言不变),两路
干跑夹具补齐并集文件集。
This commit is contained in:
host committed 2026-11-07 00:01:35 +00:00
commit c24cc0ba92
10 files changed
+2116 -278

No files matched your search

+58 -76
View File
@@ -2,7 +2,7 @@
只测本轨交付物(chv-guest/chv-cell.py、chv-broker.py、chv-browser.py、
build.sh 干跑、buildinfo.py 内容项)。全部用假 socket/假进程/假
playwright,绝不连真实网络、不起真实 VM、不改生产行为。
CDP broker,绝不连真实网络、不起真实 VM、不改生产行为。
"""
import importlib.util
import json
@@ -33,6 +33,7 @@ def _load_script(name):
CELL = _load_script("chv-cell.py")
BROKER = _load_script("chv-broker.py")
BROWSER = _load_script("chv-browser.py")
GUARD = _load_script("chv_browser_guard.py")
BUILDINFO = _load_script("buildinfo.py")
@@ -329,21 +330,21 @@ class BrokerTest(unittest.TestCase):
class BrowserTest(unittest.TestCase):
def test_validate_url(self):
self.assertTrue(BROWSER.validate_url("https://example.com")[0])
self.assertTrue(BROWSER.validate_url("http://example.com/x")[0])
self.assertTrue(GUARD.validate_url("https://example.com")[0])
self.assertTrue(GUARD.validate_url("http://example.com/x")[0])
self.assertTrue(
BROWSER.validate_url("https://user@example.com/")[0])
GUARD.validate_url("https://user@example.com/")[0])
for bad in ("ftp://example.com", "file:///etc/passwd",
"https://localhost:8080/", "http://127.0.0.1/",
"https://192.168.1.1/", "https://10.0.0.5/",
"https://172.17.0.2/", "", None):
ok, _ = BROWSER.validate_url(bad)
ok, _ = GUARD.validate_url(bad)
self.assertFalse(ok, bad)
def test_truncate_a11y_small_tree_untouched(self):
tree = {"role": "WebArea", "name": "Example",
"children": [{"role": "heading", "name": "Hi"}]}
out, truncated = BROWSER.truncate_a11y(tree)
out, truncated = GUARD.truncate_a11y(tree)
self.assertFalse(truncated)
self.assertEqual(out, tree)
@@ -351,13 +352,13 @@ class BrowserTest(unittest.TestCase):
tree = {"role": "WebArea",
"children": [{"role": "link", "name": "l%d" % i}
for i in range(300)]}
out, truncated = BROWSER.truncate_a11y(tree)
out, truncated = GUARD.truncate_a11y(tree)
self.assertTrue(truncated)
self.assertLessEqual(len(out["children"]), 200)
def test_truncate_a11y_char_cap(self):
tree = {"role": "WebArea", "name": "x" * 70000, "children": []}
out, truncated = BROWSER.truncate_a11y(tree)
out, truncated = GUARD.truncate_a11y(tree)
self.assertTrue(truncated)
self.assertTrue(out.get("truncated_chars"))
@@ -371,28 +372,26 @@ class BrowserTest(unittest.TestCase):
resp = BROWSER.handle_request(agent, {"op": "bogus"})
self.assertFalse(resp["ok"])
def test_open_rejects_bad_url_without_loading_playwright(self):
def exploding_factory():
raise AssertionError("坏 URL 不应加载 playwright")
def test_open_rejects_bad_url_without_calling_broker(self):
def exploding_broker(req):
raise AssertionError("坏 URL 不应调 broker")
agent = BROWSER.BrowserAgent(playwright_factory=exploding_factory)
agent = BROWSER.BrowserAgent(broker_call=exploding_broker)
resp = agent.open("https://127.0.0.1/secret")
self.assertFalse(resp["ok"])
def test_snapshot_and_screenshot_need_open_first(self):
agent = BROWSER.BrowserAgent(
playwright_factory=lambda: (_ for _ in ()).throw(
AssertionError("不应加载 playwright")))
self.assertFalse(agent.snapshot()["ok"])
self.assertFalse(agent.screenshot()["ok"])
self.assertTrue(agent.close()["ok"])
def test_open_returns_a11y_snapshot_with_fake_playwright(self):
page = FakePage()
with tempfile.TemporaryDirectory() as tmp:
agent = BROWSER.BrowserAgent(
playwright_factory=lambda: FakePlaywright(page),
shots_dir=tmp)
broker_call=FakeCdpBroker(tmp))
self.assertFalse(agent.snapshot()["ok"])
self.assertFalse(agent.screenshot()["ok"])
self.assertTrue(agent.close()["ok"])
def test_open_returns_a11y_snapshot_with_fake_cdp_broker(self):
with tempfile.TemporaryDirectory() as tmp:
agent = BROWSER.BrowserAgent(
broker_call=FakeCdpBroker(tmp))
resp = agent.open("https://example.com")
self.assertTrue(resp["ok"])
self.assertEqual(resp["title"], "Example Domain")
@@ -407,62 +406,43 @@ class BrowserTest(unittest.TestCase):
self.assertTrue(agent.close()["ok"])
class FakePage:
def __init__(self):
self.url = "https://example.com/"
self._closed = False
class FakeCdpBroker:
"""假 CDP broker:收到 req dict 返回 resp dict;open 后才有页。
def goto(self, url, timeout=None):
self.url = url.rstrip("/") + "/"
round-12:P4 起 BrowserAgent 不再直连 Playwright,Chromium
的所有权在单元外 CDP broker 手里;本测试用假 broker 走同一
协议,断言与 P2 原版一致。"""
def wait_for_load_state(self, state, timeout=None):
pass
def __init__(self, shots_dir):
self._shots_dir = shots_dir
self._open = False
self.calls = []
def title(self):
return "Example Domain"
@property
def accessibility(self):
return self
def snapshot(self):
return {"role": "WebArea", "name": "Example Domain",
def __call__(self, req):
self.calls.append(req.get("op"))
op = req.get("op")
tree = {"role": "WebArea", "name": "Example Domain",
"children": [{"role": "heading", "name": "Example"}]}
def screenshot(self, path=None):
with open(path, "wb") as fh:
fh.write(b"\x89PNG")
def close(self):
self._closed = True
class FakeBrowser:
def __init__(self, page):
self._page = page
def new_page(self):
return self._page
def close(self):
pass
class FakeEntered:
def __init__(self, page):
self.chromium = mock.Mock()
self.chromium.launch.return_value = FakeBrowser(page)
class FakePlaywright:
def __init__(self, page):
self._page = page
def __enter__(self):
return FakeEntered(self._page)
def __exit__(self, *exc):
return False
if op == "open":
self._open = True
return {"ok": True, "title": "Example Domain",
"url": "https://example.com/", "a11y": tree}
if op == "snapshot":
if not self._open:
return {"ok": False, "error": "尚未 open"}
return {"ok": True, "title": "Example Domain",
"url": "https://example.com/", "a11y": tree}
if op == "screenshot":
if not self._open:
return {"ok": False, "error": "尚未 open"}
path = os.path.join(self._shots_dir, "shot.png")
with open(path, "wb") as fh:
fh.write(b"\x89PNG")
return {"ok": True, "path": path}
if op == "close":
self._open = False
return {"ok": True}
return {"ok": False, "error": "未知操作"}
class CellCliTest(unittest.TestCase):
@@ -570,7 +550,9 @@ class BuildDryRunTest(unittest.TestCase):
"generate_lockfile.py", "chv-sync.sh",
"chv-cell.py", "chv-broker.py", "chv-browser.py",
"authd.py", "git-credential-chv-authd",
"chv-authd.service"):
"chv-authd.service",
"chv-cdp-broker.py", "chv_browser_guard.py",
"browser-blocklist.txt"):
shutil.copy2(ROOT / "chv-guest" / name,
os.path.join(guest, name))
shutil.copy2(ROOT / "host.py", os.path.join(repo, "host.py"))