Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Bug on waitUntilVisible() and visible #2464

Merged
merged 11 commits into from
Jan 11, 2025
2 changes: 2 additions & 0 deletions packages/patrol_finders/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Add `alignment` parameter to `waitUntilVisible` in order to improve visibility check on Row and Column widgets.
- Add `isVisibleAt` method to check if a widget is visible at a given alignment in case `visible` fails.
- Remove `exception` from `StepEntry`. When it was too long, it caused crash because of badly formed JSON
- Bump `patrol_log` version.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,12 @@ class PatrolFinder implements MatchFinder {
///
/// Timeout is globally set by [PatrolTester.config.visibleTimeout]. If you
/// want to override this global setting, set [timeout].
///
/// {@macro patrol_tester.alignment_on_visible_check}
Future<PatrolFinder> waitUntilVisible({
Duration? timeout,
bool enablePatrolLog = true,
Alignment alignment = Alignment.center,
}) =>
wrapWithPatrolLog(
action: 'waitUntilVisible',
Expand All @@ -414,6 +417,7 @@ class PatrolFinder implements MatchFinder {
this,
timeout: timeout,
enablePatrolLog: false,
alignment: alignment,
),
enablePatrolLog: enablePatrolLog,
);
Expand Down Expand Up @@ -508,9 +512,12 @@ class PatrolFinder implements MatchFinder {
@override
String describeMatch(Plurality plurality) => finder.describeMatch(plurality);

/// Returns true if this finder finds at least 1 visible widget.
bool get visible {
final isVisible = hitTestable().evaluate().isNotEmpty;
/// Returns true if this finder finds at least 1 visible widget
/// at the given [alignment].
///
/// {@macro patrol_tester.alignment_on_visible_check}
bool isVisibleAt({Alignment alignment = Alignment.center}) {
final isVisible = hitTestable(at: alignment).evaluate().isNotEmpty;
if (isVisible == true) {
assert(
exists == true,
Expand All @@ -521,6 +528,14 @@ class PatrolFinder implements MatchFinder {
return isVisible;
}

/// Returns true if this finder finds at least 1 visible widget.
///
/// will call [isVisibleAt] with [Alignment.center]
///
/// In case this returns false and you are sure that the widget is visible,
/// try calling [isVisibleAt] with a different [Alignment] parameter.
bool get visible => isVisibleAt();

@override
FinderResult<Element> evaluate() => finder.evaluate();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,48 @@ class PatrolTester {
///
/// Timeout is globally set by [PatrolTester.config.visibleTimeout]. If you
/// want to override this global setting, set [timeout].
///
/// {@template patrol_tester.alignment_on_visible_check}
/// Provide [alignment] to fine tune the visibility check by calling
/// [Finder.hitTestable] at this [alignment] of the [Widget].
/// This might be helpful in case the tested [Widget] is or contains a [Row]
/// or a [Column]. The default [Alignment.center] might always be the best
/// choice as the following example demonstrates:
///
/// ```dart
///
/// /// This [Widget] will only be found when calling
/// ///await $(Foo).waitUntilVisible(alignment: Alignment.topCenter)
/// class Foo extends StatelessWidget {
/// Foo({Key? key}) : super(key: key);
/// @override
/// Widget build(BuildContext context) {
/// return const Column(
/// children: [
/// Text(
/// 'Foo',
/// ),
/// SizedBox(height: 48),
/// Text('Bar'),
/// ],
/// )
/// }
/// }
/// ```
/// As there is an empty [SizedBox] in the center of the [Column],
/// calling ``await $(Foo).waitUntilVisible()`` will fail
/// as the underlying [Finder.hitTestable] will not find any
/// hit-testable widget. Changing the ``alignment`` parameter
/// to ``Alignment.topCenter`` and calling
/// ``await $(Foo).waitUntilVisible(alignment: Alignment.topCenter)``
/// will make the test pass as the underlying [Finder.hitTestable]
/// will find the [Text] widget at the top of the [Column].
/// {@endtemplate}
Future<PatrolFinder> waitUntilVisible(
Finder finder, {
Duration? timeout,
bool enablePatrolLog = true,
Alignment alignment = Alignment.center,
}) {
return TestAsyncUtils.guard(
() => wrapWithPatrolLog(
Expand All @@ -502,7 +540,7 @@ class PatrolTester {
function: () async {
final duration = timeout ?? config.visibleTimeout;
final end = tester.binding.clock.now().add(duration);
final hitTestableFinder = finder.hitTestable();
final hitTestableFinder = finder.hitTestable(at: alignment);
while (hitTestableFinder.evaluate().isEmpty) {
final now = tester.binding.clock.now();
if (now.isAfter(end)) {
Expand Down
30 changes: 30 additions & 0 deletions packages/patrol_finders/test/patrol_finder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,36 @@ void main() {
await $('done').waitUntilVisible();
expect($('done').visible, true);
});



patrolWidgetTest('waits until widget is only visible at the topCenter alignment', ($) async {
await $.pumpWidget(
MaterialApp(
home: FutureBuilder(
future: Future<void>.delayed(const Duration(seconds: 3)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return const Column(children: [
Text('some text'),
SizedBox(height: 60),
Text('some other text'),
],);
} else {
return const Text('in progress');
}
},
),
),
);
await $(Column).waitUntilVisible(alignment: Alignment.topCenter);
expect($(Column).visible, false);
expect($(Column).isVisibleAt(alignment: Alignment.topCenter), true);
await expectLater(
$(Column).waitUntilVisible,
throwsA(isA<WaitUntilVisibleTimeoutException>()),
);
});
});

group('scrollTo()', () {
Expand Down
Loading