When I try widget testing my flutter app, I am unable to locate the desired widgets by their ID. As a result, I took a golden screenshot to see what was happening and it appears every instance of Text is being blanked out (in the same color as the text color should be).
See side-by-side of expected app appearance and then golden screenshot.
I've simplified my problem down to a finer grained example, see the code here:
testGoldens('Example', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = Size(1284, 2778);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
Widget exampleWidget = Scaffold(
appBar: AppBar(),
body: Text('example', style: TextStyle(color: Colors.red)));
await tester.pumpWidget(MaterialApp(home: exampleWidget));
await expectLater(
find.byType(MaterialApp), matchesGoldenFile('signIn.png'));
});
And this produces this screenshot. (Notice even the debug sash is blanked out).
Anyone seen this before?
Thanks 🙏
Related
I am attempting to make a journaling and mental health app. I want to have a screen with a calendar that shows color-coded circles (colors are dependent on the selected mood) for each day a user uploads an entry (I have included an image for reference). When the user selects a specific date, I'd like for the user to be able to see their inputted data as well. I'm not sure where to start honestly, so I'm looking for suggestions people have about possible packages, widgets, advice, etc. on what I should do to start building this specific screen:
Reference Image:
Thank you and I look forward to hearing your suggestions!
https://pub.dev/packages/syncfusion_flutter_datepicker
Use selection type add multiple. There are various styling options too
void _onSelectionChanged(DateRangePickerSelectionChangedArgs args) {
// TODO: implement your code here
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: SfDateRangePicker(
onSelectionChanged: _onSelectionChanged,
selectionMode: DateRangePickerSelectionMode.multiple,
),
),
),
);
}
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);
I am learning Flutter and I want to write one test for my simple MyAppBarWidget. Below is my widget
class MyAppBarWidget extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("My first widget"),
),
));
}
}
I want to test widget tree hierarchy as
MaterialApp has Scaffold property
Scaffold has AppBar property
AppBar has title property as Text
title is My first widget
Any suggestion what kind of test I should write
I tried below test
void main() {
testWidgets("verify app bar", (WidgetTester tester) async {
await tester.pumpWidget(MyAppBarWidget());
var byWidget = find.byType(MaterialApp);
var text = find.text("My first widget");
expect(byWidget, findsOneWidget);
expect(text, findsOneWidget);
});
}
But this test does not say that my text field is inside AppBar widget
Can someone help me how should I write test to verify this ?
Thanks
I suggest not to test widget hierarchy, you will change it often and always have to adjust the test without actually knowing anything when the test fails or succeeds. It is better to test functionality, the presence of something or the absence, tap events and interaction.
You can also look into golden (screenshot) tests to ensure that screens or pages don't change.
That being said, if you really want to do this you can use
find.ancestor(find.byType(AppBar), find.text("My first widget"));
EDIT
Or with newer versions of the test library, thanks Fred Grott:
find.ancestor(of: find.byType(AppBar), matching: find.text("My first widget"));
What I'm trying to do is build a flutter web app that, when displayed in the browser, the tab shows an icon and a title (as currently it only shows the world icon and the localhost... title).
Actual Result :
Desired Result :
Edit:
I can now add the title, as this is set in the main function
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Icon(Icons.menu, color: Colors.white,),
title: Text(_your title here_),
)
...
);
}
So, the only thing I need to edit is the favicon
In order to change the title to what you desire, you need to add the parameter title (Which is a String) to your MaterialApp widget.
return MaterialApp(
title: "MyTitle",
home: MyHomeWidget());
Assuming you already have a favicon.ico file, placing it inside the your_project_dir\web folder alongside index.html as shown below is enough for the browser to pick it up.
Following is the result of placing a favicon.ico' in theweb` folder. Make sure to do a clean build.
In other case you can manually mention it using the link tag inside your index.html as explained here in this Wikipedia page.
Edit title
it is the simplest. just use the Title widget on each page or directly inside the materialApp constructor and set title string key to the title text you need.
like this:
...
Title(
color: myColors, //not important in web but still required
title: 'web page title',
child: myChildWidget,
),
...
Edit icon
If your app is only for the web, use the dart:html library to perform change using DOM access.
something like this
import 'dart:html';
...
...
updateIcon(String assetIcon){
LinkElement link = (document.querySelector("link[rel*='icon']") ??
document.createElement('link')) as LinkElement;
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = assetIcon;
}
if your application is multi-platform, you need to create separate main file for the web like main_web.dart. and declare the previous function inside this file.
Now, anywhere you need to set up the icon you just need to call the method after checking the platform using the keyword kIsWeb.
Ex: change icon inside page
...
initState(){
super.initSate();
if(kIsWeb){
WebMixin.updateIcon("assets/home_icon.png"); //WebMixin is just a helper. replace it by your one.
}
}
...
You could set the onGenerateTitle property of your MaterialApp widget, and provide a callback function to build your title. The onGenerateTitle callback is called each time the WidgetsApp rebuilds. This is useful if you want the title of your page to change dynamically or if you want to produce a localized title.
MaterialApp(
...
onGenerateTitle: (BuildContext context) {
return AppLocalizations.of(context).myTitle;
}
...
);
If you're wondering how to change the app name on your device's homepage, you can update the "name" and "short_name" values in web/manifest.json:
"name": "Ideasky",
"short_name": "Ideasky",
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?