ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GSY GitHub App PR Timeline 冒烟测试实战:基于 mcp_dart / VM Service evaluate 的无坐标驱动验证

GSY GitHub App PR Timeline 冒烟测试实战:基于 mcp_dart / VM Service evaluate 的无坐标驱动验证 GSY GitHub App PR Timeline 冒烟测试实战基于 mcp_dart / VM Service evaluate 的无坐标驱动验证【免费下载链接】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 AppFlutter仓库中的 PR timeline 冒烟路径文档展开完整讲解如何在不依赖 adb 坐标点击的前提下通过mcp_dart连接 Dart VM Service用一行evaluate表达式把应用路由到指定 PR 详情页再用widget_inspector的textPreview命中与get_runtime_errors基线对比完成事件行渲染验证。读完本文你将掌握Flutter 项目冒烟测试的一等公民操作姿势vm_service evaluate、GSY 仓库内置的 debug-only 顶层 smoke 入口机制、PR timeline 事件行渲染的源码级验证要点以及一套可直接复用的完成汇报模板。冒烟场景定位PR timeline 事件行渲染验证目标本次冒烟的目标是验证 lib/page/issue/widget/issue_timeline_item.dart 中事件行timeline event row的渲染是否正常覆盖范围包括新事件类型ready_for_review/review_requested/assigned/merged/closedreviewed事件携带body 卡片评审意见正文卡片的渲染未知事件兜底未知事件名的 generic 渲染。Fixture写死不允许换项值仓库CarGuo/gsy_github_app_flutterPR#938Copilot Android APK 优化关键事件reviewed / statecommentedbody 788 字符覆盖分支ready_for_review/review_requested/assigned/merged/closed/ 未知事件兜底Fixture 是写死的执行冒烟时必须使用该 PR不得新造 fixture PR 或用 gh cli 造评论。理由是同一个 fixture 的数据形态事件组合、body 长度、review state在多次执行间保持一致才能让widget_inspector的命中断言稳定可复现。前置条件与装机纪律前置条件设备iOS Simulator 或 Android 真机 / 模拟器已启动flutter能识别到iOSxcrun simctl list devices bootedAndroidadb devicesGSY app 已在设备上运行debug 首选release 也可以flutter run的 stdout 里能看到Dart VM Service on ... is available at: uridebug 构建才有 VM Service已登录任意 fixture 账号推荐CarSmallGuogho_token 只读。装机命令重要禁止使用flutter install该命令内部走adb uninstall pkgadb install会顺手把/data/data/com.shuyu.gsygithub.gsygithubapp_flutter/下的全部 SharedPreferences 抹掉TOKEN_KEY一并丢失——设备上等同强制登出reviewer 无法直接复核 fixture。正确姿势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 登录入口见 lib/page/login/login_page.dart粘回 token。触发路由的一等公民mcp_dart vm_service evaluate为什么触发操作必须走 evaluate 而不是 adb tapGSY 是 Flutter 项目widget 是 Dart 世界里的对象。adb shell input tap X Y只是在系统层伪造触摸事件命中的是屏幕像素点与 Flutter 的 widget hit test 没有直接映射分辨率一变、系统条高度一变、键盘弹起 / tab 数量一变坐标脚本就全炸历史 fixture 已反复吃过旋转 override、状态栏拦截、讨论 tab 可见性、IDE 缩略图坐标 vs 物理坐标的亏。而vm_service evaluate直接在 Dart 层执行表达式等同于让 Dart 自己调用NavigatorUtils.goXxx/TabController.animateTo/ 任意 controller 方法随 Flutter 版本演进、跨 iOS/Android、随 UI 微调不变。因此在本仓库中adb/xcrun simctl降级为只承担截图这一件事不承担任何业务操作职责详见 tool/ai/smoke/README.md。关键前置顶层 navKey debug-only smoke 入口GSY 在 lib/app.dart#L25-L39 把GlobalKeyNavigatorState navKey声明为顶层 final 变量并挂在MaterialApp(navigatorKey: navKey)上。之所以放在顶层是因为mcp_dart vm_service evaluate把expression塞进targetId指向的那条 library 的作用域里求值只有顶层名字才拿得到否则必须先抓 Element/State 的 objectId 再绕_element!.buildContext。同时 lib/app.dart#L258-L471 提供了一批kDebugMode保护的顶层 smoke 入口现有清单入口函数功能gsySmokeGoIssueDetail(owner, repo, number)issue / PR 详情GSY 中 issue 与 pull 复用同一个 detail pagegsySmokeGoReposDetail(owner, repo)仓库详情gsySmokeGoDiscussionDetail(owner, repo, number)Discussion 详情GraphQL 通道gsySmokeGoSearch({centerPosition})打开搜索页弧形动画原点可传Offset.zerogsySmokeGoPerson(userName)个人页这些入口在release 构建下立刻早退打一条 log 就返回不承担业务逻辑、不改 UI、不入 tree-shake 白名单——它们只是给mcp_dart vm_service evaluate用的操控入口见 lib/app.dart#L262-L264 的注释。主路径首选一行调用顶层 smoke 入口mcp_dart vm_service evaluate targetId: library id of package:gsy_github_app_flutter/app.dart expression: gsySmokeGoIssueDetail(CarGuo, gsy_github_app_flutter, 938)library id的获取方式通过mcp_dart vm_service拉Isolate→libraries[]找uri package:gsy_github_app_flutter/app.dart那条拿id。术语辨析易踩坑Dart 中root library术语专指 isolate 入口 library本项目的入口是main.dart而这里需要的是app.dart这条具体 library 的id两个概念不同。降级 A 中同样不要用Isolate.rootLibrary——它指向main.dart从它拿不到navigator_utils.dart的libraryId。拿到 library id 之后任何gsySmokeGoXxx都是一次 evaluate、一行 expression的事。新加入口直接在 lib/app.dart 底部照现有 pattern 加一个FutureObject? gsySmokeGoXxx(...)即可无需改其它任何文件reviewer 也一眼看得懂这是一个 debug-only 顶层函数。源码级原理smokePostFrame 的推帧保底所有入口内部都收敛到 lib/app.dart#L322-L375 的smokePostFrameT(tag, action)。该函数处理的真实问题是vm_service evaluate官方语义见 Dart VM Service Protocolservice.md的evaluate章节与VmService.evaluate文档只是把表达式作为一次 message 排进目标 isolate 的事件循环执行不承诺执行时SchedulerBinding.schedulerPhase一定是idle——evaluate 可能刚好排在一次帧的 build/layout/paint/postFrame 尾段之后立即执行此时同步Navigator.push触发的setState会命中WidgetsBinding._handleBuildScheduled抛Build scheduled during frame。保底思路只取两条硬事实只要当前schedulerPhase idle直接同步Navigator.push完全安全push 触发的setState会正常scheduleFrame下一帧构建 → 正常渲染只要当前schedulerPhase ! idle说明当前一定有一帧在跑Flutter 保证在帧末尾flushPostFrameCallbacks所以addPostFrameCallback里的动作一般会在当前帧末尾跑到不会 idle 挂死。push 侧异常处理见 lib/app.dart#L306-L311用Completer.completeError让异常真正沿Future冒到 evaluate 侧VM Service 那头会看到ErrorRef而不是正常完成的 Future避免evaluate 无异常但页面没跳的假阳性同时用FlutterError.reportError把异常汇报给全局错误通道mcp_dart get_runtime_errors能直接捞到冒烟流水不用额外抓栈。该函数加有visibleForTesting使 test/app/smoke_post_frame_test.dart 能直接注入 sync throw / async reject / post-frame 分支做单测而不是绕道真实业务栈。降级 A旧姿势抓 Element objectId _element!.buildContext只有当 debug 构建因为某种原因没有对应的gsySmokeGoXxx入口例如在写新入口之前想先跑一次冒烟才需要走这条路用mcp_dart widget_inspector get_selected_widget或get_widget_tree拿到任意Element的objectId例如MaterialApp或MyHomePage的元素用该objectId作为targetId触发 evalevaluate( 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再 evaluateeval 返回Futuredynamic可再 eval 一次widget_inspector拿新 widget tree 验证已到IssueDetailPage。降级 B最后的最后人肉点人肉在 iOS Simulator / Android Emulator 上点路径为首页 → 搜索 → CarGuo/gsy_github_app_flutter → ISSUE tab → #938 → 向下滚 timeline。仅当主路径 降级 A 都不可用时使用且必须在完成汇报里注明这一步为什么无法自动化。两种 evaluate 姿势的适用边界项主路径顶层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 / search任意 State 内部字段 / controller / 私有方法调用推荐度⭐ 首选一行、可读、reviewer 直接看得懂只在 debug 构建没有对应gsySmokeGoXxx顶层入口时用release 副作用无kDebugMode早退 debugPrint无release 版跑 eval 本来就不成立结论能加顶层入口就加顶层入口。若后续要覆盖新 route照 lib/app.dart 底部现有 pattern 追加一个FutureObject? gsySmokeGoXxx(...)即可不改其它任何文件。执行步骤七步走flutter run -d deviceId起 debug 构建等 stdout 打印Dart VM Service on ... is available at: urimcp_dart dtd listDtdUris→dtd connect urimcp_dart get_runtime_errors拿改动前基线应为No runtime errors found.触发路由主路径mcp_dart vm_service evaluatetargetId用package:gsy_github_app_flutter/app.dart的 library idexpression写gsySmokeGoIssueDetail(CarGuo, gsy_github_app_flutter, 938)。主路径走不通则按顺序降级 A / 降级 B并汇报原因mcp_dart widget_inspector get_widget_tree summaryOnlytrue拿完整 widget 树命中以下断言textPreview含Copilot 提交了评审意见或英文Copilot reviewed该行下方存在一个Cardwidget 承载 review body788 字符EventTimelineItem/IssueTimelineItem类型实例数 1截图仅作人眼补充证据不承担业务验证职责iOSxcrun simctl io UDID screenshot /tmp/gsy_smoke_pr_timeline_ts.pngAndroidadb exec-out screencap -p /tmp/gsy_smoke_pr_timeline_ts.pngmcp_dart get_runtime_errors再拉一次应仍为No runtime errors found.。页面内交互下拉刷新 / 上拉分页 / tab 切换不属于路由smoke 顶层入口默认不覆盖这类交互仍走抓对应State的objectId、eval_pullLoadWidgetControl.onRefresh?.call()/_tabController.animateTo(3)的姿势。若反复冒烟同一场景可考虑在 lib/app.dart 加gsySmokeRefreshHome()之类顶层入口内部走 eventBus 广播。源码级佐证事件行渲染与命中文案从哪来IssueTimelineItem 的事件分派结构lib/page/issue/widget/issue_timeline_item.dart 中的_buildContent(context, actor)通过switch (event.event)分派文案reviewed按event.reviewState再分approved/changes_requested/dismissed/ 默认commented四档文案见 L353-L367ready_for_review、merged、closed、assigned等各有独立 l10n 文案default 分支未知事件兜底调_logUnknownEventOnce()后走issue_timeline_generic(actorName, event.event)见 L440-L443。reviewed body 卡片当event.event reviewed且body非空时_shouldShowReviewBody()L216-L220在事件行下方渲染_reviewBodyCardL222-L265按reviewState上色_iconColor派生 accent左侧 3px 色带 同色半透边框圆角 6内层用GSYMarkdownWidgetshrinkWrap: true、scroll: false渲染 body实现细节上有两个明确的约束规避Border.left加宽会导致 BoxDecoration 的 non-uniform 断言现改为外层 uniform border 内层 Stack 叠色带不用IntrinsicHeight是因为GSYMarkdownWidget内部走 shrink-wrap viewport参与 intrinsic 计算会报RenderShrinkWrappingViewport does not support returning intrinsic dimensions。未知事件遥测lib/page/issue/widget/issue_timeline_item.dart#L39-L46 用static final SetString loggedUnknownEvents做进程内去重登记命中 default 分支时把事件名 actor sourceUrl 打到talkerkDebugMode下有效release 永不写入同一事件名一个进程只登记一次避免 build 刷屏。背景是 GitHub timeline event 类型动态扩展如copilot_work_started官方 REST 文档里根本没提靠人肉扫 UI 发现--- xxx丑显示再补 case 又慢又漏改为主动遥测驱动补 case。因此冒烟场景中未知事件兜底的验证重点是 generic 文案能正常渲染且不抛异常。命中文案的语言学来源widget_inspector命中文案来自 l10n arblib/common/localization/l10n/app_en.arb#L421pr_timeline_reviewed_commented{actor} reviewed对应Copilot reviewed英文命中lib/common/localization/l10n/app_ja.arb#L319{actor} がレビューしましたlib/common/localization/l10n/app_ko.arb#L319{actor} 님이 리뷰했습니다中文文案在 lib/common/localization/l10n/app_zh.arb 中对应Copilot 提交了评审意见的命中。这些文案走的是 l10n 生成的整句翻译含{actor}占位符适配中英日韩的语法差异。完成汇报里必须写清执行完冒烟后完成汇报至少包含以下字段平台 设备 id例iOS Simulator iPhone 17 ProD62B...mcp_dartDTD / VM Service URI触发路由用的具体路径eval / 降级 A / 降级 Bwidget_inspector命中textPreview摘录至少给出行内引号完整包住的一行截图绝对路径同时必须以 PR/issue 附件或 Markdown 内联图片形式贴出——evidence 目录不入 gitreviewer 拉取 PR 时看不到只写本地路径 reviewer 打不开绝对路径只作为作者自留档索引无法覆盖的分支列表例base_ref_force_pushed真机 fixture 空 → 靠test/model/*.dart单测覆盖。另外按 tool/ai/smoke/README.md 的 evidence 约定默认证据落tool/ai/smoke/evidence/yyyymmdd_hhmm/已被根 .gitignore 忽略不入 git建议按任务号建子目录get_runtime_errors前后对比要贴errorsSinceLastRequest.length和相关 error 的renderedErrorText首 3 行如无新增就写errorsSinceLastRequest[]无法覆盖的分支要显式列成 bullet不要糊成通过。反例禁止❌ 用adb shell input tap X Y/ 坐标脚本触发跳转——Flutter app 不该基于像素点用vm_service eval❌ 只截图不连 DTD 拿get_runtime_errors——截图只是人眼层面补充不是业务证据必须配widget_inspector命中 get_runtime_errors结果❌ 把人肉点当默认路径——GSY 已挂全局navKey一等公民是 eval❌ 新造 fixture PR 或用 gh cli 造评论❌ 新增adb shell input tap/swipe坐标脚本本目录全面清理的历史包袱reviewer 见到直接打回❌ 用flutter install装机会抹掉 SharedPreferences 导致强制登出❌ 拿日志里没 Exception当行为正确的证据——必须命中widget_inspector。与其他冒烟场景的关系本场景是 tool/ai/smoke/README.md 场景清单中的第一项覆盖 PR timeline 事件行 /reviewedbody 卡片与 tool/ai/smoke/open_home_dynamic.md首页 Dynamic tab / 事件识别 / 刷新分页、tool/ai/smoke/open_repo_discussions_tab.md仓库详情 → 讨论 tab / Markdown共享同一套mcp_dart驱动范式与通用步骤模板起 app → 连 DTD → 基线 → 触发路由 → 拉 tree → 截图 → 收尾。每份 md 内含目标 / fixture / 步骤 / 完成汇报必填 / 反例执行者按步骤走完把证据写进完成汇报的看运行段。附scope 边界本目录 md 不做什么本目录 md不做断言——过不过看的是widget_inspector命中 截图 get_runtime_errors本目录 md不 mock 数据——要覆盖稀有事件分支请写单测 JSON fixture如base_ref_force_pushed等真机 fixture 无法构造的分支本目录 md不依赖flutter_driver——本仓库未引入相关依赖。关于 evaluate 语义的历史勘误可参考 tool/ai/smoke/README.md 的历史勘误errata章节evaluateRPC 只承诺在目标 isolate 的事件循环里排队执行表达式从未承诺同步塞进任意回调栈中间smokePostFrame的addPostFrameCallback语义是防御性保底最多多等约 16ms而非必要的根因修复——理解这一点有助于正确判断冒烟中偶发的时序类问题。【免费下载链接】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),仅供参考
返回列表