driver: screenshot generation WIP

This commit is contained in:
Thibault Deckers 2022-01-11 21:53:05 +09:00
parent abed48e758
commit 573e6df1c7
24 changed files with 278 additions and 155 deletions

3
.gitignore vendored
View file

@ -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/

View file

@ -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());

View file

@ -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

View file

@ -103,4 +103,13 @@ class MimeTypes {
return a == b;
}
}
static String? forExtension(String extension) {
switch (extension) {
case '.jpg':
return jpeg;
case '.svg':
return svg;
}
}
}

View file

@ -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,

View file

@ -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;
}

View file

@ -0,0 +1,5 @@
enum AppDebugAction {
prepScreenshotThumbnails,
prepScreenshotStats,
mediaStoreScanDir,
}

View file

@ -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;
}
}
}

View 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'),
)
],
);
}
}

View file

@ -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;

View file

@ -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(),

View file

@ -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) {

View 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': 'Русский',
};
}

View file

@ -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,
),
),
),
);
}

View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 989 B

After

Width:  |  Height:  |  Size: 989 B

View file

Before

Width:  |  Height:  |  Size: 5 MiB

After

Width:  |  Height:  |  Size: 5 MiB

View 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';

View file

@ -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';

View file

@ -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();
}

View file

@ -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');
});
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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();
}
}