TF-4058 Update E2E test to compatible with flutter version 3.32.8

This commit is contained in:
dab246
2025-10-14 15:20:22 +07:00
committed by Dat H. Pham
parent 81ce3f6b04
commit d686b66f75
24 changed files with 74 additions and 48 deletions
@@ -7,7 +7,7 @@ on:
env: env:
JAVA_VERSION: 17 JAVA_VERSION: 17
FLUTTER_VERSION: 3.27.4 FLUTTER_VERSION: 3.32.8
jobs: jobs:
mobile_integration_test: mobile_integration_test:
@@ -1,5 +1,6 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -10,6 +11,10 @@ mixin ImageLoaderMixin {
required String imagePath, required String imagePath,
double? imageSize double? imageSize
}) { }) {
log('$runtimeType::buildImage: imagePath = $imagePath');
if (PlatformInfo.isIntegrationTesting) {
return buildNoImage(imageSize ?? 150);
}
if (isImageNetworkLink(imagePath) && isImageSVG(imagePath)) { if (isImageNetworkLink(imagePath) && isImageSVG(imagePath)) {
return SvgPicture.network( return SvgPicture.network(
imagePath, imagePath,
@@ -19,6 +24,9 @@ mixin ImageLoaderMixin {
placeholderBuilder: (_) { placeholderBuilder: (_) {
return const CupertinoActivityIndicator(); return const CupertinoActivityIndicator();
}, },
errorBuilder: (_, __, ___) {
return buildNoImage(imageSize ?? 150);
},
); );
} else if (isImageNetworkLink(imagePath)) { } else if (isImageNetworkLink(imagePath)) {
return Image.network( return Image.network(
@@ -27,16 +35,10 @@ mixin ImageLoaderMixin {
width: imageSize ?? 150, width: imageSize ?? 150,
height: imageSize ?? 150, height: imageSize ?? 150,
loadingBuilder: (_, child, loadingProgress) { loadingBuilder: (_, child, loadingProgress) {
if (loadingProgress != null && if (loadingProgress == null) return child;
loadingProgress.cumulativeBytesLoaded != loadingProgress.expectedTotalBytes) { return const Center(child: CupertinoActivityIndicator());
return const Center(
child: CupertinoActivityIndicator(),
);
}
return child;
}, },
errorBuilder: (context, error, stackTrace) { errorBuilder: (_, __, ___) {
logError('ImageLoaderMixin::buildImage:Exception = $error');
return buildNoImage(imageSize ?? 150); return buildNoImage(imageSize ?? 150);
}, },
); );
@@ -4,7 +4,7 @@ import 'dart:io' hide HttpClient;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
@@ -282,7 +282,7 @@ mixin ScenarioUtilsMixin {
IdentityInteractorsBindings().dispose(); IdentityInteractorsBindings().dispose();
} }
Future<void> hideKeyboard() async { void hideKeyboard() {
await SystemChannels.textInput.invokeMethod('TextInput.hide'); FocusManager.instance.primaryFocus?.unfocus();
} }
} }
@@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_b
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_editor_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/mobile/mobile_editor_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/list_recipients_collapsed_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/app_bar_composer_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_mobile_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
@@ -29,6 +30,10 @@ import '../extensions/patrol_finder_extension.dart';
class ComposerRobot extends CoreRobot { class ComposerRobot extends CoreRobot {
ComposerRobot(super.$); ComposerRobot(super.$);
Future<void> expandRecipientsFields() async {
await $(RecipientsCollapsedComposerWidget).$(GestureDetector).tap();
}
Future<void> addRecipientIntoField({ Future<void> addRecipientIntoField({
required PrefixEmailAddress prefixEmailAddress, required PrefixEmailAddress prefixEmailAddress,
required String email, required String email,
+1 -1
View File
@@ -16,7 +16,7 @@ class EmailRobot extends CoreRobot {
} }
Future<void> tapDownloadAllButton() async { Future<void> tapDownloadAllButton() async {
await $(AppLocalizations().archiveAndDownload).tap(); await $(#download_all_attachments_button).tap();
await $.pumpAndSettle(); await $.pumpAndSettle();
} }
@@ -16,6 +16,7 @@ class AppGridScenario extends BaseTestScenario {
@override @override
Future<void> runTestLogic() async { Future<void> runTestLogic() async {
PlatformInfo.isIntegrationTesting = true;
final threadRobot = ThreadRobot($); final threadRobot = ThreadRobot($);
final mailboxMenuRobot = MailboxMenuRobot($); final mailboxMenuRobot = MailboxMenuRobot($);
final appGridRobot = AppGridRobot($); final appGridRobot = AppGridRobot($);
@@ -52,6 +53,7 @@ class AppGridScenario extends BaseTestScenario {
} else if (PlatformInfo.isIOS) { } else if (PlatformInfo.isIOS) {
await _expectMailboxViewInVisible(); await _expectMailboxViewInVisible();
} }
PlatformInfo.isIntegrationTesting = false;
} }
Future<void> _expectMailboxViewVisible() => expectViewVisible($(MailboxView)); Future<void> _expectMailboxViewVisible() => expectViewVisible($(MailboxView));
@@ -31,7 +31,7 @@ class ComposerUploadAttachmentAndInlineImageScenario extends BaseTestScenario {
await _expectAttachment(pngName); await _expectAttachment(pngName);
await composerRobot.addInline(png); await composerRobot.addInline(png);
await $.pumpAndSettle(); await $.pumpAndSettle(duration: const Duration(seconds: 1));
await _expectInline(); await _expectInline();
await composerRobot.addAttachment(png); await composerRobot.addAttachment(png);
@@ -1,9 +1,11 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:model/email/prefix_email_address.dart'; import 'package:model/email/prefix_email_address.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import '../../base/base_test_scenario.dart'; import '../../base/base_test_scenario.dart';
import '../../robots/composer_robot.dart';
import '../../robots/email_robot.dart'; import '../../robots/email_robot.dart';
import '../../robots/mailbox_menu_robot.dart'; import '../../robots/mailbox_menu_robot.dart';
import '../../robots/thread_robot.dart'; import '../../robots/thread_robot.dart';
@@ -19,6 +21,7 @@ class ReplyToOwnSentEmailScenario extends BaseTestScenario {
final threadRobot = ThreadRobot($); final threadRobot = ThreadRobot($);
final mailboxMenuRobot = MailboxMenuRobot($); final mailboxMenuRobot = MailboxMenuRobot($);
final emailRobot = EmailRobot($); final emailRobot = EmailRobot($);
final composerRobot = ComposerRobot($);
final sendEmailScenario = SendEmailScenario($, customSubject: subject); final sendEmailScenario = SendEmailScenario($, customSubject: subject);
final appLocalizations = AppLocalizations(); final appLocalizations = AppLocalizations();
@@ -29,9 +32,20 @@ class ReplyToOwnSentEmailScenario extends BaseTestScenario {
); );
await threadRobot.openEmailWithSubject(subject); await threadRobot.openEmailWithSubject(subject);
await emailRobot.onTapReplyEmail(); await emailRobot.onTapReplyEmail();
await _expectComposerViewVisible();
await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndSettle();
_expectToFieldContainListEmailAddressCorrectly(); _expectToFieldContainListEmailAddressCorrectly();
} }
Future<void> _expectComposerViewVisible() async {
await expectViewVisible($(ComposerView));
}
void _expectToFieldContainListEmailAddressCorrectly() { void _expectToFieldContainListEmailAddressCorrectly() {
expect( expect(
$(RecipientComposerWidget).which<RecipientComposerWidget>((widget) => $(RecipientComposerWidget).which<RecipientComposerWidget>((widget) =>
@@ -49,6 +49,9 @@ class ComposeEmailFromEmailAddressScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndSettle();
await _expectToFieldContainListEmailAddressCorrectly(emailUser); await _expectToFieldContainListEmailAddressCorrectly(emailUser);
} }
@@ -1,9 +1,8 @@
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart'; import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart';
import '../../base/base_test_scenario.dart'; import '../../base/base_test_scenario.dart';
@@ -121,10 +120,12 @@ class DeformedInlinedImageScenario extends BaseTestScenario {
} }
Future<void> _expectEmailViewDisplaysNormalizedInlineImages() async { Future<void> _expectEmailViewDisplaysNormalizedInlineImages() async {
GlobalKey<HtmlContentViewState>? htmlContentViewKey;
HtmlContentViewer? htmlContentViewer; HtmlContentViewer? htmlContentViewer;
await $(HtmlContentViewer) await $(HtmlContentViewer)
.which<HtmlContentViewer>((view) { .which<HtmlContentViewer>((view) {
htmlContentViewer = view; htmlContentViewer = view;
htmlContentViewKey = view.key as GlobalKey<HtmlContentViewState>;
return true; return true;
}) })
.first .first
@@ -133,17 +134,6 @@ class DeformedInlinedImageScenario extends BaseTestScenario {
log('DeformedInlinedImageScenario::_expectEmailViewDisplaysNormalizedInlineImages:initialWidth = ${htmlContentViewer?.initialWidth}'); log('DeformedInlinedImageScenario::_expectEmailViewDisplaysNormalizedInlineImages:initialWidth = ${htmlContentViewer?.initialWidth}');
expect(htmlContentViewer?.contentHtml.isNotEmpty, isTrue); expect(htmlContentViewer?.contentHtml.isNotEmpty, isTrue);
SingleEmailController? emailController;
await $(EmailView)
.which<EmailView>((view) {
emailController = view.controller;
return true;
})
.first
.tap();
expect(emailController, isNotNull);
final htmlContentViewKey = emailController?.htmlContentViewKey;
expect(htmlContentViewKey, isNotNull); expect(htmlContentViewKey, isNotNull);
final result = await htmlContentViewKey!.currentState!.webViewController.evaluateJavascript( final result = await htmlContentViewKey!.currentState!.webViewController.evaluateJavascript(
@@ -55,6 +55,10 @@ class ForwardingEmailLostAttachmentsScenario extends BaseTestScenario {
await _expectComposerViewVisible(); await _expectComposerViewVisible();
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndSettle();
await composerRobot.addRecipientIntoField( await composerRobot.addRecipientIntoField(
prefixEmailAddress: PrefixEmailAddress.to, prefixEmailAddress: PrefixEmailAddress.to,
email: emailUser, email: emailUser,
@@ -44,6 +44,9 @@ class ReplyAllEmailScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndTrySettle();
await _expectComposerSubjectDisplayedCorrectly(appLocalizations); await _expectComposerSubjectDisplayedCorrectly(appLocalizations);
await _expectToFieldContainListEmailAddressCorrectly(); await _expectToFieldContainListEmailAddressCorrectly();
await _expectCcFieldContainListEmailAddressCorrectly(); await _expectCcFieldContainListEmailAddressCorrectly();
@@ -44,6 +44,9 @@ class ReplyEmailWithReplyToScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndTrySettle();
await _expectComposerSubjectDisplayedCorrectly(appLocalizations); await _expectComposerSubjectDisplayedCorrectly(appLocalizations);
await _expectToFieldContainReplyToEmailAddress(); await _expectToFieldContainReplyToEmailAddress();
} }
@@ -44,6 +44,9 @@ class ReplyEmailWithoutReplyToScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndTrySettle();
await _expectComposerSubjectDisplayedCorrectly(appLocalizations); await _expectComposerSubjectDisplayedCorrectly(appLocalizations);
await _expectToFieldContainFromEmailAddress(); await _expectToFieldContainFromEmailAddress();
} }
@@ -44,6 +44,9 @@ class ReplyToListEmailScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndTrySettle();
await _expectComposerSubjectDisplayedCorrectly(appLocalizations); await _expectComposerSubjectDisplayedCorrectly(appLocalizations);
await _expectToFieldContainListPostEmailAddress(); await _expectToFieldContainListPostEmailAddress();
} }
@@ -70,6 +70,7 @@ class LongPressEmptyAndRecoverSpamScenario extends BaseTestScenario {
} }
Future<void> _expectEmailWithSubjectVisible(String subject) async { Future<void> _expectEmailWithSubjectVisible(String subject) async {
await $.scrollUntilVisible(finder: $(subject));
await expectViewVisible($(subject)); await expectViewVisible($(subject));
} }
@@ -68,6 +68,7 @@ class LongPressEmptyAndRecoverTrashScenario extends BaseTestScenario {
} }
Future<void> _expectEmailWithSubjectVisible(String subject) async { Future<void> _expectEmailWithSubjectVisible(String subject) async {
await $.scrollUntilVisible(finder: $(subject));
await expectViewVisible($(subject)); await expectViewVisible($(subject));
} }
@@ -37,11 +37,11 @@ class PullToRefreshScenario extends BaseTestScenario {
_expectEmailWithSubjectInvisible(visibleAfterPullToRefresh); _expectEmailWithSubjectInvisible(visibleAfterPullToRefresh);
await threadRobot.pullToRefreshByEmailSubject(visibleBeforePullToRefresh); await threadRobot.pullToRefreshByEmailSubject(visibleBeforePullToRefresh);
_expectEmailWithSubjectVisible(visibleAfterPullToRefresh); await _expectEmailWithSubjectVisible(visibleAfterPullToRefresh);
} }
_expectEmailWithSubjectVisible(String subject) { Future<void> _expectEmailWithSubjectVisible(String subject) async {
expect($(subject), findsOneWidget); await expectViewVisible($(subject));
} }
_expectEmailWithSubjectInvisible(String subject) { _expectEmailWithSubjectInvisible(String subject) {
@@ -46,6 +46,9 @@ class ReplyEmailWithContentContainImageBase64DataScenario extends BaseTestScenar
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.expandRecipientsFields();
await $.pumpAndSettle();
await composerRobot.addRecipientIntoField( await composerRobot.addRecipientIntoField(
prefixEmailAddress: PrefixEmailAddress.to, prefixEmailAddress: PrefixEmailAddress.to,
email: emailUser, email: emailUser,
@@ -17,11 +17,12 @@ class SaveAsTemplateScenario extends BaseTestScenario {
await composerRobot.grantContactPermission(); await composerRobot.grantContactPermission();
await composerRobot.saveAsTemplate(); await composerRobot.saveAsTemplate();
await $.pumpAndTrySettle();
await _expectSaveToastSuccessVisible(); await _expectSaveToastSuccessVisible();
await composerRobot.addSubject('test subject'); await composerRobot.addSubject('test subject');
await hideKeyboard(); hideKeyboard();
await $.pumpAndTrySettle(duration: const Duration(seconds: 1));
await composerRobot.saveAsTemplate(); await composerRobot.saveAsTemplate();
await _expectUpdateToastSuccessVisible(); await _expectUpdateToastSuccessVisible();
@@ -14,7 +14,7 @@ class PersistFilterWhenChangeSearchInputTextScenario
Future<void> runTestLogic() async { Future<void> runTestLogic() async {
const email = String.fromEnvironment('BASIC_AUTH_EMAIL'); const email = String.fromEnvironment('BASIC_AUTH_EMAIL');
const subject = 'Persist search filter'; const subject = 'Persist search filter';
const queryStringFirst = 'Persist'; const queryStringFirst = 'Persist search filter';
const queryStringSecond = 'Persist search'; const queryStringSecond = 'Persist search';
final threadRobot = ThreadRobot($); final threadRobot = ThreadRobot($);
@@ -68,6 +68,7 @@ class AttachmentsInfo extends StatelessWidget {
), ),
if (onTapDownloadAllButton != null) if (onTapDownloadAllButton != null)
TMailButtonWidget( TMailButtonWidget(
key: const Key('download_all_attachments_button'),
text: AppLocalizations.of(context).downloadAll, text: AppLocalizations.of(context).downloadAll,
icon: !responsiveUtils.isMobile(context) icon: !responsiveUtils.isMobile(context)
? imagePaths.icDownloadAttachment ? imagePaths.icDownloadAttachment
+1 -7
View File
@@ -1,5 +1,5 @@
{ {
"@@last_modified": "2025-10-07T12:02:25.245876", "@@last_modified": "2025-10-14T15:18:48.964794",
"initializing_data": "Initializing data...", "initializing_data": "Initializing data...",
"@initializing_data": { "@initializing_data": {
"type": "text", "type": "text",
@@ -4592,12 +4592,6 @@
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
}, },
"archiveAndDownload": "Archive and download",
"@archiveAndDownload": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"creatingAnArchiveForDownloading": "Creating an archive for downloading", "creatingAnArchiveForDownloading": "Creating an archive for downloading",
"@creatingAnArchiveForDownloading": { "@creatingAnArchiveForDownloading": {
"type": "text", "type": "text",
@@ -4838,13 +4838,6 @@ class AppLocalizations {
); );
} }
String get archiveAndDownload {
return Intl.message(
'Archive and download',
name: 'archiveAndDownload',
);
}
String get creatingAnArchiveForDownloading { String get creatingAnArchiveForDownloading {
return Intl.message( return Intl.message(
'Creating an archive for downloading', 'Creating an archive for downloading',