首页
/ Folo mobile e2e:用 Maestro 在 iOS 模拟器与 Android 模拟器上跑通认证流程的实战指南

Folo mobile e2e:用 Maestro 在 iOS 模拟器与 Android 模拟器上跑通认证流程的实战指南

2026-09-05 17:19:44作者:钟日瑜

本文围绕 Folo 仓库中的 Agent 技能文档 SKILL.md 展开,讲解如何在 apps/mobile 上运行基于 Maestro 的移动端端到端测试:从环境自检、模拟器/App 构建,到注册、登出、登录三条认证旅程的完整执行与结果判定。读完本文,你可以独立完成 iOS/Android 双平台的 E2E 认证验证,并能读懂运行器脚本 run-maestro.sh 与各 YAML 流程文件背后的实现细节。

技能文档定位与覆盖范围

mobile-e2e 是一份面向 Agent 的操作技能文档,其 frontmatter 明确了用途:

name: mobile-e2e
description: Run apps/mobile Maestro end-to-end tests in this repo. Use when an agent needs to validate mobile auth flows on iOS Simulator or Android Emulator. Current maintained coverage is register, sign out, and sign in.
disable-model-invocation: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep

关键信息有三点:一是它只针对 apps/mobile 的 Maestro 测试;二是当前维护的覆盖范围是**注册(register)、登出(sign out)、登录(sign in)**三条认证旅程;三是 disable-model-invocation: true 表示该技能需要人工显式触发,而非由模型自动调用。

文档列出了五个“files that matter”,即整套 E2E 体系的核心组成:

角色 路径
运行器脚本 apps/mobile/e2e/run-maestro.sh
iOS 认证流程 apps/mobile/e2e/flows/ios/auth.yaml
Android 认证流程 apps/mobile/e2e/flows/android/core.yaml
共享认证流程 apps/mobile/e2e/flows/shared/ 下的 *.yaml
调试产物目录 apps/mobile/e2e/artifacts/

第一步永远是环境自检

技能文档要求在仓库根目录执行:

cd apps/mobile
pnpm run e2e:doctor
pnpm run typecheck

e2e:doctor 的实际定义见 apps/mobile/package.json

"e2e:doctor": "sh -c 'for f in e2e/flows/shared/*.yaml e2e/flows/android/*.yaml e2e/flows/ios/*.yaml; do maestro check-syntax $f; done'"

也就是说,doctor 命令会用 Maestro CLI 对 sharedandroidios 三个目录下的全部 YAML 流程做语法校验,能在真正启动模拟器之前拦住拼写错误、结构损坏的流程文件。pnpm run typecheck 则保证应用代码本身可编译。两条命令一过,才能谈得上跑设备级测试。

iOS:必须使用模拟器 .app 构建,而不是 Expo 开发客户端

SKILL.md 对 iOS 的第一条硬性要求是:Use a simulator .app build, not an Expo development client. 原因是 Expo 开发客户端依赖 Metro 服务与热更新通道,行为与最终产物不一致;E2E 要验证的是独立构建的 Folo.app 本体。

首选模拟器策略

当机器上有多台模拟器时,技能文档给出的选择策略是:优先最新的已安装 iOS 运行时 + 最新一代 iPhone 模拟器,即偏向“最新版 iOS 上的最新 iPhone 机型”。

启动模拟器

xcrun simctl boot <IOS_UDID>
xcrun simctl bootstatus <IOS_UDID> -b
open -a Simulator --args -CurrentDeviceUDID <IOS_UDID>

bootstatus <UDID> -b 会阻塞等待启动完成,保证后续安装 App 时模拟器已就绪。

App bundle 的三级解析顺序

运行器 run-maestro.shresolve_ios_app_path 函数(L43-L72)按以下顺序解析 .app 位置:

  1. 环境变量 MAESTRO_IOS_APP_PATH —— 可以是目录,也可以是一个 .tar.gz 压缩包;若是压缩包,脚本会解压到临时目录并 find 出其中的 Folo.app(见 extract_ios_app_from_tar,L35-L41);
  2. apps/mobile 目录下最新的本地 build-*.tar.gz(按文件名排序取最后一个);
  3. 已在 ~/Library/Developer/Xcode/DerivedData 中存在的 Release-iphonesimulator/Folo.app
  4. 以上都没有时,自动触发构建(见下节)。

README.md 还说明了这类 tar 包的典型来源:eas build --local --platform ios --profile e2e-ios-simulator 的本地构建产物。

Folo.app 不存在时如何构建

cd apps/mobile/ios
pod install
xcodebuild -workspace Folo.xcworkspace \
  -scheme Folo \
  -configuration Release \
  -sdk iphonesimulator \
  -destination 'id=<IOS_UDID>' \
  build

预期产物路径为:

~/Library/Developer/Xcode/DerivedData/.../Build/Products/Release-iphonesimulator/Folo.app

对照运行器里的 build_ios_simulator_app 函数(run-maestro.sh),自动构建时还会额外传入:

PROFILE=e2e-ios-simulator \
EXPO_PUBLIC_E2E_ENV_PROFILE="${EXPO_PUBLIC_E2E_ENV_PROFILE:-}" \
EXPO_PUBLIC_E2E_LANGUAGE="${EXPO_PUBLIC_E2E_LANGUAGE:-en}" \

即默认以 e2e-ios-simulator 应用配置、英文语言打包,保证模拟器构建的形态与 E2E 预期一致;构建结束后同样用 find 在 DerivedData 中定位 Folo.app 返回给上层。

Apple Silicon 模拟器编译优化

在 Apple Silicon Mac 上、且只为当前这台机器的当前模拟器构建时,技能文档建议只编译活动架构:

xcodebuild ... \
  ONLY_ACTIVE_ARCH=YES \
  ARCHS=arm64

这一策略并非写在 SKILL.md 里的“口头约定”,而是运行器里的真实逻辑:append_ios_arch_argsrun-maestro.sh)通过 uname -m 判断主机架构,仅在 arm64 时输出这两个参数,再追加到 xcodebuild 命令行末尾。文档同时划定了边界:该优化只适用于本机自测/模拟器构建;需要分发给其他机器的通用模拟器包、或运行在 Intel Mac 上时,都不能使用。

执行 iOS 认证流程

cd apps/mobile
MAESTRO_IOS_DEVICE_ID=<IOS_UDID> \
MAESTRO_IOS_APP_PATH=<PATH_TO_Folo.app> \
pnpm run e2e:ios

从源码看,e2e:iossh ./e2e/run-maestro.sh iospackage.json)。iOS 分支(run-maestro.sh)的实际执行序列是:

  1. resolve_ios_device:优先取 MAESTRO_IOS_DEVICE_ID,否则从 xcrun simctl list devices booted 中挑出第一个已启动的模拟器;
  2. 解析并校验 App bundle,拿不到直接报错退出;
  3. prepare_ios_simulator(L90-L115):shutdown → erase 清空数据 → 重启等待 bootstatus -b → 再次 shutdown,然后用 PlistBuddy 向模拟器数据目录中的三个配置 Profile plist 写入 allowPasswordAutoFill = false关闭系统密码自动填充弹窗(这类系统弹窗会干扰 UI 断言),最后再重启模拟器;
  4. xcrun simctl install 安装 .applaunch is.follow
  5. 以 JUnit 格式执行 maestro test ... e2e/flows/ios/auth.yaml,调试产物写入 ${MAESTRO_DEBUG_OUTPUT:-e2e/artifacts/ios}/auth

Android:使用 release APK,而非 Expo 开发构建

Java 与 Android SDK 环境

技能文档要求使用 Android Studio 自带的 JBR:

export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"

以及 Android SDK 路径:

export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"

apps/mobile/android/local.properties 缺失,手动创建:

echo "sdk.dir=$HOME/Library/Android/sdk" > apps/mobile/android/local.properties

构建 release APK

如果本地还没有 apps/mobile/android 原生工程,需先用 Expo prebuild / run-android 工具生成。然后:

cd apps/mobile/android
./gradlew app:assembleRelease --console=plain

预期 APK 路径:

apps/mobile/android/app/build/outputs/apk/release/app-release.apk

安装并执行

先把 release APK 装到已启动的模拟器:

adb -s emulator-5554 install -r apps/mobile/android/app/build/outputs/apk/release/app-release.apk

再运行认证流程:

cd apps/mobile
pnpm run e2e:android

从源码看,Android 分支(run-maestro.sh)在跑 Maestro 前还做了三件事:

  • wait_for_android_ready(L74-L88):最多轮询 90 次(每次 2 秒)等待 sys.boot_completed = 1 且包管理服务可用,随后再固定 sleep 20 秒让系统彻底静息——这是对冷启动后系统弹窗与动画延迟的工程化缓冲;
  • adb shell pm clear is.follow:清空应用数据,保证从“全新安装”状态开始,避免旧登录态污染用例;
  • adb shell monkey -p is.follow -c android.intent.category.LAUNCHER 1:拉起应用后再执行 maestro test ... e2e/flows/android/core.yaml,同样输出 JUnit 报告并落盘到 e2e/artifacts/android

认证流程结构:register → sign out → sign in

流程编排

iOS 的入口 flows/ios/auth.yaml 非常简洁:

appId: is.follow
name: iOS auth journey
---
- runFlow:
    file: ./register.yaml
- runFlow:
    file: ./sign-out.yaml
- runFlow:
    file: ./login.yaml

Android 的入口 flows/android/core.yaml 额外处理了开发菜单的干扰:

- runFlow:
    file: ./dismiss-dev-menu.yaml
- runFlow:
    file: ./register.yaml
- runFlow:
    file: ../shared/sign-out.yaml
- runFlow:
    file: ./dismiss-dev-menu.yaml
- runFlow:
    file: ./login.yaml

可以看出两条旅程共享同一套“注册 → 登出 → 登录”骨架,平台差异被隔离在平台目录的入口文件与 dismiss-dev-menu.yamldismiss-system-dialogs.yaml 这类清理型子流程里,主体逻辑收敛在 flows/shared/

注册流程的 UI 细节

flows/shared/register.yaml 的完整交互链:

  1. runFlow: open-auth.yaml 打开认证页;
  2. 点击 login-provider-credential(凭据登录方式);
  3. 等待并点击 register-email-inputinputText: ${E2E_EMAIL}
  4. 点击 register-password-input,先 eraseTextinputText: ${E2E_PASSWORD},确认密码框同理;
  5. pressKey: Enter 提交;
  6. 若出现 onboarding-next(新手引导),用 runFlow + when 条件块连续点击 4 次跳过引导;
  7. extendedWaitUntil 等待 timeline-view-articles 出现(30 秒超时)——到达时间线即视为注册成功。

iOS 侧的 flows/ios/register.yaml 在此之上再包了一层前置断言:先等待并断言 login-screen 可见,再 runFlow: ../shared/register.yaml

登出与登录的断言点

flows/shared/sign-out.yaml 的路径是:点击 tab-SettingsTabScreen → 等待 settings-general-linkscrollUntilVisible 滚出 settings-sign-out(方向 DOWN,15 秒超时)→ 点击 settings-sign-out → 点击文本 Sign Out 确认 → 等待 login-provider-credential 重新出现并断言可见。这一步对应 SKILL.md 中“sign-out reaches login-screen”的结果判定。

flows/shared/login.yaml 则是:点击 auth-toggle-mode 切到登录模式 → 点击 login-provider-credential → 填入 ${E2E_EMAIL} / ${E2E_PASSWORD} → 点击 login-submitextendedWaitUntil notVisible: login-provider-credential(30 秒超时)→ assertNotVisible 收尾。登录页消失即证明会话已建立。

测试账号从哪里来

若不显式指定 E2E_EMAIL,运行器会在 run-maestro.sh 中生成一次性账号:

: "${E2E_PASSWORD:=Password123!}"
: "${E2E_EMAIL:=folo-e2e-${platform}-${run_suffix}@example.com}"

其中 run_suffix 由秒级时间戳加进程号组成,保证每次运行注册的都是全新账户,天然规避“账号已存在”的不稳定性。若希望复用固定账号(例如配合 e2e:ios:bootstrap 的生产态登录引导),则显式导出 E2E_EMAIL / E2E_PASSWORD 即可。完整的环境变量清单见 README.mdE2E_EMAILE2E_PASSWORDMAESTRO_DEBUG_OUTPUTMAESTRO_IOS_APP_PATHEXPO_PUBLIC_E2E_ENV_PROFILEEXPO_PUBLIC_E2E_LANGUAGE

成功判定的三条标准

SKILL.md 对“认证验证通过”给出了明确定义:

  • register 流程跑完(最终落在 timeline-view-articles 时间线);
  • sign-out 后回到登录页(login-provider-credential 重新可见,iOS 侧对应 login-screen);
  • login 流程使登录页消失(login-provider-credential 不再可见)。

三条全部成立,才算本次 E2E 认证验证成功。

调试与产物排查

每次运行后,检查对应平台的产物目录:

apps/mobile/e2e/artifacts/ios/
apps/mobile/e2e/artifacts/android/

由于 Maestro 以 --format junit 输出,目录里会包含可解析的 JUnit XML 报告与 Maestro 自带的截图/日志材料。--debug-output 的落盘目录由 MAESTRO_DEBUG_OUTPUT 控制,默认值即 e2e/artifacts/${platform}run-maestro.sh)。

SKILL.md 最后给出的技巧是:对单个流程做聚焦调试时,直接调用 Maestro 对单个 YAML 执行。以仓库内真实路径为例,命令形态为:

cd apps/mobile
maestro test --format junit --platform ios --device <IOS_UDID> \
  --debug-output e2e/artifacts/ios/debug \
  -e E2E_EMAIL=you@example.com -e E2E_PASSWORD='Password123!' \
  e2e/flows/ios/login.yaml

这样只需几秒到几十秒即可验证某一步 UI 交互,无需重跑整条旅程。

小结

这套移动端 E2E 体系的设计思路清晰可循:用 run-maestro.sh 作为单一入口封装设备解析、环境重置(iOS 清数据并关自动填充、Android 清应用数据)、构建兜底(缺 .app 时自动 xcodebuild)等不稳定因素;用 flows/shared/ 沉淀跨平台一致的认证交互,用平台目录吸收模拟器开发菜单、系统弹窗等平台噪声;用 e2e:doctor 的语法自检把错误拦在设备启动之前。对照 SKILL.md 的操作步骤与上文源码级细节,可以稳定复现并排查 Folo 移动端“注册—登出—登录”全链路的端到端验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384