TF-2953 Set up Patrol tests

This commit is contained in:
DatDang
2024-10-11 11:58:09 +07:00
committed by Dat H. Pham
parent e8e709d63a
commit 25437ba92e
18 changed files with 437 additions and 18 deletions
+9
View File
@@ -0,0 +1,9 @@
import 'package:patrol/patrol.dart';
abstract class BaseScenario {
final PatrolIntegrationTester $;
const BaseScenario(this.$);
Future<void> execute();
}
+15
View File
@@ -0,0 +1,15 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
abstract class CoreRobot {
final PatrolIntegrationTester $;
CoreRobot(this.$);
Future<void> ensureViewVisible(PatrolFinder patrolFinder) async {
await $.waitUntilVisible(patrolFinder);
expect(patrolFinder, findsWidgets);
}
dynamic ignoreException() => $.tester.takeException();
}
+31
View File
@@ -0,0 +1,31 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
import 'package:tmail_ui_user/main.dart' as app;
class TestBase {
void runPatrolTest({
required String description,
required Function(PatrolIntegrationTester $) test,
}) {
patrolTest(
description,
config: const PatrolTesterConfig(
settlePolicy: SettlePolicy.trySettle,
visibleTimeout: Duration(minutes: 1)),
nativeAutomatorConfig: const NativeAutomatorConfig(
findTimeout: Duration(seconds: 10),
),
framePolicy: LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive,
($) async {
await app.runTmail();
// https://github.com/leancodepl/patrol/issues/1602#issuecomment-1665317814
final originalOnError = FlutterError.onError!;
FlutterError.onError = (FlutterErrorDetails details) {
originalOnError(details);
};
await test($);
});
}
}
@@ -0,0 +1,62 @@
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 'package:model/email/prefix_email_address.dart';
import 'package:rich_text_composer/rich_text_composer.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';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/app_bar_composer_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_suggestion_item_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart';
import '../base/core_robot.dart';
class ComposerRobot extends CoreRobot {
ComposerRobot(super.$);
Future<void> addRecipient(String email) async {
await $(RecipientComposerWidget)
.which<RecipientComposerWidget>((widget) => widget.prefix == PrefixEmailAddress.to)
.enterText(email);
await $(RecipientSuggestionItemWidget)
.which<RecipientSuggestionItemWidget>((widget) => widget.emailAddress.email?.contains(email) ?? false)
.tap();
}
Future<void> addSubject(String subject) async {
await $(SubjectComposerWidget).enterText(subject);
}
Future<void> addContent(String content) async {
ComposerController? composerController;
await $(ComposerView)
.which<ComposerView>((widget) {
composerController = widget.controller;
return true;
})
.$(MobileEditorView).$(HtmlEditor).$(InAppWebView).tap();
await composerController?.htmlEditorApi?.requestFocusLastChild();
await composerController!.htmlEditorApi!.insertHtml('$content <br><br>');
}
Future<void> sendEmail() async {
await $(AppBarComposerWidget)
.$(TMailButtonWidget)
.which<TMailButtonWidget>((widget) => widget.icon == ImagePaths().icSendMobile)
.tap();
}
Future<void> expectSendEmailSuccessToast() async {
expect($('Message has been sent successfully'), findsOneWidget);
}
Future<void> grantContactPermission() async {
if (await $.native.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
await $.native.grantPermissionWhenInUse();
}
}
}
+43
View File
@@ -0,0 +1,43 @@
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart';
import 'package:tmail_ui_user/features/login/presentation/login_view.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.dart';
import '../base/core_robot.dart';
class LoginRobot extends CoreRobot {
LoginRobot(super.$);
Future<void> expectLoginViewVisible() => ensureViewVisible($(LoginView));
Future<void> enterEmail(String email) async {
final finder = $(LoginView).$(TextField);
await finder.enterText(email);
await $('Next').tap();
}
Future<void> enterHostUrl(String url) async {
final finder = $(LoginView).$(TextField);
await finder.enterText(url);
await $('Next').tap();
}
Future<void> enterBasicAuthEmail(String email) async {
await $(LoginView)
.$(TypeAheadFormFieldBuilder<RecentLoginUsername>)
.$(TextField)
.enterText(email);
}
Future<void> enterBasicAuthPassword(String password) async {
await $(LoginView)
.$(LoginTextInputBuilder)
.$(TextField)
.enterText(password);
}
Future<void> loginBasicAuth() async {
await $(Container).$(ElevatedButton).tap();
}
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
import '../base/core_robot.dart';
class ThreadRobot extends CoreRobot {
ThreadRobot(super.$);
Future<void> expectThreadViewVisible() => ensureViewVisible($(ThreadView));
Future<void> openComposer() async {
await $(ComposeFloatingButton).$(InkWell).tap();
}
Future<void> expectComposerViewVisible() => ensureViewVisible($(ComposerView));
}
@@ -0,0 +1,39 @@
import '../base/base_scenario.dart';
import '../robots/login_robot.dart';
import '../robots/thread_robot.dart';
import '../utils/scenario_utils_mixin.dart';
class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin {
const LoginWithBasicAuthScenario(
super.$,
{
required this.username,
required this.hostUrl,
required this.email,
required this.password,
}
);
final String username;
final String hostUrl;
final String email;
final String password;
@override
Future<void> execute() async {
final loginRobot = LoginRobot($);
final threadRobot = ThreadRobot($);
await loginRobot.expectLoginViewVisible();
await loginRobot.enterEmail(username);
await loginRobot.enterHostUrl(hostUrl);
await loginRobot.enterBasicAuthEmail(email);
await loginRobot.enterBasicAuthPassword(password);
await loginRobot.loginBasicAuth();
await grantNotificationPermission($.native);
await threadRobot.expectThreadViewVisible();
}
}
@@ -0,0 +1,41 @@
import '../base/base_scenario.dart';
import '../robots/composer_robot.dart';
import '../robots/thread_robot.dart';
import 'login_with_basic_auth_scenario.dart';
class SendEmailScenario extends BaseScenario {
const SendEmailScenario(
super.$,
{
required this.loginWithBasicAuthScenario,
required this.additionalRecipient,
required this.subject,
required this.content
}
);
final LoginWithBasicAuthScenario loginWithBasicAuthScenario;
final String additionalRecipient;
final String subject;
final String content;
@override
Future<void> execute() async {
final threadRobot = ThreadRobot($);
final composerRobot = ComposerRobot($);
await loginWithBasicAuthScenario.execute();
await threadRobot.openComposer();
await threadRobot.expectComposerViewVisible();
await composerRobot.grantContactPermission();
await composerRobot.addRecipient(loginWithBasicAuthScenario.email);
await composerRobot.addRecipient(additionalRecipient);
await composerRobot.addSubject(subject);
await composerRobot.addContent(content);
await composerRobot.sendEmail();
await composerRobot.expectSendEmailSuccessToast();
}
}
@@ -0,0 +1,24 @@
import '../../base/test_base.dart';
import '../../scenarios/login_with_basic_auth_scenario.dart';
import '../../scenarios/send_email_scenario.dart';
void main() {
TestBase().runPatrolTest(
description: 'Should see success toast when send email successfully',
test: ($) async {
final loginWithBasicAuthScenario = LoginWithBasicAuthScenario($,
username: const String.fromEnvironment('USERNAME'),
hostUrl: const String.fromEnvironment('BASIC_AUTH_URL'),
email: const String.fromEnvironment('BASIC_AUTH_EMAIL'),
password: const String.fromEnvironment('PASSWORD'),
);
final sendEmailScenario = SendEmailScenario($,
loginWithBasicAuthScenario: loginWithBasicAuthScenario,
additionalRecipient: const String.fromEnvironment('ADDITIONAL_MAIL_RECIPIENT'),
subject: 'Test subject',
content: 'Test content');
await sendEmailScenario.execute();
},
);
}
@@ -0,0 +1,18 @@
import '../../base/test_base.dart';
import '../../scenarios/login_with_basic_auth_scenario.dart';
void main() {
TestBase().runPatrolTest(
description: 'Should see thread view when login with basic auth successfully',
test: ($) async {
final loginWithBasicAuthScenario = LoginWithBasicAuthScenario($,
username: const String.fromEnvironment('USERNAME'),
hostUrl: const String.fromEnvironment('BASIC_AUTH_URL'),
email: const String.fromEnvironment('BASIC_AUTH_EMAIL'),
password: const String.fromEnvironment('PASSWORD'),
);
await loginWithBasicAuthScenario.execute();
}
);
}
@@ -0,0 +1,9 @@
import 'package:patrol/patrol.dart';
mixin ScenarioUtilsMixin {
Future<void> grantNotificationPermission(NativeAutomator nativeAutomator) async {
if (await nativeAutomator.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
await nativeAutomator.grantPermissionWhenInUse();
}
}
}