Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup patrol integration tests #2196

Merged
merged 10 commits into from
Jan 13, 2025
6 changes: 6 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ android {
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
testInstrumentationRunner "pl.leancode.patrol.PatrolJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: "true"
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
}

signingConfigs {
Expand All @@ -75,6 +77,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
testOptions {
execution "ANDROIDX_TEST_ORCHESTRATOR"
}
}

flutter {
Expand All @@ -88,6 +93,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.multidex:multidex:2.0.1'
androidTestUtil "androidx.test:orchestrator:1.5.1"
}

apply plugin: 'com.google.gms.google-services'
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package app.twake.android.chat;

import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import pl.leancode.patrol.PatrolJUnitRunner;

@RunWith(Parameterized.class)
public class MainActivityTest {
@Parameters(name = "{0}")
public static Object[] testCases() {
PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
// replace "MainActivity.class" with "io.flutter.embedding.android.FlutterActivity.class"
// if in AndroidManifest.xml in manifest/application/activity you have
// android:name="io.flutter.embedding.android.FlutterActivity"
instrumentation.setUp(MainActivity.class);
instrumentation.waitForPatrolAppService();
return instrumentation.listDartTests();
}

public MainActivityTest(String dartTestName) {
this.dartTestName = dartTestName;
}

private final String dartTestName;

@Test
public void runDartTest() {
PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
instrumentation.runDartTest(dartTestName);
}
}
15 changes: 15 additions & 0 deletions integration_test/base/base_scenario.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';

abstract class BaseScenario {
final PatrolIntegrationTester $;

const BaseScenario(this.$);

Future<void> execute();

Future<void> expectViewVisible(PatrolFinder patrolFinder) async {
await $.waitUntilVisible(patrolFinder);
expect(patrolFinder, findsWidgets);
}
}
9 changes: 9 additions & 0 deletions integration_test/base/core_robot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:patrol/patrol.dart';

abstract class CoreRobot {
final PatrolIntegrationTester $;

CoreRobot(this.$);

dynamic ignoreException() => $.tester.takeException();
}
35 changes: 35 additions & 0 deletions integration_test/base/test_base.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
import 'package:fluffychat/main.dart' as app;

class TestBase {
void runPatrolTest({
required String description,
required Function(PatrolIntegrationTester $) test,
}) {
patrolTest(description,
config: const PatrolTesterConfig(
printLogs: true,
visibleTimeout: Duration(minutes: 1),
),
nativeAutomatorConfig: const NativeAutomatorConfig(
connectionTimeout: Duration(minutes: 1, seconds: 10),
findTimeout: Duration(seconds: 60),
keyboardBehavior: KeyboardBehavior.alternative,
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
),
framePolicy: LiveTestWidgetsFlutterBindingFramePolicy.fullyLive,
($) async {
await initTwakeChat();
final originalOnError = FlutterError.onError!;
FlutterError.onError = (FlutterErrorDetails details) {
originalOnError(details);
};
await test($);
});
}

Future<void> initTwakeChat() async {
app.main();
}
}
53 changes: 53 additions & 0 deletions integration_test/robots/login_robot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
import 'package:patrol/patrol.dart';

import '../base/core_robot.dart';

class LoginRobot extends CoreRobot {
LoginRobot(super.$);

Future<void> grantNotificationPermission(
NativeAutomator nativeAutomator,
) async {
if (await nativeAutomator.isPermissionDialogVisible(
timeout: const Duration(seconds: 15),
)) {
await nativeAutomator.grantPermissionWhenInUse();
}
}

Future<void> tapOnUseYourCompanyServer() async {
await $('Use your company server').tap();
}

Future<void> enterServerUrl(String serverUrl) async {
await $.enterText($(HomeserverTextField), serverUrl);
}

Future<void> confirmServerUrl() async {
await $.tap($('Continue'));
}

Future<void> enterUsernameSsoLogin(String username) async {
await $.native.enterText(
Selector(resourceId: 'login'),
text: username,
);
}

Future<void> enterPasswordSsoLogin(String password) async {
await $.native.enterText(
Selector(resourceId: 'password'),
text: password,
);
}

Future<void> pressSignInSsoLogin() async {
await $.native.tap(
Selector(
text: 'Sign in',
instance: 1,
),
);
}
}
42 changes: 42 additions & 0 deletions integration_test/scenarios/login_scenario.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
import 'package:fluffychat/pages/twake_welcome/twake_welcome.dart';
import 'package:flutter_test/flutter_test.dart';
import '../base/base_scenario.dart';
import '../robots/login_robot.dart';

class LoginScenario extends BaseScenario {
final String username;

final String serverUrl;

final String password;

LoginScenario(
super.$, {
required this.username,
required this.serverUrl,
required this.password,
});
@override
Future<void> execute() async {
final loginRobot = LoginRobot($);
await $.waitUntilVisible($(TwakeWelcome));
await expectViewVisible($(TwakeWelcome));
await loginRobot.tapOnUseYourCompanyServer();
await $.waitUntilVisible(
$(HomeserverPickerView),
);
await loginRobot.enterServerUrl(serverUrl);
await loginRobot.confirmServerUrl();

await loginRobot.enterUsernameSsoLogin(username);
await loginRobot.enterPasswordSsoLogin(password);
await loginRobot.pressSignInSsoLogin();
await $.waitUntilVisible(
$(HomeserverPickerView),
);
await loginRobot.grantNotificationPermission($.nativeAutomator);
await expectViewVisible($(ChatList));
}
}
18 changes: 18 additions & 0 deletions integration_test/tests/login/login_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import '../../base/test_base.dart';
import '../../scenarios/login_scenario.dart';

void main() {
TestBase().runPatrolTest(
description: 'Should see chat list after successful login',
test: ($) async {
final loginScenario = LoginScenario(
$,
username: const String.fromEnvironment('USERNAME'),
serverUrl: const String.fromEnvironment('SERVER_URL'),
password: const String.fromEnvironment('PASSWORD'),
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
);

await loginScenario.execute();
},
);
}
36 changes: 34 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
dispose_scope:
dependency: transitive
description:
name: dispose_scope
sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
dynamic_color:
dependency: "direct main"
description:
Expand Down Expand Up @@ -2172,6 +2180,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.1"
patrol:
dependency: "direct dev"
description:
name: patrol
sha256: "7e7c346890fa234af948fbdf024e09e80142deef7aef572dead1001446ca853c"
url: "https://pub.dev"
source: hosted
version: "3.13.1"
patrol_finders:
dependency: transitive
description:
name: patrol_finders
sha256: "6dfa07aa951f1d769f0a736f4fd4eb63d99ef32931aaaa39e0d24126ddfe9cd9"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
patrol_log:
dependency: transitive
description:
name: patrol_log
sha256: fb67013a5305cfd30a374cce3c5ea5a99613335d8cf31c250ecb0dda75deec21
url: "https://pub.dev"
source: hosted
version: "0.2.1"
permission_handler:
dependency: "direct main"
description:
Expand Down Expand Up @@ -3401,5 +3433,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.0-259.0.dev <4.0.0"
flutter: ">=3.19.3"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 7 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ dev_dependencies:
msix: ^3.6.2
translations_cleaner: ^0.0.5
mockito: 5.4.4
patrol: 3.13.1


flutter_launcher_icons:
Expand Down Expand Up @@ -325,4 +326,9 @@ dependency_overrides:

cider:
link_template:
tag: https://github.com/linagora/twake-on-matrix/releases/tag/%tag% # initial release link template
tag: https://github.com/linagora/twake-on-matrix/releases/tag/%tag% # initial release link template

patrol:
app_name: Twake Chat
android:
package_name: app.twake.android.chat
Loading