Flutter测试中find.byIcon返回数组的排序规则是什么?
问题描述
我在Scaffold中一共放置了3个使用Icons.add图标的IconButton控件:
- 1个位于appBar区域,绑定了点击后累加计数的
_incrementCounter逻辑 - 剩余2个位于body区域,未绑定有效点击逻辑,点击无响应
我编写了对应的widget测试用例,想要点击appBar中的add按钮触发计数。调用find.byIcon(Icons.add)后会返回包含这3个按钮的数组,我原本以为垂直位置更靠上的appBar内按钮会排在数组第一位,但实际测试发现appBar中的按钮排在数组最后一位:
- 执行以下代码测试失败:
await tester.tap(find.byIcon(Icons.add).first);
- 执行以下代码测试成功:
await tester.tap(find.byIcon(Icons.add).last);
查阅官方文档未找到对应的排序规则说明,为何垂直位置更高的appBar内按钮,会在返回数组中排在最后而非首位?
完整代码
main.dart
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Row(children: [ Text(widget.title), IconButton(onPressed: _incrementCounter, icon: const Icon(Icons.add)), ]), ), body: Center( child: Column( children: <Widget>[ Row( children: [ const Text( 'First add button, does nothing', ), IconButton(onPressed: () {}, icon: const Icon(Icons.add)), ], ), Row( children: [ const Text( 'Second add button, also does nothing', ), IconButton(onPressed: () {}, icon: const Icon(Icons.add)), ], ), Row( children: [ Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ], ), ), ); } }
widget_test.dart
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pokemon/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add).last); // Why is it last and not first? await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
原因说明
find.byIcon返回的控件匹配顺序和屏幕物理位置没有任何关系,它默认按照Widget树的深度优先遍历顺序返回结果。
Scaffold内部是用Stack实现层叠布局的,为了让appBar悬浮显示在body上层,Stack的子组件会先放入body、后放入appBar。深度优先遍历的时候会先走完body整个子树,把body里的两个add按钮先收集到,最后才遍历到appBar子树里的按钮,所以appBar里的按钮自然排在返回列表的最后一位。
写测试的时候不要依赖
.first/.last这类位置属性定位控件,一旦UI结构调整顺序就会直接失效。更稳妥的方式是给目标控件加唯一Key,或者限定查找范围(比如在appBar的子树范围内查找add图标),避免匹配到无关控件。
内容的提问来源于stack exchange,提问作者Ivone Djaja
相关产品推荐
相关产品推荐

