TF-2953 Set up Patrol tests
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import 'package:patrol/patrol.dart';
|
||||
|
||||
abstract class BaseScenario {
|
||||
final PatrolIntegrationTester $;
|
||||
|
||||
const BaseScenario(this.$);
|
||||
|
||||
Future<void> execute();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user