How to test canvas with testWidgets in a flutter mobile app project - flutter

I was reading about how to build tests for flutter app widgets with the testWidgets function, like this test which comes by default when creating a new flutter project:
// <project>/test/widget_test.dart
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
await tester.pumpWidget(CounterApp());
// validate counter starts at zero
expect(find.text('0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
}
}
But how would one test a canvas to see if the drawings/patterns and paragraphs are being displayed correctly? Does the finder object finds a Text widget if I draw a paragraph with canvas.drawParagraph(...)? I couldn't find info about this in the docs.

Testing Canvas in general (no matter the technology) should be performed using visual testing (or visual regression testing).
flutter_test package provides matchesGoldenFile function that makes it possible. There's a nice tutorial on Flutter's Github that will guide you through.

Related

How to reinstall the app between every testWidgets in Flutter integration test?

In Flutter integration tests, is there any way we can kill the app & reinstall it for every testWidgets?
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('TC_01 Home page validation', (tester) async {
app.main();
await tester.pumpAndSettle(const Duration(seconds: 5));
await tester.tap(find.byKey(const ValueKey('startButton')));
await tester.pumpAndSettle();
});
testWidgets('TC_02 Home page text validation ', (tester) async {
app.main();
await tester.pumpAndSettle(const Duration(seconds: 5));
expect(find.text('Home Screen'), findsOneWidget);
expect(find.text('Transactions'), findsOneWidget);
});
}
I want to reinstall the app for every new test that runs.
This is not possible, because, in Flutter, the tests (integration tests included) live inside your app. If you uninstalled the app after the first test passed, you'd also uninstall the test suite (which runs inside your app) - which doesn't make sense.
A better question is to ask: what do you want to achieve? If you want to reset state, you can pump your app once again and it will reset the whole widget tree, along with all blocs/providers you might've created.

How to Tap on native iOS popup to allow Notifications. (Flutter Integration Test)

Flutter Driver code has to tap on the native "Allow" button to continue and simulate the correct user behaviour.
See this screenshot. Native iOS popup before app starts - Allow Notifications
App has not yet completely started and is waiting for this tap.
How does one get the driver to tap on the native iOS popup?
Any suggestions and ideas are welcome.
Here is the code for one attempt to wait for the app before continuing with other tests; it just awaits indefinitely:
setUpAll(() async {
driver = await FlutterDriver.connect();
await driver.waitUntilFirstFrameRasterized();
});
Here is another attempt at finding the word "Allow" in the popup and tapping on it:
test('Allow app to send Notifications.', () async {
final allow = find.byTooltip("Allow");
await delay(750);
await driver.tap(allow);
});
It does not find the word.
The issue is probably that Flutter Driver is not aware of the iOS native popup.
Other tests are very simple once in the app, for example, to tap on fields, enter text, scroll pages, etc.
Any ideas on how to do this?
Unfortunately this feature is currently not possible. Flutter Driver can't interact with native elements (v1.20.4).
https://github.com/flutter/flutter/issues/34345
https://github.com/flutter/flutter/issues/12561

Flutter widget vs Flutter driver

I'm writing tests for a mobile App written in Flutter.
I followed this Flutter Cookbook on testing Flutter apps, to learn how to write widget and integration tests.
This tutorial works perfectly, but I'm still stuck with my own Integration tests.
To simplify, let's assume I have an app, containing only a TextField:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyAppp',
home: Scaffold(
body: Center(child: TextField()),
),
);
}
}
I want to write a test for this app. For instance, I want to test the following scenario:
Open the app
Check that the TextField is empty
Select the TextField
Enter "hello, world!"
Check that TextField contains "hello, world!"
I wrote the following Widget test, which works fine:
void main() {
testWidgets('TextField behavior', (WidgetTester tester) async {
// Create app
await tester.pumpWidget(MyApp());
// Find TextField, check there is 1
final textFieldFinder = find.byType(TextField);
expect(textFieldFinder, findsOneWidget);
// Retrieve TextField Widget from Finder
TextField textField = tester.widget(textFieldFinder);
// Check TextField is empty
expect(textField.controller.text, equals(""));
// Enter text
await tester.enterText(textFieldFinder, "hello, world!");
// Check TextField contains text
expect(textField.controller.text, equals("hello, world!"));
});
}
This test passes, but I wanted to write an Integration test, doing more or less the same, to be able to test it on a real device.
Indeed if this Widget test passes, it will probably pass on all device. But in my app I have more complex widgets and interactions between them, I want to be able to launch tests on both Android and iOS.
I tried to write integration tests using Flutter driver, but I did not find what I wanted in the documentation and examples.
How can I check Widget properties, to verify that my App behaves as expected?
I wrote the following sample:
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('TextField', () {
final textFieldFinder = find.byType('TextField');
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test('TextField behavior', () async {
// ??? how to check that the TextField is empty
await driver.tap(textFieldFinder);
await driver.enterText("hello, world!");
// ??? how to check that the TextField contains hello, world!
});
});
}
The Flutter Cookbook tutorial explains how to retrieve an object by its Text, this would help to check if the Text is present, but for instance is it possible to check that a Container color is red?
Where is the limit between Widget and Integration tests?
Writing Widget tests is pretty straighforward, but I didn't find many examples or documentation about how to write more complexe integration tests using Flutter driver.
If anyone is interested:
To be able to test what I want (not only the presence of widgets, but also theirs states, their properties,...) test driver was not enough for me.
What I did in my project, is to use the flutter_test to write widget tests and check the properties I want.
To test it on a real device (Android or iOS), I used the integration_test package (previously e2e package) available here, which adapts flutter_test results to a format compatible with flutter drive.
To use integration_test, add this line in the main of your widget tests:
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
Then create a file (e.g. integration_test.dart) in your test_driver folder with this content:
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
Then you can launch these driver test by running:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=test/widget_test/widget_test.dart
With your widgets tests wrote in the file test/widget_test/widget_test.dart.
It really helped me, since some widgets did not have the same behavior on Android and iOS.
Today I use both flutter_test (launched on real device with integration_test) and real flutter_driver tests:
I write widget tests to check a single widget, or a single page,
I use flutter driver to write more sophisticated scenarios to test the whole application.
I think there's a difference between flutter widget and flutter driver. In that, flutter widgets is for widget tests and flutter driver is for integration tests. I'm not exactly sure what that means on a practical standpoint but I think integration test are easier to run tests on application interactions that would be made by a user since integration tests run on an actual device as compared to widget tests. Will update if I find more information on this.

ERROR in flutter: widget_test.dart cannot detect MyApp() pls helpme

Being a total beginner I am trying out various flutter feature and I am stuck at running the main.dart due to errors in the widget_test.dart file. Please point out if the error is due to some other reason.
main.dart
import 'package:flutter/material.dart';
import 'package:food_app/pages/HomePage.dart';
import 'package:food_app/pages/StarterPage.dart';
void main() => runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(fontFamily: 'Roboto'),
home: StarterPage(),
)
);
widget_test.dart
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:food_app/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
This widget_test.dart file is a file for your tests (obviously). It is separate of your main code and its purpose is only for writing unit, widget, integration and probably some other tests.
I believe that your question came up because your IDE shows you a problem in that code. You don't have to worry about it. As long as you run flutter run or flutter build tests aren't touched.
You can delete the whole tests/ directory if you don't plan on maintaining them.
I deeply suggest you learn about Flutter unit and widget tests as you and your skills will benefit from it, it is also an industry standard to write tests. You can learn more about them in Testing Flutter apps. If you're only getting started with Flutter, you can leave it for later though.
However, if you plan to fix those test, firstly, you should put your MaterialApp in some custom widget, for example FoodApp (guessing from the package name) and pump it in tests. You will need to tweak those tests a little bit more than that. Please refer to the resource I linked above.

In Flutter widget testing, how to pause app to find suggested Finder?

I am following flutter widget testing as https://flutter.io/testing/#widget-testing.
It says that
simply flutter run test/widget_test.dart to see your test run in your preferred runtime environment such as a simulator or a device. During a flutter run session on a widget test, you can also interactively tap parts of the screen for the Flutter tool to print the suggested Finder.
I am using flutter run test/widget_test.dart so that i can tap and get suggested finders. But I am not able to pause the test so that i can actually tap. The test runs and automatically ends. So that there are no widgets on the screen except Text('Test Finished').
Thank you very much!
I know it's been almost 2 years since the question was asked, but anyway. tester.idle() helped me to pause an app.
testWidgets('idle', (WidgetTester tester) async {
await tester.pumpWidget(
//
);
await tester.tap(find.byIcon(Icons.bluetooth));
await tester.pump();
tester.idle();
});