【免费下载链接】paseo
Orchestrate multiple coding agents from desktop and mobile
Paseo 的移动端测试体系以Agent Device(.ad脚本)为主力,配合Maestro 兼容层、bash 自验证循环与真机验证流程,覆盖 iOS/Android 上的终端、键盘、工作区创建、语音音频等核心场景。本文以 docs/mobile-testing.md 为骨架,结合 packages/app/e2e/mobile 与 packages/app/maestro 下的真实脚本,讲解从录制回放到原生构建验证的完整移动端测试工作流,读完即可在本地复现整套套件并扩展自己的.ad流程。
Agent Device:移动端 E2E 的主格式
Agent Device.ad脚本是 Paseo 移动端 E2E 的主要格式。工作流是"录制—回放":一个 Agent 交互式地发现可用流程,把成功的命令保存下来,然后回放运行器在本地或 CI 中执行同一份"打字计划"(typed plan),确保流程可重复、可断言、可回归。
录制一个流程
在正常驱动 App 的过程中录制流程:
agent-device open sh.paseo.debug \ --platform ios \ --session terminal-author \ --save-script ./packages/app/e2e/mobile/agent-device/terminal.ios.ad agent-device snapshot -i --session terminal-author agent-device press 'id="workspace-header-menu-trigger"' --session terminal-author # Continue the flow and verify its result with wait/get/is/find. agent-device close --session terminal-author关键点:
open <appId>启动 App 并开启会话,--save-script指定最终落盘路径;snapshot输出当前 UI 层级,press按选择器点击;- 会话内的断言只用
wait、get、is、find这四类命令表达; close会把会话中的全部命令写入脚本——截图只是证据,不是断言。
选择器要基于稳定的 App ID(如id="workspace-header-menu-trigger"),避免依赖易变的文案;wait "text" "__AD_OUTPUT_OK__"这类文本断言只在输出足够独特时使用。
运行整套移动端套件
npm run test:e2e:mobile运行器(runner)会依次完成:
- 使用隔离的 Agent Device 状态目录,不污染日常开发环境;
- 校验或启动本 checkout 对应的 Metro;
- 预热 iOS 运行器(prewarm);
- 从每个脚本的
context头解析出目标平台; - 清理会话、运行器租约(lease)、daemon 以及它自己启动的 Metro 进程。
尝试结果(attempt results)、耗时、日志与失败产物统一落在.dev/agent-device-artifacts目录。
如果当前 worktree 的 Metro 已经占用了非默认端口,用环境变量指定端口:
PASEO_MOBILE_E2E_METRO_PORT=62093 npm run test:e2e:mobile最小的.ad示例:原生终端
仓库中 native-terminal-basic.ios.ad 与 native-terminal-basic.android.ad 是最小的完整示例。iOS 版:
context platform=ios timeout=60000 retries=1 env APP_ID=sh.paseo.debug open "${APP_ID}" wait "id=\"workspace-header-menu-trigger\"" 20000 press "id=\"workspace-header-menu-trigger\"" press "id=\"workspace-header-new-terminal\"" wait "id=\"terminal-keyboard-toggle\"" 10000 press "id=\"terminal-keyboard-toggle\"" type "printf '__AD_%s__\\n' OUTPUT_OK" --delay-ms 0 press "id=\"terminal-key-enter\"" wait "text" "__AD_OUTPUT_OK__" 10000 closeAndroid 版(native-terminal-basic.android.ad)结构相同,但元素定位方式不同——iOS 用press "id=...",Android 用wait "role=\"button\" label=\"New terminal\"",这说明跨平台脚本必须按各自平台的无障碍语义编写。
两个脚本做的事情完全一致:打开全新终端 → 以零延迟(--delay-ms 0)输入命令 → 提交 → 断言其独有输出。运行前提是 App 已连接到一个持有活跃 workspace 的 daemon。context头中的timeout与retries让慢速 CI 环境也能稳定通过。
录制后的脚本维护
当回放(replay)偏离预期时,运行器会输出排序后的选择器建议(ranked selector suggestions),帮助你定位是哪个元素没找到。修复方式是有意识地编辑脚本后从头重跑。--update参数仅为了兼容而保留,不再自动重写脚本——防止回放偏差被静默"修正"掩盖。
Android 键盘连续性专项验证
键盘交互是移动端最脆弱的部分,Paseo 为它准备了独立的验证路径。
终端键盘(terminal-keyboard)
要在当前 checkout 中验证 Android 键盘连续性:在 App 里打开一个空提示符的空闲终端,收起键盘后运行:
ANDROID_SERIAL=emulator-5554 node packages/app/e2e/mobile/terminal-keyboard/android.mjs这个 harness 使用真实的停靠软件键盘(如 Gboard),它会:
- 点击 Ctrl、Esc、Enter 物理键序列;
- 检查 Android 聚焦输入的身份(focused input identity)与 IME 隐藏/显示事件;
- 把截图与日志保存到
.dev/agent-device-artifacts/terminal-keyboard-android。
设置PASEO_TERMINAL_KEYBOARD_APP_ID=sh.paseo可改为测试已安装的生产构建。该 harness从不提交聊天消息,避免污染对话历史。
组合器键盘回归(composer-keyboard)
npm run test:e2e:composer-keyboard:android该命令保留聊天控件与键盘的回归流程,然后在聊天、workspace 草稿页、New workspace三个 host 上分别跑同一套场景:
- 增长(growth)、保持高度(retained-height)、边界(bounds);
- 长按删除(hold-to-delete)与点击背景关闭键盘(background-tap dismissal);
- 在输入高度不变的前提下,检查空内容点击与顶部点击;
- 每个 host 还要在键盘开/关两种状态下分别打开:命令/文件自动补全、模型选择器、附件菜单、forge 选择器;
- 最后从每个 host 提交一段长 fixture,并在两次时机校验原生流位移(native stream displacement):响应稳定之后一次,回到底部等滚动条消失后一次。图片对比会排除滚动条区域——仅靠键盘消失并不能证明原生滚动接收到了触摸。
产物按 host 分组放在.dev/agent-device-artifacts/composer-keyboard-android。daemon、Metro、设备等配置通过PASEO_COMPOSER_KEYBOARD_*环境变量注入,见 android.sh。
必须使用软件键盘:headless 输入助手无法验证可见的组合器约束。
在 Play 镜像模拟器上,先禁用 Gmail 与 Calendar——它们的欢迎页会自行启动并抢占输入焦点,导致 App 看起来仍有焦点但报IME did not become visible:
adb shell pm disable-user --user 0 com.google.android.gm adb shell pm disable-user --user 0 com.google.android.calendar从 android.sh 源码还能看到实现细节:它通过dumpsys window中type=ime frame=[0,N]的帧顶坐标判断 IME 是否可见并计算键盘位移,用com.callstack.agentdevice.imehelper/.TestInputMethodService作为无头 IME 助手,并在 IME 切换后等待 100 次轮询确认稳定——Gboard 首次显示在负载主机上可能耗时数秒。
Maestro 兼容层
存量 Maestro 流程位于 packages/app/maestro。Agent Device 可通过agent-device test <path> --maestro执行其支持的 Maestro 子集,为.ad覆盖率替换旧流程提供迁移路径。
运行一个 Maestro 流程
maestro test packages/app/maestro/my-flow.yaml可复用的子流程放在packages/app/maestro/flows/。
截图输出目录
takeScreenshot写入当前工作目录——YAML 里无法配置输出路径。为避免污染 checkout,先cd到临时目录,再用绝对路径引用流程:
FLOW="$(pwd)/packages/app/maestro/my-flow.yaml" mkdir -p /tmp/maestro-out cd /tmp/maestro-out && maestro test "$FLOW"packages/app/maestro/.gitignore 排除了*.png作为兜底安全网。
元素定位:testID/nativeID 优先
在组件上使用testID或nativeID,流程中用id:定位。优先于文本匹配——文案一改,文本匹配就碎了:
// 组件 <Pressable testID="sidebar-sessions" onPress={onPress}># 流程 - tapOn: id: "sidebar-sessions" - assertVisible: id: "sidebar-sessions"条件步骤
只有特定元素在屏幕上时才执行的步骤,用runFlow:when:visible:
- runFlow: when: visible: id: "sidebar-sessions" commands: - swipe: direction: LEFT duration: 300这正是 flows/dev-client.yaml 处理 Expo dev client 只在 dev 构建出现的屏幕的方式。
不要对运行中的 dev App 使用 launchApp
launchApp会杀掉并重启 App,破坏 Expo dev client 状态与 host 连接。测试已在运行的 dev App时,干脆省略 launchApp,直接与屏幕上的内容交互。只有需要干净启动的流程(如 onboarding 测试)才用launchApp。
滑动手势
用start/end百分比坐标获得精确控制:
# 从左侧边缘滑动打开侧边栏 - swipe: start: "5%,50%" end: "80%,50%" duration: 300direction: RIGHT更简单但不够精确——通用滑动用它,起始位置关键的场景(边缘手势、避开特定 UI 区域)用坐标。
断言语义
assertVisible检查的是实际屏幕可见性,不只是视图树中存在。元素在树里但屏幕外(如translateX: -400)会正确失败——这正是捕捉"状态说 open 但视图实际隐藏"这类动画 bug 的关键。
异步元素用extendedWaitUntil:
- extendedWaitUntil: visible: ".*Online.*" timeout: 90000Dev client 处理与"进入聊天"原语
两个可复用流程处理启动后的 Expo dev client 屏幕:
- flows/launch.yaml —— 处理 dev launcher、关闭 dev menu、断言 "Welcome to Paseo";
- flows/dev-client.yaml —— 同上但不断言具体路由。
flows/land-in-chat.yaml 是"进入聊天"的规范原语:clearState→ 运行launch.yaml→ 点击欢迎屏的直连选项 → 输入127.0.0.1:6767→ 提交 → 等待message-input-root。任何组合器级 fixture 都可以叠在它上面:
appId: sh.paseo --- - runFlow: flows/land-in-chat.yaml # ...你的场景从这里开始,此时组合器已就绪参考 image-picker-repro.yaml。
本地 E2E 优先直连,不用 relay 配对:relay 需要把 400+ 字符的配对 URL 输入输入框,直连只需要127.0.0.1:6767。daemon 监听 6767,模拟器可直接访问。
New Workspace 创建回归
Android 工作区创建回归有专用 harness:
bash packages/app/maestro/test-workspace-create-android-crash.sh录制启动/连接/侧边栏准备之后的短窗口:
bash packages/app/maestro/record-workspace-create-android-focus.sh流程细节记录在 packages/app/maestro/README.md。核心规则是:有效的 new-workspace 断言必须证明重定向完成——选择真实模型 → 点Create→ 等workspace-header-title→ 等message-input-root→ 断言New workspace消失 → 断言 Android redbox 字符串不存在。只等组合器太弱,因为校验失败后它仍可能是/new路由。
新工作区场景应组合 flows/ 下的可复用子流程:
android-dev-client.yamlconnect-direct-if-welcome.yamlopen-prepared-project-sidebar.yamlnew-workspace-open-from-sidebar.yamlnew-workspace-select-codex-gpt54.yamlnew-workspace-submit-and-assert-created.yaml
workspace-create 的 shell 脚本会先把这些子流程渲染到临时目录再运行 Maestro,这样嵌套的runFlow路径与${PASEO_MAESTRO_*}占位符能协同工作。可选环境变量:
PASEO_MAESTRO_APP_ID=sh.paseo.debug PASEO_MAESTRO_DIRECT_ENDPOINT=127.0.0.1:6767 PASEO_MAESTRO_DAEMON_WS_URL=ws://127.0.0.1:6767/ws PASEO_MAESTRO_PROJECT_PATH=/path/to/git/repo脚本假定已安装包 ID 为sh.paseo.debug的开发构建、本地 daemon 运行在127.0.0.1:6767、有已连接的 Android 设备/模拟器,并会调用adb reverse tcp:6767 tcp:6767,但不会重启 daemon。
Maestro 输入控件的实现约束
Maestro 的inputText是逐字符触发的。React Native 的受控TextInput会每次按键重渲染;如果受控输入的状态更新滞后或在输入中途重挂载,字符会被静默丢弃——最终屏幕值是截断/错乱的。
因此,E2E 流程要输入的控件(host 端点、配对 URL 等)必须用非受控 ref 支撑的输入:defaultValue+onChangeText写入useRef,提交时通过 ref 读取。没有逐键重渲染,就不会丢字符。参考pair-link-modal.tsx的模式(useRef支撑的onChangeText,无value=prop)。改源码时务必配套:inputText之后立即加一个对输入id + text的 MaestroassertVisible,让回归当场暴露。
iOS 上触发原生 present 的下拉菜单
iOS 上,当下拉菜单(DropdownMenu/ RNModal)的某个项需要启动原生 presenter(如图片选择器的PHPickerViewController、UIDocumentPicker)时,回调不能在Modal还在关闭过程中触发。UIKit 的关闭完成跨越 React 卸载后的多个帧;在关闭中途启动原生 presenter 会残留一个不可见 backdrop,吞掉后续所有触摸。
DropdownMenu的处理方式:把选中项的onSelect推迟到Modal.onDismiss(UIKit 层关闭完成)触发之后,再追加一小段缓冲才调用。见components/ui/dropdown-menu.tsx的selectItem/flushPendingSelect。新组件组合下拉菜单与原生 presenter 时,直接复用这个 dropdown,不要发明新的时序 shim。
自验证循环:把系统级状态包进 bash
Maestro 只能与 App UI 交互——它无法切换 iOS 外观、改 locale、模拟网络条件。依赖系统级状态的 bug,需要用 bash 脚本把系统变更包在 Maestro 运行之间。这个模式也允许 Agent在无人手动测试的情况下自验证修复。
模式
- 跑基线 Maestro 流程(确认功能正常)
- 通过
xcrun simctl做系统级变更(切外观等) - 重跑 Maestro 流程(确认功能仍正常)
- 重复 N 次迭代以捕捉偶发失败
脚本在临时目录内运行maestro test,避免截图弄脏 checkout。规范示例是 test-sidebar-theme.sh:
bash packages/app/maestro/test-sidebar-theme.sh 6 1 # 参数:iterations=6, wait_seconds=1(切换与测试之间的等待秒数)脚本模式的关键元素:
set -euo pipefail ITERATIONS="${1:-3}" for i in $(seq 1 "$ITERATIONS"); do # 切换系统状态 xcrun simctl ui booted appearance light # 等待变更传播 sleep 1 # 运行 Maestro 流程并捕获结果 if maestro test "$FLOW" 2>&1 | tee "$ITER_DIR/test.log"; then echo "PASS" else echo "FAIL" xcrun simctl io booted screenshot "$ITER_DIR/failure-state.png" fi doneAndroid 音频焦点中断
语音模式使用自定义的expo-two-way-audioAndroid 模块,因此来电与其他系统音频所有者必须用模拟器/系统命令测试,不能只写 JS 测试。验证语音恢复在音频焦点被拒时不会崩溃:
adb shell am start -n sh.paseo/.MainActivity # 在已有组合器中启动语音模式,然后用 Home 把 Paseo 退到后台 adb emu gsm call 5551234 # 来电仍在响铃时把 Paseo 带回前台预期结果:Paseo 不抛RuntimeException: Audio focus request failed;原生音频上报中断,语音模式连贯地停止或暂停。
空闲时释放音频会话
Paseo 在不采集也不播放时必须释放 OS 音频会话,否则用户的背景音乐会一直暂停。iOS 上这不只是"录音中"的问题:.playAndRecord/.voiceChat类别是不可混音(non-mixing)的,且能存活到后台,iOS 每次 App 回到前台都会重新断言它——所以一次听写就会让音乐在整个进程生命周期内、每次打开都中断,直到强杀 App。Android 端持有AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE有同样效果。还必须重置MODE_IN_COMMUNICATION并清除选中的通信设备——仅放弃焦点可能让蓝牙耳机停留在窄带通话路由上却没有激活的麦克风指示。
audio-engine.native.ts 的createAudioEngine会在采集停止且播放队列排空时调用releaseAudioSession()(源码中refs.captureActive || refs.activePlayback || refs.queue.length > 0任一为真则跳过),iOS 模块还会在OnAppEntersBackground时释放。原生侧再用isRecording/speechPlayer.isPlaying二次守卫,因为原生引擎是被多个 JS 引擎包装器(语音 provider + 听写)共享的单例,只有它知道真实状态。
这无法用 JS 测试验证——必须在设备上验证:用蓝牙耳机播放音乐 → 打开 Paseo → 用一次听写 → 停止 → 确认音乐以全质量恢复;然后后台/前台切换 App,确认音乐持续全质量播放。
Unistyles + Reanimated 冲突
崩溃现象
把 Unistyles 的主题响应式样式(StyleSheet.create((theme) => ...))直接应用到Animated.View,会在主题切换时报"Unable to find node on an unmounted component"。
原因:Unistyles 把 styled 组件包进<UnistylesComponent>并通过 C++ 打补丁原生视图属性;Reanimated 同时管理同一个原生节点的动画变换。主题切换时两个系统同时更新该节点,视图崩溃。
修复方式
Animated.View的静态定位用普通 React NativeStyleSheet.create;主题相关值用useUnistyles()以内联样式传入:
// 错误:Unistyles 动态样式用在 Animated.View 上 const styles = StyleSheet.create((theme) => ({ sidebar: { position: "absolute", top: 0, left: 0, bottom: 0, backgroundColor: theme.colors.surfaceSidebar, // 主题响应式 overflow: "hidden", }, })); <Animated.View style={[styles.sidebar, animatedStyle]} />;// 正确:静态样式表 + 内联主题值 import { StyleSheet as RNStyleSheet } from "react-native"; const staticStyles = RNStyleSheet.create({ sidebar: { position: "absolute", top: 0, left: 0, bottom: 0, overflow: "hidden", }, }); const { theme } = useUnistyles(); <Animated.View style={[staticStyles.sidebar, animatedStyle, { backgroundColor: theme.colors.surfaceSidebar }]} />;普通View可以安全使用 Unistyles 动态样式——冲突只针对Animated.View。
原生聊天流布局
原生 agent stream 使用倒置FlatList,因此聊天布局有三套坐标系:
- 时间顺序的流顺序(chronological stream order)
- strategy 排序的数组顺序(strategy-ordered array order)
- 原生倒置单元格的视觉顺序(native inverted cell visual order)
不要在 React 渲染循环里计算流邻居、历史/live-head 接缝、turn footer 归属、助手块间距、工具序列结尾。这些策略都在 packages/app/src/agent-stream/layout.ts 中实现,并在不渲染 React Native的情况下做单元测试(StreamLayoutItem携带aboveItem/belowItem/gapBelow/assistantSpacing/toolSequence/frameOrder等字段)。
平台特定的流边缘属于StreamStrategy:
- 正向 web:把最后一条历史项作为历史/live-head 边界,内容渲染在 footer 之前;
- 原生倒置:把第一条历史项作为边界,并补偿倒置单元格的子顺序。
如果移动端聊天 footer 出现重复或跑到助手消息上方,先看 packages/app/src/agent-stream/layout.test.ts。不要为这类 bug 新增 React Native 渲染器测试——先让纯布局不变量失败。
本地 iOS 真机构建
原生改动(packages/expo-two-way-audio/ios/下的任何内容,或任何新的 Expo 模块)不能通过 Metro 或 EAS Update 测试——它们只分发 JS。陈旧的 dev-client 二进制会静默缺少新原生函数,功能 no-op,测试证明不了任何东西。必须重新构建二进制。
陈旧 dev client 的症状:启动时报Cannot find native module 'ExpoDocumentPicker',随后是一连串Route "./X.tsx" is missing the required default export警告。这些路由警告不是 router bug——模块级 throw 中止了_layout.tsx求值,导致其 default export 从未被赋值。一个缺失的原生模块,N 条令人困惑的警告。
前置条件:Xcode 需要一个已登录的 Apple ID 作为签名团队,且带有有效的 keychain token。仅仅出现在 Xcode → Settings → Accounts 里不够——如果Xcode-Token缺失,下面的构建会在签名阶段失败,却把责任推给"证书"。
cd packages/app npm --prefix ../.. run build:client APP_VARIANT=development npx expo prebuild --platform ios APP_VARIANT=development npx expo run:ios --deviceAPP_VARIANT=development必须显式设置。iosnpm 脚本不设置它,而app.config.js默认production——所以裸跑npm run ios会构建sh.paseo与 App Store 安装冲突,而不是sh.paseo.debugdev client。忽略 prebuild 的--non-interactive is not supported警告;需要非交互时用CI=1。
签名需要可用的 Xcode Apple ID token
真机构建会以一组全部指向错误方向的错误失败:
error: No Accounts: Add a new account in Accounts settings. error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't include signing certificate "Apple Development: <name>". error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't include the aps-environment entitlement.No Accounts并不一定意味着没有配置账户。先看构建日志里它上面一行:
DVTDeveloperAccountManager: Failed to load credentials for <apple-id>: Invalid credentials in keychain for <apple-id>, missing Xcode-Token这才是真实失败:Apple ID 已在 Xcode 注册,但它的Xcode-Tokenkeychain 项丢失(通常发生在 Apple ID 改密码或 keychain 重置之后)。Xcode 把它报成No Accounts,然后回退到缓存的过期 profile——如果机器上持有比该 profile 更新的 dev 证书,就出现"证书不匹配"这个二阶症状。追证书错误是死路。
验证实际配置而不是轻信报错信息:
# Xcode 知道的账户 defaults read com.apple.dt.Xcode DVTDeveloperAccountManagerAppleIDLists # 实际缺失的凭据(有 error => 无 token) security find-generic-password -s "Xcode-Token"修复:Xcode → Settings → Accounts,对列出的 Apple ID 重新登录(删除再重加会强制刷新 token)。这会恢复-allowProvisioningUpdates、profile 再生成与推送。
用CODE_SIGN_STYLE=Manual给 Xcode 托管的 profile 签名不是变通方案;Xcode 会以is Xcode managed, but signing settings require a manually managed profile拒绝。
如果 token 修好之前就需要真机构建,可以构建未签名版本、再用缓存 profile 里已嵌入的证书手工签名(用security cms -D -i <profile>.mobileprovision列出,与security find-identity -v -p codesigning对比):
xcodebuild ... CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" build cp <profile>.mobileprovision "$APP/embedded.mobileprovision" # 先签嵌套 frameworks/dylibs,再签 bundle,然后: xcrun devicectl device install app --device <udid> "$APP"这是临时补救,不是修复:expo prebuild会重新生成packages/app/ios/并丢弃它。它还需要剥离缓存 profile 缺乏的 entitlements(如aps-environment),所以此类构建中推送不可用。
只想验证原生 Swift 能否编译,完全跳过签名——pods 有自己的 scheme:
cd packages/app/ios xcodebuild -workspace PaseoDebug.xcworkspace -scheme ExpoTwoWayAudio \ -sdk iphoneos -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO build确认日志编译的是你的文件而不是缓存副本:SwiftCompile行应指向packages/expo-two-way-audio/ios/...,而不是 DerivedData 内的路径。
Headless 启动无法验证运行时行为
xcrun devicectl device process launch在手机屏幕关闭时,启动约一秒后报告The app terminated with the exit code 0——App 在 JS bundle 加载前被挂起并杀死。这不是构建缺陷。用对照实验确认:用同样方式启动com.apple.Preferences,观察它同样退出。Headless 安装与启动只能证明二进制有效、启动到达 RN init;任何屏幕行为都需要真人握着手机。
iOS 模拟器常用命令
# 截图 xcrun simctl io booted screenshot /tmp/screenshot.png # 深色/浅色模式 xcrun simctl ui booted appearance # 查看当前 xcrun simctl ui booted appearance dark # 设为深色 xcrun simctl ui booted appearance light # 设为浅色Expo dev server 日志在运行npm run dev的 tmux 面板里。daemon 日志在$PASEO_HOME/daemon.log(参见 development.md)。
总结:移动端测试的选型矩阵
Paseo 的移动端测试策略可以归纳为四层:
| 场景 | 工具 | 典型命令 |
|---|---|---|
| 常规 E2E 流程(终端、菜单、导航) | Agent Device.ad | agent-device open/press/wait/close |
| 存量 Maestro 流程与迁移 | agent-device test --maestro/maestro test | npm run test:e2e:mobile |
| 系统级状态依赖的回归 | bash 自验证循环包裹 Maestro | bash packages/app/maestro/test-sidebar-theme.sh 6 1 |
| 原生模块改动验证 | 真机构建(必须重新编译二进制) | APP_VARIANT=development npx expo run:ios --device |
核心原则贯穿始终:截图是证据不是断言、用稳定 ID 定位而不是文案、JS 测试覆盖不到的(音频焦点、IME、系统外观)交给系统命令、原生改动必须重编 dev client 而不是靠 Metro。遵循这些约束,.ad与 Maestro 流程才能成为可信的回归防线,也让 Agent 能在无人值守时自验证修复。
【免费下载链接】paseo
Orchestrate multiple coding agents from desktop and mobile
相关推荐
Paseo 测试工程指南:从行为驱动单元测试到真实端到端验证的分层体系
Paseo 测试工程指南:从行为驱动单元测试到真实端到端验证的分层体系 导读 Paseo 是一个从桌面与移动端编排多个本地编码 Agent 的开发环境(仓库根目
expo-updates 端到端测试指南:用 Maestro 搭建 E2E 测试环境并手动验证 Updates API
expo updates 端到端测试指南:用 Maestro 搭建 E2E 测试环境并手动验证 Updates API 本文是 expo updates 端到端
移动开发前端跨平台原生移动OpenCore Legacy Patcher完整教程:4步让旧Mac焕发新生
OpenCore Legacy Patcher完整教程:4步让旧Mac焕发新生 OpenCore Legacy Patcher是一款革命性的开源工具,专为那些被
操作系统固件驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考