E2E Fix integration test Twake Mail v0.18.4

E2E Fix integration test Twake Mail v0.18.4
This commit is contained in:
Dang Dat
2025-09-09 10:22:14 +07:00
committed by Dat H. Pham
parent fb53ba6568
commit c6ace7a9b5
29 changed files with 97 additions and 114 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ class TestBase {
description,
config: const PatrolTesterConfig(
settlePolicy: SettlePolicy.trySettle,
visibleTimeout: Duration(minutes: 1),
visibleTimeout: Duration(seconds: 30),
printLogs: true,
),
nativeAutomatorConfig: const NativeAutomatorConfig(
@@ -4,6 +4,7 @@ import 'dart:io' hide HttpClient;
import 'package:collection/collection.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
@@ -270,4 +271,8 @@ mixin ScenarioUtilsMixin {
IdentityInteractorsBindings().dispose();
}
Future<void> hideKeyboard() async {
await SystemChannels.textInput.invokeMethod('TextInput.hide');
}
}
+5 -7
View File
@@ -10,6 +10,7 @@ import 'package:model/extensions/session_extension.dart';
import 'package:model/upload/file_info.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart';
import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.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/view/mobile/mobile_editor_view.dart';
@@ -33,13 +34,6 @@ class ComposerRobot extends CoreRobot {
}) async {
final finder = $(RecipientComposerWidget).which<RecipientComposerWidget>(
(widget) => widget.prefix == prefixEmailAddress);
final isTextFieldFocused = finder
.which<RecipientComposerWidget>(
(view) => view.focusNode?.hasFocus ?? false)
.exists;
if (!isTextFieldFocused) {
await finder.tap();
}
await finder.enterTextWithoutTapAction(email);
await $(RecipientSuggestionItemWidget)
@@ -163,6 +157,10 @@ class ComposerRobot extends CoreRobot {
fileSize: await file.length(),
fileName: file.path.split('/').last,
)));
await controller.viewState.stream.firstWhere((state) => state.fold(
(failure) => false,
(success) => success is DownloadImageAsBase64Success,
));
}
Future<void> toggleReadReceipt() async {
@@ -12,7 +12,7 @@ class EmailAddressDialogRobot extends CoreRobot {
}
Future<void> tapCopyEmailAddressButton(AppLocalizations appLocalizations) async {
await $(appLocalizations.copy_email_address).tap();
await $(#copy_email_address).tap();
}
Future<void> tapComposeEmailButton(AppLocalizations appLocalizations) async {
+14 -20
View File
@@ -1,7 +1,6 @@
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tmail_ui_user/features/base/widget/material_text_button.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import '../base/core_robot.dart';
@@ -40,47 +39,42 @@ class EmailRobot extends CoreRobot {
}
Future<void> tapMarkAsUnreadOptionInContextMenu() async {
await $(#markAsUnread_action).tap();
await $(AppLocalizations().mark_as_unread).tap();
}
Future<void> tapEmailDetailedStarButton() async {
await $(#email_detailed_star_button).tap();
await $(AppLocalizations().starred).tap();
}
Future<void> tapEmailDetailedUnstarButton() async {
await $(AppLocalizations().not_starred).tap();
}
Future<void> tapEmailDetailedMoveEmailButton() async {
await $(#email_detailed_move_email_button).tap();
await $(AppLocalizations().moveMessage).tap();
}
Future<void> tapEmailDetailedDeleteEmailButton() async {
await $(#email_detailed_delete_email_button).tap();
await $(AppLocalizations().move_to_trash).tap();
}
Future<void> tapMarkAsSpamOptionInContextMenu() async {
await $(#moveToSpam_action).tap();
await $(AppLocalizations().markAsSpam).tap();
}
Future<void> tapArchiveMessageOptionInContextMenu() async {
await $(#archiveMessage_action).tap();
await $(AppLocalizations().archiveMessage).tap();
}
Future<void> tapSenderEmailAddress(String email) async {
await $(MaterialTextButton)
.which<MaterialTextButton>((widget) => widget.label.contains(email))
.first
.tap();
await $(InformationSenderAndReceiverBuilder).$(email).tap();
}
Future<void> tapRecipientEmailAddress(String email) async {
await $(TMailButtonWidget)
.which<TMailButtonWidget>((widget) => widget.text.contains(email))
.first
.tap();
await $(InformationSenderAndReceiverBuilder).$(email).tap();
}
Future<void> longPressSenderEmailAddress(String email) async {
await $(MaterialTextButton)
.which<MaterialTextButton>((widget) => widget.label.contains(email))
.first
.longPress();
await $(InformationSenderAndReceiverBuilder).$(email).longPress();
}
}
@@ -2,7 +2,6 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/label_mailbox_item_widget.dart';
@@ -71,11 +70,7 @@ class MailboxMenuRobot extends CoreRobot {
}
Future<void> closeMenu() async {
await $(IconButton)
.which<IconButton>((widget) {
return widget.tooltip == AppLocalizations().close;
})
.tap();
await $.native.swipe(from: const Offset(0.9, 0.5), to: const Offset(0, 0.5));
}
int getCurrentInboxCount() {
+3 -3
View File
@@ -57,14 +57,14 @@ class ThreadRobot extends CoreRobot {
}
Future<void> selectAttachmentFilter() async {
await $(#filter_email_attachments).tap();
await $(#attachments_filter).tap();
}
Future<void> selectUnreadFilter() async {
await $(#filter_email_unread).tap();
await $(#unread_filter).tap();
}
Future<void> selectStarredFilter() async {
await $(#filter_email_starred).tap();
await $(#starred_filter).tap();
}
}
@@ -27,10 +27,11 @@ class ComposerUploadAttachmentAndInlineImageScenario extends BaseTestScenario {
final png = await preparingPngWithName(pngName);
await composerRobot.addAttachment(png);
await $.pumpAndSettle();
await _expectAttachment(pngName);
await composerRobot.addInline(png);
await $.pumpAndSettle();
_expectAttachment(pngName);
await _expectInline();
await composerRobot.addAttachment(png);
@@ -50,13 +51,11 @@ class ComposerUploadAttachmentAndInlineImageScenario extends BaseTestScenario {
);
}
void _expectAttachment(String pngName) {
return expect(
$(AttachmentItemComposerWidget)
.which<AttachmentItemComposerWidget>(
(widget) => widget.fileName.contains(pngName)
Future<void> _expectAttachment(String pngName) async {
await expectViewVisible(
$(AttachmentItemComposerWidget).which<AttachmentItemComposerWidget>(
(widget) => widget.fileName.contains(pngName),
),
findsOneWidget,
);
}
@@ -38,10 +38,10 @@ class ArchiveEmailScenario extends BaseTestScenario {
_expectEmailDetailedMoreButtonVisible();
await emailRobot.tapEmailDetailedMoreButton();
await $.pumpAndSettle(duration: const Duration(seconds: 2));
await $.pumpAndSettle(duration: const Duration(seconds: 1));
await emailRobot.tapArchiveMessageOptionInContextMenu();
await $.pumpAndSettle(duration: const Duration(seconds: 3));
await $.pumpAndSettle(duration: const Duration(seconds: 1));
await emailRobot.onTapBackButton();
await $.pumpAndSettle();
@@ -37,8 +37,9 @@ class DeleteEmailScenario extends BaseTestScenario {
await threadRobot.openEmailWithSubject(subject);
await $.pumpAndSettle();
await emailRobot.tapEmailDetailedMoreButton();
await emailRobot.tapEmailDetailedDeleteEmailButton();
await $.pumpAndSettle(duration: const Duration(seconds: 3));
await $.pumpAndSettle(duration: const Duration(seconds: 1));
_expectEmailViewInvisible();
@@ -52,7 +52,7 @@ class DisplayEmailAddressInfoDialogScenario extends BaseTestScenario {
Future<void> _expectEmailAddressInfoDialogVisible(
AppLocalizations appLocalizations,
) async {
await expectViewVisible($(appLocalizations.copy_email_address));
await expectViewVisible($(#copy_email_address));
await expectViewVisible($(appLocalizations.quickCreatingRule));
await expectViewVisible($(appLocalizations.compose_email));
}
@@ -1,6 +1,3 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../base/base_test_scenario.dart';
@@ -18,7 +15,6 @@ class MarkAsStarEmailScenario extends BaseTestScenario {
const emailUser = String.fromEnvironment('BASIC_AUTH_EMAIL');
final threadRobot = ThreadRobot($);
final emailRobot = EmailRobot($);
final imagePaths = ImagePaths();
await provisionEmail(
[
@@ -34,32 +30,29 @@ class MarkAsStarEmailScenario extends BaseTestScenario {
await threadRobot.openEmailWithSubject(subject);
await $.pumpAndSettle();
_expectEmailDetailedStarButtonVisible();
await emailRobot.tapEmailDetailedMoreButton();
await _expectEmailDetailedStarButtonVisible();
await emailRobot.tapEmailDetailedStarButton();
await $.pumpAndSettle(duration: const Duration(seconds: 2));
await _expectDisplayedStarIcon(imagePaths);
await $.pumpAndSettle(duration: const Duration(seconds: 1));
await emailRobot.tapEmailDetailedMoreButton();
await _expectDisplayedUnStarIcon();
await emailRobot.tapEmailDetailedStarButton();
await $.pumpAndSettle(duration: const Duration(seconds: 2));
await _expectDisplayedUnStarIcon(imagePaths);
await emailRobot.tapEmailDetailedUnstarButton();
await $.pumpAndSettle(duration: const Duration(seconds: 1));
await emailRobot.tapEmailDetailedMoreButton();
await _expectDisplayedStarIcon();
}
void _expectEmailDetailedStarButtonVisible() {
expect($(#email_detailed_star_button), findsOneWidget);
Future<void> _expectEmailDetailedStarButtonVisible() async {
await expectViewVisible($(#markAsStarred_action));
}
Future<void> _expectDisplayedStarIcon(ImagePaths imagePaths) async {
await expectViewVisible(
$(#email_detailed_star_button).which<TMailButtonWidget>(
(widget) => widget.icon == imagePaths.icStar),
);
Future<void> _expectDisplayedStarIcon() async {
await expectViewVisible($(#markAsStarred_action));
}
Future<void> _expectDisplayedUnStarIcon(ImagePaths imagePaths) async {
await expectViewVisible(
$(#email_detailed_star_button).which<TMailButtonWidget>(
(widget) => widget.icon == imagePaths.icUnStar),
);
Future<void> _expectDisplayedUnStarIcon() async {
await expectViewVisible($(#unMarkAsStarred_action));
}
}
@@ -38,6 +38,7 @@ class MoveEmailToFolderScenario extends BaseTestScenario {
await threadRobot.openEmailWithSubject(subject);
await $.pumpAndSettle();
await emailRobot.tapEmailDetailedMoreButton();
await emailRobot.tapEmailDetailedMoveEmailButton();
await $.pumpAndSettle(duration: const Duration(seconds: 2));
@@ -79,12 +79,15 @@ class ReplyEmailWithReplyToScenario extends BaseTestScenario {
}
Future<void> _expectToFieldContainReplyToEmailAddress() async {
expect(
$(RecipientComposerWidget).which<RecipientComposerWidget>((widget) =>
await expectViewVisible(
$(RecipientComposerWidget).which<RecipientComposerWidget>(
(widget) =>
widget.prefix == PrefixEmailAddress.to &&
isMatchingEmailList(widget.listEmailAddress, {'emma-reply-to@example.com'})
).visible,
isTrue,
isMatchingEmailList(
widget.listEmailAddress,
{'emma-reply-to@example.com'},
),
),
);
}
}
@@ -14,7 +14,7 @@ import '../robots/thread_robot.dart';
class ForwardEmailScenario extends BaseTestScenario {
static const String queryString = 'Fwd: Forward email to Bob';
static const String queryString = 'Forward email';
const ForwardEmailScenario(super.$);
@@ -21,7 +21,7 @@ class SaveAsTemplateScenario extends BaseTestScenario {
await _expectSaveToastSuccessVisible();
await composerRobot.addSubject('test subject');
await hideKeyboard();
await composerRobot.saveAsTemplate();
await _expectUpdateToastSuccessVisible();
@@ -42,12 +42,15 @@ class ThreadDetailReplyRealTimeUpdateScenario extends BaseTestScenario {
await composerRobot.grantContactPermission();
await composerRobot.addContent(replyContent);
await composerRobot.sendEmail(imagePaths);
await $.waitUntilVisible($(ThreadDetailCollapsedEmail));
expect(
$(ThreadDetailCollapsedEmail).which<ThreadDetailCollapsedEmail>((widget) {
await _expectNewCollapsedEmail(replyContent);
}
Future<void> _expectNewCollapsedEmail(String replyContent) async {
final collapsedEmail = $(ThreadDetailCollapsedEmail)
.which<ThreadDetailCollapsedEmail>((widget) {
return widget.preview.contains(replyContent);
}),
findsOneWidget,
);
});
await expectViewVisible(collapsedEmail);
}
}
@@ -18,6 +18,7 @@ class ContextMenuDialogView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: actions.map((menuAction) => ContextMenuDialogItem(
key: menuAction.key != null ? Key(menuAction.key!) : null,
menuAction: menuAction,
onContextMenuActionClick: onContextMenuActionClick,
)).toList(),
@@ -6,11 +6,12 @@ typedef OnContextMenuActionClick = void Function(ContextMenuItemAction action);
abstract class ContextMenuItemAction<T> with EquatableMixin {
final T action;
final String? key;
ContextMenuItemAction(this.action);
ContextMenuItemAction(this.action, {this.key});
@override
List<Object?> get props => [action];
List<Object?> get props => [action, key];
String get actionName;
@@ -33,7 +34,7 @@ mixin OptionalSelectedIcon<T> {
abstract class ContextMenuItemActionRequiredIcon<T>
extends ContextMenuItemAction<T> with OptionalIcon {
ContextMenuItemActionRequiredIcon(super.action);
ContextMenuItemActionRequiredIcon(super.action, {super.key});
}
abstract class ContextMenuItemActionRequiredSelectedIcon<T>
@@ -41,12 +42,12 @@ abstract class ContextMenuItemActionRequiredSelectedIcon<T>
final T? selectedAction;
ContextMenuItemActionRequiredSelectedIcon(super.action, this.selectedAction);
ContextMenuItemActionRequiredSelectedIcon(super.action, this.selectedAction, {super.key});
}
abstract class ContextMenuItemActionRequiredFull<T>
extends ContextMenuItemAction<T> with OptionalIcon, OptionalSelectedIcon<T> {
final T selectedAction;
ContextMenuItemActionRequiredFull(super.action, this.selectedAction);
ContextMenuItemActionRequiredFull(super.action, this.selectedAction, {super.key});
}
@@ -37,6 +37,7 @@ class EmailAddressWithCopyWidget extends StatelessWidget {
),
if (label.isNotEmpty)
TMailButtonWidget.fromIcon(
key: const ValueKey('copy_email_address'),
icon: copyLabelIcon,
backgroundColor: Colors.transparent,
iconSize: 20,
@@ -13,8 +13,9 @@ class ContextItemEmailAction
ContextItemEmailAction(
super.action,
this.appLocalizations,
this.imagePaths,
);
this.imagePaths, {
super.key,
});
@override
String get actionIcon => action.getIcon(imagePaths);
@@ -591,6 +591,7 @@ class EmailActionReactor {
action,
AppLocalizations.of(currentContext!),
imagePaths,
key: '${action.name}_action',
),
)
.toList(),
@@ -162,7 +162,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
);
Widget _getMoveEmailButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
key: const Key('email_detailed_move_email_button'),
icon: _imagePaths.icMoveEmail,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
@@ -177,7 +176,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
);
Widget _getMarkStarButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
key: const Key('email_detailed_star_button'),
icon: presentationEmail.hasStarred
? _imagePaths.icStar
: _imagePaths.icUnStar,
@@ -200,7 +198,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
);
Widget _getDeleteButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
key: const Key('email_detailed_delete_email_button'),
icon: _imagePaths.icDeleteComposer,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
@@ -20,6 +20,7 @@ extension HandleSelectMessageFilterExtension on ThreadController {
selectedOption,
AppLocalizations.of(context),
imagePaths,
key: '${filter.name}_filter',
);
}).toList();
@@ -12,8 +12,9 @@ class ContextItemFilterMessageOptionAction
super.action,
super.selectedAction,
this.appLocalizations,
this.imagePaths,
);
this.imagePaths, {
super.key,
});
@override
String get actionIcon => action.getContextMenuIcon(imagePaths);
-6
View File
@@ -794,12 +794,6 @@
"placeholders_order": [],
"placeholders": {}
},
"copy_email_address": "Copy email address",
"@copy_email_address": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"compose_email": "Compose email",
"@compose_email": {
"type": "text",
@@ -805,12 +805,6 @@ class AppLocalizations {
name: 'hide');
}
String get copy_email_address {
return Intl.message(
'Copy email address',
name: 'copy_email_address');
}
String get compose_email {
return Intl.message(
'Compose email',
@@ -13,7 +13,6 @@ User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15;
rv:131.0) Gecko/20100101 Firefox/131.0
Content-Type: multipart/alternative;
boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-"
List-Post: <mailto:emma-reply-to-list@example.com>
---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-
Content-Type: text/plain; charset=UTF-8
@@ -2,7 +2,7 @@
# Define users and folders
users=("alice" "bob" "brian" "charlotte" "david" "emma")
bobFolders=("Search Emails" "Forward Emails" "Disposition" "MailBase64" "Calendar" "Reply Emails" "Reply Emails")
bobFolders=("Search Emails" "Forward Emails" "Disposition" "MailBase64" "Calendar" "Reply Emails")
# Add users
for user in "${users[@]}"; do