Flutter Integration Screenshot not working for Android - flutter

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();

Related

Record Audio and upload file to firebase storage Flutter Web

I am using the flutter_sound package to record and play audio. On Flutter Web, on stopping the recording the recorder returns a path/URL of this type: blob:http://localhost:63986/b60f31ce-b94d-48c8-8a4a-2d939effe6d8
I want to upload the audio recording to Firebase Storage but dart.io can't be used for flutter-web so can't use the File method. Even after searching, I didn't find a way to achieve it. I don't know how to proceed. How can I write the audio to file and upload it to firebase?
My Code:
import 'dart:html' as html;
import 'dart:io' as io;
final recorder = FlutterSoundRecorder();
final player = FlutterSoundPlayer();
String fileName;
#override
void initState() {
super.initState();
initRecorder();
}
#override
void dispose() {
recorder.closeRecorder();
player.closePlayer();
super.dispose();
}
Future<void> initRecorder() async {
if (!kIsWeb) {
final status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Grant Permission form mic first!')),
);
}
}
await recorder.openRecorder();
await player.openPlayer();
recorder.setSubscriptionDuration(Duration(milliseconds: 500));
}
Future<void> record() async {
fileName = DateTime.now().toString();
await recorder.startRecorder(toFile: fileName);
}
Future<void> stop() async {
path = await recorder.stopRecorder();
if (kIsWeb) {
if (path == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Grant Permission for mic first!')),
);
} else {
// Get File from path and upload it to Firebase
print(path);
// not working for Web
// final audioFile = io.File(path);
// html.File() doesn't take path/Url as parameter but
// File(List<Object> fileBits, String fileName,[Map? options])
/*
await FirebaseStorage.instance
.ref()
.child('users/uploads/$fileName.mp3')
.putData(file!.bytes!);*/
}
} else if (!kIsWeb) {
if (path == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Grant Permission for mic first!')),
);
} else {
//final audioFile = io.File(path);
// await FirebaseStorage.instance
// .ref()
// .child('users/uploads/$fileName.mp3')
// .putFile(audioFile);
}
}
}
I've been working on this for days, and I finally figured it out!
When you call startRecorder(toFile: audioLocation), audioLocation will be the location of the file after you call stopRecorder(), stored in html.window.sessionStorage (html as in import 'dart:html' as html;).
So you need to add import 'package:http/http.dart' as http;, create a fileName (whatever you want the file to be called in Firebase Storage), and then insert the following piece of code.
var ref = await storage.ref().child('path/to/file/$fileName');
Uri blobUri = Uri.parse(html.window.sessionStorage[audioFile]!);
http.Response response = await http.get(blobUri);
await ref.putData(response.bodyBytes, SettableMetadata(contentType: 'video/mp4'));
This might not be the best way, but it is the way I finally got it to work! Good luck!!

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.

Flutter integration test raising an error "null check operator used on a null value"

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:integration_test/integration_test.dart';
import 'package:knights_bridge/main.dart' as app;
import 'dart:io';
import 'package:knights_bridge/screens/shared/bigButtonFilled.dart';
void main() {
group('Sign in test', () {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Validate sign in and dashboard', (tester) async {
app.main();
await tester.pumpAndSettle();
final emailField = find.byKey(Key('login'));
final passwordField = find.byKey(Key('password'));
final signInButton = find.text('Sign in');
// final signInButton = find.byType(BigFilledButton);
print("Starting typing in email field");
await tester.enterText(emailField, "ashiq#gmail.com");
print("Starting typing in password field");
await tester.enterText(passwordField, "123456789As#");
await tester.pumpAndSettle();
print("Clicking on sign in button");
await tester.tap(signInButton);
await tester.pumpAndSettle();
final signInMessage = find.text("Login successful");
print("Started verifying the message for successful login.");
await tester.ensureVisible(signInMessage);
await tester.pumpAndSettle(Duration(seconds: 4));
print("Successfully the success message in dashboard.");
});
});
}
Here is the error screenshot:
When I'm executing this code it is running the automation but giving an error and test is failing.
There are no such error raising while I'm running this app manually only raising when executing integration test.
Please check and tell me what's could be the solution for this.
Thanks in advance.
Looks like the issue comes from your login page line 38 (from looking at your error stack trace)
The error is caused by you using a null-check-operator ! on a value which was null. You should try and check for a null value before instead of using the !.
e.g.
if(possiblyNullValue == null){
do something
} else {
do the thing you wanted with possiblyNullValue
}
The compiler will help you in this case as your code should no longer need the ! operator if you check for the possible null. (should show a yellow line under your ! telling you it isn't needed anymore)

Flutter Integration testing failed for multiple test cases in a single file

I have a simple login page with email and password text field. A button to login and another to sign up. I tried to write integration testing for the Sign in page.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('''could type email and password in text filed''',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final textFieldEmail = find.byType(InputTextWidget).first;
final textFieldPassword = find.byType(InputTextWidget).last;
await tester.enterText(textFieldEmail, "asis.adh#gmail.com");
await tester.pumpAndSettle();
expect(find.text("asis.adh#gmail.com"), findsOneWidget);
});
testWidgets(
'should redirect to Sign Up Page when create an account is tapped',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final createAnAccount = find.text("Create an account");
await tester.tap(createAnAccount);
await tester.pumpAndSettle();
expect(find.byType(SignupPage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
}
When I execute the test case, it fails with the following error:
The following ArgumentError was thrown running a test: Invalid
argument(s): Object/factory with type AmenitiesProvider is already
registered inside GetIt.
Here is my main.dart file
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
configureInjection(Environment.prod);
/// for registering the factory.
await Future.delayed(const Duration(seconds: 2));
runApp(RoopaApp());
}
I tried with a main_test.dart and adding configureInjection(Environment.test) but nothing changes. I am not sure how to fix the error. Is there a way to clean the app or destroy it before going to new test case. If I combine the both testcase into one then it works without any problem.
Here is configureInjection
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I am using get_it and injection package for dependency injection.
Here is the auto generated initGetIt
GetIt $initGetIt(
GetIt get, {
String environment,
EnvironmentFilter environmentFilter,
}) {
final gh = GetItHelper(get, environment, environmentFilter);
final httpClientInjectableModule = _$HttpClientInjectableModule();
final flutterStorageModule = _$FlutterStorageModule();
gh.lazySingleton<AmenitiesProvider>(() => AmenitiesProvider());
gh.factory<Client>(() => httpClientInjectableModule.client);
gh.lazySingleton<FileProvider>(() => FileProvider());
gh.lazySingleton<FlutterSecureStorage>(
() => flutterStorageModule.secureStorate);
gh.factory<ProfilePageBloc>(() => ProfilePageBloc());
gh.factory<SplashScreenBloc>(() => SplashScreenBloc());
gh.lazySingleton<AuthLocalDataSourceProtocol>(
() => AuthLocalDataSource(secureStorage: get<FlutterSecureStorage>()));
gh.lazySingleton<AuthRemoteDataSourceProtocol>(
() => AuthRemoteDataSource(client: get<Client>()));
return get;
}
In my config page.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I just created a test environment and added following like, now its working as expected.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
if (environment == Environment.test) {
getIt.allowReassignment = true;
}
}
tearDown(() async {
final getIt = GetIt.instance;
await getIt.reset();
});

Flutter widget test - wait Future completion

I have a widget test in flutter that pumps the widget under test,
and when it opens, performs a background operation (staticWrapper.requestPermission()) which returns a Future and, based on its result, set a state.
The problem is that the test is not waiting this future to complete, test code:
/// When micro permission denied, should show error message.
testWidgets('When micro permission denied, should show error message.',
(WidgetTester tester) async {
when(staticWrapper.requestPermission(Permission.RecordAudio))
.thenAnswer((_) => Future.value(PermissionStatus.denied));
await tester.pumpWidget(widget);
final loginText = find.text(callScreen_microPermissionDenied);
expect(loginText, findsOneWidget);
});
Affected widget code:
void _requestMicroPermission() async {
final result =
await staticWrapper.requestPermission(Permission.RecordAudio);
debugPrint("Microphone permission status: $result");
if (result == PermissionStatus.authorized) {
native.init();
} else {
setState(() {
_loginText = tnsManager.getText(TranslationsManager.callScreen_microPermissionDenied);
});
}
}
The expect() method is called before the setState() call.
Any help?
Finally I solved the issue using WidgetTester#pumpAndSettle():
testWidgets('When micro permission denied, should show error message.',
(WidgetTester tester) async {
when(staticWrapper.requestPermission(Permission.RecordAudio))
.thenAnswer((_) => Future.value(PermissionStatus.denied));
await tester.pumpWidget(widget);
await tester.pump();
final loginText = find.text(callScreen_microPermissionDenied);
expect(loginText, findsOneWidget);
});