Process.run() on local machine during Flutter integration test - flutter

I write some integration tests for my Flutter application and I need to execute some shell commands on my local machine during testing.
I know that I can execute these commands with Process.run(), but during the integration tests this command is executed on my android phone instead of my local machine.
Is there any way to run a command on my local machine during my integration test?

Somehow with flutter driver it works running processes on your test machine.
In my case it is opening a deep link while running flutter tests. then you get the context.
flutter drive \
--driver=test_driver/driver_tests/your_test.dart \
--target=test_driver/your_driver.dart
your_driver.dart:
import 'package:flutter_driver/driver_extension.dart';
import 'package:arriba/main.dart' as app;
void main() {
enableFlutterDriverExtension();
app.main();
}
your_test.dart:
import 'dart:io';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
//!! DOES NOT RUN WITHOUT FLUTTER DRIVER MAIN !!
void main() {
late FlutterDriver driver;
const String shareLink = 'your_link.html';
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
await driver.close();
});
test(
'starting deep link test',
() async {
driver.waitUntilFirstFrameRasterized();
await Process.run(
'adb',
[
'-d',
'shell',
'am',
'start',
'-a android.intent.action.VIEW',
'-c android.intent.category.BROWSABLE',
'-d $shareLink',
'YOUR PACKAGE NAME',
],
).then((result) {
stdout.write(result.stdout);
stderr.write(result.stderr);
});
},
timeout: const Timeout(Duration(seconds: 60)),
);
}

Related

Flutter Web - Firestore Security Rules Unit Tests

So I have an web app written with flutter, and recently I added security rules on Firestore DB, but I cannot find any documentation on how to test these security rules with flutter test.
I also want to do this test against the Firebase Emulator, which I setup.
I found lots of examples to test it with node and npm, like HERE, but I don't use JS, I wrote everything within Flutter.
I have tried different scenarios which mostly failed:
Mocking Firebase.initializeApp() with this solution but since it's mocked I cannot use any implementation for FirebaseFirestore.instance or FirebaseAuth.instance.
Sample code:
Future<void> main() async {
setupFirebaseAuthMocks();
await Firebase.initializeApp();
final FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
firebaseFirestore.useFirestoreEmulator(
EMULATOR_HOST,
FIREBASE_PORT,
sslEnabled: false,
);
final CollectionReference colUserRef =
firebaseFirestore.collection('users');
BaseUser user = BaseUser(
uid: 'uid',
email: 'email',
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
);
test('Check if', () async {
await firebaseFirestore.collection('users').doc().set(user.toJson());
print('works?');
});
}
Error I got:
package:flutter/src/services/platform_channel.dart 294:7 MethodChannel._invokeMethod
MissingPluginException(No implementation found for method DocumentReference#set on channel plugins.flutter.io/firebase_firestore)
I tried to use FakeFirebaseFirestore() but this class doesn't have useFirestoreEmulator()
Sample code:
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
...
FirestoreService firestoreService = FirestoreService.mock(firestoreInstance: FakeFirebaseFirestore());
firestoreService.firebaseFirestore.useFirestoreEmulator('127.0.0.1', 8081);
...
Error I got:
Class 'FakeFirebaseFirestore' has no instance method 'useFirestoreEmulator' with matching arguments.
Receiver: Instance of 'FakeFirebaseFirestore'
Solution that I found to finally work!
I just created a fakeApp() which I use with the integration tests that Flutter Web has HERE. Since I have to initialize Firebase and my requirement is to use Firebase Emulator within a pipeline.
Sample Code:
// Simple and bare bone flutter app
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(context) => const Center(
child: Text('Firebase Security Rules Test!'));
}
Future<void> fakeApp() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instanceFor(app: Firebase.app());
runApp(const MyApp());
}
// Tests implementation
Future<void> main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
late FirebaseFirestore firebaseFirestore;
late FirebaseAuth firebaseAuth;
late CollectionReference colUserRef;
late BaseUser user;
setUpAll(() async {
await fakeApp();
firebaseFirestore = FirebaseFirestore.instance;
firebaseAuth = FirebaseAuth.instance;
firebaseAuth.useAuthEmulator(
EMULATOR_HOST,
FIREBASE_AUTH_PORT,
);
firebaseFirestore.useFirestoreEmulator(
EMULATOR_HOST,
FIREBASE_PORT,
sslEnabled: false,
);
colUserRef = firebaseFirestore.collection('users');
user = BaseUser(
uid: '1234567890',
email: 'email#email.com',
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
);
});
group('Firestore Security Rules Tests -', () {
group('/users/* collection Tests-', () {
group('Unauthenticated Tests -', () {
/*
* Test if Unauthenticated User can list all users collection from firestore
* Should give permission-denied
* Rule LIST
*/
testWidgets('U: list users/*', (WidgetTester tester) async {
late String eCode;
try {
await colUserRef.get();
eCode = 'allowed';
} on FirebaseException catch (e) {
eCode = e.code;
}
expect(eCode, 'permission-denied');
});
});
});
});
}
Flutter Drive command that I use:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/sec_rules_test.dart \
--device-id web-server \
--dart-define=PROJECT_ID=someProjectId
Also by using IntegrationTestWidgetsFlutterBinding.ensureInitialized() I make sure:
I get my tests status logs
I get the fakeApp() to be close automatically when the tests finish.
If I don't use IntegrationTestWidgetsFlutterBinding, and use with web-server I get no logs.
If I don't use IntegrationTestWidgetsFlutterBinding, and use with chrome I get logs but the tests are executed twice.
If I don't use flutter drive for my tests, and use flutter test, I get Web devices are not supported for integration tests yet.
So basically I use flutter web integration tests for this to work.
What do you think about this approach? Should I go back and use a more mature security rules unit test with node? Do I have other possibilities to unit test my security rules?
I was thinking also to test these rules with restAPI calls from here and here, I think these are not within admin sdk context so they behave the same.
Do you see, maybe, something that I don't see within the possibility of these unit tests?
Anyhow, hope this helps other in my situation.

Can't load asset on flutter integration test

I am writing a integration test in my flutter app, it works if I run app. But when I run integration test class, it seems failed to load json file from asset. This is my integration test file
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('login test', () {
testWidgets('full login test', (tester) async {
app.mainCommon("dev");
await tester.pumpAndSettle();
});
});
}
and this is how i load json file
configString = await rootBundle.loadString('config/dev.json');

Flutter Integration Screenshot not working for Android

I am trying to capture screenshots using integration_test but the
await binding.takeScreenshot('${platform}-1');
Hangs for Android with a message
VMServiceFlutterDriver: request_data message is taking a long time to complete...
The app is the Flutter default app with
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'dart:io';
import 'package:flutter_stam/main.dart' as app;
void main() {
final IntegrationTestWidgetsFlutterBinding binding = IntegrationTestWidgetsFlutterBinding();
group('Screenshot', () {
testWidgets('Screenshot-1', (WidgetTester tester) async {
String platform;
if (Platform.isAndroid) {
platform = 'android';
} else if (Platform.isIOS) {
platform = 'ios';
} else {
platform = Platform.operatingSystem;
}
app.main();
await tester.pumpAndSettle();
// Verify the counter starts at 0.
expect(find.text('0'), findsOneWidget);
// Finds the floating action button to tap on.
final Finder fab = find.byTooltip('Increment');
// Emulate a tap on the floating action button.
await tester.tap(fab);
// Trigger a frame.
await tester.pumpAndSettle();
await binding.convertFlutterSurfaceToImage();
await binding.takeScreenshot('${platform}-screenshot-1');
// Verify the counter increments by 1.
expect(find.text('1'), findsOneWidget);
});
});
}
script
#!/bin/zsh
flutter drive \
--driver=integration_test/driver.dart \
--target=integration_test/app_test.dart \
-d "iPhone 13 Pro Max"
flutter drive \
--driver=integration_test/driver.dart \
--target=integration_test/app_test.dart \
-d "emulator-5554"
Driver
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';
Future<void> main() async {
try {
await integrationDriver(
onScreenshot: (String screenshotName, List<int> screenshotBytes) async {
final File image = await File('screenshots/$screenshotName.png')
.create(recursive: true);
image.writeAsBytesSync(screenshotBytes);
return true;
},
);
} catch (e) {
print('An error occurred: $e');
}
}
For iOS, I also had an issue but I managed to solve it
I was getting an error
The following MissingPluginException was thrown running a test:
MissingPluginException(No implementation found for method
captureScreenshot on channel plugins.flutter.io/integration_test)
I changed IntegrationTestPlugin.m
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
[[IntegrationTestPlugin instance] setupChannels:registrar.messenger];
}
Adding an additional tester.pumpAndSettle(); after convertFlutterSurfaceToImage fixed the problem
await tester.pumpAndSettle();
await binding.convertFlutterSurfaceToImage();
await tester.pumpAndSettle();

Run mutliple integration tests in Flutter

I have two integrations test files: login_test.dart and homepage_test.dart (each including single test case with 1 testWidgets method)
I'm using command line to run both tests:
flutter test integration_test/
But the problem is when the test has run and passed login_test . it completely reinstalled my app so second test will fail because have no login credentials (stuck at login screen -> failed)
Any idea how I can solve this ?
Appreciate your helps.
login_test.dart
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end to end tests', () {
testWidgets('test login', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
final Finder textFieldFinder = find.byType(TextFormField);
expect(textFieldFinder, findsNWidgets(2));
final userName = textFieldFinder.first;
final password = textFieldFinder.last;
await tester.enterText(userName, 'myusername');
await tester.enterText(password, 'mypassword');
expect(find.text('Login'), findsOneWidget);
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
expect(find.byKey(Key('createTicketBtn')), findsOneWidget);
});
});
}
homepage_test.dart
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end to end tests', () {
testWidgets('test homepage', (tester) async {
app.main();
await tester.pumpAndSettle();
expect(find.byKey(Key('createTicketBtn')), findsOneWidget);
});
});
}
You can use this package to scan a folder inside the integration_test directory for files named like *_test.dart. This will produce a file with a new main importing all test files, so you can just run this new generated file.
You can also create manually a file like this and launch it:
import homepage_test.dart as homepage;
import login_test.dart as login;
void main() {
homepage.main();
login.main();
}
In any case, each test will start from the beginning so you need to login each time. The app will not lose its data though, unless you reset its state in the tearDown.

FlutterDriver.Connect Requires VM_SERVICE_URL or String?

My app is the basic counter, with a FlutterDriver for UI Automation. My conundrum is when I attempt to run my test, it tells me that I need to specify a connection or set the VM_SERVICE_URL
ERROR:
DriverError: Could not determine URL to connect to application. Either
the VM_SERVICE_URL environment variable should be set, or an explicit
URL should be provided to the FlutterDriver.connect() method.
I've tried a few things.
Using FlutterDriver.connect();
Setting the VM_SERVICE_URL in Terminal (MacOS)
Setting the Dart Command Line to include VM_SERVICE_URL with a value
The most success I've had is with the code below. By adding enableFlutterDriverExtension to the lib/main.dart, then executing lib/main.dart, I can copy/paste the ws://127.0.0.1 connection into the test/my_test.dart. This allows me to successfully run my tests, but this isn't an ideal process.
Is there a way to pull in the connection string automatically?
Why does Platform.environment['VM_SERVICE_URL'] always return null despite my having set it?
lib/main.dart
void main() {
enableFlutterDriverExtension();
runApp(const MyApp());
}
test/main_app.dart
void main() {
// enableFlutterDriverExtension();
MainApp.main();
MyTest.main();
}
test/my_test.dart
void main() {
FlutterDriver? driver;
dynamic DartVmServiceUrl;
DartVmServiceUrl ??= Platform.environment['VM_SERVICE_URL'];
print('VM_SERVICE_URL:\t${DartVmServiceUrl}');
String vmServURL = 'ws://127.0.0.1:59488/WK8KTNVXXOo=/ws';
setUpAll( () async {
driver = await FlutterDriver.connect(dartVmServiceUrl: vmServURL);
});
tearDownAll( () {
driver?.close();
});
test('Push Button',() async {
var pushMeButton = find.byValueKey('IncrementButton');
await driver!.tap(pushMeButton);
} );
}
you have to move the files in the specific folders you see below, then try to run from terminal with
flutter drive \
--driver=test/my_test.dart \
--target=test_driver/test_driver.dart
in your lib/main.dart you don't need enableFlutterDriverExtension(); because it is already linked to your main() in the test_driver.dart
also your main in test_driver/test_driver.dart should look like this:
import 'package:{here}/main.dart' as app; // insert here your main app
import 'package:flutter_driver/driver_extension.dart';
void main() {
enableFlutterDriverExtension();
app.main();
}
your my_test.dart should look like this:
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
late FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() {
driver.close();
});
test('check flutter driver health', () async {
Health health = await driver.checkHealth();
print(health.status);
});
}
give attention to use the correct packages to avoid this error.
Error: Not found: 'dart:ui'
import 'dart:ui';