Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #217 - onboarding screenshot generation. #1529

Merged
merged 5 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/smooth_app/integration_test/app_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smooth_app/main.dart' as app;

Future<void> _initScreenshot(
final IntegrationTestWidgetsFlutterBinding binding,
) async {
if ((!kIsWeb) && Platform.isAndroid) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be Web when it's android or am I missing something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, but I explicitly say that I handle the kIsWeb case - which wouldn't be obvious without that additional test. Or it would be a comment: I prefer code to comment.

await binding.convertFlutterSurfaceToImage();
}
}

Future<void> _takeScreenshot(
final WidgetTester tester,
final IntegrationTestWidgetsFlutterBinding binding,
final String screenshotName,
) async {
if ((!kIsWeb) && Platform.isAndroid) {
await tester.pumpAndSettle();
}
await binding.takeScreenshot(screenshotName);
}

// flutter drive --driver=test_driver/screenshot_driver.dart --target=integration_test/app_test.dart

/// Onboarding screenshots.
void main() {
final IntegrationTestWidgetsFlutterBinding binding =
IntegrationTestWidgetsFlutterBinding.ensureInitialized()
as IntegrationTestWidgetsFlutterBinding;

setUpAll(
() => SharedPreferences.setMockInitialValues(
const <String, Object>{
'IMPORTANCE_AS_STRINGnutriscore': 'important',
'IMPORTANCE_AS_STRINGnova': 'important',
'IMPORTANCE_AS_STRINGecoscore': 'important',
},
),
);

group('end-to-end test', () {
testWidgets('just a single screenshot', (WidgetTester tester) async {
await tester.runAsync(() async {
await _initScreenshot(binding);

await app.main(screenshots: true);
await tester.pumpAndSettle();

sleep(const Duration(seconds: 30));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think pumpAndSettle already takes care of waiting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of anything anymore. Especially when there's async load: ok the pumpAndSettle will tell you that the display is refreshed, but not that the data was loaded async'ly in order to refresh the display.

await _takeScreenshot(
tester, binding, 'test-screenshot-onboarding-home');
sleep(const Duration(seconds: 10));

await tester.tap(find.byKey(const Key('next')));
await tester.pumpAndSettle();

await _takeScreenshot(
tester, binding, 'test-screenshot-onboarding-scan');
sleep(const Duration(seconds: 10));

await tester.tap(find.byKey(const Key('next')));
await tester.pumpAndSettle();

await _takeScreenshot(
tester, binding, 'test-screenshot-onboarding-health');
sleep(const Duration(seconds: 10));

await tester.tap(find.byKey(const Key('next')));
await tester.pumpAndSettle();

await _takeScreenshot(
tester, binding, 'test-screenshot-onboarding-eco');
sleep(const Duration(seconds: 10));

await tester.tap(find.byKey(const Key('next')));
await tester.pumpAndSettle();

await _takeScreenshot(
tester, binding, 'test-screenshot-onboarding-prefs');
sleep(const Duration(seconds: 10));
});
});
});
}
6 changes: 6 additions & 0 deletions packages/smooth_app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ PODS:
- TOCropViewController (~> 2.6.1)
- image_picker (0.0.1):
- Flutter
- integration_test (0.0.1):
- Flutter
- iso_countries (0.0.1):
- Flutter
- matomo_forever (0.0.1):
Expand Down Expand Up @@ -106,6 +108,7 @@ DEPENDENCIES:
- google_ml_barcode_scanner (from `.symlinks/plugins/google_ml_barcode_scanner/ios`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- iso_countries (from `.symlinks/plugins/iso_countries/ios`)
- matomo_forever (from `.symlinks/plugins/matomo_forever/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
Expand Down Expand Up @@ -150,6 +153,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_cropper/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
iso_countries:
:path: ".symlinks/plugins/iso_countries/ios"
matomo_forever:
Expand Down Expand Up @@ -183,6 +188,7 @@ SPEC CHECKSUMS:
GTMSessionFetcher: 4577a4cc914a5a07c40a8a0ad0acc22080418c2d
image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98
image_picker: 541dcbb3b9cf32d87eacbd957845d8651d6c62c3
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
iso_countries: eb09d40f388e4c65e291e0bb36a701dfe7de6c74
matomo_forever: 7e5e5fd8f355f64979591282cad4e858fa4c9fae
MLImage: 647f3d580c10b3c9a3f6154f5d7baead60c42a0c
Expand Down
10 changes: 9 additions & 1 deletion packages/smooth_app/lib/helpers/analytics_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ class AnalyticsHelper {
return event;
}

static void initMatomo(final BuildContext context) {
static void initMatomo(
final BuildContext context,
final bool screenshotMode,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this parameter as it's a global value

) {
if (screenshotMode) {
setCrashReports(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about putting the same logic in initSentry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not that I particulary care about Matomo rather than Sentry; it's just that initMatomo froze the app in screenshot mode. And I'm sort of forced to call initMatomoso that the next matomo calls don't crash.
In screenshot mode I don't even call initSentry.

setAnalyticsReports(false);
return;
}
MatomoForever.init(
'https://analytics.openfoodfacts.org/matomo.php',
2,
Expand Down
93 changes: 61 additions & 32 deletions packages/smooth_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ import 'package:smooth_app/themes/theme_provider.dart';

List<CameraDescription> cameras = <CameraDescription>[];

Future<void> main() async {
late bool _screenshots;

Future<void> main({final bool screenshots = false}) async {
_screenshots = screenshots;
if (_screenshots) {
await _init1();
runApp(const SmoothApp());
return;
}
final WidgetsBinding widgetsBinding =
WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
Expand All @@ -48,17 +56,50 @@ class SmoothApp extends StatefulWidget {
State<SmoothApp> createState() => _SmoothAppState();
}

late UserPreferences _userPreferences;
late ProductPreferences _productPreferences;
late LocalDatabase _localDatabase;
late ThemeProvider _themeProvider;
bool _init1done = false;

// Had to split init in 2 methods, for test/screenshots reasons.
// Don't know why, but some init codes seem to freeze the test.
// Now we run them before running the app, during the tests.
Future<bool> _init1() async {
if (_init1done) {
return false;
}
_userPreferences = await UserPreferences.getUserPreferences();
_localDatabase = await LocalDatabase.getLocalDatabase();
_productPreferences = ProductPreferences(
ProductPreferencesSelection(
setImportance: _userPreferences.setImportance,
getImportance: _userPreferences.getImportance,
notify: () => _productPreferences.notifyListeners(),
),
daoString: DaoString(_localDatabase),
);

AnalyticsHelper.setCrashReports(_userPreferences.crashReports);
AnalyticsHelper.setAnalyticsReports(_userPreferences.analyticsReports);
ProductQuery.setCountry(_userPreferences.userCountryCode);
_themeProvider = ThemeProvider(_userPreferences);
ProductQuery.setQueryType(_userPreferences);

cameras = await availableCameras();

await ProductQuery.setUuid(_localDatabase);
_init1done = true;
return true;
}

class _SmoothAppState extends State<SmoothApp> {
late UserPreferences _userPreferences;
late ProductPreferences _productPreferences;
late LocalDatabase _localDatabase;
late ThemeProvider _themeProvider;
final UserManagementProvider _userManagementProvider =
UserManagementProvider();

bool systemDarkmodeOn = false;
final Brightness brightness =
SchedulerBinding.instance?.window.platformBrightness ?? Brightness.light;
bool systemDarkmodeOn = false;

// We store the argument of FutureBuilder to avoid re-initialization on
// subsequent builds. This enables hot reloading. See
Expand All @@ -68,35 +109,18 @@ class _SmoothAppState extends State<SmoothApp> {
@override
void initState() {
super.initState();
_initFuture = _init();
_initFuture = _init2();
}

Future<void> _init() async {
Future<bool> _init2() async {
await _init1();
systemDarkmodeOn = brightness == Brightness.dark;
_userPreferences = await UserPreferences.getUserPreferences();
_localDatabase = await LocalDatabase.getLocalDatabase();
_productPreferences = ProductPreferences(
ProductPreferencesSelection(
setImportance: _userPreferences.setImportance,
getImportance: _userPreferences.getImportance,
notify: () => _productPreferences.notifyListeners(),
),
daoString: DaoString(_localDatabase),
);

await _productPreferences.init(DefaultAssetBundle.of(context));
await _userPreferences.init(_productPreferences);

AnalyticsHelper.setCrashReports(_userPreferences.crashReports);
AnalyticsHelper.setAnalyticsReports(_userPreferences.analyticsReports);
ProductQuery.setCountry(_userPreferences.userCountryCode);
_themeProvider = ThemeProvider(_userPreferences);
ProductQuery.setQueryType(_userPreferences);

cameras = await availableCameras();

await ProductQuery.setUuid(_localDatabase);
AnalyticsHelper.initMatomo(context);
if (!_screenshots) {
await _userPreferences.init(_productPreferences);
}
AnalyticsHelper.initMatomo(context, _screenshots);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just put it in the if statement above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I can't: I do things in AnalyticsHelper.initMatomo(context, _screenshots);.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need _screenshots as argument as it's a global variable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is: it's private.

return true;
}

@override
Expand All @@ -118,7 +142,11 @@ class _SmoothAppState extends State<SmoothApp> {
ChangeNotifierProvider<T> provide<T extends ChangeNotifier>(T value) =>
ChangeNotifierProvider<T>(create: (BuildContext context) => value);

FlutterNativeSplash.remove();
if (!_screenshots) {
// ending FlutterNativeSplash.preserve()
FlutterNativeSplash.remove();
}

return MultiProvider(
providers: <ChangeNotifierProvider<ChangeNotifier>>[
provide<UserPreferences>(_userPreferences),
Expand All @@ -140,6 +168,7 @@ class _SmoothAppState extends State<SmoothApp> {
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
debugShowCheckedModeBanner: !(kReleaseMode || _screenshots),
navigatorObservers: <NavigatorObserver>[
SentryNavigatorObserver(),
],
Expand Down
3 changes: 2 additions & 1 deletion packages/smooth_app/lib/pages/onboarding/next_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import 'package:smooth_app/themes/theme_provider.dart';

/// Next button showed at the bottom of the onboarding flow.
class NextButton extends StatelessWidget {
const NextButton(this.currentPage);
// we need a Key for the test/screenshots
const NextButton(this.currentPage) : super(key: const Key('next'));

final OnboardingPage currentPage;

Expand Down
36 changes: 36 additions & 0 deletions packages/smooth_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -343,6 +348,11 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fwfh_selectable_text:
dependency: "direct main"
description:
Expand Down Expand Up @@ -457,6 +467,11 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.4"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
intl:
dependency: transitive
description:
Expand Down Expand Up @@ -945,6 +960,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
sync_http:
dependency: transitive
description:
name: sync_http
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
term_glyph:
dependency: transitive
description:
Expand Down Expand Up @@ -1071,13 +1093,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.2"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "7.5.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
webdriver:
dependency: transitive
description:
name: webdriver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
win32:
dependency: transitive
description:
Expand Down
Loading