GSY GitHub App 冒烟操作手册基于 mcp_dart 与 VM Service 的 Flutter 运行时验证实践【免费下载链接】gsy_github_app_flutterFlutter 超完整的开源项目功能丰富适合学习和日常使用。GSYGithubApp 系列的优势我们目前已经拥有 Flutter、Weex、ReactNative、Kotlin View、Kotlin Jetpack Compose Compose MultiPlatformHarmony ArkUI 七个版本功能齐全项目框架内技术涉及面广完成度高持续维护配套文章适合全面学习对比参考。项目地址: https://gitcode.com/gh_mirrors/gs/gsy_github_app_flutter本文是 gsy_github_app_flutter 仓库中tool/ai/smoke/冒烟操作手册的完整讲解核心回答一个问题在 Flutter 项目中如何用可复核、可自动化的方式验证UI 渲染 / 文案 / 事件行级别的改动。你会读到这套冒烟体系的全部前置条件、装机命令、通用步骤模板、两种vm_service evaluate操控姿势的边界以及仓库在 lib/app.dart 中为冒烟专门预留的顶层入口与防御性保底机制读完即可照抄跑通 PR timeline、首页动态、仓库 Discussions 三个真实场景。冒烟验证在 GSY 协作流程中的定位在进入操作细节之前需要先理解这套手册为什么存在。仓库根目录 AGENTS.md 的运行时冒烟验证强制章节给出了一条硬性规则能编译过 装机不崩不算测试通过。任何涉及运行时行为UI 渲染、事件解析、状态流转、网络分支、多语言文案的改动在宣告完成前author 必须在真机或模拟器上跑通对应改动路径并把真实证据截图 / 文案 dump / 错误日志以文件形式产出并写清路径禁止只凭app 启动了、日志没红就报完成。AGENTS.md 随后把最低证据要求按改动类型分级改动类型最低证据要求纯模型 / 纯工具函数flutter analyze 单测若 test 目录已存在无需截图UI 渲染 / 文案 / 事件行至少 1 张真机截图 mcp_dartwidget_inspector get_widget_tree命中目标 widget 或textPreviewget_runtime_errors无异常关键路径登录 / 网络栈 / 根装配 / 状态边界主路径截图 widget tree 命中 get_runtime_errors无异常 至少 1 个失败/边界分支的证据tool/ai/smoke/README.md正是为满足中间这一档UI 渲染 / 文案 / 事件行的最低证据要求而编写它是一份冒烟操作手册说明用mcp_dart该走哪条路径、该 grep widget tree 的哪几个命中项、该抓哪几张截图。工具选型变迁2026-09-02 全面转向 mcp_dart手册开篇记录了一次重要的工具决策。历史上tool/ai/smoke/目录堆了一堆.sh/.ps1坐标脚本基于adb shell input tap/swipe2026-09-02 作者拍板全部删除回归mcp_dart理由有三adb 只是 Android 平台工具天然把 iOS 排除在外——GSY 用 iOS Simulator 冒烟时它一点忙都帮不上坐标硬编码脆弱分辨率一变就全坏系统条高度、键盘弹起、tab 数量变化都会导致 tap 落错旋转 override / 状态栏拦截 / 讨论 tab 是否可见 / IDE 缩略图坐标 vs 物理坐标都反复吃过亏mcp_dart是随 Flutter 演进的一等公民直接连 DTD/VM Service能拉真实的widget_inspector get_widget_tree含textPreview文案、拉get_runtime_errors跨平台、随版本演进、天然消除坐标依赖。因此该目录不再放执行脚本每个冒烟场景改为一份路径描述 md。这条决策与 AGENTS.md 的工具选型章节完全一致mcp_dart是唯一主路径adb/xcrun simctl只降级为只截图的工具不再承担业务操作职责。前置条件开始任何冒烟场景前必须满足四条前提设备iOS Simulator 或 Android 真机 / 模拟器已启动、flutter能识别到iOSxcrun simctl list devices bootedAndroidadb devicesGSY app 已在设备上运行debug 首选release 也可以。flutter run的 stdout 里能看到Dart VM Service on ... is available at: uridebug 才有。已登录任意 fixture 账号推荐CarSmallGuogho_token 只读。注意mcp_dart依赖 DTD/VM Service URI这条 URI 只在 debug 构建的flutter runstdout 里出现所以冒烟自动化路径默认以 debug 构建为前提。装机命令为什么禁止 flutter install手册特别强调一个装机反模式禁止使用flutter install。该命令内部走adb uninstall pkgadb install会顺手把/data/data/com.shuyu.gsygithub.gsygithubapp_flutter/下的全部 SharedPreferences 抹掉TOKEN_KEY一并丢失——设备上等同强制登出reviewer 无法直接复核 fixture。这条教训在 AGENTS.md 的禁止行为章节被固化为打回红线2026 年装 discussions 冒烟版本时曾因flutter install清空了 CarSmallGuo 的 gho_ token属于 author 责任事故。正确的装机姿势iOSflutter build ios --release # 装机走 Xcode 或 xcrun simctl install UDID build/ios/iphonesimulator/Runner.appAndroid# 1. 构建 release APK首选 arm64跟 CarGuo 主设备一致 flutter build apk --release --target-platformandroid-arm64 --no-shrink # 2. 用 adb install -r 覆盖安装保留 app data adb install -r build/app/outputs/flutter-apk/app-release.apk # ^^ 关键-r reinstall保留 /data/data/pkg/如果必须重装例如包名或签名变了先手动导出 tokenAndroid 走run-ascat shared_prefs/FlutterSharedPreferences.xmliOS 走 Xcode Container 拷贝Library/Preferences/*.plist。装完可以用 GSY 登录页的 Token 登录 入口见 login_page.dart把 token 粘回来。场景清单手册固化了一份场景清单每个场景一份 md内含目标 / fixture / 步骤 / 完成汇报必填 / 反例场景 md覆盖对象open_pr_timeline.mdPR timeline 事件行 /reviewed body 卡片open_home_dynamic.md首页 Dynamic tab / 事件识别 / 下拉刷新 上拉分页open_repo_discussions_tab.md仓库详情 → 讨论 tab / discussion 列表 / 详情页 Markdown执行者按步骤走一遍把证据widget tree 命中项 截图绝对路径 get_runtime_errors结果写进 AGENTS.md 完成汇报三段式看代码 / 看编译 / 看运行的看运行段。三段任一缺失 任务未完成。通用步骤模板7 步完成一次冒烟手册给出了一套所有场景通用的步骤模板。这是一个 Flutter 项目点击 / 触发 / 验证一律走mcp_dartVM Service 一等公民不基于adb/ 坐标 / 屏幕像素adb/xcrun simctl只承担截图这一件事。起 appflutter run -d deviceId等 stdout 打印 DTD/VM Service URI。连 DTDmcp_dartdtd listDtdUris→dtd connect uri。基线mcp_dartget_runtime_errors应为No runtime errors found.。触发路由 / 交互一等公民 mcp_dartvm_serviceevaluate详见下一节。拉 treemcp_dartwidget_inspector get_widget_tree summaryOnlytrue在返回 JSON 里 grep 该场景 md 指定的textPreview或 widget 类型。截图仅人眼补充证据不承担业务验证职责iOSxcrun simctl io UDID screenshot pathAndroidadb exec-out screencap -p path。收尾mcp_dartget_runtime_errors再拉一次应仍空。第 4 步展开触发路由的三种姿势GSY 已经在 app.dart 把GlobalKeyNavigatorState navKey声明为顶层 final 变量挂在MaterialApp(navigatorKey: navKey)上同时 app.dart 提供了一批kDebugMode保护的顶层 smoke 入口。这套组合意味着vm_service evaluate里一行就能跳到任何目标页。主路径首选拉一次Isolate→libraries[]找uri package:gsy_github_app_flutter/app.dart那条拿id作为targetId然后evaluate一行evaluate( targetId: library id of package:gsy_github_app_flutter/app.dart, expression: gsySmokeGoIssueDetail(CarGuo, gsy_github_app_flutter, 938) )关键辨析library id是package:gsy_github_app_flutter/app.dart这个具体 library的id从Isolate.libraries[]里查不是Isolate.rootLibrary字段——Dart 中 root library 术语专指 isolate 入口 library本项目是main.dart两个概念不同。app.dart 的注释对这一点有完整解释open_pr_timeline.md与open_repo_discussions_tab.md两份场景 md 也反复提醒。现有顶层入口清单都在 lib/app.dart 底部源码可见入口函数签名跳转目标gsySmokeGoIssueDetail(owner, repo, issueNumber)issue / PR 详情GSY 中两者复用同一 detail pagegsySmokeGoReposDetail(owner, repo)仓库详情gsySmokeGoDiscussionDetail(owner, repo, number)Discussion 详情GraphQL 通道gsySmokeGoSearch({Offset centerPosition Offset.zero})搜索页route-topology 后 shellDetail 语义gsySmokeGoPerson(userName)个人页这些函数全部有kDebugMode早退门release 构建下只debugPrint一条忽略日志并返回不承担业务逻辑。需要新用例就照现有 pattern 加一个FutureObject? gsySmokeGoXxx(...)即可不改其它任何文件。页面内交互下拉刷新 / 上拉分页 / tab 切换这类不是路由smoke 顶层入口默认不覆盖。仍走抓对应State的objectId、eval_pullLoadWidgetControl.onRefresh?.call()/_tabController.animateTo(3)这种姿势。例如 open_home_dynamic.md 里首页动态的下拉刷新走 gsy_pull_load_widget.dart 的GSYPullLoadWidgetControl控制器实例实际挂在DynamicBloc上见 dynamic_bloc.dartevaluate 直接调用_pullLoadWidgetControl.onRefresh?.call()与onLoadMore?.call()即可触发刷新和分页。如果反复冒烟同一场景再考虑给 app.dart 加gsySmokeRefreshHome()之类顶层入口让它内部走 eventBus 广播。降级 A旧姿势如果 debug 构建因某种原因没有相应的gsySmokeGoXxx顶层入口退回到抓任意 ElementobjectId_element!.buildContextNavigatorUtils.goXxx的老姿势evaluate( targetId: element_object_id, expression: (() { final ctx _element!.buildContext; return NavigatorUtils.goIssueDetail( ctx, CarGuo, gsy_github_app_flutter, 938, ); })() )如果 eval 的作用域拿不到NavigatorUtils未 import改用evaluateInFrame并从Isolate.libraries[]里查uri package:gsy_github_app_flutter/common/utils/navigator_utils.dart那条拿libraryId同样不要用Isolate.rootLibrary。降级 B最后的最后人肉在 Simulator 上点。必须在完成汇报里说明这一步为什么无法自动化。为什么触发操作走 vm_service eval 而不是 adb shell input tap手册专门用一节解释这条原则核心逻辑GSY 是Flutter 项目widget 是 Dart 世界里的对象。adb shell input tap X Y只是在系统层伪造触摸事件命中的是屏幕像素点跟 Flutter 的 widget hit test 没有直接映射分辨率变一变、系统条高度变一变、键盘弹起 / tab 数量变一变全炸vm_serviceevaluate直接在 Dart 层执行表达式等同于让 Dart 自己调用NavigatorUtils.goXxx/TabController.animateTo/ 任意 controller 方法随 Flutter 版本演进、跨 iOS/Android、随 UI 微调不变天生就是 Flutter 项目该有的操控姿势adb/xcrun simctl因此在本仓库里降级为只截图的工具不再承担业务操作职责。两种 eval 姿势的适用边界项主路径顶层gsySmokeGoXxx()降级 AElement buildContexttargetIdpackage:gsy_github_app_flutter/app.dart的 libraryid任意在线Element的objectIdexpression一行gsySmokeGoIssueDetail(..., ..., 938)多行(() { final ctx _element!.buildContext; return NavigatorUtils.goIssueDetail(ctx, ...); })()依赖debug 构建app.dart里现有的gsySmokeGoXxx()顶层函数任意已挂载 widget 的 Element 存在 NavigatorUtils在 eval 作用域可解析覆盖场景route 类跳转issue / repo / discussion / person任意 State 内部字段 / controller / 私有方法调用推荐度首选一行、可读、reviewer 直接看得懂只在 debug 构建没有对应gsySmokeGoXxx顶层入口时用release 副作用无kDebugMode早退 debugPrint无release 版跑 eval 本来就不成立结论能加顶层入口就加顶层入口。目前 4 个 route 入口够 PR / 仓库 / discussion / 用户页 4 大场景之后要覆盖新 route 时照现有 pattern 追加一个FutureObject? gsySmokeGoXxx(...)就行——不改其它任何文件reviewer 也一眼看得懂这就是一个 debug-only 顶层函数。smokePostFrameevaluate 时机上的防御性保底值得深入的是 app.dart 中的smokePostFrameT函数——它是所有gsySmokeGoXxx共用的 push 时机保底。其设计基于两条官方语义硬事实只要当前schedulerPhase idle直接同步Navigator.push完全安全push 触发的setState会正常scheduleFrame只要当前schedulerPhase ! idle说明当前一定有一帧在跑Flutter 保证帧末尾 flush post-frame callbacks所以在addPostFrameCallback里执行动作一般不会挂死。同时它做了异常透传处理用Completer.completeError让异常沿Future冒到 evaluate 侧VM Service 那头看到ErrorRef而非正常完成的 Future避免evaluate 无异常但页面没跳的假阳性并用FlutterError.reportError把异常汇报给全局错误通道mcp_dart get_runtime_errors能直接捞到。这个函数的四条分支navKey null 早退 / idle sync throw / idle async reject / post-frame path都有单测守约见 test/app/smoke_post_frame_test.dart它通过可控 scheduler 的testWidgets覆盖了真机上很难自然触发的 post-frame 分支这正是 AGENTS.md 稀有分支覆盖率无法靠真机保证时优先加模型层单测条款的直接应用。evidence/ 目录约定证据如何留档与汇报默认 evidence 落到tool/ai/smoke/evidence/yyyymmdd_hhmm/已通过根 .gitignore 里的tool/ai/smoke/evidence/忽略不入 git建议按任务号建子目录例evidence/c1/、evidence/d1_selftest/在完成汇报里把子目录绝对路径贴出来reviewer 就能定位到当次证据PR / 完成汇报必须内联贴摘要不能只写证据在 evidence/xxx/就交差。因为 evidence 目录不入 gitreviewer 拉取 PR 时看不到里面的东西所以汇报正文必须手动摘录足够的关键片段reviewer 才能不 checkout 就完成 review。摘要至少覆盖get_runtime_errors前后对比改动前基线 关键路径跑完之后各拉一次贴errorsSinceLastRequest.length和相关 error 的renderedErrorText首 3 行如无新增就写errorsSinceLastRequest[]widget_inspector 命中项命中的 widget 类型 / 关键textPreview字符串例Copilot 提交了评审意见至少给出行内引号完整包住的一行截图必须以 PR/issue 附件或 Markdown 内联图片形式贴出来——只写本地绝对路径 reviewer 打不开evidence 已 gitignore。绝对路径只作为作者自留档索引如果用 iOS / Android 平台的分享上传Slack / 飞书 / 邮件附件也可以只要 reviewer 不 checkout 就能拿到图无法覆盖的分支列表显式列成 bullet不要糊成通过。反例这些做法会被 reviewer 直接打回手册列出一份明确的禁止清单与 AGENTS.md 的禁止行为章节互为呼应❌新增adb shell input tap/swipe坐标脚本本次全面清理的历史包袱reviewer 见到直接打回。Flutter 项目触发操作走vm_service eval不基于像素点❌把人肉在 Simulator 上点当默认路径默认路径永远是mcp_dartvm_service eval。只有 eval 走不通 降级 Adebug-only 顶层函数也走不通才允许降级 B人肉点且必须在完成汇报里说明这一步为什么无法自动化❌只截图不连 DTD截图只是人眼层面补充不是业务证据必须配widget_inspector命中和get_runtime_errors结果❌用flutter install装机见装机命令章节❌让用户手动操作 UI 代替自己自测author 必须自己走完路径❌拿日志里没 Exception当行为正确的证据必须命中widget_inspector。本目录不做什么三个边界约束保证这套冒烟手册职责单一、不与其它测试手段重叠本目录 md不做断言要不要过看的是widget_inspector命中 截图 get_runtime_errors本目录 md不 mock 数据要覆盖稀有事件分支请写单测 JSON fixture例如 test/model 目录下的模型层单测本目录 md不依赖flutter_driver本仓库未引入相关依赖因此 AGENTS.md 中flutter_driver_command子工具默认不可用。历史勘误commit 224a0d8 的编造因果挂账手册末尾挂了一笔重要的历史勘误errata用于纠正对vm_service evaluate时序语义的错误认知——这条订正已经同步写入 AGENTS.md 的禁止行为章节在文档 / commit message / code comment 里编造 VM Service / mcp_dart 时序细节。编造内容commit224a0d8body 的「看代码」段落里写了mcp_dart vm_service evaluate是同步塞进 isolate 当前任意回调栈里跑的会撞进 build/layout/paint/semantics 遍历直接Navigator.push就抛 Build scheduled during frame并把_smokePostFrame定性为从根上修 evaluate 时机不是补丁实际语义对照 Dart VM Service Protocolservice.md的evaluate章节与官方文档evaluateRPC 只承诺在目标 isolate 的事件循环里排队执行表达式从未承诺同步塞进任意回调栈中间Dart isolate 是单线程消息循环跨进程注入的表达式必须等当前 message 处理完才轮到不可能同步打断 build/paint 半程。若要真正做到在栈中间求值走的是另一个 RPCevaluateInFrame且需 isolate 处于 paused 状态实际因果_smokePostFrame为何保留Navigator.push触发的setState仍有可能在 evaluate 排到的那一轮 message 结束、下一帧 layout/paint 开始时才被WidgetsBinding._handleBuildScheduled感知存在边缘触发 Build scheduled during frame 的可能addPostFrameCallback只是防御性保底最多多等 ~16ms。语义上是防边缘炸不是必须的根因修复订正 commited7077e已把 lib/app.dart 里对应的 doc comment 重写把「必要修复」降级为「防御性保底 官方语义引用」代码行为无改动。224a0d8的 commit message body 由于 git 历史不可变保留原文但以本条 errata 显式挂账——凡看git log 224a0d8的人务必对照本节修正对因果的理解。小结一份可直接落地的 Flutter 冒烟方法论从tool/ai/smoke/README.md出发可以看到 GSY 仓库把运行时冒烟验证从一句口号落地成了一整套可执行的工程规范以mcp_dartDTD/VM Service为一等公民用widget_inspector的真实 widget tree含textPreview作为业务证据用get_runtime_errors作为运行时健康基线用adb/xcrun simctl只做截图补充配合 lib/app.dart 中kDebugMode保护的顶层 smoke 入口实现一次 evaluate、一行跳转的自动化路径最后以 evidence 目录 完成汇报三段式保证 reviewer 不看设备也能复核。这套方法论不仅适用于本仓库对任何必须验证 UI 渲染与文案而不仅仅是编译通过的 Flutter 项目都具备直接借鉴价值——关键动作就是三条装别用flutter install、触发走vm_service eval、证据必须命中widget_inspector。【免费下载链接】gsy_github_app_flutterFlutter 超完整的开源项目功能丰富适合学习和日常使用。GSYGithubApp 系列的优势我们目前已经拥有 Flutter、Weex、ReactNative、Kotlin View、Kotlin Jetpack Compose Compose MultiPlatformHarmony ArkUI 七个版本功能齐全项目框架内技术涉及面广完成度高持续维护配套文章适合全面学习对比参考。项目地址: https://gitcode.com/gh_mirrors/gs/gsy_github_app_flutter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
