Repository: flutter
Issue: Proper way to find Scrollable for SingleChildScrollView with TextField(s) in integration tests
Labels: a: tests,framework,f: integration_test,has reproducible steps,P2,found in release: 2.10,found in release: 2.13,team-framework,triaged-framework


Assume we have a simple UI 


```
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

/// SingleChildScrollView with TextField issue
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        key: Key('singleChildScrollView'),
        child: Column(
          children: [
            SizedBox(height: 20),
            Container(
              height: 200,
              width: double.infinity,
              color: Colors.red,
            ),
            SizedBox(height: 20),
            Container(
              height: 200,
              width: double.infinity,
              color: Colors.red,
            ),
            SizedBox(height: 20),
            Container(
              height: 100,
              width: double.infinity,
              color: Colors.red,
            ),
            SizedBox(height: 20),
            Container(
              key: Key('desiredContainer'),
              height: 100,
              width: double.infinity,
              color: Colors.yellow,
            ),
            TextField(),
          ],
        ),
      ),
    );
  }
}
```



We are trying to scroll to Container with key 'desiredContainer'. First of all, I tried to scrollUntilVisible like this

```
  testWidgets('Scroll until visible in SingleChildScrollView', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    await tester.scrollUntilVisible(
      find.byKey(Key('desiredContainer')),
      50,
      scrollable: find.byKey(Key('singleChildScrollView')),
    );
  });
```

But I got:

```
The following _CastError was thrown running a test:
type 'SingleChildScrollView' is not a subtype of type
'Scrollable' in type cast

```



Then I looked "under the hood" of SingleChildScrollView, and changed scrollable finder to this 

```
  testWidgets('Scroll until visible in SingleChildScrollView', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    await tester.scrollUntilVisible(
      find.byKey(Key('desiredContainer')),
      50,
      scrollable: find.descendant(
        of: find.byKey(Key('singleChildScrollView')),
        matching: find.byType(Scrollable),
      ),
    );
  });
```

Then I got 


```
I/flutter (27109): ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
I/flutter (27109): The following StateError was thrown running a test:
I/flutter (27109): Bad state: Too many elements
I/flutter (27109): 
I/flutter (27109): When the exception was thrown, this was the stack:
I/flutter (27109): #0      Iterable.single (dart:core/iterable.dart:560:24)
I/flutter (27109): #1      WidgetController.widget (package:flutter_test/src/controller.dart:68:30)
I/flutter (27109): #2      WidgetController.scrollUntilVisible.<anonymous closure> (package:flutter_test/src/controller.dart:991:14)
I/flutter (27109): #5      TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:72:41)
I/flutter (27109): #6      WidgetController.scrollUntilVisible (package:flutter_test/src/controller.dart:989:27)
I/flutter (27109): #7      main.<anonymous closure> (file:///Users/r.kapshuk/AndroidStudioProjects/integration_test_issue/integration_test/foo_test.dart:22:18)
I/flutter (27109): <asynchronous suspension>
I/flutter (27109): #8      main.<anonymous closure> (file:///Users/r.kapshuk/AndroidStudioProjects/integration_test_issue/integration_test/foo_test.dart)
I/flutter (27109): #9      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:146:29)
I/flutter (27109): <asynchronous suspension>
I/flutter (27109): #10     testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart)
I/flutter (27109): #11     TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:784:19)
I/flutter (27109): <asynchronous suspension>
I/flutter (27109): #14     TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:764:14)
I/flutter (27109): #15     LiveTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1592:12)
I/flutter (27109): #16     IntegrationTestWidgetsFlutterBinding.runTest (package:integration_test/integration_test.dart:194:17)
I/flutter (27109): #17     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:138:24)
I/flutter (27109): #18     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:175:19)
I/flutter (27109): <asynchronous suspension>
I/flutter (27109): #19     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart)
I/flutter (27109): #24     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:173:13)
I/flutter (27109): #25     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:231:15)
I/flutter (27109): #30     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:228:5)
I/flutter (27109): #31     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:383:17)
I/flutter (27109): <asynchronous suspension>
I/flutter (27109): #32     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart)
I/flutter (27109): #37     Invoker._onRun.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:370:9)
I/flutter (27109): #38     Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:415:15)
I/flutter (27109): #39     Invoker._onRun.<anonymous closure> (package:test_api/src/backend/invoker.dart:369:7)
I/flutter (27109): #46     Invoker._onRun (package:test_api/src/backend/invoker.dart:368:11)
I/flutter (27109): #47     LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11)
I/flutter (27109): (elided 33 frames from dart:async and package:stack_trace)
I/flutter (27109): 
I/flutter (27109): The test description was:
I/flutter (27109):   Scroll until visible in SingleChildScrollView

```


But if I remove TextField all become fine. After a small investigation, I understood that "too many elements" exception caused because TextField uses Scrollable to achieve text scrolling.

So I had to change scrollable finder to this one:

```
  testWidgets('Scroll until visible in SingleChildScrollView', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    await tester.scrollUntilVisible(
      find.byKey(Key('desiredContainer')),
      50,
      scrollable: find.descendant(
        of: find.byKey(Key('singleChildScrollView')),
        matching: find.byWidgetPredicate((widget) {

          if (widget is Scrollable) {
            return widget.restorationId == null;
        // return widget.axis == Axis.vertical; // We can do this only if TextField is not expandable (expand != true)
          }
          return false;
        }),
      ),
    );
  });

```


I don't really think this is correct way to find root scrollable. 


My flutter doctor -v:

![image](https://user-images.githubusercontent.com/16819254/109491539-57b18e00-7a92-11eb-8c85-3a621d0e2b7c.png)