baguette-ios-simulator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBaguette iOS Simulator Manager
Baguette iOS Simulator Manager
Skill by ara.so — Daily 2026 Skills collection.
Baguette is a Swift CLI tool that creates, boots, and shuts down iOS Simulator devices, streams their screens at 60fps, and injects taps/swipes/multi-finger gestures entirely headlessly — no Simulator.app GUI required. It also serves a self-contained web UI for single-device and multi-device (farm) control.
由ara.so开发的技能工具 — 2026每日技能合集。
Baguette是一款Swift CLI工具,可创建、启动和关闭iOS Simulator设备,以60fps的帧率传输设备画面,并且完全以无头模式注入点击/滑动/多指手势操作——无需Simulator.app图形界面。它还提供一个独立的Web UI,支持单设备和多设备(集群)控制。
Requirements
要求
- Apple Silicon Mac only
- macOS 15+
- Xcode 26 (links against private SimulatorKit/CoreSimulator frameworks)
- 仅支持Apple Silicon芯片的Mac
- macOS 15及以上版本
- Xcode 26(依赖私有SimulatorKit/CoreSimulator框架)
Install
安装
bash
brew install tddworks/tap/baguettebash
brew install tddworks/tap/baguetteBuild from Source
从源码构建
bash
git clone https://github.com/tddworks/baguette
cd baguette
make # release build via ./build.sh
swift test # run the test suitebash
git clone https://github.com/tddworks/baguette
cd baguette
make # 通过./build.sh进行发布构建
swift test # 运行测试套件Key CLI Commands
核心CLI命令
Device Management
设备管理
bash
undefinedbash
undefinedList all simulators (default + custom sets)
列出所有模拟器(默认设备集+自定义设备集)
baguette list
baguette list
Boot a simulator headlessly (no Simulator.app window)
以无头模式启动模拟器(无Simulator.app窗口)
baguette boot --udid <UDID>
baguette boot --udid <UDID>
Shutdown a simulator
关闭模拟器
baguette shutdown --udid <UDID>
undefinedbaguette shutdown --udid <UDID>
undefinedScreen Streaming
画面流传输
bash
undefinedbash
undefinedStream frames to stdout as MJPEG (default)
将画面以MJPEG格式输出到标准输出(默认)
baguette stream --udid <UDID> --fps 60 --format mjpeg
baguette stream --udid <UDID> --fps 60 --format mjpeg
Stream as H.264/AVCC
以H.264/AVCC格式传输画面
baguette stream --udid <UDID> --fps 60 --format avcc
baguette stream --udid <UDID> --fps 60 --format avcc
Pipe MJPEG to ffplay for local preview
将MJPEG画面管道输出到ffplay进行本地预览
baguette stream --udid <UDID> --fps 30 --format mjpeg | ffplay -i -
undefinedbaguette stream --udid <UDID> --fps 30 --format mjpeg | ffplay -i -
undefinedOne-Shot Gesture Input
单次手势输入
Coordinates are in device points; / are the simulator screen size in points.
--width--heightbash
undefined坐标为设备点;/为模拟器屏幕的点尺寸。
--width--heightbash
undefinedTap at a point
在指定位置点击
baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954
baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954
Tap with custom duration
自定义时长的点击
baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954 --duration 0.1
baguette tap --udid <UDID> --x 219 --y 478 --width 438 --height 954 --duration 0.1
Swipe from top to bottom (scroll down)
从上到下滑动(向下滚动)
baguette swipe --udid <UDID>
--startX 219 --startY 190
--endX 219 --endY 760
--width 438 --height 954
--startX 219 --startY 190
--endX 219 --endY 760
--width 438 --height 954
baguette swipe --udid <UDID>
--startX 219 --startY 190
--endX 219 --endY 760
--width 438 --height 954
--startX 219 --startY 190
--endX 219 --endY 760
--width 438 --height 954
Pinch to zoom in (startSpread < endSpread)
捏合放大(startSpread < endSpread)
baguette pinch --udid <UDID>
--cx 219 --cy 478
--startSpread 60 --endSpread 200
--width 438 --height 954
--cx 219 --cy 478
--startSpread 60 --endSpread 200
--width 438 --height 954
baguette pinch --udid <UDID>
--cx 219 --cy 478
--startSpread 60 --endSpread 200
--width 438 --height 954
--cx 219 --cy 478
--startSpread 60 --endSpread 200
--width 438 --height 954
Two-finger pan
双指平移
baguette pan --udid <UDID>
--x1 175 --y1 478
--x2 263 --y2 478
--dx 0 --dy -100
--width 438 --height 954
--x1 175 --y1 478
--x2 263 --y2 478
--dx 0 --dy -100
--width 438 --height 954
baguette pan --udid <UDID>
--x1 175 --y1 478
--x2 263 --y2 478
--dx 0 --dy -100
--width 438 --height 954
--x1 175 --y1 478
--x2 263 --y2 478
--dx 0 --dy -100
--width 438 --height 954
Hardware buttons
硬件按键操作
baguette press --udid <UDID> --button home
baguette press --udid <UDID> --button lock
undefinedbaguette press --udid <UDID> --button home
baguette press --udid <UDID> --button lock
undefinedStreaming Gesture Input (stdin JSON)
流式手势输入(标准输入JSON)
For real-time or scripted gesture sequences, pipe newline-delimited JSON to :
baguette inputbash
baguette input --udid <UDID>Each line gets an ack: or .
{"ok":true}{"ok":false,"error":"..."}对于实时或脚本化的手势序列,将换行分隔的JSON管道输入到:
baguette inputbash
baguette input --udid <UDID>每一行都会收到响应: 或 。
{"ok":true}{"ok":false,"error":"..."}Web UI Server
Web UI服务器
bash
undefinedbash
undefinedStart the web server (default port 8421)
启动Web服务器(默认端口8421)
baguette serve
baguette serve
Custom port and host
自定义端口和主机地址
baguette serve --port 9000 --host 0.0.0.0
baguette serve --port 9000 --host 0.0.0.0
Custom device set
使用自定义设备集
baguette serve --port 8421 --device-set /path/to/device-set
baguette serve --port 8421 --device-set /path/to/device-set
Open single-device dashboard
打开单设备控制面板
Open multi-device farm dashboard
打开多设备集群控制面板
undefinedundefinedDeviceKit Chrome/Bezel Data
DeviceKit Chrome/边框数据
bash
undefinedbash
undefinedPrint bezel layout JSON for a booted device
输出已启动设备的边框布局JSON
baguette chrome layout --udid <UDID>
baguette chrome layout --udid <UDID>
Write composite PNG (device screenshot + bezel) to stdout
将合成PNG(设备截图+边框)输出到标准输出
baguette chrome composite --udid <UDID> > screenshot.png
baguette chrome composite --udid <UDID> > screenshot.png
By device name (no UDID needed)
通过设备名称操作(无需UDID)
baguette chrome layout --device-name "iPhone 17 Pro"
baguette chrome composite --device-name "iPhone 17 Pro" > iphone17pro_bezel.png
undefinedbaguette chrome layout --device-name "iPhone 17 Pro"
baguette chrome composite --device-name "iPhone 17 Pro" > iphone17pro_bezel.png
undefinedWire Protocol — Streaming Input via stdin
有线协议 — 通过标准输入流式输入
Send newline-delimited JSON to :
baguette input --udid <UDID>json
{"type":"tap", "x":219, "y":478, "width":438, "height":954, "duration":0.05}
{"type":"swipe", "startX":219, "startY":760, "endX":219, "endY":190, "width":438, "height":954, "duration":0.3}
{"type":"touch1-down", "x":219, "y":478, "width":438, "height":954}
{"type":"touch1-move", "x":225, "y":485, "width":438, "height":954}
{"type":"touch1-up", "x":225, "y":485, "width":438, "height":954}
{"type":"touch2-down", "x1":175, "y1":478, "x2":263, "y2":478, "width":438, "height":954}
{"type":"touch2-move", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954}
{"type":"touch2-up", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954}
{"type":"pinch", "cx":219, "cy":478, "startSpread":60, "endSpread":200, "width":438, "height":954}
{"type":"button", "button":"home"}
{"type":"button", "button":"lock"}
{"type":"scroll", "deltaX":0, "deltaY":-50}将换行分隔的JSON发送到:
baguette input --udid <UDID>json
{"type":"tap", "x":219, "y":478, "width":438, "height":954, "duration":0.05}
{"type":"swipe", "startX":219, "startY":760, "endX":219, "endY":190, "width":438, "height":954, "duration":0.3}
{"type":"touch1-down", "x":219, "y":478, "width":438, "height":954}
{"type":"touch1-move", "x":225, "y":485, "width":438, "height":954}
{"type":"touch1-up", "x":225, "y":485, "width":438, "height":954}
{"type":"touch2-down", "x1":175, "y1":478, "x2":263, "y2":478, "width":438, "height":954}
{"type":"touch2-move", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954}
{"type":"touch2-up", "x1":150, "y1":478, "x2":288, "y2":478, "width":438, "height":954}
{"type":"pinch", "cx":219, "cy":478, "startSpread":60, "endSpread":200, "width":438, "height":954}
{"type":"button", "button":"home"}
{"type":"button", "button":"lock"}
{"type":"scroll", "deltaX":0, "deltaY":-50}WebSocket Protocol (for Web UI / Custom Clients)
WebSocket协议(适用于Web UI/自定义客户端)
Connect to (or ).
ws://localhost:8421/simulators/<UDID>/stream?format=mjpegavcc连接到(或)。
ws://localhost:8421/simulators/<UDID>/stream?format=mjpegavccServer → Client (binary frames)
服务器 → 客户端(二进制帧)
- MJPEG: raw JPEG bytes per message
- AVCC: 1-byte tag prefix:
- — avcC description
0x01 - — keyframe
0x02 - — delta frame
0x03 - — JPEG seed frame (renders before H.264 IDR)
0x04
- MJPEG:每条消息为原始JPEG字节
- AVCC:1字节标签前缀:
- — avcC描述信息
0x01 - — 关键帧
0x02 - — 增量帧
0x03 - — JPEG种子帧(在H.264 IDR之前渲染)
0x04
Client → Server (text JSON)
客户端 → 服务器(文本JSON)
json
{"type":"set_bitrate", "bps": 2000000}
{"type":"set_fps", "fps": 30}
{"type":"set_scale", "scale": 0.5}
{"type":"force_idr"}
{"type":"snapshot"}Gesture input messages (same format as stdin wire protocol above) are also accepted over the WebSocket.
json
{"type":"set_bitrate", "bps": 2000000}
{"type":"set_fps", "fps": 30}
{"type":"set_scale", "scale": 0.5}
{"type":"force_idr"}
{"type":"snapshot"}手势输入消息(格式与上述标准输入有线协议相同)也可通过WebSocket发送。
Web UI Routes
Web UI路由
| Method | Path | Description |
|---|---|---|
| | Redirects → |
| | Device list HTML |
| | |
| | Stream page HTML |
| | Boot device |
| | Shutdown device |
| | Bezel layout JSON |
| | Rasterized bezel PNG |
| | Live stream + input |
| | Multi-device farm HTML |
| 方法 | 路径 | 描述 |
|---|---|---|
| | 重定向至 → |
| | 设备列表HTML页面 |
| | 返回 |
| | 画面流页面HTML |
| | 启动设备 |
| | 关闭设备 |
| | 边框布局JSON数据 |
| | 光栅化边框PNG图片 |
| | 实时画面流+输入控制 |
| | 多设备集群HTML页面 |
Code Examples
代码示例
Scripting Gestures from Swift
Swift脚本化手势操作
swift
import Foundation
// Build a gesture sequence as newline-delimited JSON
func makeGestureScript() -> String {
let gestures: [[String: Any]] = [
// Boot sequence: tap the app icon
["type": "tap", "x": 100, "y": 200, "width": 390, "height": 844, "duration": 0.05],
// Scroll down in a list
["type": "swipe", "startX": 195, "startY": 600,
"endX": 195, "endY": 200, "width": 390, "height": 844, "duration": 0.4],
// Pinch to zoom
["type": "pinch", "cx": 195, "cy": 422,
"startSpread": 50, "endSpread": 180, "width": 390, "height": 844],
// Press home
["type": "button", "button": "home"]
]
return gestures.compactMap { dict -> String? in
guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
return String(data: data, encoding: .utf8)
}.joined(separator: "\n")
}
// Run baguette input with the script
func runGestureScript(udid: String, script: String) async throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/local/bin/baguette")
process.arguments = ["input", "--udid", udid]
let inputPipe = Pipe()
let outputPipe = Pipe()
process.standardInput = inputPipe
process.standardOutput = outputPipe
try process.run()
let inputData = (script + "\n").data(using: .utf8)!
inputPipe.fileHandleForWriting.write(inputData)
inputPipe.fileHandleForWriting.closeFile()
process.waitUntilExit()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let acks = String(data: outputData, encoding: .utf8) ?? ""
print("Acks:\n\(acks)")
}swift
import Foundation
// 将手势序列构建为换行分隔的JSON
func makeGestureScript() -> String {
let gestures: [[String: Any]] = [
// 启动流程:点击应用图标
["type": "tap", "x": 100, "y": 200, "width": 390, "height": 844, "duration": 0.05],
// 在列表中向下滚动
["type": "swipe", "startX": 195, "startY": 600,
"endX": 195, "endY": 200, "width": 390, "height": 844, "duration": 0.4],
// 捏合放大
["type": "pinch", "cx": 195, "cy": 422,
"startSpread": 50, "endSpread": 180, "width": 390, "height": 844],
// 按下Home键
["type": "button", "button": "home"]
]
return gestures.compactMap { dict -> String? in
guard let data = try? JSONSerialization.data(withJSONObject: dict) else { return nil }
return String(data: data, encoding: .utf8)
}.joined(separator: "\n")
}
// 使用脚本运行baguette input
func runGestureScript(udid: String, script: String) async throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/local/bin/baguette")
process.arguments = ["input", "--udid", udid]
let inputPipe = Pipe()
let outputPipe = Pipe()
process.standardInput = inputPipe
process.standardOutput = outputPipe
try process.run()
let inputData = (script + "\n").data(using: .utf8)!
inputPipe.fileHandleForWriting.write(inputData)
inputPipe.fileHandleForWriting.closeFile()
process.waitUntilExit()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let acks = String(data: outputData, encoding: .utf8) ?? ""
print("响应:\n\(acks)")
}Listing and Booting Simulators
列出并启动模拟器
swift
import Foundation
struct SimulatorInfo: Codable {
let running: [Device]
let available: [Device]
struct Device: Codable {
let udid: String
let name: String
let os: String
let state: String
}
}
func fetchSimulators(port: Int = 8421) async throws -> SimulatorInfo {
let url = URL(string: "http://localhost:\(port)/simulators.json")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(SimulatorInfo.self, from: data)
}
func bootSimulator(udid: String, port: Int = 8421) async throws {
let url = URL(string: "http://localhost:\(port)/simulators/\(udid)/boot")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (_, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw URLError(.badServerResponse)
}
}
// Usage
Task {
let sims = try await fetchSimulators()
print("Running: \(sims.running.map(\.name))")
if let first = sims.available.first {
try await bootSimulator(udid: first.udid)
print("Booted \(first.name)")
}
}swift
import Foundation
struct SimulatorInfo: Codable {
let running: [Device]
let available: [Device]
struct Device: Codable {
let udid: String
let name: String
let os: String
let state: String
}
}
func fetchSimulators(port: Int = 8421) async throws -> SimulatorInfo {
let url = URL(string: "http://localhost:\(port)/simulators.json")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(SimulatorInfo.self, from: data)
}
func bootSimulator(udid: String, port: Int = 8421) async throws {
let url = URL(string: "http://localhost:\(port)/simulators/\(udid)/boot")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (_, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw URLError(.badServerResponse)
}
}
// 使用示例
Task {
let sims = try await fetchSimulators()
print("运行中: \(sims.running.map(\.name))")
if let first = sims.available.first {
try await bootSimulator(udid: first.udid)
print("已启动 \(first.name)")
}
}Connecting to a Live Stream WebSocket
连接实时画面流WebSocket
swift
import Foundation
func connectToSimulatorStream(udid: String, port: Int = 8421) {
let url = URL(string: "ws://localhost:\(port)/simulators/\(udid)/stream?format=mjpeg")!
let session = URLSession(configuration: .default)
let task = session.webSocketTask(with: url)
task.resume()
// Reduce bitrate and fps for a thumbnail
let setFps = URLSessionWebSocketTask.Message.string(
#"{"type":"set_fps","fps":15}"#
)
let setBitrate = URLSessionWebSocketTask.Message.string(
#"{"type":"set_bitrate","bps":500000}"#
)
task.send(setFps) { _ in }
task.send(setBitrate) { _ in }
// Receive MJPEG frames
func receiveFrame() {
task.receive { result in
switch result {
case .success(.data(let jpegData)):
// jpegData is a raw JPEG — display in NSImageView / UIImageView
print("Received frame: \(jpegData.count) bytes")
receiveFrame()
case .success(.string(let text)):
print("Control message: \(text)")
receiveFrame()
case .failure(let error):
print("Stream ended: \(error)")
}
}
}
receiveFrame()
}swift
import Foundation
func connectToSimulatorStream(udid: String, port: Int = 8421) {
let url = URL(string: "ws://localhost:\(port)/simulators/\(udid)/stream?format=mjpeg")!
let session = URLSession(configuration: .default)
let task = session.webSocketTask(with: url)
task.resume()
// 降低码率和帧率以生成缩略图
let setFps = URLSessionWebSocketTask.Message.string(
#"{"type":"set_fps","fps":15}"#
)
let setBitrate = URLSessionWebSocketTask.Message.string(
#"{"type":"set_bitrate","bps":500000}"#
)
task.send(setFps) { _ in }
task.send(setBitrate) { _ in }
// 接收MJPEG帧
func receiveFrame() {
task.receive { result in
switch result {
case .success(.data(let jpegData)):
// jpegData为原始JPEG数据 — 可在NSImageView/UIImageView中显示
print("收到帧: \(jpegData.count) 字节")
receiveFrame()
case .success(.string(let text)):
print("控制消息: \(text)")
receiveFrame()
case .failure(let error):
print("流已结束: \(error)")
}
}
}
receiveFrame()
}Sending Gestures over WebSocket
通过WebSocket发送手势
swift
func sendTapOverWebSocket(task: URLSessionWebSocketTask, x: Double, y: Double,
width: Double, height: Double) {
let gesture: [String: Any] = [
"type": "tap",
"x": x, "y": y,
"width": width, "height": height,
"duration": 0.05
]
guard let data = try? JSONSerialization.data(withJSONObject: gesture),
let json = String(data: data, encoding: .utf8) else { return }
task.send(.string(json)) { error in
if let error { print("Send error: \(error)") }
}
}
func sendPinchOverWebSocket(task: URLSessionWebSocketTask,
cx: Double, cy: Double,
startSpread: Double, endSpread: Double,
width: Double, height: Double) {
let gesture: [String: Any] = [
"type": "pinch",
"cx": cx, "cy": cy,
"startSpread": startSpread,
"endSpread": endSpread,
"width": width, "height": height
]
guard let data = try? JSONSerialization.data(withJSONObject: gesture),
let json = String(data: data, encoding: .utf8) else { return }
task.send(.string(json)) { _ in }
}swift
func sendTapOverWebSocket(task: URLSessionWebSocketTask, x: Double, y: Double,
width: Double, height: Double) {
let gesture: [String: Any] = [
"type": "tap",
"x": x, "y": y,
"width": width, "height": height,
"duration": 0.05
]
guard let data = try? JSONSerialization.data(withJSONObject: gesture),
let json = String(data: data, encoding: .utf8) else { return }
task.send(.string(json)) { error in
if let error { print("发送错误: \(error)") }
}
}
func sendPinchOverWebSocket(task: URLSessionWebSocketTask,
cx: Double, cy: Double,
startSpread: Double, endSpread: Double,
width: Double, height: Double) {
let gesture: [String: Any] = [
"type": "pinch",
"cx": cx, "cy": cy,
"startSpread": startSpread,
"endSpread": endSpread,
"width": width, "height": height
]
guard let data = try? JSONSerialization.data(withJSONObject: gesture),
let json = String(data: data, encoding: .utf8) else { return }
task.send(.string(json)) { _ in }
}Shell Script: Full Automation Flow
Shell脚本:完整自动化流程
bash
#!/usr/bin/env bash
set -euo pipefailbash
#!/usr/bin/env bash
set -euo pipefailStart the baguette server in background
在后台启动baguette服务器
baguette serve --port 8421 &
SERVER_PID=$!
sleep 1
baguette serve --port 8421 &
SERVER_PID=$!
sleep 1
Get the first available simulator UDID
获取第一个可用模拟器的UDID
UDID=$(baguette list | grep "iPhone 17 Pro" | head -1 | awk '{print $1}')
echo "Using simulator: $UDID"
UDID=$(baguette list | grep "iPhone 17 Pro" | head -1 | awk '{print $1}')
echo "使用模拟器: $UDID"
Boot it
启动模拟器
baguette boot --udid "$UDID"
sleep 3
baguette boot --udid "$UDID"
sleep 3
Run a tap sequence
执行点击操作
baguette tap --udid "$UDID" --x 195 --y 422 --width 390 --height 844
baguette tap --udid "$UDID" --x 195 --y 422 --width 390 --height 844
Pipe a gesture script
管道输入手势脚本
cat <<EOF | baguette input --udid "$UDID"
{"type":"tap","x":195,"y":200,"width":390,"height":844}
{"type":"swipe","startX":195,"startY":600,"endX":195,"endY":200,"width":390,"height":844,"duration":0.3}
{"type":"button","button":"home"}
EOF
cat <<EOF | baguette input --udid "$UDID"
{"type":"tap","x":195,"y":200,"width":390,"height":844}
{"type":"swipe","startX":195,"startY":600,"endX":195,"endY":200,"width":390,"height":844,"duration":0.3}
{"type":"button","button":"home"}
EOF
Capture a screenshot with bezel
捕获带边框的截图
baguette chrome composite --udid "$UDID" > screenshot.png
echo "Screenshot saved to screenshot.png"
baguette chrome composite --udid "$UDID" > screenshot.png
echo "截图已保存至screenshot.png"
Shutdown
关闭模拟器
baguette shutdown --udid "$UDID"
kill $SERVER_PID
undefinedbaguette shutdown --udid "$UDID"
kill $SERVER_PID
undefinedConfiguration
配置
Environment Variables
环境变量
| Variable | Description |
|---|---|
| Override the served web root (e.g. point to |
bash
undefined| 变量 | 描述 |
|---|---|
| 覆盖Web服务的根目录(例如,指向 |
bash
undefinedLive-iterate on web UI without rebuilding
在不重新构建的情况下实时迭代Web UI
export BAGUETTE_WEB_DIR="$(pwd)/Sources/Baguette/Resources/Web"
baguette serve
undefinedexport BAGUETTE_WEB_DIR="$(pwd)/Sources/Baguette/Resources/Web"
baguette serve
undefinedDevice Sets
设备集
bash
undefinedbash
undefinedUse a custom simulator device set
使用自定义模拟器设备集
baguette serve --device-set /path/to/my-device-set
baguette list # uses default device set
undefinedbaguette serve --device-set /path/to/my-device-set
baguette list # 使用默认设备集
undefinedCommon Patterns
常见使用模式
CI/CD: Boot, Test, Shutdown
CI/CD:启动、测试、关闭
bash
#!/usr/bin/env bash
UDID=$(xcrun simctl list devices available -j | \
python3 -c "import sys,json; devs=[d for v in json.load(sys.stdin)['devices'].values() for d in v if 'iPhone 17' in d['name'] and d['isAvailable']]; print(devs[0]['udid'])")
baguette boot --udid "$UDID"bash
#!/usr/bin/env bash
UDID=$(xcrun simctl list devices available -j | \
python3 -c "import sys,json; devs=[d for v in json.load(sys.stdin)['devices'].values() for d in v if 'iPhone 17' in d['name'] and d['isAvailable']]; print(devs[0]['udid'])")
baguette boot --udid "$UDID"Run your XCTest suite or UI tests here
在此处运行你的XCTest套件或UI测试
xcodebuild test -scheme MyApp -destination "id=$UDID"
baguette shutdown --udid "$UDID"
undefinedxcodebuild test -scheme MyApp -destination "id=$UDID"
baguette shutdown --udid "$UDID"
undefinedStreaming to a File
画面流录制到文件
bash
undefinedbash
undefinedCapture 10 seconds of MJPEG to file
捕获10秒的MJPEG画面到文件
baguette stream --udid <UDID> --fps 30 --format mjpeg
| head -c $((10 * 30 * 50000)) > recording.mjpeg
| head -c $((10 * 30 * 50000)) > recording.mjpeg
baguette stream --udid <UDID> --fps 30 --format mjpeg
| head -c $((10 * 30 * 50000)) > recording.mjpeg
| head -c $((10 * 30 * 50000)) > recording.mjpeg
Convert to MP4 with ffmpeg
使用ffmpeg转换为MP4格式
baguette stream --udid <UDID> --fps 30 --format mjpeg
| ffmpeg -f mjpeg -i - -t 10 -c:v libx264 output.mp4
| ffmpeg -f mjpeg -i - -t 10 -c:v libx264 output.mp4
undefinedbaguette stream --udid <UDID> --fps 30 --format mjpeg
| ffmpeg -f mjpeg -i - -t 10 -c:v libx264 output.mp4
| ffmpeg -f mjpeg -i - -t 10 -c:v libx264 output.mp4
undefinedMulti-Finger Gesture Sequence (Real-Time)
多指手势序列(实时)
bash
undefinedbash
undefinedSend a real-time 2-finger swipe up (simulate pull-to-refresh for two fingers)
发送实时双指向上滑动(模拟双指下拉刷新)
cat <<'EOF' | baguette input --udid <UDID>
{"type":"touch2-down","x1":160,"y1":600,"x2":230,"y2":600,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":500,"x2":230,"y2":500,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":400,"x2":230,"y2":400,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":300,"x2":230,"y2":300,"width":390,"height":844}
{"type":"touch2-up","x1":160,"y1":300,"x2":230,"y2":300,"width":390,"height":844}
EOF
undefinedcat <<'EOF' | baguette input --udid <UDID>
{"type":"touch2-down","x1":160,"y1":600,"x2":230,"y2":600,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":500,"x2":230,"y2":500,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":400,"x2":230,"y2":400,"width":390,"height":844}
{"type":"touch2-move","x1":160,"y1":300,"x2":230,"y2":300,"width":390,"height":844}
{"type":"touch2-up","x1":160,"y1":300,"x2":230,"y2":300,"width":390,"height":844}
EOF
undefinedTroubleshooting
故障排除
"Command not found: baguette"
"Command not found: baguette"
bash
undefinedbash
undefinedEnsure Homebrew's bin is in PATH
确保Homebrew的bin目录在PATH中
export PATH="/opt/homebrew/bin:$PATH"
export PATH="/opt/homebrew/bin:$PATH"
Or check install location
或检查安装位置
brew --prefix tddworks/tap/baguette
undefinedbrew --prefix tddworks/tap/baguette
undefinedSimulator Won't Boot
模拟器无法启动
bash
undefinedbash
undefinedCheck Xcode 26 is selected (required for SimulatorKit frameworks)
确认已选择Xcode 26(SimulatorKit框架必需)
xcode-select -p
xcode-select -p
Should show Xcode 26 path, e.g. /Applications/Xcode-26.0.app/Contents/Developer
应显示Xcode 26路径,例如 /Applications/Xcode-26.0.app/Contents/Developer
sudo xcode-select -s /Applications/Xcode-26.0.app/Contents/Developer
sudo xcode-select -s /Applications/Xcode-26.0.app/Contents/Developer
Verify the UDID exists
验证UDID是否存在
baguette list
undefinedbaguette list
undefinedStream Connects but No Frames
流已连接但无画面
bash
undefinedbash
undefinedMake sure the simulator is booted (not just created)
确保模拟器已启动(而非仅创建)
baguette boot --udid <UDID>
sleep 3
baguette stream --udid <UDID> --fps 30 --format mjpeg | xxd | head
undefinedbaguette boot --udid <UDID>
sleep 3
baguette stream --udid <UDID> --fps 30 --format mjpeg | xxd | head
undefinedInput Gestures Not Registering
输入手势无响应
- Coordinates must be in device points, not pixels. For a 3x display at 390pt wide, pixel width is 1170 — always use point values.
- Ensure and
--widthmatch the simulator's actual screen size in points (check with--height).baguette chrome layout --udid <UDID> - Only and
homebuttons are functional on iOS 26 (lockcommand).press
- 坐标必须为设备点,而非像素。对于3倍分辨率、宽390pt的设备,像素宽度为1170 — 务必使用点值。
- 确保和
--width与模拟器实际屏幕点尺寸匹配(可通过--height查看)。baguette chrome layout --udid <UDID> - iOS 26上仅和
home按键可通过lock命令操作。press
Web UI Not Updating
Web UI不更新
bash
undefinedbash
undefinedUse BAGUETTE_WEB_DIR for live file iteration
使用BAGUETTE_WEB_DIR实时迭代文件
export BAGUETTE_WEB_DIR="$(pwd)/Sources/Baguette/Resources/Web"
baguette serve
export BAGUETTE_WEB_DIR="$(pwd)/Sources/Baguette/Resources/Web"
baguette serve
Edit .html/.js/.css files and hard-refresh the browser
编辑.html/.js/.css文件后强制刷新浏览器
undefinedundefinedPort Already in Use
端口已被占用
bash
undefinedbash
undefinedCheck what's using port 8421
检查哪个进程占用了8421端口
lsof -i :8421
lsof -i :8421
Use a different port
使用其他端口
baguette serve --port 9000
open http://localhost:9000/simulators
undefinedbaguette serve --port 9000
open http://localhost:9000/simulators
undefinedBuild Failures (Source Build)
源码构建失败
bash
undefinedbash
undefinedEnsure arm64e target and Xcode 26 SDK
确保目标为arm64e且使用Xcode 26 SDK
xcrun --sdk macosx --show-sdk-path
xcrun --sdk macosx --show-sdk-path
Should show macOS 15+ SDK from Xcode 26
应显示Xcode 26提供的macOS 15+ SDK
swift --version
swift --version
Should show Swift 6.1+
应显示Swift 6.1+
make clean && make
undefinedmake clean && make
undefined