driver: screenshot generation WIP
This commit is contained in:
parent
abed48e758
commit
573e6df1c7
24 changed files with 278 additions and 155 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -45,5 +45,6 @@ app.*.map.json
|
|||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# temporary output for screenshot generation
|
||||
# screenshot generation
|
||||
/test_driver/assets/screenshots/
|
||||
/screenshots/
|
||||
|
|
|
@ -32,6 +32,10 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
|||
PathFilter.type,
|
||||
];
|
||||
|
||||
final bool not;
|
||||
|
||||
const CollectionFilter({this.not = false});
|
||||
|
||||
static CollectionFilter? fromJson(String jsonString) {
|
||||
if (jsonString.isEmpty) return null;
|
||||
|
||||
|
@ -69,8 +73,6 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
|
|||
return null;
|
||||
}
|
||||
|
||||
const CollectionFilter();
|
||||
|
||||
Map<String, dynamic> toMap();
|
||||
|
||||
String toJson() => jsonEncode(toMap());
|
||||
|
|
|
@ -12,23 +12,25 @@ class TagFilter extends CollectionFilter {
|
|||
@override
|
||||
List<Object?> get props => [tag];
|
||||
|
||||
TagFilter(this.tag) {
|
||||
TagFilter(this.tag, {bool not = false}) : super(not: not) {
|
||||
if (tag.isEmpty) {
|
||||
_test = (entry) => entry.tags.isEmpty;
|
||||
_test = not ? (entry) => entry.tags.isNotEmpty : (entry) => entry.tags.isEmpty;
|
||||
} else {
|
||||
_test = (entry) => entry.tags.contains(tag);
|
||||
_test = not ? (entry) => !entry.tags.contains(tag) : (entry) => entry.tags.contains(tag);
|
||||
}
|
||||
}
|
||||
|
||||
TagFilter.fromMap(Map<String, dynamic> json)
|
||||
: this(
|
||||
json['tag'],
|
||||
not: json['not'] ?? false,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => {
|
||||
'type': type,
|
||||
'tag': tag,
|
||||
'not': not,
|
||||
};
|
||||
|
||||
@override
|
||||
|
|
|
@ -103,4 +103,13 @@ class MimeTypes {
|
|||
return a == b;
|
||||
}
|
||||
}
|
||||
|
||||
static String? forExtension(String extension) {
|
||||
switch (extension) {
|
||||
case '.jpg':
|
||||
return jpeg;
|
||||
case '.svg':
|
||||
return svg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,8 +182,9 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
Flexible(
|
||||
child: Text(
|
||||
filter.getLabel(context),
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: AvesFilterChip.fontSize,
|
||||
decoration: filter.not ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:aves/services/android_debug_service.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DebugAndroidEnvironmentSection extends StatefulWidget {
|
||||
const DebugAndroidEnvironmentSection({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_DebugAndroidEnvironmentSectionState createState() => _DebugAndroidEnvironmentSectionState();
|
||||
}
|
||||
|
||||
class _DebugAndroidEnvironmentSectionState extends State<DebugAndroidEnvironmentSection> with AutomaticKeepAliveClientMixin {
|
||||
late Future<Map> _loader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loader = AndroidDebugService.getEnv();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return AvesExpansionTile(
|
||||
title: 'Android Environment',
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: FutureBuilder<Map>(
|
||||
future: _loader,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||
final data = SplayTreeMap.of(snapshot.data!.map((k, v) => MapEntry(k.toString(), v?.toString() ?? 'null')));
|
||||
return InfoRowGroup(info: data);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
5
lib/widgets/debug/app_debug_action.dart
Normal file
5
lib/widgets/debug/app_debug_action.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
enum AppDebugAction {
|
||||
prepScreenshotThumbnails,
|
||||
prepScreenshotStats,
|
||||
mediaStoreScanDir,
|
||||
}
|
|
@ -1,14 +1,23 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
import 'package:aves/model/filters/path.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/analysis_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/debug/android_apps.dart';
|
||||
import 'package:aves/widgets/debug/android_codecs.dart';
|
||||
import 'package:aves/widgets/debug/android_dirs.dart';
|
||||
import 'package:aves/widgets/debug/android_env.dart';
|
||||
import 'package:aves/widgets/debug/app_debug_action.dart';
|
||||
import 'package:aves/widgets/debug/cache.dart';
|
||||
import 'package:aves/widgets/debug/database.dart';
|
||||
import 'package:aves/widgets/debug/media_store_scan_dialog.dart';
|
||||
import 'package:aves/widgets/debug/overlay.dart';
|
||||
import 'package:aves/widgets/debug/report.dart';
|
||||
import 'package:aves/widgets/debug/settings.dart';
|
||||
|
@ -40,6 +49,27 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Debug'),
|
||||
actions: [
|
||||
MenuIconTheme(
|
||||
child: PopupMenuButton<AppDebugAction>(
|
||||
// key is expected by test driver
|
||||
key: const Key('appbar-menu-button'),
|
||||
itemBuilder: (context) => AppDebugAction.values
|
||||
.map((v) => PopupMenuItem(
|
||||
// key is expected by test driver
|
||||
key: Key('menu-${v.name}'),
|
||||
value: v,
|
||||
child: MenuRow(text: v.name),
|
||||
))
|
||||
.toList(),
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
unawaited(_onActionSelected(action));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
|
@ -49,7 +79,6 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
const DebugAndroidAppSection(),
|
||||
const DebugAndroidCodecSection(),
|
||||
const DebugAndroidDirSection(),
|
||||
const DebugAndroidEnvironmentSection(),
|
||||
const DebugCacheSection(),
|
||||
const DebugAppDatabaseSection(),
|
||||
const DebugErrorReportingSection(),
|
||||
|
@ -127,4 +156,34 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onActionSelected(AppDebugAction action) async {
|
||||
switch (action) {
|
||||
case AppDebugAction.prepScreenshotThumbnails:
|
||||
final source = context.read<CollectionSource>();
|
||||
source.changeFilterVisibility(settings.hiddenFilters, true);
|
||||
source.changeFilterVisibility({
|
||||
TagFilter('aves-thumbnail', not: true),
|
||||
}, false);
|
||||
await favourites.clear();
|
||||
await favourites.add(source.visibleEntries);
|
||||
break;
|
||||
case AppDebugAction.prepScreenshotStats:
|
||||
final source = context.read<CollectionSource>();
|
||||
source.changeFilterVisibility(settings.hiddenFilters, true);
|
||||
source.changeFilterVisibility({
|
||||
PathFilter('/storage/emulated/0/Pictures/Dev'),
|
||||
}, false);
|
||||
break;
|
||||
case AppDebugAction.mediaStoreScanDir:
|
||||
// scan files copied from test assets
|
||||
// we do it via the app instead of broadcasting via ADB
|
||||
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
|
||||
await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => const MediaStoreScanDirDialog(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
52
lib/widgets/debug/media_store_scan_dialog.dart
Normal file
52
lib/widgets/debug/media_store_scan_dialog.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class MediaStoreScanDirDialog extends StatefulWidget {
|
||||
const MediaStoreScanDirDialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MediaStoreScanDirDialogState createState() => _MediaStoreScanDirDialogState();
|
||||
}
|
||||
|
||||
class _MediaStoreScanDirDialogState extends State<MediaStoreScanDirDialog> {
|
||||
final TextEditingController _pathController = TextEditingController();
|
||||
bool _processing = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pathController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesDialog(
|
||||
content: _processing ? const CircularProgressIndicator() : TextField(controller: _pathController),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _processing
|
||||
? null
|
||||
: () async {
|
||||
final dir = _pathController.text;
|
||||
if (dir.isNotEmpty) {
|
||||
setState(() => _processing = true);
|
||||
await Future.forEach<FileSystemEntity>(Directory(dir).listSync(), (file) async {
|
||||
if (file is File) {
|
||||
final mimeType = MimeTypes.forExtension(p.extension(file.path));
|
||||
await mediaStoreService.scanFile(file.path, mimeType!);
|
||||
}
|
||||
});
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Scan'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -314,7 +314,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
context: context,
|
||||
initialDate: _setDateTime,
|
||||
firstDate: DateTime(0),
|
||||
lastDate: DateTime.now(),
|
||||
lastDate: DateTime(2100),
|
||||
confirmText: context.l10n.nextButtonLabel,
|
||||
);
|
||||
if (_date == null) return;
|
||||
|
|
|
@ -270,6 +270,8 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||
}
|
||||
|
||||
Widget get debugTile => PageNavTile(
|
||||
// key is expected by test driver
|
||||
key: const Key('drawer-debug'),
|
||||
topLevel: false,
|
||||
routeName: AppDebugPage.routeName,
|
||||
pageBuilder: (_) => const AppDebugPage(),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/settings/language/locales.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
@ -48,21 +49,7 @@ class LocaleTile extends StatelessWidget {
|
|||
String _getLocaleName(Locale locale) {
|
||||
// the package `flutter_localized_locales` has the answer for all locales
|
||||
// but it comes with 3 MB of assets
|
||||
switch (locale.languageCode) {
|
||||
case 'de':
|
||||
return 'Deutsch';
|
||||
case 'en':
|
||||
return 'English';
|
||||
case 'es':
|
||||
return 'Español (México)';
|
||||
case 'fr':
|
||||
return 'Français';
|
||||
case 'ko':
|
||||
return '한국어';
|
||||
case 'ru':
|
||||
return 'Русский';
|
||||
}
|
||||
return locale.toString();
|
||||
return SupportedLocales.languagesByLanguageCode[locale.languageCode] ?? locale.toString();
|
||||
}
|
||||
|
||||
LinkedHashMap<Locale, String> _getLocaleOptions(BuildContext context) {
|
||||
|
|
12
lib/widgets/settings/language/locales.dart
Normal file
12
lib/widgets/settings/language/locales.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
// this class is kept minimal, without import
|
||||
// so it can be reused in driver tests
|
||||
class SupportedLocales {
|
||||
static const languagesByLanguageCode = {
|
||||
'de': 'Deutsch',
|
||||
'en': 'English',
|
||||
'es': 'Español (México)',
|
||||
'fr': 'Français',
|
||||
'ko': '한국어',
|
||||
'ru': 'Русский',
|
||||
};
|
||||
}
|
|
@ -15,6 +15,7 @@ import 'package:aves/utils/color_utils.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/utils/mime_utils.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
|
@ -138,10 +139,13 @@ class StatsPage extends StatelessWidget {
|
|||
appBar: AppBar(
|
||||
title: Text(context.l10n.statsPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
body: GestureAreaProtectorStack(
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ flutter:
|
|||
# language files:
|
||||
# - /lib/l10n/app_{language}.arb
|
||||
# - /android/app/src/main/res/values-{language}/strings.xml
|
||||
# - edit locale name resolution for language setting
|
||||
# - edit locale name in /lib/widgets/settings/language/locales.dart
|
||||
|
||||
# generate `AppLocalizations`
|
||||
# % flutter gen-l10n
|
||||
|
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 5 MiB After Width: | Height: | Size: 5 MiB |
16
test_driver/common_test.dart
Normal file
16
test_driver/common_test.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
This file is imported by driver test files.
|
||||
It should not import, directly or indirectly,
|
||||
`dart:ui`, `flutter/widgets.dart', etc.
|
||||
*/
|
||||
|
||||
const shadersSourcePicturesDir = 'test_driver/assets/shaders/';
|
||||
const shadersTargetPicturesDir = '/sdcard/Pictures/Aves Test Driver/';
|
||||
const shadersTargetPicturesDirEmulated = '/storage/emulated/0/Pictures/Aves Test Driver';
|
||||
|
||||
// Cover items should be:
|
||||
// - dated in the future,
|
||||
// - geotagged for each country to cover.
|
||||
const coversSourcePicturesDir = 'test_driver/assets/screenshots/covers';
|
||||
const coversTargetPicturesDir = '/sdcard/Pictures/TD/Aves/';
|
||||
const coversTargetPicturesDirEmulated = '/storage/emulated/0/Pictures/TD/Aves';
|
|
@ -1,3 +0,0 @@
|
|||
const sourcePicturesDir = 'test_driver/assets/';
|
||||
const targetPicturesDir = '/sdcard/Pictures/Aves Test Driver/';
|
||||
const targetPicturesDirEmulated = '/storage/emulated/0/Pictures/Aves Test Driver';
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/main_play.dart' as app;
|
||||
import 'package:aves/model/settings/defaults.dart';
|
||||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
|
@ -6,15 +7,10 @@ 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();
|
||||
}
|
||||
void main() => configureAndLaunch();
|
||||
|
||||
Future<void> configureAndLaunch() async {
|
||||
enableFlutterDriverExtension();
|
||||
await settings.init(monitorPlatformSettings: false);
|
||||
settings
|
||||
// app
|
||||
|
@ -25,7 +21,15 @@ Future<void> configureAndLaunch() async {
|
|||
..homePage = HomePageSetting.collection
|
||||
..setTileExtent(CountryListPage.routeName, 112)
|
||||
..setTileLayout(CountryListPage.routeName, TileLayout.grid)
|
||||
// collection
|
||||
..collectionSectionFactor = EntryGroupFactor.month
|
||||
..collectionSortFactor = EntrySortFactor.date
|
||||
..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions
|
||||
..showThumbnailFavourite = false
|
||||
..showThumbnailLocation = false
|
||||
..hiddenFilters = {}
|
||||
// viewer
|
||||
..viewerQuickActions = SettingsDefaults.viewerQuickActions
|
||||
..showOverlayOnOpening = true
|
||||
..showOverlayMinimap = false
|
||||
..showOverlayInfo = true
|
||||
|
@ -37,8 +41,5 @@ Future<void> configureAndLaunch() async {
|
|||
..infoMapZoom = 11
|
||||
..coordinateFormat = CoordinateFormat.dms
|
||||
..unitSystem = UnitSystem.metric;
|
||||
|
||||
// TODO TLAD covers.set(LocationFilter(LocationLevel.country, location), contentId)
|
||||
|
||||
app.main();
|
||||
}
|
||||
|
|
|
@ -2,19 +2,26 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aves/widgets/debug/app_debug_action.dart';
|
||||
import 'package:aves/widgets/settings/language/locales.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'common_test.dart';
|
||||
import 'utils/adb_utils.dart';
|
||||
import 'utils/driver_extension.dart';
|
||||
|
||||
late FlutterDriver driver;
|
||||
String _languageCode = '';
|
||||
|
||||
const outputDirectory = 'screenshots';
|
||||
|
||||
void main() {
|
||||
group('[Aves app]', () {
|
||||
setUpAll(() async {
|
||||
await Directory(directory).create();
|
||||
await Directory(outputDirectory).create();
|
||||
|
||||
await copyContent(coversSourcePicturesDir, coversTargetPicturesDir);
|
||||
await Future.forEach<String>(
|
||||
[
|
||||
'deckers.thibault.aves.debug',
|
||||
|
@ -28,16 +35,16 @@ void main() {
|
|||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await removeDirectory(coversTargetPicturesDir);
|
||||
unawaited(driver.close());
|
||||
});
|
||||
|
||||
[
|
||||
'de',
|
||||
'en',
|
||||
// TODO TLAD other locales
|
||||
].forEach((v) async {
|
||||
setLanguage(v);
|
||||
test('scan media dir', () => driver.scanMediaDir(coversTargetPicturesDirEmulated));
|
||||
SupportedLocales.languagesByLanguageCode.keys.forEach((languageCode) {
|
||||
setLanguage(languageCode);
|
||||
configureCollectionVisibility(AppDebugAction.prepScreenshotThumbnails);
|
||||
collection();
|
||||
configureCollectionVisibility(AppDebugAction.prepScreenshotStats);
|
||||
viewer();
|
||||
info();
|
||||
stats();
|
||||
|
@ -46,24 +53,44 @@ void main() {
|
|||
}, timeout: const Timeout(Duration(seconds: 30)));
|
||||
}
|
||||
|
||||
const directory = 'screenshots';
|
||||
String screenshotLocale = '';
|
||||
Future<void> _search(String query, String chipKey) async {
|
||||
await driver.tapKeyAndWait('menu-searchCollection');
|
||||
await driver.tap(find.byType('TextField'));
|
||||
await driver.enterText(query);
|
||||
final chip = find.byValueKey(chipKey);
|
||||
await driver.waitFor(chip);
|
||||
await driver.tap(chip);
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
}
|
||||
|
||||
Future<void> takeScreenshot(FlutterDriver driver, String name) async {
|
||||
Future<void> _takeScreenshot(FlutterDriver driver, String name) async {
|
||||
final pixels = await driver.screenshot();
|
||||
final file = File('$directory/$screenshotLocale-$name.png');
|
||||
final file = File('$outputDirectory/$_languageCode-$name.png');
|
||||
await file.writeAsBytes(pixels);
|
||||
print('* saved screenshot to ${file.path}');
|
||||
}
|
||||
|
||||
void setLanguage(String locale) {
|
||||
void setLanguage(String languageCode) {
|
||||
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 driver.tapKeyAndWait(languageCode);
|
||||
_languageCode = languageCode;
|
||||
|
||||
await pressDeviceBackButton();
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
});
|
||||
}
|
||||
|
||||
void configureCollectionVisibility(AppDebugAction action) {
|
||||
test('configure collection visibility', () async {
|
||||
await driver.tapKeyAndWait('appbar-leading-button');
|
||||
await driver.tapKeyAndWait('drawer-debug');
|
||||
|
||||
await driver.tapKeyAndWait('appbar-menu-button');
|
||||
await driver.tapKeyAndWait('menu-${action.name}');
|
||||
|
||||
await pressDeviceBackButton();
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
@ -72,12 +99,12 @@ void setLanguage(String locale) {
|
|||
|
||||
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 driver.tapKeyAndWait('drawer-type-favourite');
|
||||
await _search('birds', 'tag-birds');
|
||||
await _search('South Korea', 'tag-South Korea');
|
||||
|
||||
await takeScreenshot(driver, '1-collection');
|
||||
await _takeScreenshot(driver, '1-collection');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -85,13 +112,9 @@ 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();
|
||||
await driver.tapKeyAndWait('appbar-leading-button');
|
||||
await driver.tapKeyAndWait('drawer-type-null');
|
||||
await _search(query, 'query-$query');
|
||||
|
||||
// delay to avoid flaky descendant resolution
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
@ -107,7 +130,7 @@ void viewer() {
|
|||
await driver.doubleTap(imageView);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
await takeScreenshot(driver, '2-viewer');
|
||||
await _takeScreenshot(driver, '2-viewer');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,7 +141,7 @@ void info() {
|
|||
await driver.scroll(verticalPageView, 0, -600, const Duration(milliseconds: 400));
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
await takeScreenshot(driver, '3-info-basic');
|
||||
await _takeScreenshot(driver, '3-info-basic');
|
||||
|
||||
await driver.scroll(verticalPageView, 0, -800, const Duration(milliseconds: 600));
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
@ -130,7 +153,7 @@ void info() {
|
|||
await driver.tap(gpsTile);
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
await takeScreenshot(driver, '3-info-metadata');
|
||||
await _takeScreenshot(driver, '3-info-metadata');
|
||||
|
||||
await pressDeviceBackButton();
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
@ -142,15 +165,13 @@ void info() {
|
|||
|
||||
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 _takeScreenshot(driver, '5-stats');
|
||||
|
||||
await pressDeviceBackButton();
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
@ -159,15 +180,9 @@ void stats() {
|
|||
|
||||
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();
|
||||
await _takeScreenshot(driver, '6-countries');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,39 +3,25 @@ import 'dart:ui';
|
|||
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/services/media/media_store_service.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'constants.dart';
|
||||
|
||||
void main() {
|
||||
enableFlutterDriverExtension();
|
||||
|
||||
// scan files copied from test assets
|
||||
// we do it via the app instead of broadcasting via ADB
|
||||
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
|
||||
PlatformMediaStoreService()
|
||||
..scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml')
|
||||
..scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
|
||||
|
||||
// something like `configure().then((_) => app.main());` does not behave as expected
|
||||
// and starts the app without waiting for `configure` to complete
|
||||
configureAndLaunch();
|
||||
}
|
||||
void main() => configureAndLaunch();
|
||||
|
||||
Future<void> configureAndLaunch() async {
|
||||
enableFlutterDriverExtension();
|
||||
await settings.init(monitorPlatformSettings: false);
|
||||
settings
|
||||
..keepScreenOn = KeepScreenOn.always
|
||||
// app
|
||||
..hasAcceptedTerms = false
|
||||
..isErrorReportingAllowed = false
|
||||
..isInstalledAppAccessAllowed = true
|
||||
..isErrorReportingAllowed = false
|
||||
..locale = const Locale('en')
|
||||
..keepScreenOn = KeepScreenOn.always
|
||||
..homePage = HomePageSetting.collection
|
||||
..infoMapStyle = EntryMapStyle.googleNormal
|
||||
..imageBackground = EntryBackground.checkered;
|
||||
|
||||
// viewer
|
||||
..imageBackground = EntryBackground.checkered
|
||||
// info
|
||||
..infoMapStyle = EntryMapStyle.googleNormal;
|
||||
app.main();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:flutter_driver/flutter_driver.dart';
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
import 'common_test.dart';
|
||||
import 'utils/adb_utils.dart';
|
||||
import 'utils/driver_extension.dart';
|
||||
|
||||
|
@ -15,7 +15,7 @@ late FlutterDriver driver;
|
|||
void main() {
|
||||
group('[Aves app]', () {
|
||||
setUpAll(() async {
|
||||
await copyContent(sourcePicturesDir, targetPicturesDir);
|
||||
await copyContent(shadersSourcePicturesDir, shadersTargetPicturesDir);
|
||||
await Future.forEach<String>(
|
||||
[
|
||||
'deckers.thibault.aves.debug',
|
||||
|
@ -29,10 +29,11 @@ void main() {
|
|||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await removeDirectory(targetPicturesDir);
|
||||
await removeDirectory(shadersTargetPicturesDir);
|
||||
unawaited(driver.close());
|
||||
});
|
||||
|
||||
test('scan media dir', () => driver.scanMediaDir(shadersTargetPicturesDirEmulated));
|
||||
agreeToTerms();
|
||||
visitAbout();
|
||||
visitSettings();
|
||||
|
@ -159,7 +160,7 @@ void searchAlbum() {
|
|||
test('[collection] search album', () async {
|
||||
await driver.tapKeyAndWait('menu-searchCollection');
|
||||
|
||||
const albumPath = targetPicturesDirEmulated;
|
||||
const albumPath = shadersTargetPicturesDirEmulated;
|
||||
final albumDisplayName = p.split(albumPath).last;
|
||||
await driver.tap(find.byType('TextField'));
|
||||
await driver.enterText(albumDisplayName);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:aves/widgets/debug/app_debug_action.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
|
||||
import 'adb_utils.dart';
|
||||
|
||||
extension ExtraFlutterDriver on FlutterDriver {
|
||||
static const doubleTapDelay = Duration(milliseconds: 100); // in [kDoubleTapMinTime = 40 ms, kDoubleTapTimeout = 300 ms]
|
||||
|
||||
|
@ -13,4 +16,21 @@ extension ExtraFlutterDriver on FlutterDriver {
|
|||
await tap(find.byValueKey(key));
|
||||
await waitUntilNoTransientCallbacks();
|
||||
}
|
||||
|
||||
Future<void> scanMediaDir(String dir) async {
|
||||
await tapKeyAndWait('appbar-leading-button');
|
||||
await tapKeyAndWait('drawer-debug');
|
||||
|
||||
await tapKeyAndWait('appbar-menu-button');
|
||||
await tapKeyAndWait('menu-${AppDebugAction.mediaStoreScanDir.name}');
|
||||
|
||||
await tap(find.byType('TextField'));
|
||||
await enterText(dir);
|
||||
|
||||
await tap(find.byType('TextButton'));
|
||||
await waitUntilNoTransientCallbacks();
|
||||
|
||||
await pressDeviceBackButton();
|
||||
await waitUntilNoTransientCallbacks();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue