Repository: flutter
Issue: [integration_test] allow custom binding initialization when running integration_test tests
Labels: tool,framework,f: integration_test,P2,team-framework,triaged-framework

This is a revival of, and copy-paste from, #92254. We need the feature mentioned in there in our [Patrol testing framework](https://github.com/leancodepl/patrol).

## Use case


I'd like to be in control of the test binding I choose for integration tests. In my case, I have a custom binding to set the [TestBindingEventSource](https://api.flutter.dev/flutter/flutter_test/TestBindingEventSource.html) of all touch events as `test`, not `device`.

[That's how my custom binding looks like](https://github.com/leancodepl/patrol/blob/master/packages/patrol/lib/src/native/binding.dart).

The cookbook contains documentation about adding the ensureInitialized call, but (at least on the latest beta version) flutter run and flutter test generate an _intermediate file_ that calls:

```dart
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
```

That _intermediate file_ is called `listener.dart` autogenerated in a path like `/var/folders/z8/vl6s_16515s6h_j8z7bs1tmm0000gn/T/flutter_tools.LfIUH0/flutter_test_listener.SK8Sr1/listener.dart`. To find its location, run `flutter test integration_test -v` (without `-v` the path to the autogenerated file is not shown).

Here's its code:

<details>
<summary>listener.dart</summary>

[Here's the method](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/test/flutter_platform.dart#L127) which generates this file.

```dart
// @dart=2.17
import 'dart:async';
import 'dart:convert';  // flutter_ignore: dart_convert_import
import 'dart:io';  // flutter_ignore: dart_io_import
import 'dart:isolate';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'dart:developer' as developer;
import 'package:test_api/src/remote_listener.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:stack_trace/stack_trace.dart';

import 'file:///Users/bartek/dev/leancode/patrol/packages/patrol/example/integration_test/example_test.dart' as test;

/// Returns a serialized test suite.
StreamChannel<dynamic> serializeSuite(Function getMain()) {
  return RemoteListener.start(getMain);
}

Future<void> _testMain() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  await Future(test.main);
}

/// Capture any top-level errors (mostly lazy syntax errors, since other are
/// caught below) and report them to the parent isolate.
void catchIsolateErrors() {
  final ReceivePort errorPort = ReceivePort();
  // Treat errors non-fatal because otherwise they'll be double-printed.
  Isolate.current.setErrorsFatal(false);
  Isolate.current.addErrorListener(errorPort.sendPort);
  errorPort.listen((dynamic message) {
    // Masquerade as an IsolateSpawnException because that's what this would
    // be if the error had been detected statically.
    final IsolateSpawnException error = IsolateSpawnException(
        message[0] as String);
    final Trace stackTrace = message[1] == null ?
        Trace(const <Frame>[]) : Trace.parse(message[1] as String);
    Zone.current.handleUncaughtError(error, stackTrace);
  });
}

void main() {
  String serverPort = Platform.environment['SERVER_PORT'] ?? '';
  String server = Uri.decodeComponent('ws%3A%2F%2F127.0.0.1:$serverPort');
  StreamChannel<dynamic> testChannel = serializeSuite(() {
    catchIsolateErrors();
    goldenFileComparator = LocalFileComparator(Uri.parse('file:///Users/bartek/dev/leancode/patrol/packages/patrol/example/integration_test/example_test.dart'));
    autoUpdateGoldenFiles = false;
    return _testMain;
  });
  final callback = (method, params) async {
    testChannel.sink.add(json.decode(params['data'] as String));

    // Result is ignored but null is not accepted here.
    return developer.ServiceExtensionResponse.result('{}');
  };

  developer.registerExtension('ext.flutter.integrationTest', callback);

  testChannel.stream.listen((x) {
    developer.postEvent(
      'Flutter.IntegrationTest',
      {'data': json.encode(x)},
    );
  });
} 
```
</details>

### Proposal

Remove ensureInitialized from the generated file so users can choose what binding (and what configuration) they want the test to use (or at least allow to initialize only subclasses of `IntegrationTestWidgetsFlutterBinding`).

Also I think that generating this file is not communicated clearly enough (or at all) by the integration testing docs, which are focused only on very simple cases.