docs: 新增 Windows 移植交接文档
docs/WINDOWS_PORT.md 覆盖: - 逐文件可移植性审计(核心逻辑层约 90% 可直接复用) - 核心难点:iOS 17+ 的 RSD 隧道无法在 Windows 复用(macOS 实现依赖 XPC + ObjC runtime 私有框架),改用 pymobiledevice3 的 userspace 隧道 - 已实测确认的命令与 API(--userspace 挂在 lockdown start-tunnel / mounter auto-mount 上,remote start-tunnel 不支持;类名是 UserspaceRsdTunnel) - 窗口启动、数据目录、打包与分发(DMG → Inno Setup)、图标(.icns → .ico) - macOS 侧踩过的坑:镜像挂载用 Python API、必须打包包元数据、不要把 distutils 放进 excludes、AlreadyMountedError 当成功处理 - 设计约束(DVT 长连接、只能改经纬度、last_point 与 last_reported 分离等) - 验收基线(复用现有测试套件)与建议的工作顺序
This commit is contained in:
commit
85e9a53750
65 files changed
+18603
No files matched your search
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""给界面截图(视觉验收用)。
|
||||
|
||||
做法:headless Chrome 的一次性 --screenshot 模式 + 独立 user-data-dir,
|
||||
不依赖 CDP WebSocket(本机没有 websocket-client 库,Chrome 的 CDP 又需要它)。
|
||||
--screenshot 在页面 load 事件后立即截图,用一次注入脚本做等待/交互,
|
||||
脚本通过 URL fragment 传不进去,所以这里用临时 HTML 包装页来间接执行前置动作。
|
||||
|
||||
用法:
|
||||
./.venv/bin/python tools/screenshot.py http://127.0.0.1:8787/ out.png [--wait 9]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
CHROME_CANDIDATES = [
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
]
|
||||
|
||||
|
||||
def find_chrome() -> str:
|
||||
for path in CHROME_CANDIDATES:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
raise SystemExit("未找到 Chrome / Edge / Chromium")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("url")
|
||||
parser.add_argument("out", nargs="?", default="screenshot.png")
|
||||
parser.add_argument("--wait", type=float, default=9.0, help="页面加载后额外等待秒数(等瓦片/WS 渲染)")
|
||||
parser.add_argument("--width", type=int, default=1440)
|
||||
parser.add_argument("--height", type=int, default=900)
|
||||
parser.add_argument("--profile", default="/tmp/iosloc-shot-profile")
|
||||
parser.add_argument("--json", action="store_true", help="同时打印页面控制台日志(--enable-logging)")
|
||||
args = parser.parse_args()
|
||||
|
||||
chrome = find_chrome()
|
||||
profile = Path(args.profile)
|
||||
if profile.exists():
|
||||
shutil.rmtree(profile, ignore_errors=True)
|
||||
profile.mkdir(parents=True, exist_ok=True)
|
||||
out = Path(args.out).resolve()
|
||||
if out.exists():
|
||||
out.unlink()
|
||||
|
||||
command = [
|
||||
chrome,
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
"--hide-scrollbars",
|
||||
"--disable-extensions",
|
||||
"--force-device-scale-factor=2",
|
||||
f"--window-size={args.width},{args.height}",
|
||||
f"--user-data-dir={profile}",
|
||||
# Chrome 没有"加载后等待 N 秒"的开关,用虚拟时间预算让定时器与网络请求跑完
|
||||
f"--virtual-time-budget={int(args.wait * 1000)}",
|
||||
f"--screenshot={out}",
|
||||
args.url,
|
||||
]
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, start_new_session=True)
|
||||
try:
|
||||
output, _ = process.communicate(timeout=max(60.0, args.wait * 6))
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
||||
except Exception:
|
||||
process.kill()
|
||||
output, _ = process.communicate()
|
||||
print("Chrome 超时被终止")
|
||||
if args.json and output:
|
||||
for line in output.splitlines():
|
||||
if any(key in line for key in ("ERROR", "SEVERE", "Uncaught", "console")):
|
||||
print(" [chrome]", line[:220])
|
||||
if not out.exists() or out.stat().st_size == 0:
|
||||
print("截图失败")
|
||||
if output:
|
||||
print(output[-1500:])
|
||||
return 1
|
||||
print(f"截图已保存:{out}({out.stat().st_size // 1024} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user