Same code generating different goldens images on different machines - flutter

I added a screenshot test for a screen on my flutter app and was adding my project on Circle CI, but for some reason, the image generated on the ci and on my machine are slightly different and the test is failing.
Bellow you can see the snippet of code of the test:
Future<BudgetList> loadState(WidgetTester tester) async {
final screen = BudgetList();
mockBloc = MockBloc();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.dark(),
home: Provider.value(
value: mockBloc,
child: screen,
),
),
);
return screen;
}
group('When the budget list is open', () {
testWidgets('It should be displayed', (tester) async {
await loadState(tester);
await expectLater(
find.byType(BudgetList),
matchesGoldenFile('screenshots/with-items.png'),
);
});
});
This is the image generated on my machine:
This is the image generated on circleci:
Does someone know how to make sure that the same golden image is generated every time independently of the machine?

Related

SyncFusion Calendar Flutter widget unclear how to test it

I used the widget to build a basic application for self learning purpose. I would like to widget test what I am done but there's something unclear to me on the topic. In the widget test I wrote this:
// Render the widget.
await tester.pumpWidget(MaterialApp(
title: 'Firestore test', home: Calendar(firestore: firestore)));
// Let the snapshots stream fire a snapshot.
await tester.idle();
// Re-render.
await tester.pump();
// Verify the output.
expect(find.text('Appointments'), findsOneWidget);
expect(find.text('No selected date'), findsOneWidget);
With last line that verifies that without any interaction the SyncFusion calendar should display "No selected date" in the agenda. That line fails as no widget are found. I also tried to do a test with the tap to a given date to display the events for a day, but the tap does not work as well.
// pre filled data before
// ...
await tester.pumpWidget(MaterialApp(
title: 'Firestore test', home: Calendar(firestore: firestore)));
// Let the snapshots stream fire a snapshot.
await tester.idle();
// Re-render.
await tester.pump();
await tester.tap(find.text(datetime.day.toString()));
await tester.pumpAndSettle();
await tester.pump();
// Verify the output.
expect(find.text('Appointments'), findsOneWidget);
expect(find.text('Mark', skipOffstage: false), findsOneWidget);
Could you help me understand what I am missing in order to verify the above behaviour?

Flutter widget test tap - would not hit test on the specified widget

My widget test is failing after the following warning is outputted:
flutter: Warning: A call to tap() with finder "exactly one widget with text "Tab 2" (ignoring offstage widgets): Text("Tab 2", softWrap: no wrapping except at line break characters, overflow: fade, dependencies: [MediaQuery, DefaultTextStyle])" derived an Offset (Offset(600.0, 23.0)) that would not hit test on the specified widget.
flutter: Maybe the widget is actually off-screen, or another widget is obscuring it, or the widget cannot receive pointer events.
The tap is never executed so the next part of the test fails. I put some delays in the test and it appears that the test is attempting to tap the correct widget - it is not offscreen, not obscured, and was able to receive pointer events in the past - not sure why it's currently failing.
Here is a minimal reproducible example:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: TabBar(
labelColor: Color(0xff8391e4),
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
],
),
body: TabBarView(
children: <Widget>[
Text('Tab 1 Text'),
Text('Tab 2 Text'),
],
),
),
),
);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('My Test', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();
// Warning thrown on this tap - tap never executed
await tester.tap(find.text('Tab 2'));
await tester.pumpAndSettle();
// Test fails here
expect(find.text('Tab 2 Text'), findsOneWidget);
});
}
Try to set ensureVisible() before tap():
// Warning thrown on this tap - tap never executed
await tester.ensureVisible(find.text('Tab 2'));
await tester.tap(find.text('Tab 2'));
await tester.pumpAndSettle();
Incase, anyone comes across this question in the future.
I had this same problem it was because I had animation still running in the background. The fix is to call await tester.pumpAndSettle(); which flushes out all pending animations.
I believe a side effect of ensureVisible() is something similar which is why it works.
I found a solution to my problem, but it may not be a universal solution. The app that this test is for is exclusively a web app so it's designed to run on larger screen sizes. When I'd run the test on chrome it would pass, but would fail when run heedlessly.
To fix, I run the test at a larger screen size and it now passes heedlessly.
So if you run into the A call to tap() ... that would not hit test on the specified widget error, adjusting the screen size might fix the issue.
testWidgets('My test', (WidgetTester tester) async {
// Here we use physicalSizeTestValue to adjust the test screen size to
// simulate running on a desktop computer which the app was designed for
tester.binding.window.physicalSizeTestValue = Size(1080, 1920);
tester.binding.window.devicePixelRatioTestValue = 1.0;
In my case, when I have modal screen, this works:
await tester.tap(find.byKey(Key('some_key')), warnIfMissed: false);

Looking up a deactivated widget's ancestor is unsafe. At this point the state of the widget's element tree is no longer stable. In flutter

Before you vote down please read full problem.
I am getting this error I know the reason behind this error, the reason is userDetails Widget is removed from the tree when the login screen is selected. The question is how I re-insert that widget back to Tree. I have tried each and every solution available on StackOverflow and GitHub but no solution worked for me. I have tried using new keyword and global key in the scaffold.
Main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var userPhoneNumber = prefs.getString('phoneNumber');
var userAlternateNumber = prefs.getString('AlternateNumber');
print(userPhoneNumber);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(
MaterialApp(
theme: ThemeData(
textSelectionHandleColor: kOrangeColor, cursorColor: kOrangeColor),
home: userPhoneNumber == null
? new LoginScreen()
: (userAlternateNumber == null
? new UserDetails()
: new HomeScreen()),
),
);
});
}
Function in LoginScreen.dart
void pushToUserDetailsScreen(BuildContext context) async {
Navigator.push(
context, MaterialPageRoute(builder: (context) => new UserDetails()));
}
I suggest you use a temporary selector page, meaning that you can create a new page, and in this page you don't have to show any UI (Just black screen).
What this would do is that it would decide what page you want to go next to, and this page would always be your homescreen, so when it's launched it will check if the phone number is null and would pushreplacement Login Screen , otherwise it would go to some other page.
Also don't worry about it being a "Black Screen" as this all happens too fast (That depends on the conditions that allow you to decide the starting screen) and it directly takes you to the screen you want depending on the conditions you have.

Flutter loading Image asset too slow

So, I have a screen which shows a text and an image above the text and loads, in background, some data from the shared preferences.
But the image is taking forever to load, and when I remove the call to get the shared preferences data the image loads very quiclky. Do someone knows if I'm doing something wrong with this call?
here's the code:
class WelcomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
precacheImage(AssetImage(Images.tomato), context);
_loadData(context);
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
Strings.appName,
style: TextStyle(
color: AppColors.black,
fontSize: 50.0,
fontWeight: FontWeight.w600,
),
),
Image.asset(Images.tomato),
],
),
),
);
}
void _loadData(BuildContext context) async {
final PreferencesRepository repository = PreferenceRepositoryImp();
repository.loadAll().then(
(_) {
sleep(Duration(seconds: 2));
Navigator.pushNamedAndRemoveUntil(context, Routes.HOME, (_) => false);
},
);
}
}
and that's the image
You're using sleep(Duration(seconds: 2)); which halts everything. You should not be using sleep, especially inside a build method. Dart is single threaded so that means when it halts a thread, it prevents the UI from being built.
Also, why is _loadData marked as async if you're not using the await keyword? You only need it if you're going to use the await keyword, otherwise, remove it. If you want to make use of it, you should use await Future.delayed(Duration(seconds: 2)) which makes it wait. Here:
void _loadData(BuildContext context) async {
final PreferencesRepository repository = PreferenceRepositoryImp();
await repository.loadAll();
await Future.delayed(Duration(seconds: 2));
Navigator.pushNamedAndRemoveUntil(context, Routes.HOME, (_) => false);
}
How does await differ from sleep?
await makes Dart say "Hey, I'm going to keep my eye out for this in the future" (haha get it? Future? hahahhahaha I'm funny). Anyways, it won't continue the _loadData function because it wants that value but it will keep running (in your example) the build method and then when the Future comes back with a value, it will be like "Hey! There's that value I was looking for! Now I can keep running that function!"
sleep on the other hand makes Dart say "I'm going to take a nap for the duration of time I get" (in this case two seconds). It won't do anything until it "wakes up" since Dart is single-threaded. After it takes its two-second nap, it will continue the function and after it finishes, the build method will carry on, therefore, loading your image which explains why it takes so long.

how specify Window for tests?

I need the screen in the test to look the same as on the physical device(or simulator). How can I do it? In my case device id Iphone SE.
I wrote a test that saves a screenshot to disk:
testWidgets('test', (WidgetTester tester) async {
final AutomatedTestWidgetsFlutterBinding binding = tester.binding;
binding.renderView.configuration = TestViewConfiguration(size: Size(640, 1136));
var widget = Scaffold(
appBar: AppBar(title: Text('title'),),
body: Column(children: <Widget>[
RaisedButton(
child: Text('button'),
onPressed: () {},)
],),
);
var key = new GlobalKey();
await tester.pumpWidget(
MaterialApp(home: RepaintBoundary(key: key, child: widget),),
);
await tester.pumpAndSettle();
await tester.runAsync(() async {
RenderRepaintBoundary boundary = key.currentContext.findRenderObject();
var image = await boundary.toImage();
var byteData = await image.toByteData(format: ImageByteFormat.png);
var pngBytes = byteData.buffer.asUint8List();
await File('screen.png').writeAsBytes(pngBytes);
});
});
if use ViewConfiguration with devicePixelRatio instead TestViewConfiguration, devicePixelRatio ignoring
MediaQuery too don work, if wrap MaterialApp
appbar and button less then on simulator
screen from test:
but expected(widgets scale):
You get the blocks instead of text because Flutter uses a specific test font (Ahem) that has all characters just blocks.
This makes it easier to render them equally on Linux (CI) and other platforms. I don't know if there are other reasons.
I also wasn't able to make images work in golden tests.
https://github.com/flutter/engine/pull/6913 was a recently merged fix to allow loading custom fonts in tests.
You can use flutter run --use-test-fonts to make Flutter use the Ahem font when you run the app on a real device so you can visualize how the test will look.
Related issues
https://github.com/flutter/flutter/issues/24405
https://github.com/flutter/flutter/issues/17910#issuecomment-445184463
I don't know if font's loaded this way work in golden tests though (they might still not work similar to images)
If you want to specify different screen sizes see (not tested myself) How to test Flutter widgets on different screen sizes?
Not sure if this suggestion is still of any value. I found it quite limited and the above suggestion probably works better) In Flutter Widget testing, how to make media.orientation to portrait?