fzyzcjy / flutter_convenient_test

Write and debug tests easily, with full action history, time travel, screenshots, rapid re-execution, video records, interactivity, isolation and more
pub.dev/packages/convenient_test
MIT License
489 stars 44 forks source link

Can't detect Dialog, Bottom Sheet, Overlay OR it also detect widgets below the Overlay #375

Closed AngDrew closed 7 months ago

AngDrew commented 8 months ago

Describe the bug The test can't detect object above the screen such as Dialog, Bottom Sheet, Overlay which has their own context detached from the MaterialApp context. if it's possible to detect, the screen below the Dialog, Bottom Sheet, Overlay will still visible to the tester

To Reproduce Steps to reproduce the behavior:

  1. Make a Widget that show screen (Scaffold, with Button is fine)
  2. Run the test to click the Button.
  3. Show dialog or use overlay or showBottomSheet on Button pressed
  4. make the test to read the Text above the Dialog / Overlay / Bottom Sheet

Expected behavior the finder should not be able to see the widget below the Dialog, Bottom Sheet, Overlay

Screenshots image

Desktop (please complete the following information):

Smartphone (please complete the following information):

Additional context is there something that i missed? in the screen shot, you can see there is too many "start date" while there is only one above my bottom sheet. there's many "start date" below the bottom sheet. is there work around for me to check if there's bottom sheet opened?

thanks!

welcome[bot] commented 8 months ago

Hi! Thanks for opening your first issue here! :smile:

fzyzcjy commented 8 months ago

in the screen shot, you can see there is too many "start date" while there is only one

Hmm, IIRC some widgets with weird behaviors moving around the tree may make the finder confused. Could you please make a minimal reproducible sample, e.g. an empty screen with only one dialog with that text? Then it would be easy to see what is wrong.

Btw, try the "expand" button and see whether it tells you more, e.g. the full ancestors of all the widgets found

AngDrew commented 8 months ago

indeed i have many animation on this app, maybe this cause some issue to the finder. anyway ill try empty screen with dialog opened using onInit() and "expand" the same error log ill try to reproduce the test first

AngDrew commented 8 months ago

tried to print using find.text('Contract').allCandidates.length and it prints 869 widgets wihle dialog are open image

AngDrew commented 8 months ago

i was wondering, is there any way to scroll while have showModalBottomSheet open?

AngDrew commented 8 months ago

the best way to select the widget is using find.byWidgetPredicate(...) we can query any widget, and certain attribute as we wanted but one thing i still don't understand.. how to scroll if there's multiple scrollable in the screen πŸ€” how to scroll only modal bottom sheet, how to scroll from the certain position of screen (using offside or coordinate maybe?)

AngDrew commented 8 months ago

I found out that if we want to specifically scroll within a scrollable widget, we can use t.tester.scrollUntilVisible(...) and specify the scrollable parameter with a Finder.

Now, I've encountered another issue. I have a splash screen in my app that redirects users to the dashboard if they have an access token, otherwise they are redirected to the login screen. When I run the app by executing the main code (not the main test code) and receive a token, somehow the test no longer works for me. I have to uninstall the app and restart the test from scratch without a token.

Is this a bug or did I miss something? I'm relatively new to integration testing in Flutter. In the past, I've only implemented it in small projects and I'm still trying to fully understand it.

image The test won't start and gets stuck if I skip the splash screen. image log:

══║ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
The following assertion was thrown running a test:
pumpWithRunAsyncUntil timed out (startTime=WallAndFakeClock(wallClock: 2024-03-13 15:40:10.877824,
fakeClock: 2024-03-13 15:40:10.877813), endTime=WallAndFakeClock(wallClock: 2024-03-13
15:41:10.877824, fakeClock: 2024-03-13 15:41:10.877813), now=WallAndFakeClock(wallClock: 2024-03-13
15:41:10.963041, fakeClock: 2024-03-13 15:41:10.963029), pumpCount=434)

When the exception was thrown, this was the stack:
#0      ExtWidgetTesterPump.pumpWithRunAsyncUntil.<anonymous closure> (package:convenient_test_dev/src/functions/widget_tester.dart:132:11)
<asynchronous suspension>
#1      TestAsyncUtils.guard.<anonymous closure> (package:flutter_test/src/test_async_utils.dart:117:7)
<asynchronous suspension>
#2      ExtWidgetTesterPump.pumpAndSettleWithRunAsync (package:convenient_test_dev/src/functions/widget_tester.dart:92:7)
<asynchronous suspension>
#3      tTestWidgets.<anonymous closure>.<anonymous closure> (package:convenient_test_dev/src/functions/test_widgets.dart:37:16)
<asynchronous suspension>
#4      ConvenientTest.withActiveInstance (package:convenient_test_dev/src/functions/instance.dart:29:7)
<asynchronous suspension>
#5      tTestWidgets.<anonymous closure> (package:convenient_test_dev/src/functions/test_widgets.dart:26:23)
<asynchronous suspension>
#6      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:168:15)
<asynchronous suspension>
#7      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1013:5)
<asynchronous suspension>
#8      TestWidgetsFlutterBinding._createTestCompletionHandler.<anonymous closure> (package:flutter_test/src/binding.dart:804:12)
<asynchronous suspension>

The test description was:
  login, schedule, rate, booking
════════════════════════════════════════════════════════════════════════════════════════════════════
package:convenient_test_dev/src/functions/widget_tester.dart 132:11  ExtWidgetTesterPump.pumpWithRunAsyncUntil.<fn>
===== asynchronous gap ===========================
package:flutter_test/src/test_async_utils.dart 117:7                 TestAsyncUtils.guard.<fn>
===== asynchronous gap ===========================
package:convenient_test_dev/src/functions/widget_tester.dart 92:7    ExtWidgetTesterPump.pumpAndSettleWithRunAsync
===== asynchronous gap ===========================
package:convenient_test_dev/src/functions/test_widgets.dart 37:16    tTestWidgets.<fn>.<fn>
===== asynchronous gap ===========================
package:convenient_test_dev/src/functions/instance.dart 29:7         ConvenientTest.withActiveInstance
===== asynchronous gap ===========================
package:convenient_test_dev/src/functions/test_widgets.dart 26:23    tTestWidgets.<fn>
===== asynchronous gap ===========================
package:flutter_test/src/widget_tester.dart 168:15                   testWidgets.<fn>.<fn>
===== asynchronous gap ===========================
package:flutter_test/src/binding.dart 1013:5                         TestWidgetsFlutterBinding._runTestBody
===== asynchronous gap ===========================
package:flutter_test/src/binding.dart 804:12                         TestWidgetsFlutterBinding._createTestCompletionHandler.<fn>
fzyzcjy commented 8 months ago

So if I understand correctly, your question now is about the app token thing?

I personally do something like: during test setup (e.g. setUpAll / setUp), remove that token programmatically.

AngDrew commented 8 months ago

turns out the token was not the issue here..

ExtWidgetTesterPump.pumpWithRunAsyncUntil. (package:convenient_test_dev/src/functions/widget_tester.dart:132:11)

image I got this exception while trying to run the test. It installs the app but cannot start the test somehow. It works perfectly fine if I uninstall the app using adb command and run the test as if my app is freshly installed, with no setup on my test. But if I make another test group, it restarts the app (the token already saved using secure storage), the settings have been saved to shared preferences. Then the test will pump loop and eventually timeout.

I've changed my binding.dart, but the issue is not solved.

here's my test code:

import 'package:convenient_test/convenient_test.dart';
import 'package:convenient_test_dev/convenient_test_dev.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:super_app/core/core.dart';
import 'package:super_app/flavor.dart';
import 'package:super_app/main_for_test.dart' as app;
import 'package:super_app/modules/modules.dart';

import 'booking_flow_integration_test.dart';
import 'login_flow_integration_test.dart';
import 'rate_flow_integration_test.dart';
import 'schedule_flow_integration_test.dart';

class MyConvenientTestSlot extends ConvenientTestSlot {
  @override
  Future<void> appMain(AppMainExecuteMode mode) async => app.main();

  @override
  BuildContext? getNavContext(ConvenientTest t) => navigatorKey.currentContext;
}

// --dart-define PERSISTENT_ARGUMENTS="clSs.clSp"
//
// flutter run integration_test/main_test.dart --flavor dev --host-vmservice-port 9753 --disable-service-auth-codes --dart-define CONVENIENT_TEST_MANAGER_HOST=10.0.2.2 --dart-define CONVENIENT_TEST_APP_CODE_DIR="D:/projects/flutter-project/super_app" --dart-define TEST_ENTRY_POINT="dashboardNoPin"
void main() {
  String entryPoint = const String.fromEnvironment('TEST_ENTRY_POINT');
  ConvenientTestWrapperWidget.convenientTestActive = true;
  ConvenientTestWrapperWidget.enableGestureVisualizer = true;

  convenientTestMain(MyConvenientTestSlot(), () {
    group('booking flow', () {
      setUp(() async {
        F.appFlavor = Flavor.dev;

        // const String dartDefineArg =
        //     String.fromEnvironment('PERSISTENT_ARGUMENTS');

        // if (dartDefineArg.contains('clSp')) {
        // }
        // if (dartDefineArg.contains('clSs')) {
        // }
        SharedPreferences sp = await SharedPreferences.getInstance();
        await sp.clear();
        await Storage.deleteAll();

        await sp.setBool(StorageKeys.firstTimeOpen, false);

        await TokenStorage.writeToken('xx');
        await TokenStorage.writeRefreshToken('xx');

        await UserPinStorage.writePreferBiometric(false);
        await UserPinStorage.writeUserPin('123456');

        return null;
      });

      tTestWidgets(
        'login, schedule, rate, booking',
        (ConvenientTest t) async {
          if (entryPoint == 'onboarding') {
            await loginFlow(t);
          }
          if (entryPoint == 'dashboardNoPin') {
            await t.get(find.text('Quick Tracking')).should(findsOneWidget);
            await scheduleFlow(t);
            await rateFlow(t);
            await bookingFlow(t);
          }
        },
      );
    });
  });
}

my main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  Directory dir = await getApplicationDocumentsDirectory();
  // initializing isar database schema
  isarBookingDraft ??= await Isar.open(
    <CollectionSchema<dynamic>>[
      BookingDraftSchema,
    ],
    directory: dir.path,
    name: 'booking_drafts',
  );

  await SystemChrome.setPreferredOrientations(
    <DeviceOrientation>[DeviceOrientation.portraitUp],
  );

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  FirebaseMessaging firebaseInstance = FirebaseMessaging.instance;

  String? fcmToken = await firebaseInstance.getToken();
  await firebaseInstance.setAutoInitEnabled(true);
  print('FCMToken $fcmToken');

  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  AndroidInitializationSettings initializationSettingsAndroid =
      const AndroidInitializationSettings('@mipmap/ic_launcher');
  DarwinInitializationSettings initializationSettingsIOS =
      const DarwinInitializationSettings(
    requestAlertPermission: false,
    requestBadgePermission: false,
    requestSoundPermission: false,
  );
  InitializationSettings initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsIOS,
  );
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    RemoteNotification? notification = message.notification;
    AndroidNotification? android = message.notification?.android;
    if (notification != null && android != null) {
      flutterLocalNotificationsPlugin.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              'super_app_notification_channel',
              'Super App Notification Channel',
              channelDescription: 'Super App Default General Channel',
              icon: android.smallIcon,
              // other properties...
            ),
          ));
    }
  });

  runApp(const ProviderScope(child: ConvenientTestWrapperWidget(child: App())));
}
AngDrew commented 8 months ago

tl;dr: i cannot restart the test, it will loop infinitely and timeout

fzyzcjy commented 8 months ago

Hmm, firstly try to bisect which line causes this problem (e.g. add a ton of prints and see). Then feel free to post the details if it is nontrivial to fix!

AngDrew commented 7 months ago

aight, i've found the solution. it's not the library issue, but the flutter itself.

If you would like to scroll through dialog, you should use dragUntilVisible instead of scrollUntilVisible reference. that said, use scrollUntilVisible if it's scrollable and not blocked by ignore pointer or other pointer blocking widget, and use dragUntilVisible if it's overlay (dialog, bottom sheet, pop up, etc.)

thanks a lot ^^

fzyzcjy commented 7 months ago

You are welcome and happy to see it is solved!

github-actions[bot] commented 7 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.