From f7b9f2c1099b7a61e5f851f2e03e42a4746fadab Mon Sep 17 00:00:00 2001 From: Janusz Janus Date: Tue, 24 Dec 2024 15:31:38 +0100 Subject: [PATCH] test(cat-voices): integration tests start automating onboarding (#1435) * wip * wip * dic fix --- .config/dictionaries/project.dic | 1 + .../integration_test/onboarding_test.dart | 50 ++++++ .../pageobject/app_bar_page.dart | 2 + .../pageobject/common_page.dart | 10 ++ .../pageobject/onboarding_page.dart | 168 ++++++++++++++++++ .../types/registration_state.dart | 15 ++ .../utils/translations_utils.dart | 8 + .../get_started/get_started_panel.dart | 3 +- .../registration_details_panel.dart | 1 + .../registration/registration_info_panel.dart | 1 + .../widgets/information_panel.dart | 5 +- .../widgets/registration_stage_message.dart | 2 + .../lib/widgets/buttons/voices_buttons.dart | 1 + .../lib/widgets/common/affix_decorator.dart | 4 +- .../widgets/modals/voices_desktop_dialog.dart | 1 + 15 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 catalyst_voices/apps/voices/integration_test/onboarding_test.dart create mode 100644 catalyst_voices/apps/voices/integration_test/pageobject/common_page.dart create mode 100644 catalyst_voices/apps/voices/integration_test/pageobject/onboarding_page.dart create mode 100644 catalyst_voices/apps/voices/integration_test/types/registration_state.dart create mode 100644 catalyst_voices/apps/voices/integration_test/utils/translations_utils.dart diff --git a/.config/dictionaries/project.dic b/.config/dictionaries/project.dic index 2f33d343d5e..1b2bb638c44 100644 --- a/.config/dictionaries/project.dic +++ b/.config/dictionaries/project.dic @@ -322,6 +322,7 @@ Wireframes Wmissing Wnullable Woverlength +Writedown xcassets xcconfig xcfilelist diff --git a/catalyst_voices/apps/voices/integration_test/onboarding_test.dart b/catalyst_voices/apps/voices/integration_test/onboarding_test.dart new file mode 100644 index 00000000000..55c15876d7e --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/onboarding_test.dart @@ -0,0 +1,50 @@ +import 'package:catalyst_voices/app/view/app.dart'; +import 'package:catalyst_voices/configs/bootstrap.dart'; +import 'package:catalyst_voices/routes/routes.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import 'pageobject/app_bar_page.dart'; +import 'pageobject/onboarding_page.dart'; + +void main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + late final GoRouter router; + + setUpAll(() async { + router = buildAppRouter(); + await bootstrap(router: router); + }); + + setUp(() async { + await registerDependencies(); + router.go(const DiscoveryRoute().location); + }); + + tearDown(() async { + await restartDependencies(); + }); + + patrolWidgetTest( + 'Onboarding - visitor - get started button works', + (PatrolTester $) async { + await $.pumpWidgetAndSettle(App(routerConfig: router)); + await $(AppBarPage.getStartedBtn) + .tap(settleTimeout: const Duration(seconds: 10)); + expect($(OnboardingPage.registrationInfoPanel), findsOneWidget); + expect($(OnboardingPage.registrationDetailsPanel), findsOneWidget); + }, + ); + + patrolWidgetTest( + 'Onboarding - visitor - get started screen looks as expected', + (PatrolTester $) async { + await $.pumpWidgetAndSettle(App(routerConfig: router)); + await $(AppBarPage.getStartedBtn) + .tap(settleTimeout: const Duration(seconds: 10)); + await OnboardingPage.getStartedScreenLooksAsExpected($); + }, + ); +} diff --git a/catalyst_voices/apps/voices/integration_test/pageobject/app_bar_page.dart b/catalyst_voices/apps/voices/integration_test/pageobject/app_bar_page.dart index 32e7018cb77..ba80fdd9624 100644 --- a/catalyst_voices/apps/voices/integration_test/pageobject/app_bar_page.dart +++ b/catalyst_voices/apps/voices/integration_test/pageobject/app_bar_page.dart @@ -4,4 +4,6 @@ import 'package:flutter/material.dart'; class AppBarPage { static const spacesDrawerButton = Key('DrawerButton'); + static const getStartedBtn = Key('GetStartedButton'); + } diff --git a/catalyst_voices/apps/voices/integration_test/pageobject/common_page.dart b/catalyst_voices/apps/voices/integration_test/pageobject/common_page.dart new file mode 100644 index 00000000000..8581d62e1a4 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/pageobject/common_page.dart @@ -0,0 +1,10 @@ +library dashboard_page; + +import 'package:flutter/material.dart'; + +class CommonPage { + static const decoratorData = Key('DecoratorData'); + static const decoratorIconBefore = Key('DecoratorIconBefore'); + static const decoratorIconAfter = Key('DecoratorIconAfter'); + static const dialogCloseButton = Key('DialogCloseButton'); +} diff --git a/catalyst_voices/apps/voices/integration_test/pageobject/onboarding_page.dart b/catalyst_voices/apps/voices/integration_test/pageobject/onboarding_page.dart new file mode 100644 index 00000000000..1c7e3acf455 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/pageobject/onboarding_page.dart @@ -0,0 +1,168 @@ +library dashboard_page; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../types/registration_state.dart'; +import '../utils/translations_utils.dart'; +import 'common_page.dart'; + +class OnboardingPage { + static const registrationInfoPanel = Key('RegistrationInfoPanel'); + static const registrationDetailsPanel = Key('RegistrationDetailsPanel'); + static const registrationInfoLearnMoreButton = Key('LearnMoreButton'); + static const headerTitle = Key('HeaderTitle'); + static const headerSubtitle = Key('HeaderSubtitle'); + static const headerBody = Key('HeaderBody'); + static const registrationInfoPictureContainer = Key('PictureContainer'); + static const registrationInfoTaskPicture = Key('TaskPictureIconBox'); + static const registrationDetailsTitle = Key('RegistrationDetailsTitle'); + static const registrationDetailsBody = Key('RegistrationDetailsBody'); + + static Future infoPartHeaderTitleText(PatrolTester $) async { + return $(registrationInfoPanel).$(headerTitle).text; + } + + static Future infoPartHeaderSubtitleText(PatrolTester $) async { + return $(registrationInfoPanel).$(headerSubtitle).text; + } + + static Future infoPartHeaderBodyText(PatrolTester $) async { + return $(registrationInfoPanel).$(headerBody).text; + } + + static Future infoPartLearnMoreButtonText(PatrolTester $) async { + final child = find.descendant( + of: $(registrationInfoPanel).$(CommonPage.decoratorData), + matching: find.byType(Text), + ); + return $(child).text; + } + + static Finder infoPartTaskPicture(PatrolTester $) { + final child = find.descendant( + of: $(registrationInfoPanel).$(registrationInfoPictureContainer), + matching: find.byType(IconTheme), + ); + return child; + } + + static String? detailsPartGetStartedTitle(PatrolTester $) { + final child = find.descendant( + of: $(registrationDetailsPanel).$(registrationDetailsTitle), + matching: find.byType(Text), + ); + return $(child).text; + } + + static String? detailsPartGetStartedBody(PatrolTester $) { + final child = find.descendant( + of: $(registrationDetailsPanel).$(registrationDetailsBody), + matching: find.byType(Text), + ); + return $(child).text; + } + + static String? detailsPartGetStartedQuestionText(PatrolTester $) { + return $(registrationDetailsPanel).$(const Key('GetStartedQuestion')).text; + } + + static Future detailsPartGetStartedCreateNewBtn( + PatrolTester $, + ) async { + return $(registrationDetailsPanel) + .$(const Key('CreateAccountType.createNew')); + } + + static Future detailsPartGetStartedRecoverBtn( + PatrolTester $, + ) async { + return $(registrationDetailsPanel) + .$(const Key('CreateAccountType.recover')); + } + + static Future getStartedScreenLooksAsExpected(PatrolTester $) async { + await registrationInfoPanelLooksAsExpected($, RegistrationState.getStarted); + await registrationDetailsPanelLooksAsExpected( + $, + RegistrationState.getStarted, + ); + } + + static Future registrationInfoPanelLooksAsExpected( + PatrolTester $, + RegistrationState step, + ) async { + switch (step) { + case RegistrationState.getStarted: + expect(await infoPartHeaderTitleText($), T.get('Get Started')); + expect(await infoPartLearnMoreButtonText($), T.get('Learn More')); + expect(infoPartTaskPicture($), findsOneWidget); + break; + case RegistrationState.checkYourKeychain: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.createKeychain: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.keychainCreated: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.keychainRestoreInfo: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.keychainRestoreInput: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.keychainRestoreStart: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.keychainRestoreSuccess: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.mnemonicInput: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.mnemonicVerified: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.mnemonicWritedown: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.passwordInfo: + // TODO: Handle this case. + throw UnimplementedError(); + case RegistrationState.passwordInput: + // TODO: Handle this case. + throw UnimplementedError(); + } + } + + static Future registrationDetailsPanelLooksAsExpected( + PatrolTester $, RegistrationState getStarted,) async { + expect( + detailsPartGetStartedTitle($), + T.get('Welcome to Catalyst'), + ); + expect( + detailsPartGetStartedBody($), isNotEmpty, + ); + expect( + detailsPartGetStartedQuestionText($), + T.get('What do you want to do?'), + ); + expect( + await detailsPartGetStartedCreateNewBtn($), + findsOneWidget, + ); + expect( + await detailsPartGetStartedRecoverBtn($), + findsOneWidget, + ); + expect( + $(CommonPage.dialogCloseButton), + findsOneWidget, + ); + } +} diff --git a/catalyst_voices/apps/voices/integration_test/types/registration_state.dart b/catalyst_voices/apps/voices/integration_test/types/registration_state.dart new file mode 100644 index 00000000000..c40c7ddd5b6 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/types/registration_state.dart @@ -0,0 +1,15 @@ +enum RegistrationState { + checkYourKeychain, + createKeychain, + getStarted, + keychainCreated, + keychainRestoreInfo, + keychainRestoreInput, + keychainRestoreStart, + keychainRestoreSuccess, + mnemonicInput, + mnemonicVerified, + mnemonicWritedown, + passwordInfo, + passwordInput; +} diff --git a/catalyst_voices/apps/voices/integration_test/utils/translations_utils.dart b/catalyst_voices/apps/voices/integration_test/utils/translations_utils.dart new file mode 100644 index 00000000000..f3064792d01 --- /dev/null +++ b/catalyst_voices/apps/voices/integration_test/utils/translations_utils.dart @@ -0,0 +1,8 @@ +//wrapper that we should adapt to read actual i18n translations we use in the app +//it will also support different locales once we have it +//right now this class is here so we can easily replace this implementation and know where +class T { + static String get(String key, {String? locale}) { + return key; + } +} diff --git a/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart index 7add0d58cd5..cd5a83ad0c6 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/get_started/get_started_panel.dart @@ -28,6 +28,7 @@ class GetStartedPanel extends StatelessWidget { ), const SizedBox(height: 32), Text( + key: const Key('GetStartedQuestion'), context.l10n.accountCreationGetStatedWhatNext, style: theme.textTheme.titleSmall?.copyWith( color: theme.colors.textOnPrimaryLevel0, @@ -40,7 +41,7 @@ class GetStartedPanel extends StatelessWidget { children: CreateAccountType.values .map((type) { return RegistrationTile( - key: ValueKey(type), + key: Key(type.toString()), icon: type._icon, title: type._getTitle(context.l10n), subtitle: type._getSubtitle(context.l10n), diff --git a/catalyst_voices/apps/voices/lib/pages/registration/registration_details_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/registration_details_panel.dart index 6fe0c6023bf..d7a29b8a769 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/registration_details_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/registration_details_panel.dart @@ -16,6 +16,7 @@ class RegistrationDetailsPanel extends StatelessWidget { @override Widget build(BuildContext context) { return BlocSelector( + key: const Key('RegistrationDetailsPanel'), selector: (state) => state.step, builder: (context, state) { return switch (state) { diff --git a/catalyst_voices/apps/voices/lib/pages/registration/registration_info_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/registration_info_panel.dart index 5d320a93ef9..8c2928cb06d 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/registration_info_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/registration_info_panel.dart @@ -50,6 +50,7 @@ class RegistrationInfoPanel extends StatelessWidget { ); return InformationPanel( + key: const Key('RegistrationInfoPanel'), title: headerStrings.title, subtitle: headerStrings.subtitle, body: headerStrings.body, diff --git a/catalyst_voices/apps/voices/lib/pages/registration/widgets/information_panel.dart b/catalyst_voices/apps/voices/lib/pages/registration/widgets/information_panel.dart index 9f4104b4333..9f270fe665f 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/widgets/information_panel.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/widgets/information_panel.dart @@ -30,7 +30,7 @@ class InformationPanel extends StatelessWidget { body: body, ), const SizedBox(height: 12), - Expanded(child: Center(child: picture)), + Expanded(key: const Key('PictureContainer'), child: Center(child: picture)), const SizedBox(height: 12), _Footer( progress: progress, @@ -64,16 +64,19 @@ class _Header extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( + key: const Key('HeaderTitle'), title, style: theme.textTheme.titleLarge?.copyWith(color: textColor), ), if (subtitle != null) Text( + key: const Key('HeaderSubtitle'), subtitle, style: theme.textTheme.titleMedium?.copyWith(color: textColor), ), if (body != null) Text( + key: const Key('HeaderBody'), body, style: theme.textTheme.bodyMedium?.copyWith(color: textColor), ), diff --git a/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_message.dart b/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_message.dart index 52cc5f6e92f..ccdf1d2fe45 100644 --- a/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_message.dart +++ b/catalyst_voices/apps/voices/lib/pages/registration/widgets/registration_stage_message.dart @@ -25,11 +25,13 @@ class RegistrationStageMessage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DefaultTextStyle( + key: const Key('RegistrationDetailsTitle'), style: theme.textTheme.titleMedium!.copyWith(color: textColor), child: title, ), SizedBox(height: spacing), DefaultTextStyle( + key: const Key('RegistrationDetailsBody'), style: theme.textTheme.bodyMedium!.copyWith(color: textColor), child: subtitle, ), diff --git a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart index 27d91ad462b..a5768ea08a2 100644 --- a/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart +++ b/catalyst_voices/apps/voices/lib/widgets/buttons/voices_buttons.dart @@ -166,6 +166,7 @@ class VoicesLearnMoreButton extends StatelessWidget { @override Widget build(BuildContext context) { return VoicesTextButton( + key: const Key('LearnMoreButton'), trailing: VoicesAssets.icons.externalLink.buildIcon(), onTap: onTap, child: Text(context.l10n.learnMore), diff --git a/catalyst_voices/apps/voices/lib/widgets/common/affix_decorator.dart b/catalyst_voices/apps/voices/lib/widgets/common/affix_decorator.dart index b9ff8621fc9..40c45517ba1 100644 --- a/catalyst_voices/apps/voices/lib/widgets/common/affix_decorator.dart +++ b/catalyst_voices/apps/voices/lib/widgets/common/affix_decorator.dart @@ -57,15 +57,17 @@ class AffixDecorator extends StatelessWidget { children: [ if (prefix != null) ...[ IconTheme( + key: const Key('DecoratorIconBefore'), data: iconTheme ?? IconTheme.of(context), child: prefix, ), SizedBox(width: gap), ], - Flexible(child: child), + Flexible(key: const Key('DecoratorData'),child: child,), if (suffix != null) ...[ SizedBox(width: gap), IconTheme( + key: const Key('DecoratorIconAfter'), data: iconTheme ?? IconTheme.of(context), child: suffix, ), diff --git a/catalyst_voices/apps/voices/lib/widgets/modals/voices_desktop_dialog.dart b/catalyst_voices/apps/voices/lib/widgets/modals/voices_desktop_dialog.dart index c3677d45f03..1fb74349024 100644 --- a/catalyst_voices/apps/voices/lib/widgets/modals/voices_desktop_dialog.dart +++ b/catalyst_voices/apps/voices/lib/widgets/modals/voices_desktop_dialog.dart @@ -142,6 +142,7 @@ class _DialogCloseButton extends StatelessWidget { ); return Align( + key: const Key('DialogCloseButton'), alignment: Alignment.topRight, child: IconButtonTheme( data: const IconButtonThemeData(style: buttonStyle),