Finding a widget inside of a CustomScrollView in Flutter test
Asked Answered
C

2

8


I have encountered an error when trying to test my Flutter App. I have a custom widget that is at the bottom (out of the first viewport) of a CustomScrollView widget. In my test, I want to verify that it is actually there.
I have already tried to use WidgetTester.scrollUntilVisible as well as WidgetTester.drag (like it was done in the tests of the flutter framework). Additionally, I tried to refactor my tests with the FlutterDriver which just messed everything else up completely.
How do I scroll to the bottom of a CustomScrollView inside of a test?
This minimal reproduction shows the application containing the CustomScrollView which has a Container widget with the height of one screen (so that the widget I want to find is out of the initial view)

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          body: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Container(
              height: MediaQuery.of(context).size.height,
            ),
          ),
          MyWidget(),
        ],
      )),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverToBoxAdapter(
      child: Container(
        height: 100,
        width: 100,
      ),
    );
  }
}

Here is the test I initially wrote to find the widget, which failed of course.

void main() {
  testWidgets('main contains mywidget', (WidgetTester tester) async {
    // arange
    await tester.pumpWidget(MyApp());
    // act
    final myWidget = find.byType(MyWidget);
    // assert
    expect(myWidget, findsOneWidget);
  });
}

In this iteration, I used the WidgetTester.scrollUntilVisible function but got the error below.

void main() {
  testWidgets('main contains mywidget', (WidgetTester tester) async {
    // arange
    await tester.pumpWidget(MyApp());
    // act
    final myWidget = find.byType(MyWidget);
    final customScrollView = find.byType(CustomScrollView);
    await tester.scrollUntilVisible(myWidget, 100,
        scrollable: customScrollView);
    await tester.pump();
    // assert
    expect(myWidget, findsOneWidget);
  });
}
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following _CastError was thrown running a test:
type 'CustomScrollView' is not a subtype of type 'Scrollable' in type cast

When the exception was thrown, this was the stack:
#0      WidgetController.widget (package:flutter_test/src/controller.dart:66:44)
#1      WidgetController.scrollUntilVisible.<anonymous closure> (package:flutter_test/src/controller.dart:995:15)
#2      WidgetController.scrollUntilVisible.<anonymous closure> (package:flutter_test/src/controller.dart:993:39)
#5      TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:72:41)
#6      WidgetController.scrollUntilVisible (package:flutter_test/src/controller.dart:993:27)
#7      main.<anonymous closure> (file:///C:/Users/X/test_example/test/main_test.dart:12:18)
<asynchronous suspension>
<asynchronous suspension>
(elided 3 frames from dart:async and package:stack_trace)

I appreciate any help or suggestions on how to fix this issue and successfully test my CustomScrollView

Cavie answered 13/4, 2021 at 11:52 Comment(0)
H
15

I had the same problem and I ended up using the dragUntilVisible() method instead of scrollUntilVisible().

Example of scrolling down until the widget is visible:

await tester.dragUntilVisible(myWidget, customScrollView, Offset(0, -500));
Hydrous answered 30/6, 2021 at 8:27 Comment(1)
I'm curious of the logic behind that. Got same issue, you saved me.Butterandeggs
H
0

Just use "await tester.ensureVisible(find.byKey(Key("key_of_the_element")));" before tapping. It will scroll until the given element is visible. It is better than dragUntilVisible or scrollUntilVisible, because drageUntilVisble wont work for non-render box widgets, and scrollUntilVisible won't work for non-scrollable widgets.

package:flutter_test/src/controller.dart

Future ensureVisible(Finder finder)
Containing class: WidgetController
Type: Future Function(Finder)
Given a widget W specified by finder and a Scrollable widget S in its ancestry tree, this scrolls S so as to make W visible. Usually the finder for this method should be labeled skipOffstage: false, so that the Finder deals with widgets that are off the screen correctly. This does not work when S is long enough, and W far away enough from the displayed part of S, that S has not yet cached W's element. Consider using scrollUntilVisible in such a situation. See also: Scrollable.ensureVisible, which is the production API used to implement this method.

Heteronomous answered 25/7, 2023 at 8:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.