Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7709292f1c | |||
| b93b2ea36e |
@@ -3,6 +3,14 @@
|
|||||||
本文件对应技能市场 **「更新日志」** Tab。
|
本文件对应技能市场 **「更新日志」** Tab。
|
||||||
发布 CI 会按当前 tag 版本提取匹配的 `##` 小节。
|
发布 CI 会按当前 tag 版本提取匹配的 `##` 小节。
|
||||||
|
|
||||||
|
## 2.0.28
|
||||||
|
|
||||||
|
- 「打开浏览器」识别现代 Chrome `Default/Sessions/Session_*`(及 `Tabs_*`),有可恢复会话时不再强制带平台首页 URL,避免冲掉 Session Cookie 登录态
|
||||||
|
|
||||||
|
## 2.0.27
|
||||||
|
|
||||||
|
- 「打开浏览器」:Profile 已占用时复用窗口;加固 Preferences/Local State「继续上次会话」;有历史会话时冷启动不带 URL,避免冲掉登录态
|
||||||
|
|
||||||
## 2.0.26
|
## 2.0.26
|
||||||
|
|
||||||
- 「打开浏览器」启动前写入 Profile「继续上次会话」,减轻独立 Profile 下 Session Cookie 关窗后丢失(科力普等站点更明显)
|
- 「打开浏览器」启动前写入 Profile「继续上次会话」,减轻独立 Profile 下 Session Cookie 关窗后丢失(科力普等站点更明显)
|
||||||
|
|||||||
2
SKILL.md
2
SKILL.md
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: 账号与凭据管理
|
name: 账号与凭据管理
|
||||||
description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。"
|
description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。"
|
||||||
version: 2.0.26
|
version: 2.0.28
|
||||||
author: 深圳匠厂科技有限公司
|
author: 深圳匠厂科技有限公司
|
||||||
metadata:
|
metadata:
|
||||||
openclaw:
|
openclaw:
|
||||||
|
|||||||
@@ -59,4 +59,4 @@ description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**作者**:深圳匠厂科技有限公司 · **版本**:2.0.26
|
**作者**:深圳匠厂科技有限公司 · **版本**:2.0.27
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import sys
|
|||||||
|
|
||||||
from db.connection import get_conn, init_db
|
from db.connection import get_conn, init_db
|
||||||
from db.accounts_repo import get_account_by_id, get_platform_from_db
|
from db.accounts_repo import get_account_by_id, get_platform_from_db
|
||||||
from service.chrome_profile_prefs import ensure_session_restore
|
from service.chrome_profile_prefs import (
|
||||||
|
ensure_session_restore,
|
||||||
|
profile_has_restorable_session,
|
||||||
|
)
|
||||||
from util.logging_config import get_skill_logger
|
from util.logging_config import get_skill_logger
|
||||||
from util.platforms import PLATFORMS
|
from util.platforms import PLATFORMS
|
||||||
|
|
||||||
@@ -192,6 +195,51 @@ def cmd_reveal_profile(account_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_profile_browser_pids(profile_dir: str):
|
||||||
|
"""找出已占用本 Profile 的 chrome/msedge PID(与采集技能同逻辑)。"""
|
||||||
|
import json as _json
|
||||||
|
|
||||||
|
profile_abs = os.path.abspath(profile_dir)
|
||||||
|
needle = os.path.normcase(profile_abs)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
return []
|
||||||
|
ps = (
|
||||||
|
"Get-CimInstance Win32_Process -Filter "
|
||||||
|
"\"name='chrome.exe' or name='msedge.exe'\" |"
|
||||||
|
" Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
completed = subprocess.run(
|
||||||
|
["powershell", "-NoProfile", "-Command", ps],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=20,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
raw = (completed.stdout or "").strip()
|
||||||
|
if not raw:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
payload = _json.loads(raw)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
rows = payload if isinstance(payload, list) else [payload]
|
||||||
|
pids = []
|
||||||
|
for row in rows:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
cmdline = str(row.get("CommandLine") or "")
|
||||||
|
if needle not in os.path.normcase(cmdline):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
pids.append(int(row.get("ProcessId")))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
return sorted(set(pids))
|
||||||
|
|
||||||
|
|
||||||
def cmd_open(account_id):
|
def cmd_open(account_id):
|
||||||
"""
|
"""
|
||||||
打开该账号的持久化浏览器,仅用于肉眼核对 / 手工登录 / 抓 DOM。
|
打开该账号的持久化浏览器,仅用于肉眼核对 / 手工登录 / 抓 DOM。
|
||||||
@@ -226,6 +274,39 @@ def cmd_open(account_id):
|
|||||||
profile_abs = os.path.abspath(profile_dir)
|
profile_abs = os.path.abspath(profile_dir)
|
||||||
os.makedirs(profile_abs, exist_ok=True)
|
os.makedirs(profile_abs, exist_ok=True)
|
||||||
|
|
||||||
|
existing = _list_profile_browser_pids(profile_abs)
|
||||||
|
if existing:
|
||||||
|
log.info(
|
||||||
|
"browser_open_reuse account_id=%s profile_dir=%s pids=%s",
|
||||||
|
account_id,
|
||||||
|
profile_abs,
|
||||||
|
existing,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"type": "result",
|
||||||
|
"schema_version": 1,
|
||||||
|
"status": "success",
|
||||||
|
"message": "已复用打开中的浏览器窗口",
|
||||||
|
"success": True,
|
||||||
|
"data": {
|
||||||
|
"account_id": int(target["id"]) if target.get("id") is not None else account_id,
|
||||||
|
"account_label": target.get("account_label"),
|
||||||
|
"browser": "Google Chrome",
|
||||||
|
"browser_exe": browser_exe,
|
||||||
|
"profile_dir": profile_abs,
|
||||||
|
"url": resolve_account_browser_url(target),
|
||||||
|
"pid": existing[0],
|
||||||
|
"reused": True,
|
||||||
|
"pids": existing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# 独立 Profile 默认不会「继续上次会话」,Session Cookie 关窗即丢;打开前写入 Preferences
|
# 独立 Profile 默认不会「继续上次会话」,Session Cookie 关窗即丢;打开前写入 Preferences
|
||||||
prefs_result = ensure_session_restore(profile_abs)
|
prefs_result = ensure_session_restore(profile_abs)
|
||||||
log.info("browser_open_prefs %s", prefs_result)
|
log.info("browser_open_prefs %s", prefs_result)
|
||||||
@@ -236,14 +317,19 @@ def cmd_open(account_id):
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# 与 profile_shortcut 同一套人用参数:独立 user-data-dir 保存登录态
|
# 有可恢复会话时不带 URL;首次/空 Profile 仍打开平台首页便于登录。
|
||||||
cmd = [browser_exe, *human_chrome_launch_args(profile_abs, url)]
|
# 须识别现代 Chrome 的 Default/Sessions/Session_*(仅认旧文件名会导致每次都开首页冲掉 Session Cookie)。
|
||||||
|
has_session = profile_has_restorable_session(profile_abs)
|
||||||
|
launch_url = None if has_session else url
|
||||||
|
cmd = [browser_exe, *human_chrome_launch_args(profile_abs, launch_url)]
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
"browser_open_launch exe=%s profile_dir=%s url=%s platform=%s restore_on_startup=%s",
|
"browser_open_launch exe=%s profile_dir=%s launch_url=%s has_session=%s "
|
||||||
|
"platform=%s restore_on_startup=%s",
|
||||||
browser_exe,
|
browser_exe,
|
||||||
profile_abs,
|
profile_abs,
|
||||||
url,
|
launch_url or "(restore)",
|
||||||
|
has_session,
|
||||||
target.get("platform_key"),
|
target.get("platform_key"),
|
||||||
prefs_result.get("restore_after"),
|
prefs_result.get("restore_after"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,21 +3,107 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from util.logging_config import get_skill_logger
|
||||||
|
|
||||||
RESTORE_CONTINUE_WHERE_LEFT_OFF = 1
|
RESTORE_CONTINUE_WHERE_LEFT_OFF = 1
|
||||||
|
|
||||||
|
# Chromium ≤ roughly mid-era: session files lived under Default/
|
||||||
|
_LEGACY_SESSION_FILES = (
|
||||||
|
"Current Session",
|
||||||
|
"Last Session",
|
||||||
|
"Current Tabs",
|
||||||
|
"Last Tabs",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def profile_has_restorable_session(profile_dir: str) -> bool:
|
||||||
|
"""True when the profile has on-disk session data Chrome can restore.
|
||||||
|
|
||||||
|
Modern Chrome/Edge write ``Default/Sessions/Session_*`` (and ``Tabs_*``).
|
||||||
|
Older builds used ``Default/Current Session`` etc. Either form counts.
|
||||||
|
"""
|
||||||
|
default_dir = os.path.join(os.path.abspath(profile_dir), "Default")
|
||||||
|
if any(
|
||||||
|
os.path.isfile(os.path.join(default_dir, name))
|
||||||
|
for name in _LEGACY_SESSION_FILES
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
sessions_dir = os.path.join(default_dir, "Sessions")
|
||||||
|
if not os.path.isdir(sessions_dir):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
names = os.listdir(sessions_dir)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return any(
|
||||||
|
name.startswith("Session_") or name.startswith("Tabs_")
|
||||||
|
for name in names
|
||||||
|
if os.path.isfile(os.path.join(sessions_dir, name))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def preferences_path(profile_dir: str) -> str:
|
def preferences_path(profile_dir: str) -> str:
|
||||||
return os.path.join(os.path.abspath(profile_dir), "Default", "Preferences")
|
return os.path.join(os.path.abspath(profile_dir), "Default", "Preferences")
|
||||||
|
|
||||||
|
|
||||||
|
def local_state_path(profile_dir: str) -> str:
|
||||||
|
return os.path.join(os.path.abspath(profile_dir), "Local State")
|
||||||
|
|
||||||
|
|
||||||
|
def _patch_preferences_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
before = int((data.get("session") or {}).get("restore_on_startup") or 0)
|
||||||
|
exit_before = str((data.get("profile") or {}).get("exit_type") or "")
|
||||||
|
|
||||||
|
session = data.setdefault("session", {})
|
||||||
|
session["restore_on_startup"] = RESTORE_CONTINUE_WHERE_LEFT_OFF
|
||||||
|
session.pop("startup_urls", None)
|
||||||
|
|
||||||
|
profile = data.setdefault("profile", {})
|
||||||
|
if str(profile.get("exit_type") or "").lower() == "crashed":
|
||||||
|
profile["exit_type"] = "Normal"
|
||||||
|
profile["exited_cleanly"] = True
|
||||||
|
|
||||||
|
browser = data.setdefault("browser", {})
|
||||||
|
browser["has_seen_welcome_page"] = True
|
||||||
|
browser["check_default_browser"] = False
|
||||||
|
|
||||||
|
return {"restore_before": before, "exit_type_before": exit_before}
|
||||||
|
|
||||||
|
|
||||||
|
def _patch_local_state(profile_dir: str, log) -> Dict[str, Any]:
|
||||||
|
path = local_state_path(profile_dir)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return {"ok": True, "skipped": True, "path": path}
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("chrome_local_state_read_failed path=%s err=%s", path, exc)
|
||||||
|
return {"ok": False, "path": path, "error": str(exc)}
|
||||||
|
|
||||||
|
profile = data.setdefault("profile", {})
|
||||||
|
info = profile.setdefault("info_cache", {}).setdefault("Default", {})
|
||||||
|
info["active_time"] = float(info.get("active_time") or 0.0) or 1.0
|
||||||
|
exited = profile.get("exited_cleanly", True)
|
||||||
|
profile["exited_cleanly"] = True
|
||||||
|
try:
|
||||||
|
with open(path, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(data, fh, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("chrome_local_state_write_failed path=%s err=%s", path, exc)
|
||||||
|
return {"ok": False, "path": path, "error": str(exc), "exited_cleanly_before": exited}
|
||||||
|
|
||||||
|
log.info("chrome_local_state_patched path=%s exited_cleanly %s->True", path, exited)
|
||||||
|
return {"ok": True, "path": path, "exited_cleanly_before": exited}
|
||||||
|
|
||||||
|
|
||||||
def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
||||||
"""启动浏览器前写入 session.restore_on_startup=1;Crashed → Normal。"""
|
"""启动浏览器前写入 session.restore_on_startup=1;清 startup_urls;Local State 干净退出。"""
|
||||||
|
log = get_skill_logger()
|
||||||
profile_abs = os.path.abspath(profile_dir)
|
profile_abs = os.path.abspath(profile_dir)
|
||||||
prefs = preferences_path(profile_abs)
|
prefs = preferences_path(profile_abs)
|
||||||
os.makedirs(os.path.dirname(prefs), exist_ok=True)
|
os.makedirs(os.path.dirname(prefs), exist_ok=True)
|
||||||
@@ -28,24 +114,18 @@ def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
|||||||
with open(prefs, "r", encoding="utf-8") as fh:
|
with open(prefs, "r", encoding="utf-8") as fh:
|
||||||
data = json.load(fh)
|
data = json.load(fh)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("chrome_prefs_read_failed path=%s", prefs)
|
log.exception("chrome_prefs_read_failed path=%s", prefs)
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
before = int((data.get("session") or {}).get("restore_on_startup") or 0)
|
meta = _patch_preferences_dict(data)
|
||||||
exit_before = str((data.get("profile") or {}).get("exit_type") or "")
|
before = int(meta["restore_before"])
|
||||||
|
exit_before = str(meta["exit_type_before"] or "")
|
||||||
session = data.setdefault("session", {})
|
|
||||||
session["restore_on_startup"] = RESTORE_CONTINUE_WHERE_LEFT_OFF
|
|
||||||
|
|
||||||
profile = data.setdefault("profile", {})
|
|
||||||
if str(profile.get("exit_type") or "").lower() == "crashed":
|
|
||||||
profile["exit_type"] = "Normal"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(prefs, "w", encoding="utf-8") as fh:
|
with open(prefs, "w", encoding="utf-8") as fh:
|
||||||
json.dump(data, fh, ensure_ascii=False, separators=(",", ":"))
|
json.dump(data, fh, ensure_ascii=False, separators=(",", ":"))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("chrome_prefs_write_failed path=%s err=%s", prefs, exc)
|
log.warning("chrome_prefs_write_failed path=%s err=%s", prefs, exc)
|
||||||
return {
|
return {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
"path": prefs,
|
"path": prefs,
|
||||||
@@ -58,9 +138,10 @@ def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
|||||||
|
|
||||||
after = int((data.get("session") or {}).get("restore_on_startup") or 0)
|
after = int((data.get("session") or {}).get("restore_on_startup") or 0)
|
||||||
exit_after = str((data.get("profile") or {}).get("exit_type") or "")
|
exit_after = str((data.get("profile") or {}).get("exit_type") or "")
|
||||||
logger.info(
|
local = _patch_local_state(profile_abs, log)
|
||||||
|
log.info(
|
||||||
"chrome_session_restore_ensured profile_dir=%s prefs=%s existed=%s "
|
"chrome_session_restore_ensured profile_dir=%s prefs=%s existed=%s "
|
||||||
"restore_on_startup %s->%s exit_type %s->%s",
|
"restore_on_startup %s->%s exit_type %s->%s local_state=%s",
|
||||||
profile_abs,
|
profile_abs,
|
||||||
prefs,
|
prefs,
|
||||||
existed,
|
existed,
|
||||||
@@ -68,6 +149,7 @@ def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
|||||||
after,
|
after,
|
||||||
exit_before or "-",
|
exit_before or "-",
|
||||||
exit_after or "-",
|
exit_after or "-",
|
||||||
|
local,
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
@@ -77,4 +159,5 @@ def ensure_session_restore(profile_dir: str) -> Dict[str, Any]:
|
|||||||
"restore_after": after,
|
"restore_after": after,
|
||||||
"exit_type_before": exit_before,
|
"exit_type_before": exit_before,
|
||||||
"exit_type_after": exit_after,
|
"exit_type_after": exit_after,
|
||||||
|
"local_state": local,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,45 @@ class TestPlaywrightLaunchHelpers:
|
|||||||
assert "--start-maximized" in args
|
assert "--start-maximized" in args
|
||||||
|
|
||||||
|
|
||||||
|
class TestProfileHasRestorableSession:
|
||||||
|
def test_empty_profile_false(self, tmp_path):
|
||||||
|
from service.chrome_profile_prefs import profile_has_restorable_session
|
||||||
|
|
||||||
|
assert profile_has_restorable_session(str(tmp_path / "prof")) is False
|
||||||
|
|
||||||
|
def test_legacy_current_session(self, tmp_path):
|
||||||
|
from service.chrome_profile_prefs import profile_has_restorable_session
|
||||||
|
|
||||||
|
default = tmp_path / "prof" / "Default"
|
||||||
|
default.mkdir(parents=True)
|
||||||
|
(default / "Current Session").write_bytes(b"x")
|
||||||
|
assert profile_has_restorable_session(str(tmp_path / "prof")) is True
|
||||||
|
|
||||||
|
def test_modern_sessions_dir(self, tmp_path):
|
||||||
|
from service.chrome_profile_prefs import profile_has_restorable_session
|
||||||
|
|
||||||
|
sessions = tmp_path / "prof" / "Default" / "Sessions"
|
||||||
|
sessions.mkdir(parents=True)
|
||||||
|
(sessions / "Session_13434010951759956").write_bytes(b"sess")
|
||||||
|
assert profile_has_restorable_session(str(tmp_path / "prof")) is True
|
||||||
|
|
||||||
|
def test_modern_tabs_only(self, tmp_path):
|
||||||
|
from service.chrome_profile_prefs import profile_has_restorable_session
|
||||||
|
|
||||||
|
sessions = tmp_path / "prof" / "Default" / "Sessions"
|
||||||
|
sessions.mkdir(parents=True)
|
||||||
|
(sessions / "Tabs_13434010952144072").write_bytes(b"tabs")
|
||||||
|
assert profile_has_restorable_session(str(tmp_path / "prof")) is True
|
||||||
|
|
||||||
|
|
||||||
class TestCmdOpenDirectBrowser:
|
class TestCmdOpenDirectBrowser:
|
||||||
"""account open = same-process Chrome/Edge launch (like reveal-profile)."""
|
"""account open = same-process Chrome/Edge launch (like reveal-profile)."""
|
||||||
|
|
||||||
def test_cmd_open_launches_browser_exe(self, clean_db, tmp_path, capsys, monkeypatch):
|
def _seed_account(self, clean_db, tmp_path, profile):
|
||||||
from db.accounts_repo import insert_account
|
from db.accounts_repo import insert_account
|
||||||
from service import browser_service
|
|
||||||
from util.platforms import seed_platforms
|
from util.platforms import seed_platforms
|
||||||
|
|
||||||
seed_platforms(clean_db)
|
seed_platforms(clean_db)
|
||||||
profile = tmp_path / "prof"
|
|
||||||
aid = insert_account(
|
aid = insert_account(
|
||||||
clean_db,
|
clean_db,
|
||||||
platform_key="sohu",
|
platform_key="sohu",
|
||||||
@@ -45,7 +74,9 @@ class TestCmdOpenDirectBrowser:
|
|||||||
auth_strategy="password_auto",
|
auth_strategy="password_auto",
|
||||||
)
|
)
|
||||||
clean_db.commit()
|
clean_db.commit()
|
||||||
|
return aid
|
||||||
|
|
||||||
|
def _patch_launch(self, monkeypatch, browser_service, tmp_path):
|
||||||
fake_exe = str(tmp_path / "chrome.exe")
|
fake_exe = str(tmp_path / "chrome.exe")
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
browser_service,
|
browser_service,
|
||||||
@@ -64,6 +95,14 @@ class TestCmdOpenDirectBrowser:
|
|||||||
return FakeProc()
|
return FakeProc()
|
||||||
|
|
||||||
monkeypatch.setattr(browser_service.subprocess, "Popen", fake_popen)
|
monkeypatch.setattr(browser_service.subprocess, "Popen", fake_popen)
|
||||||
|
return fake_exe, spawned
|
||||||
|
|
||||||
|
def test_cmd_open_launches_browser_exe(self, clean_db, tmp_path, capsys, monkeypatch):
|
||||||
|
from service import browser_service
|
||||||
|
|
||||||
|
profile = tmp_path / "prof"
|
||||||
|
aid = self._seed_account(clean_db, tmp_path, profile)
|
||||||
|
fake_exe, spawned = self._patch_launch(monkeypatch, browser_service, tmp_path)
|
||||||
|
|
||||||
browser_service.cmd_open(str(aid))
|
browser_service.cmd_open(str(aid))
|
||||||
out = capsys.readouterr().out.strip()
|
out = capsys.readouterr().out.strip()
|
||||||
@@ -88,6 +127,27 @@ class TestCmdOpenDirectBrowser:
|
|||||||
assert "open_child_runner" not in joined
|
assert "open_child_runner" not in joined
|
||||||
assert "python" not in joined.lower()
|
assert "python" not in joined.lower()
|
||||||
|
|
||||||
|
def test_cmd_open_skips_url_when_modern_sessions_exist(
|
||||||
|
self, clean_db, tmp_path, capsys, monkeypatch
|
||||||
|
):
|
||||||
|
"""Modern Chrome Sessions/ must restore without forcing homepage URL."""
|
||||||
|
from service import browser_service
|
||||||
|
|
||||||
|
profile = tmp_path / "prof"
|
||||||
|
sessions = profile / "Default" / "Sessions"
|
||||||
|
sessions.mkdir(parents=True)
|
||||||
|
(sessions / "Session_13434010951759956").write_bytes(b"sess")
|
||||||
|
aid = self._seed_account(clean_db, tmp_path, profile)
|
||||||
|
_fake_exe, spawned = self._patch_launch(monkeypatch, browser_service, tmp_path)
|
||||||
|
|
||||||
|
browser_service.cmd_open(str(aid))
|
||||||
|
out = capsys.readouterr().out.strip()
|
||||||
|
payload = json.loads(out.splitlines()[-1])
|
||||||
|
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert "https://example.com" not in spawned["cmd"]
|
||||||
|
assert spawned["cmd"][-1].startswith("--")
|
||||||
|
|
||||||
|
|
||||||
class TestHumanChromeLaunchArgs:
|
class TestHumanChromeLaunchArgs:
|
||||||
def test_flags_then_url(self, tmp_path):
|
def test_flags_then_url(self, tmp_path):
|
||||||
|
|||||||
Reference in New Issue
Block a user