flutter icon indicating copy to clipboard operation
flutter copied to clipboard

[integration_test] Plugin not registered unless running via XCTest

Open tritao opened this issue 4 years ago • 31 comments

So as the title says, the integration_test native plugin is not being correctly registered unless running tests via XCTest, like when running via flutter drive.

This unfortunately breaks use cases like automatic taking screenshots via https://github.com/mmcc007/screenshots for instance.

I've tracked the issue down to a fix in https://github.com/flutter/plugins/pull/2465.

Changing IntegrationTestPlugin.m:registerWithRegistrar to the code below fixes the issue and seems to make things work, but I am assuming might bring back the XCTest init issue the PR tried to fix originally.

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
    [[IntegrationTestPlugin instance] setupChannels:registrar.messenger];
}

@gaaclarke, can you take a look / any idea on what should be done here?

Steps to Reproduce

  1. flutter drive --target=integration_test/main_test.dart --driver=test_driver/integration_test.dart

Expected results:

Actual results:

Code sample
void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized()
      as IntegrationTestWidgetsFlutterBinding;

  testWidgets("main test", (WidgetTester tester) async {
    await binding.takeScreenshot(name);
  }
}
Logs
flutter: ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
flutter: The following MissingPluginException was thrown running a test:
flutter: MissingPluginException(No implementation found for method captureScreenshot on channel
flutter: plugins.flutter.io/integration_test)
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
flutter: <asynchronous suspension>
flutter: #1      IOCallbackManager.takeScreenshot (package:integration_test/_callback_io.dart:108:9)
flutter: <asynchronous suspension>
flutter: #2      IntegrationTestWidgetsFlutterBinding.takeScreenshot (package:integration_test/integration_test.dart:180:9)
flutter: <asynchronous suspension>
flutter: #3      screenshot (file:///Users/dev/mobile/integration_test/main_test.dart:139:3)
flutter: <asynchronous suspension>
flutter: #4      main.<anonymous closure> (file:///Users/dev/mobile/integration_test/main_test.dart:26:5)
flutter: <asynchronous suspension>
flutter: #5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:172:15)
flutter: <asynchronous suspension>
flutter: #6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:803:5)
flutter: <asynchronous suspension>
flutter:
flutter: The test description was:
flutter:   main test
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
[✓] Flutter (Channel master, 2.6.0-12.0.pre.240, on macOS 11.4 20F71 darwin-x64, locale en-PT)
    • Flutter version 2.6.0-12.0.pre.240 at /Users/joao/dev/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 46a52d03bd (5 days ago), 2021-10-07 08:52:09 -0700
    • Engine revision e914da14f1
    • Dart version 2.15.0 (build 2.15.0-178.0.dev)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Users/joao/Library/Android/sdk
    • Platform android-30, build-tools 30.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.0)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.10.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)

[✓] IntelliJ IDEA Community Edition (version 2020.2.4)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.61.0)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.27.0

[✓] Connected device (3 available)
    • iPhone 11 Pro Max (mobile) • 1714BBB8-050B-4C2D-B118-456B827FFC2C • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-0 (simulator)
    • macOS (desktop)            • macos                                • darwin-x64     • macOS 11.4 20F71 darwin-x64
    • Chrome (web)               • chrome                               • web-javascript • Google Chrome 94.0.4606.71

• No issues found!

tritao avatar Oct 12 '21 09:10 tritao

Hi @tritao, Thanks for filing the issue. I can reproduce the issue on the latest stable and the master channel.

logs

Master

mahesh@Maheshs-MacBook-Air-M1 performance % flutter test integration_test
Running "flutter pub get" in performance...                      2,213ms
00:10 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                               Ru00:11 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                           974ms
00:29 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                                
00:33 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                            4.7s
Xcode build done.                                           22.0s
00:37 +0: main test                                                                                                                                 
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following MissingPluginException was thrown running a test:
MissingPluginException(No implementation found for method captureScreenshot on channel
plugins.flutter.io/integration_test)

When the exception was thrown, this was the stack:
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:165:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

The test description was:
  main test
════════════════════════════════════════════════════════════════════════════════════════════════════
00:37 +0 -1: main test [E]                                                                                                                          
  Test failed. See exception logs above.
  The test description was: main test
  
00:37 +0 -1: Some tests failed.  

Stable 2.5.2

mahesh@Maheshs-MacBook-Air-M1 performance % flutter test integration_test 
00:07 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                                R00:08 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                         1,018ms
00:27 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                                
00:34 +0: loading /Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart                                            6.9s
Xcode build done.                                           26.4s
00:38 +0: main test                                                                                                                                 
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following StateError was thrown running a test:
Bad state: Call convertFlutterSurfaceToImage() before taking a screenshot

When the exception was thrown, this was the stack:
#0      IOCallbackManager.takeScreenshot (package:integration_test/_callback_io.dart:88:7)
#1      IntegrationTestWidgetsFlutterBinding.takeScreenshot (package:integration_test/integration_test.dart:175:61)
#2      main.<anonymous closure> (file:///Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart:16:19)
#3      main.<anonymous closure> (file:///Users/mahesh/Desktop/nevercode/ISSUES/performance/integration_test/main_test.dart:15:28)
#4      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:176:29)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

The test description was:
  main test
════════════════════════════════════════════════════════════════════════════════════════════════════
00:38 +0 -1: main test [E]                                                                                                                          
  Test failed. See exception logs above.
  The test description was: main test
  

maheshj01 avatar Oct 12 '21 14:10 maheshj01

I am also having this same issue. Here is my code

Screen Shot 2021-10-19 at 2 03 04 PM

Flutter Doctor: Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel master, 2.6.0-12.0.pre.390, on macOS 11.6 20G165 darwin-x64, locale en-US) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 13.0) [✓] Chrome - develop for the web [✓] Android Studio (version 4.1) [✓] VS Code (version 1.61.1) [✓] Connected device (2 available)

tnchoe98 avatar Oct 19 '21 21:10 tnchoe98

Hi there, I got the same issue when run flutter drive --dart-define=testing_mode=true --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart to take a screenshot on latest master channel.

await binding.convertFlutterSurfaceToImage();
await tester.pumpAndSettle();
await binding.takeScreenshot('screenshot1');
Error logs
Failure in method: Test app flow
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═════════════════
The following MissingPluginException was thrown running a test:
MissingPluginException(No implementation found for method
captureScreenshot on channel plugins.flutter.io/integration_test)

When the exception was thrown, this was the stack:
#0      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:175:7)
<asynchronous suspension>
#1      IOCallbackManager.takeScreenshot (package:integration_test/_callback_io.dart:96:33)
<asynchronous suspension>
#2      IntegrationTestWidgetsFlutterBinding.takeScreenshot (package:integration_test/integration_test.dart:175:39)
<asynchronous suspension>
#3      main.<anonymous closure> (file:///Users/admin/flutter/myapp/integration_test/app_test.dart:28:5)
<asynchronous suspension>
#4      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:170:15)
<asynchronous suspension>
#5      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:806:5)
<asynchronous suspension>
flutter doctor -v
[✓] Flutter (Channel master, 2.6.0-12.0.pre.701, on Mac OS X 10.15.7 19H1519 darwin-x64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[!] Xcode - develop for iOS and macOS (Xcode 12.4)
    ! Flutter recommends a minimum Xcode version of 13.
      Download the latest version or update via the Mac App Store.
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] VS Code (version 1.58.2)
[✓] VS Code (version 1.63.0-insider)
[✓] Connected device (3 available)

Is there anyway to workaround this one?

ductranit avatar Nov 12 '21 12:11 ductranit

Is there anyway to workaround this one?

I posted a workaround in the initial post.

tritao avatar Nov 12 '21 15:11 tritao

hi @maheshmnj I wonder when we can have the plan to fix this issue? Thank to @tritao, it can work with the workaround, but then I can't upgrade flutter unless stash this changed before doing upgrade, and the problem is that I can't do it on CI testing.

ductranit avatar Dec 07 '21 04:12 ductranit

@ductranit, Please check the wiki to understand how the issues are prioritized. https://github.com/flutter/flutter/wiki/Issue-hygiene#when-will-my-bug-be-fixed

maheshj01 avatar Dec 07 '21 06:12 maheshj01

Can confirm the issue as well as the fix, thanks! Unfortunately fixing this locally doesn't help when working with remote CI tools like CodeMagic. So +1 for making this part of one of the next releases.

bwaide avatar Jan 21 '22 07:01 bwaide

I can also confirm that editing flutter/packages/integration_test/ios/Classes/IntegrationTestPlugin.m and then running flutter clean fixes the problem for me as well.

ewaters avatar Feb 05 '22 23:02 ewaters

I have the issue as well and it is a blocker, because I want to generate screenshots in CodeMagic as well.

/offtopic:

@tritao I thought https://github.com/mmcc007/screenshots uses a deprecated test API. Are you using it with integration_tests? I thought the package is also 2 years old? Would love if you could share some resources in regards of this.

@bwaide I'm testing CodeMagic as well, could you share some resources how to trigger tests in CodeMagic and how to upload those screenshots to the App Store backends?

martin-braun avatar Apr 03 '22 01:04 martin-braun

We are also running into this issue. Hope it gets fixed soon.

Ruben2112 avatar Apr 05 '22 13:04 Ruben2112

@tritao I thought https://github.com/mmcc007/screenshots uses a deprecated test API. Are you using it with integration_tests? I thought the package is also 2 years old? Would love if you could share some resources in regards of this.

You can check out the version I am using at https://github.com/tritao/screenshots/commits/integration_test_latest

tritao avatar Apr 05 '22 15:04 tritao

@tritao Thanks for linking and maintaining it. Would it be possible to publish the integration_test_latest branch on pub.dev for easier accessibility?

After doing some research I figured out that integration_tests are limited in a sense that I have to run them for each device type in sequence whereas traditional flutter tests run on the host and thus are capable of spawning several AVDs/Simulators to do all the screenshots for all device types in one go.

Since your fork is using integration_tests I wonder how you would implement generating screenshots for all devices within any CI/CD workflow, like on codemagic. May I ask if you fully automate screenshot generation with it, or so you manually run your tests?

martin-braun avatar Apr 06 '22 04:04 martin-braun

Thank you @tritao , could you please provide a sample of how the pubspec.yaml would look in order to use the version you posted. Currenly mine look like this.

dev_dependencies:
  integration_test:
    sdk: flutter

ryanheitner avatar Apr 06 '22 07:04 ryanheitner

So is the current approach to integration testing in flutter, that if you want to capture a screenshot, that you need to change/edit the source to Flutter SDK so that the Flutter supplied integration_test_driver_extended drivers work and actually do what the code was supposed to do - so is the "extended" driver currently outside Flutter's own testing program? How ironic that the testing tools aren't properly being tested... LOL... sorry for the light hearted comments here, but I just wanted to make sure that I've not missed another path to testing integration with Flutter.

gslender avatar Apr 29 '22 00:04 gslender

Yep this is painful for and Flutter iOS first app 😭

workerbee22 avatar Apr 29 '22 11:04 workerbee22

Anyone seen any movement on this?

workerbee22 avatar May 20 '22 00:05 workerbee22

I've just stopped using integration_test and continued using the older test driver that still works fine. I use Emulators package to control the simulators and perform screenshots for testing. It works and seems to be fairly issues free.

gslender avatar May 20 '22 00:05 gslender

@maheshmnj @darshankawar Sorry I just don't understand, the fix (or at least workaround) is simple. Why did it take too much time for this bug? I see it is marked as p4 for a long time but there is no working progress for it. This issue stops developers to take screenshot in testing, and I don't think it's minor. Do I need to create a pull request with the workaround myselft? As I'm just not sure this is the correct way.

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
    [[IntegrationTestPlugin instance] setupChannels:registrar.messenger];
}

ductranit avatar May 20 '22 02:05 ductranit

@ductranit Where do we make this change/work around?

workerbee22 avatar May 30 '22 22:05 workerbee22

@workerbee22 it's in flutter/packages/integration_test/ios/Classes/IntegrationTestPlugin.m registerWithRegistrar method

ductranit avatar May 31 '22 02:05 ductranit

Taking screenshots for testing is very common. Hopefully, this fix will be merged soon!

ShahrearBinAmin avatar Jul 13 '22 10:07 ShahrearBinAmin

This is still present in flutter 3. Anyone trying to follow the instructions at https://github.com/flutter/flutter/blob/main/packages/integration_test/README.md on iOS hits this issue.

speaking-in-code avatar Jul 17 '22 01:07 speaking-in-code

Despite testing being a Flutter team goal for Y22 there seems to be little effort being directed here... so I've found the traditional test driver to work fine. YMMV

gslender avatar Jul 17 '22 01:07 gslender

cc: @jmagman

maheshj01 avatar Jul 18 '22 05:07 maheshj01

almost one year since it was reported.. still happening..

dr0-dev avatar Jul 28 '22 15:07 dr0-dev

Hey everyone, here's a quick script using the solution above that you can add to your pipeline. You should be able to run this anywhere:

# Set a variable with Flutter's path
FLUTTER_PATH="$(which flutter)"
FLUTTER_PATH=${FLUTTER_PATH/\/bin\/flutter/""}
# Update IntegrationTestPlugin.m
sed -i '' -e "51s/^//p; 51s/^.*/  \[\[IntegrationTestPlugin instance\] setupChannels\:registrar\.messenger\]\;/" $FLUTTER_PATH'/packages/integration_test/ios/Classes/IntegrationTestPlugin.m'

To rollback, simply go to your Flutter's folder and run git reset --hard and then flutter doctor to redownload dependencies.

edgarfroes avatar Sep 19 '22 17:09 edgarfroes

@jmagman I was able to reproduce this in the following versions: 3.0.1, 3.0.5, and 3.3.2.

edgarfroes avatar Sep 19 '22 17:09 edgarfroes

Hey everyone, here's a quick script using the solution above that you can add to your pipeline. You should be able to run this anywhere:

To rollback, simply go to your Flutter's folder and run git reset --hard and then flutter doctor to redownload dependencies.

A slight improvement to make it work with the symlink brew uses, and not to add it again if its already been run...

# Set a variable with Flutter's path
FLUTTER_PATH="$(readlink -f `which flutter`)"
FLUTTER_PATH=${FLUTTER_PATH/\/bin\/flutter/""}
FILE_TO_UPDATE=$FLUTTER_PATH'/packages/integration_test/ios/Classes/IntegrationTestPlugin.m'

if ! grep -q "setupChannels:registrar.messenger" "$FILE_TO_UPDATE"; then
    # Update IntegrationTestPlugin.m
    sed -i '' -e "51s/^//p; 51s/^.*/  \[\[IntegrationTestPlugin instance\] setupChannels\:registrar\.messenger\]\;/" $FILE_TO_UPDATE
fi

elliots avatar Sep 20 '22 00:09 elliots

I agree with others that this is a problem, and also agree that it's unfortunate that I cannot run an integration test from VS Code and have it work as expected on iOS.

There is a workaround that doesn't involve patching the SDK and allows integration with CI/CD for iOS again, but it requires some changes to your app code and minor changes to your testing code.

  1. Create wrapper...
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class Screenshotable extends StatefulWidget {
  Screenshotable({
    required this.child,
    super.key,
  });

  final Widget child;

  @override
  ScreenshotableState createState() => ScreenshotableState();
}

class ScreenshotableState extends State<Screenshotable> {
  final GlobalKey _renderKey = GlobalKey();

  /// Captures the image from the current widget.  This will return `null` if
  /// the image cannot be captured for any reason.
  Future<Uint8List?> captureImage() async {
    Uint8List? image;
    try {
      var boundary = _renderKey.currentContext!.findRenderObject()
          as RenderRepaintBoundary?;

      if (boundary != null &&
          (!kDebugMode || boundary.debugNeedsPaint != true)) {
        var mq = MediaQuery.of(context);
        var img = await boundary.toImage(pixelRatio: mq.devicePixelRatio);
        var byteData = await img.toByteData(
          format: ui.ImageByteFormat.png,
        );
        image = byteData?.buffer.asUint8List();
      }
    } catch (e) {
      // no-op; ignore it because screenshots may not always be supported
    }

    return image?.isEmpty == true ? null : image;
  }

  @override
  Widget build(BuildContext context) => RepaintBoundary(
        key: _renderKey,
        child: widget.child,
      );
}
  1. Update your launch config:
runApp(
  (!kIsWeb && Platform.isIOS)
      ? MaterialApp( // This provides the MediaQuery
          home: Screenshotable(
            child: MyApp(),
          ),
        )
      : MyApp(),
);
  1. In your test, rather than calling the takeScreenshot() directly against the WidgetsTester, use this instead...
@override
Future<Uint8List> takeScreenshot() async {
  Uint8List? image;

  try {
    if (!kIsWeb && Platform.isIOS) {
      try {
        var ss = find.byType(Screenshotable).evaluate().first as StatefulElement;

        await tester.pump();
        image = await (ss.state as ScreenshotableState).captureImage();
      } catch (e) {
        _logger.info('Screenshot failed', e);
      }
    } else {
      try {
        if (!kIsWeb && Platform.isAndroid) {
          await binding.convertFlutterSurfaceToImage();
        }
      } catch (e) {
        // no-op
      }

      await tester.pump();
      image = Uint8List.fromList(await binding.takeScreenshot('screenshot'));
    }
  } catch (e) {
    _logger.info('Screenshot failed', e);
  }

  return image;
}

jpeiffer avatar Oct 06 '22 14:10 jpeiffer

problem reproducible in 3.3.7 too

Charlinjoeaht avatar Nov 03 '22 21:11 Charlinjoeaht