driver: screenshot generation WIP
This commit is contained in:
parent
d7f7a7226b
commit
bb56ee7729
11 changed files with 247 additions and 16 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -44,3 +44,6 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# temporary output for screenshot generation
|
||||||
|
/screenshots/
|
||||||
|
|
|
@ -201,6 +201,8 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||||
return typeBookmarks
|
return typeBookmarks
|
||||||
.where((filter) => !hiddenFilters.contains(filter))
|
.where((filter) => !hiddenFilters.contains(filter))
|
||||||
.map((filter) => CollectionNavTile(
|
.map((filter) => CollectionNavTile(
|
||||||
|
// key is expected by test driver
|
||||||
|
key: Key('drawer-type-${filter?.key}'),
|
||||||
leading: DrawerFilterIcon(filter: filter),
|
leading: DrawerFilterIcon(filter: filter),
|
||||||
title: DrawerFilterTitle(filter: filter),
|
title: DrawerFilterTitle(filter: filter),
|
||||||
filter: filter,
|
filter: filter,
|
||||||
|
@ -257,6 +259,8 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageNavTile(
|
return PageNavTile(
|
||||||
|
// key is expected by test driver
|
||||||
|
key: Key('drawer-page-$route'),
|
||||||
trailing: trailing,
|
trailing: trailing,
|
||||||
routeName: route,
|
routeName: route,
|
||||||
pageBuilder: pageBuilder ?? (_) => const SizedBox(),
|
pageBuilder: pageBuilder ?? (_) => const SizedBox(),
|
||||||
|
|
|
@ -28,6 +28,8 @@ class LanguageSection extends StatelessWidget {
|
||||||
final currentUnitSystem = context.select<Settings, UnitSystem>((s) => s.unitSystem);
|
final currentUnitSystem = context.select<Settings, UnitSystem>((s) => s.unitSystem);
|
||||||
|
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
|
// key is expected by test driver
|
||||||
|
key: const Key('section-language'),
|
||||||
// use a fixed value instead of the title to identify this expansion tile
|
// use a fixed value instead of the title to identify this expansion tile
|
||||||
// so that the tile state is kept when the language is modified
|
// so that the tile state is kept when the language is modified
|
||||||
value: 'language',
|
value: 'language',
|
||||||
|
|
|
@ -18,6 +18,8 @@ class LocaleTile extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
// key is expected by test driver
|
||||||
|
key: const Key('tile-language'),
|
||||||
title: Text(context.l10n.settingsLanguage),
|
title: Text(context.l10n.settingsLanguage),
|
||||||
subtitle: Selector<Settings, Locale?>(
|
subtitle: Selector<Settings, Locale?>(
|
||||||
selector: (context, s) => settings.locale,
|
selector: (context, s) => settings.locale,
|
||||||
|
|
|
@ -112,11 +112,11 @@ flutter:
|
||||||
################################################################################
|
################################################################################
|
||||||
# Test driver
|
# Test driver
|
||||||
|
|
||||||
# run (any device):
|
# capture shaders (profile mode, real device only):
|
||||||
# % flutter drive --flavor play -t test_driver/driver_play.dart --profile
|
# % flutter drive --flavor play -t test_driver/driver_shaders.dart --profile --cache-sksl --write-sksl-on-exit shaders.sksl.json
|
||||||
|
|
||||||
# capture shaders in profile mode (real device only):
|
# generate screenshots (profile mode, specific collection):
|
||||||
# % flutter drive --flavor play -t test_driver/driver_play.dart --profile --cache-sksl --write-sksl-on-exit shaders.sksl.json
|
# % flutter drive --flavor play -t test_driver/driver_screenshots.dart --profile
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Adaptations
|
# Adaptations
|
||||||
|
|
BIN
screenshots/en-settings.png
Normal file
BIN
screenshots/en-settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
44
test_driver/driver_screenshots.dart
Normal file
44
test_driver/driver_screenshots.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:aves/main_play.dart' as app;
|
||||||
|
import 'package:aves/model/settings/enums.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/enums.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
enableFlutterDriverExtension();
|
||||||
|
|
||||||
|
// something like `configure().then((_) => app.main());` does not behave as expected
|
||||||
|
// and starts the app without waiting for `configure` to complete
|
||||||
|
configureAndLaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> configureAndLaunch() async {
|
||||||
|
await settings.init(monitorPlatformSettings: false);
|
||||||
|
settings
|
||||||
|
// app
|
||||||
|
..hasAcceptedTerms = true
|
||||||
|
..isInstalledAppAccessAllowed = true
|
||||||
|
..isErrorReportingAllowed = false
|
||||||
|
..keepScreenOn = KeepScreenOn.always
|
||||||
|
..homePage = HomePageSetting.collection
|
||||||
|
..setTileExtent(CountryListPage.routeName, 112)
|
||||||
|
..setTileLayout(CountryListPage.routeName, TileLayout.grid)
|
||||||
|
// viewer
|
||||||
|
..showOverlayOnOpening = true
|
||||||
|
..showOverlayMinimap = false
|
||||||
|
..showOverlayInfo = true
|
||||||
|
..showOverlayShootingDetails = false
|
||||||
|
..enableOverlayBlurEffect = true
|
||||||
|
..viewerUseCutout = true
|
||||||
|
// info
|
||||||
|
..infoMapStyle = EntryMapStyle.stamenWatercolor
|
||||||
|
..infoMapZoom = 11
|
||||||
|
..coordinateFormat = CoordinateFormat.dms
|
||||||
|
..unitSystem = UnitSystem.metric;
|
||||||
|
|
||||||
|
// TODO TLAD covers.set(LocationFilter(LocationLevel.country, location), contentId)
|
||||||
|
|
||||||
|
app.main();
|
||||||
|
}
|
173
test_driver/driver_screenshots_test.dart
Normal file
173
test_driver/driver_screenshots_test.dart
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
// ignore_for_file: avoid_print
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'utils/adb_utils.dart';
|
||||||
|
import 'utils/driver_extension.dart';
|
||||||
|
|
||||||
|
late FlutterDriver driver;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('[Aves app]', () {
|
||||||
|
setUpAll(() async {
|
||||||
|
await Directory(directory).create();
|
||||||
|
|
||||||
|
await Future.forEach<String>(
|
||||||
|
[
|
||||||
|
'deckers.thibault.aves.debug',
|
||||||
|
'deckers.thibault.aves.profile',
|
||||||
|
],
|
||||||
|
(package) => grantPermissions(package, [
|
||||||
|
'android.permission.READ_EXTERNAL_STORAGE',
|
||||||
|
'android.permission.ACCESS_MEDIA_LOCATION',
|
||||||
|
]));
|
||||||
|
driver = await FlutterDriver.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
unawaited(driver.close());
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
'de',
|
||||||
|
'en',
|
||||||
|
// TODO TLAD other locales
|
||||||
|
].forEach((v) async {
|
||||||
|
setLanguage(v);
|
||||||
|
collection();
|
||||||
|
viewer();
|
||||||
|
info();
|
||||||
|
stats();
|
||||||
|
countries();
|
||||||
|
});
|
||||||
|
}, timeout: const Timeout(Duration(seconds: 30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const directory = 'screenshots';
|
||||||
|
String screenshotLocale = '';
|
||||||
|
|
||||||
|
Future<void> takeScreenshot(FlutterDriver driver, String name) async {
|
||||||
|
final pixels = await driver.screenshot();
|
||||||
|
final file = File('$directory/$screenshotLocale-$name.png');
|
||||||
|
await file.writeAsBytes(pixels);
|
||||||
|
print('* saved screenshot to ${file.path}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLanguage(String locale) {
|
||||||
|
test('set language', () async {
|
||||||
|
await driver.tapKeyAndWait('appbar-leading-button');
|
||||||
|
await driver.tapKeyAndWait('drawer-settings-button');
|
||||||
|
await driver.tapKeyAndWait('section-language');
|
||||||
|
await driver.tapKeyAndWait('tile-language');
|
||||||
|
await driver.tapKeyAndWait(locale);
|
||||||
|
screenshotLocale = locale;
|
||||||
|
|
||||||
|
await pressDeviceBackButton();
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void collection() {
|
||||||
|
test('1. Collection', () async {
|
||||||
|
// TODO TLAD hidden filters: reverse of TagFilter('aves-screenshot-collection')
|
||||||
|
|
||||||
|
await driver.tapKeyAndWait('appbar-leading-button');
|
||||||
|
await driver.tapKeyAndWait('drawer-type-null');
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '1-collection');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void viewer() {
|
||||||
|
test('2. Viewer', () async {
|
||||||
|
const query = 'Singapore 087 Zoo - Douc langur';
|
||||||
|
|
||||||
|
await driver.tapKeyAndWait('menu-searchCollection');
|
||||||
|
await driver.tap(find.byType('TextField'));
|
||||||
|
await driver.enterText(query);
|
||||||
|
final queryChip = find.byValueKey('query-$query');
|
||||||
|
await driver.waitFor(queryChip);
|
||||||
|
await driver.tap(queryChip);
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
|
||||||
|
// delay to avoid flaky descendant resolution
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
await driver.tap(find.descendant(
|
||||||
|
of: find.byValueKey('collection-grid'),
|
||||||
|
matching: find.byType('MetaData'),
|
||||||
|
firstMatchOnly: true,
|
||||||
|
));
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
|
final imageView = find.byValueKey('image_view');
|
||||||
|
await driver.doubleTap(imageView);
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '2-viewer');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void info() {
|
||||||
|
test('3. Info (basic), 4. Info (metadata)', () async {
|
||||||
|
final verticalPageView = find.byValueKey('vertical-pageview');
|
||||||
|
|
||||||
|
await driver.scroll(verticalPageView, 0, -600, const Duration(milliseconds: 400));
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '3-info-basic');
|
||||||
|
|
||||||
|
await driver.scroll(verticalPageView, 0, -800, const Duration(milliseconds: 600));
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
final gpsTile = find.descendant(
|
||||||
|
of: find.byValueKey('tilecard-GPS'),
|
||||||
|
matching: find.byType('ListTile'),
|
||||||
|
);
|
||||||
|
await driver.tap(gpsTile);
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '3-info-metadata');
|
||||||
|
|
||||||
|
await pressDeviceBackButton();
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
|
||||||
|
await pressDeviceBackButton();
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stats() {
|
||||||
|
test('5. Stats', () async {
|
||||||
|
// TODO TLAD hidden filters: PathFilter('/storage/emulated/0/Pictures/Dev')
|
||||||
|
|
||||||
|
await driver.tapKeyAndWait('appbar-leading-button');
|
||||||
|
await driver.tapKeyAndWait('drawer-type-null');
|
||||||
|
|
||||||
|
await driver.tapKeyAndWait('appbar-menu-button');
|
||||||
|
await driver.tapKeyAndWait('menu-stats');
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '5-stats');
|
||||||
|
|
||||||
|
await pressDeviceBackButton();
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void countries() {
|
||||||
|
test('6. Countries', () async {
|
||||||
|
// TODO TLAD hidden filters: reverse of TagFilter('aves-screenshot-collection')
|
||||||
|
// TODO TLAD OR 1) set country covers, 2) hidden filters: PathFilter('/storage/emulated/0/Pictures/Dev')
|
||||||
|
|
||||||
|
await driver.tapKeyAndWait('appbar-leading-button');
|
||||||
|
await driver.tapKeyAndWait('drawer-page-/countries');
|
||||||
|
|
||||||
|
await takeScreenshot(driver, '6-countries');
|
||||||
|
|
||||||
|
await pressDeviceBackButton();
|
||||||
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
});
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ Future<void> configureAndLaunch() async {
|
||||||
..keepScreenOn = KeepScreenOn.always
|
..keepScreenOn = KeepScreenOn.always
|
||||||
..hasAcceptedTerms = false
|
..hasAcceptedTerms = false
|
||||||
..isErrorReportingAllowed = false
|
..isErrorReportingAllowed = false
|
||||||
|
..isInstalledAppAccessAllowed = true
|
||||||
..locale = const Locale('en')
|
..locale = const Locale('en')
|
||||||
..homePage = HomePageSetting.collection
|
..homePage = HomePageSetting.collection
|
||||||
..infoMapStyle = EntryMapStyle.googleNormal
|
..infoMapStyle = EntryMapStyle.googleNormal
|
|
@ -12,22 +12,19 @@ import 'utils/driver_extension.dart';
|
||||||
|
|
||||||
late FlutterDriver driver;
|
late FlutterDriver driver;
|
||||||
|
|
||||||
extension ExtraFlutterDriver on FlutterDriver {
|
|
||||||
Future<void> tapKeyAndWait(String key) async {
|
|
||||||
await driver.tap(find.byValueKey(key));
|
|
||||||
await driver.waitUntilNoTransientCallbacks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('[Aves app]', () {
|
group('[Aves app]', () {
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
await copyContent(sourcePicturesDir, targetPicturesDir);
|
await copyContent(sourcePicturesDir, targetPicturesDir);
|
||||||
await grantPermissions('deckers.thibault.aves.debug', [
|
await Future.forEach<String>(
|
||||||
'android.permission.READ_EXTERNAL_STORAGE',
|
[
|
||||||
'android.permission.WRITE_EXTERNAL_STORAGE',
|
'deckers.thibault.aves.debug',
|
||||||
'android.permission.ACCESS_MEDIA_LOCATION',
|
'deckers.thibault.aves.profile',
|
||||||
]);
|
],
|
||||||
|
(package) => grantPermissions(package, [
|
||||||
|
'android.permission.READ_EXTERNAL_STORAGE',
|
||||||
|
'android.permission.ACCESS_MEDIA_LOCATION',
|
||||||
|
]));
|
||||||
driver = await FlutterDriver.connect();
|
driver = await FlutterDriver.connect();
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,4 +8,9 @@ extension ExtraFlutterDriver on FlutterDriver {
|
||||||
await Future.delayed(doubleTapDelay);
|
await Future.delayed(doubleTapDelay);
|
||||||
await tap(finder, timeout: timeout);
|
await tap(finder, timeout: timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapKeyAndWait(String key) async {
|
||||||
|
await tap(find.byValueKey(key));
|
||||||
|
await waitUntilNoTransientCallbacks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue