From 1d163066e413e42be90b5d764e5e327335c6e4d7 Mon Sep 17 00:00:00 2001 From: Peter Jhong Date: Mon, 13 Jul 2026 16:56:47 +0800 Subject: [PATCH 1/4] fix(golden): repair wifi_settings dialog_edit_name interaction finder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dialog_edit_name golden interaction used find.bySemanticsLabel( 'wifi-name-2.4GHz'), which returned zero nodes and crashed with "Bad state: No element" when tester.tap() evaluated it. The golden runner never calls tester.ensureSemantics(), so the Flutter test environment does not build a semantics tree — bySemanticsLabel can never match, regardless of the production semanticLabel. This was the only interaction in the entire golden suite relying on semantics. Switch to find.text('HomeNetwork') (the SSID from fixture data, locale-independent), consistent with the sibling dialog interactions (dialog_edit_password, dialog_edit_security_mode). The 2.4GHz and 5GHz main cards share this SSID, so .first deterministically opens the same Name dialog with identical rendering. Co-Authored-By: Claude Opus 4.8 --- .../localizations/usp_wifi_settings_view_test.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart b/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart index 7599e3718..8102c890b 100644 --- a/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart +++ b/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart @@ -73,7 +73,11 @@ void main() { ), ), steps: (tester) async { - await tester.tap(find.bySemanticsLabel('wifi-name-2.4GHz').first); + // SSID value from fixture data — locale-independent. The golden + // runner does not build a semantics tree, so bySemanticsLabel finds + // nothing; tap the SSID text like the sibling dialog interactions. + // First per-band card is 2.4GHz (ssid: 'HomeNetwork'). + await tester.tap(find.text('HomeNetwork').first); await tester.pump(); for (int i = 0; i < 10; i++) { await tester.pump(const Duration(milliseconds: 50)); From a41e3e7afdc7e86fa706805a81d9964d6eb2143a Mon Sep 17 00:00:00 2001 From: Peter Jhong Date: Wed, 15 Jul 2026 10:56:16 +0800 Subject: [PATCH 2/4] feat(golden): link golden image from passing rows and drop --embed 1. Add a "View golden" link to each passing test row in the verify report that opens the original golden image in the existing lightbox; compute a report-relative goldenPath for every test with complete metadata. 2. Generalize the lightbox to a {src, caption} model so it serves both the failure comparison images (with cross-image navigation) and single golden images (nav arrows hidden). 3. Remove the --embed / embedImages mechanism from combine_results.dart and run_golden_verify.sh; failure image paths are always made relative to the report location. Co-Authored-By: Claude Opus 4.8 --- run_golden_verify.sh | 14 ++---- test_scripts/combine_results.dart | 50 +++++++++++++-------- test_scripts/html_generate_functions.dart | 55 ++++++++++++++++++----- 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/run_golden_verify.sh b/run_golden_verify.sh index 40027e079..954282969 100755 --- a/run_golden_verify.sh +++ b/run_golden_verify.sh @@ -9,20 +9,13 @@ else DART="dart" fi -EMBED_FLAG="" - -while getopts l:s:f:v:-: flag +while getopts l:s:f:v: flag do case "${flag}" in l) locales=${OPTARG};; s) screens=${OPTARG};; f) file=${OPTARG};; v) version=${OPTARG};; - -) - case "${OPTARG}" in - embed) EMBED_FLAG="--embed";; - esac - ;; esac done @@ -43,7 +36,6 @@ echo "*********************Golden Test Verification********************" echo "Locales: $locales" echo "Screens: $screens" echo "Version: $version" -echo "Embed images: ${EMBED_FLAG:-no}" if [ -z "$file" ]; then IFS=',' read -ra LOCS <<< "$locales" @@ -57,7 +49,7 @@ if [ -z "$file" ]; then rm -f $REPORT_DIR/tests.json done - $DART run test_scripts/combine_results.dart $REPORT_DIR "$version" $EMBED_FLAG || FAILED=1 + $DART run test_scripts/combine_results.dart $REPORT_DIR "$version" || FAILED=1 echo "" echo "Report generated: $REPORT_DIR/golden_verify_report.html" else @@ -68,7 +60,7 @@ else --dart-define=visualEffects=0 || FAILED=1 $DART run test_scripts/test_result_parser.dart $REPORT_DIR/tests.json "$locales" || FAILED=1 rm -f $REPORT_DIR/tests.json - $DART run test_scripts/combine_results.dart $REPORT_DIR "$version" $EMBED_FLAG || FAILED=1 + $DART run test_scripts/combine_results.dart $REPORT_DIR "$version" || FAILED=1 echo "" echo "Report generated: $REPORT_DIR/golden_verify_report.html" fi diff --git a/test_scripts/combine_results.dart b/test_scripts/combine_results.dart index 89b396dc9..e092888e2 100644 --- a/test_scripts/combine_results.dart +++ b/test_scripts/combine_results.dart @@ -7,6 +7,14 @@ part 'html_generate_functions.dart'; const _coverageIgnore = ['apps', 'system_log', 'test_console']; +/// Strip the report folder prefix so [path] is relative to the report location. +String _relativeToReport(String path, String folderStr) { + if (path.startsWith('$folderStr/')) { + return path.substring(folderStr.length + 1); + } + return path; +} + List> _loadOverflowWarnings() { final file = File('goldens/overflow_warnings.json'); if (!file.existsSync()) return []; @@ -88,7 +96,6 @@ void main(List args) { exit(1); } final version = args.length > 1 ? args[1] : '0.0.0'; - final embedImages = args.contains('--embed'); // find parsed test report json files on target folder final files = folder.listSync().where((e) => @@ -125,29 +132,37 @@ void main(List args) { } final devices = deviceSet.toList(); - // Process failure image paths + // Process failure image paths — make them relative to the report location for (final test in jsonObjects) { final failureImages = test['failureImages'] as Map?; if (failureImages == null) continue; for (final key in ['expected', 'actual', 'diff']) { final path = failureImages[key] as String?; if (path == null) continue; - if (embedImages) { - final file = File(path); - if (file.existsSync()) { - final bytes = file.readAsBytesSync(); - final b64 = base64Encode(bytes); - failureImages[key] = 'data:image/png;base64,$b64'; - } - } else { - // Strip folder prefix so path is relative to the report location - if (path.startsWith('$folderStr/')) { - failureImages[key] = path.substring(folderStr.length + 1); - } else { - failureImages[key] = path; - } - } + failureImages[key] = _relativeToReport(path, folderStr); + } + } + + // Attach the golden image path for every test (including passing ones) so the + // report can link to the original golden. Goldens live in a `goldens/` + // directory next to the test file, named `{tsName}-{deviceType}-{locale}.png`. + for (final test in jsonObjects) { + final testCaseFilePath = test['testCaseFilePath'] as String?; + final tsName = test['tsName'] as String?; + final deviceType = test['deviceType'] as String?; + final locale = test['locale'] as String?; + if (testCaseFilePath == null || + tsName == null || + deviceType == null || + locale == null) { + continue; } + final relativeTestPath = testCaseFilePath.startsWith('/') + ? testCaseFilePath.substring(1) + : testCaseFilePath; + final testDir = relativeTestPath.replaceFirst(RegExp(r'/[^/]+$'), ''); + final goldenPath = '$testDir/goldens/$tsName-$deviceType-$locale.png'; + test['goldenPath'] = _relativeToReport(goldenPath, folderStr); } // Scan coverage @@ -180,7 +195,6 @@ void main(List args) { resultObj['overflowCount'] = overflowWarnings.length; resultObj['version'] = version; resultObj['timestamp'] = DateTime.now().toIso8601String(); - resultObj['embedImages'] = embedImages; final htmlReport = generateHTMLReport(resultObj, version); final reportHTMLFile = File('$folderStr/golden_verify_report.html'); diff --git a/test_scripts/html_generate_functions.dart b/test_scripts/html_generate_functions.dart index 1ac1c390a..4961f070d 100644 --- a/test_scripts/html_generate_functions.dart +++ b/test_scripts/html_generate_functions.dart @@ -184,6 +184,11 @@ String generateHTMLReport(Map result, String version) { .test-icon.fail { color: var(--color-fail); } .test-name { flex: 1; } .test-meta { font-size: 0.75rem; color: var(--color-text-muted); } + .view-golden { + font-size: 0.75rem; color: var(--color-accent); cursor: pointer; + margin-right: 0.75rem; text-decoration: none; white-space: nowrap; + } + .view-golden:hover { text-decoration: underline; } .overflow-badge { font-size: 0.65rem; padding: 0.125rem 0.375rem; border-radius: 3px; background: #fef3c7; color: #92400e; @@ -598,6 +603,10 @@ String generateHTMLReport(Map result, String version) { let html = '
'; html += '' + icon + ''; html += '' + escapeHtml(name) + overflowTag + ''; + if (isPass && t.goldenPath) { + const goldenCaption = name + ' (' + (t.deviceType || '') + ' / ' + (t.locale || '') + ')'; + html += 'View golden'; + } html += '' + (t.deviceType || '') + ' / ' + (t.locale || '') + ''; html += '
'; @@ -646,14 +655,39 @@ String generateHTMLReport(Map result, String version) { return div.innerHTML; } - // Lightbox + // Lightbox — holds a list of { src, caption } items let lbImages = []; let lbIndex = 0; + // Open the comparison images (expected/actual/diff) with cross-image nav function openLightbox(img) { - lbImages = [...document.querySelectorAll('.image-comparison img[onclick]')]; - lbIndex = lbImages.indexOf(img); + const nodes = [...document.querySelectorAll('.image-comparison img[onclick]')]; + lbImages = nodes.map(function(node) { + const figure = node.closest('figure'); + const label = figure ? figure.querySelector('figcaption')?.textContent || '' : ''; + const details = figure ? figure.closest('.failure-details') : null; + const row = details ? details.previousElementSibling : null; + const testName = row ? row.querySelector('.test-name')?.textContent || '' : ''; + return { src: node.src, caption: testName + ' (' + label + ')' }; + }); + lbIndex = nodes.indexOf(img); if (lbIndex === -1) lbIndex = 0; + openLightboxAt(lbImages, lbIndex); + } + + // Open a single golden image (no cross-image nav) + function openGolden(link) { + const src = link.getAttribute('data-golden-src'); + const caption = link.getAttribute('data-golden-caption') || ''; + openLightboxAt([{ src: src, caption: caption }], 0); + } + + function openLightboxAt(images, index) { + lbImages = images; + lbIndex = index; + const single = lbImages.length <= 1; + document.querySelector('.lb-prev').style.display = single ? 'none' : ''; + document.querySelector('.lb-next').style.display = single ? 'none' : ''; showLightboxImage(); document.getElementById('lightbox').classList.add('open'); } @@ -663,20 +697,17 @@ String generateHTMLReport(Map result, String version) { } function navLightbox(dir) { + if (lbImages.length <= 1) return; lbIndex = (lbIndex + dir + lbImages.length) % lbImages.length; showLightboxImage(); } var showLightboxImage = function() { - const img = lbImages[lbIndex]; - document.getElementById('lb-img').src = img.src; - const figure = img.closest('figure'); - const label = figure ? figure.querySelector('figcaption')?.textContent || '' : ''; - const details = figure ? figure.closest('.failure-details') : null; - const row = details ? details.previousElementSibling : null; - const testName = row ? row.querySelector('.test-name')?.textContent || '' : ''; - const pos = (lbIndex + 1) + '/' + lbImages.length; - document.getElementById('lb-caption').textContent = pos + ' — ' + testName + ' (' + label + ')'; + const item = lbImages[lbIndex]; + if (!item) return; + document.getElementById('lb-img').src = item.src; + const pos = lbImages.length > 1 ? (lbIndex + 1) + '/' + lbImages.length + ' — ' : ''; + document.getElementById('lb-caption').textContent = pos + item.caption; }; document.addEventListener('keydown', (e) => { From a938793e633358a61e10c03f05c86eee13800d3f Mon Sep 17 00:00:00 2001 From: Peter Jhong Date: Wed, 15 Jul 2026 18:22:28 +0800 Subject: [PATCH 3/4] fix(golden): stabilize dashboard edit-mode screenshot via reduce-motion 1. JiggleShake now honors the platform reduce-motion accessibility flag: when MediaQuery.disableAnimations is on, cards render at a fixed 0-degree angle and the shake ticker never starts. The flag is read in didChangeDependencies (not initState) and build() is the single source of truth for the rendered angle. This is a genuine accessibility improvement for users who enable Reduce Motion at the OS level. 2. The golden runner forces disableAnimations: true for every golden via the MaterialApp builder, so non-deterministic or infinite animations (like the edit-mode jiggle) render at a stable, static frame. This eliminates the intermittent dashboard-edit_mode diff without touching the global diffThreshold, so no other screen's comparison is loosened. Co-Authored-By: Claude Opus 4.8 --- .../components/effects/jiggle_shake.dart | 42 ++++++++++++------- .../golden_framework/golden_runner.dart | 11 +++++ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/lib/page/dashboard/views/components/effects/jiggle_shake.dart b/lib/page/dashboard/views/components/effects/jiggle_shake.dart index 702142ab7..cb414e05f 100644 --- a/lib/page/dashboard/views/components/effects/jiggle_shake.dart +++ b/lib/page/dashboard/views/components/effects/jiggle_shake.dart @@ -26,6 +26,10 @@ class _JiggleShakeState extends State late final Animation _animation; final _random = Random(); + /// Platform "reduce motion" accessibility flag. Read from MediaQuery in + /// didChangeDependencies (not initState, where inherited widgets are unsafe). + bool _reduceMotion = false; + @override void initState() { super.initState(); @@ -42,28 +46,38 @@ class _JiggleShakeState extends State begin: startPositive ? -maxRad : maxRad, end: startPositive ? maxRad : -maxRad, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); + } - if (widget.active) { - _startShaking(); - } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // MediaQuery is only safe to read here — not in initState. + _reduceMotion = MediaQuery.maybeDisableAnimationsOf(context) ?? false; + _syncAnimation(); } @override void didUpdateWidget(covariant JiggleShake oldWidget) { super.didUpdateWidget(oldWidget); if (widget.active != oldWidget.active) { - if (widget.active) { - _startShaking(); - } else { - _stopShaking(); - } + _syncAnimation(); + } + } + + /// Starts or stops the shake based on [active] and the reduce-motion flag. + void _syncAnimation() { + if (widget.active && !_reduceMotion) { + _startShaking(); + } else { + _stopShaking(); } } void _startShaking() { + if (_controller.isAnimating) return; // Add a tiny random delay before starting to enhance the organic feel Future.delayed(Duration(milliseconds: _random.nextInt(50)), () { - if (mounted && widget.active) { + if (mounted && widget.active && !_reduceMotion) { _controller.repeat(reverse: true); } }); @@ -71,7 +85,6 @@ class _JiggleShakeState extends State void _stopShaking() { _controller.stop(); - _controller.animateTo(0.5); // Reset to center (approx) or just reset _controller.reset(); } @@ -83,13 +96,14 @@ class _JiggleShakeState extends State @override Widget build(BuildContext context) { - // Optimization: If not active and not animating, return child directly (or Rotation with 0) - // But RotationTransition with 0 turns is cheap. return AnimatedBuilder( animation: _animation, builder: (context, child) { - // If inactive, force 0 rotation - final turns = widget.active ? _animation.value : 0.0; + // build() is the single source of truth for the rendered angle: + // force 0 when inactive or when reduce-motion is on (deterministic, + // static rendering — also what golden tests rely on). + final turns = + (widget.active && !_reduceMotion) ? _animation.value : 0.0; return Transform.rotate( angle: turns, child: child, diff --git a/test/golden_test/golden_framework/golden_runner.dart b/test/golden_test/golden_framework/golden_runner.dart index c5b60fcd6..bab134ff0 100644 --- a/test/golden_test/golden_framework/golden_runner.dart +++ b/test/golden_test/golden_framework/golden_runner.dart @@ -314,6 +314,17 @@ Widget _buildGoldenWidget( themeMode: brightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light, + // Force "reduce motion" for every golden so non-deterministic / + // looping animations (e.g. dashboard JiggleShake) render at a + // fixed, static frame. Applied inside the app via builder so the + // views under test actually observe disableAnimations: true + // (a MediaQuery wrapped outside MaterialApp would be overridden). + // This does NOT touch the global diffThreshold; it only removes the + // animation source of flakiness. + builder: (context, child) => MediaQuery( + data: MediaQuery.of(context).copyWith(disableAnimations: true), + child: child ?? const SizedBox.shrink(), + ), routerConfig: router, ), ), From 214b7e9f199354083aad0e0ae534ece377e81ea0 Mon Sep 17 00:00:00 2001 From: Peter Jhong Date: Fri, 17 Jul 2026 18:01:07 +0800 Subject: [PATCH 4/4] fix(golden): switch tabs via TabController to fix long-label locale crashes 1. Golden interaction steps switched tabs with tester.tap(find.byType(Tab).at(N)), which taps the tab widget's geometric center. The USP detail views use a non-scrollable TabBar (TabAlignment.fill), so long localized labels (e.g. Danish, German) push the rightmost tab's center off-screen. The tap then misses, the tab never switches, and every interaction expecting that tab's content throws a RangeError when it finds zero buttons. This surfaced as port_forwarding dialog_add/edit_port_triggering failing in da. 2. Add a shared switchToTab(tester, index) helper in golden_framework/golden_interactions.dart that drives TabController.animateTo(index) directly and pumps until the transition settles. This expresses the real intent (show the Nth tab) and is locale-independent. 3. Apply the helper across all three affected golden tests (port_forwarding, statistics, wifi_settings), replacing 13 geometric tab taps and removing the now-redundant per-call pump loops. Co-Authored-By: Claude Opus 4.8 --- .../golden_framework/golden_interactions.dart | 40 ++++++++++++++++ .../usp_port_forwarding_detail_view_test.dart | 47 ++++--------------- .../usp_statistics_view_test.dart | 15 ++---- .../usp_wifi_settings_view_test.dart | 14 ++---- 4 files changed, 56 insertions(+), 60 deletions(-) create mode 100644 test/golden_test/golden_framework/golden_interactions.dart diff --git a/test/golden_test/golden_framework/golden_interactions.dart b/test/golden_test/golden_framework/golden_interactions.dart new file mode 100644 index 000000000..fefcf5050 --- /dev/null +++ b/test/golden_test/golden_framework/golden_interactions.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Shared interaction helpers for golden `Interaction.steps`. +/// +/// These exist to keep interaction steps locale-independent and free of +/// brittle geometry-based gestures. + +/// Switches to the tab at [index] by driving the [TabController] directly, +/// then pumps until the tab-change animation settles. +/// +/// Why not `tester.tap(find.byType(Tab).at(index))`? The USP pages use a +/// non-scrollable TabBar (`TabAlignment.fill`), so long localized labels +/// (e.g. Danish, German, Russian) push the rightmost tab's center off-screen. +/// A geometric tap then lands outside the surface, misses, and the tab never +/// switches — breaking every interaction that expects that tab's content, but +/// only in the locales with long labels. Driving the controller expresses the +/// real intent ("show the Nth tab") and works in every locale. +/// +/// Assumes exactly one [TabBar] is on screen (the common case for USP detail +/// views). Pass [tabBarFinder] to disambiguate if a view ever nests TabBars. +Future switchToTab( + WidgetTester tester, + int index, { + Finder? tabBarFinder, +}) async { + final finder = tabBarFinder ?? find.byType(TabBar); + final tabBar = tester.widget(finder.first); + final controller = tabBar.controller; + assert(controller != null, 'TabBar has no controller to drive.'); + controller!.animateTo(index); + + // Let the tab-change + TabBarView transition settle. TabController animations + // run ~300ms; pump generously past that so the target tab's content is fully + // built and interactive before the caller queries the tree. + await tester.pump(); + for (int i = 0; i < 10; i++) { + await tester.pump(const Duration(milliseconds: 50)); + } +} diff --git a/test/golden_test/page/port_forwarding/localizations/usp_port_forwarding_detail_view_test.dart b/test/golden_test/page/port_forwarding/localizations/usp_port_forwarding_detail_view_test.dart index 6c8f093dd..c95d4f4d9 100644 --- a/test/golden_test/page/port_forwarding/localizations/usp_port_forwarding_detail_view_test.dart +++ b/test/golden_test/page/port_forwarding/localizations/usp_port_forwarding_detail_view_test.dart @@ -9,6 +9,7 @@ import 'package:privacy_gui/page/port_forwarding/views/components/usp_single_por import 'package:privacy_gui/page/port_forwarding/views/usp_port_forwarding_detail_view.dart'; import 'package:ui_kit_library/ui_kit.dart' show AppIconButton; +import '../../../golden_framework/golden_interactions.dart'; import '../../../golden_framework/golden_runner.dart'; import '../../../golden_framework/golden_test_config.dart'; import '../../../golden_framework/mocks/mock_port_forwarding.dart'; @@ -38,9 +39,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 300)); + await switchToTab(tester, 1); }, ), 'tab_port_triggering': Interaction( @@ -48,9 +47,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(2)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 300)); + await switchToTab(tester, 2); }, ), // --- Empty tab views --- @@ -59,9 +56,7 @@ void main() { portForwardingOverrides(emptyDataState), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 300)); + await switchToTab(tester, 1); }, ), 'empty_port_triggering': Interaction( @@ -69,9 +64,7 @@ void main() { portForwardingOverrides(emptyDataState), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(2)); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 300)); + await switchToTab(tester, 2); }, ), // --- Add dialogs --- @@ -94,11 +87,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); final addBtn = find.descendant( of: find.byType(UspPortRangeTab), matching: find.byType(AppIconButton), @@ -115,11 +104,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(2)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 2); final addBtn = find.descendant( of: find.byType(UspPortTriggeringTab), matching: find.byType(AppIconButton), @@ -154,11 +139,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); final buttons = find.descendant( of: find.byType(UspPortRangeTab), matching: find.byType(AppIconButton), @@ -175,11 +156,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(2)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 2); final buttons = find.descendant( of: find.byType(UspPortTriggeringTab), matching: find.byType(AppIconButton), @@ -255,11 +232,7 @@ void main() { portForwardingOverrides(dataState()), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); final addBtn = find.descendant( of: find.byType(UspPortRangeTab), matching: find.byType(AppIconButton), diff --git a/test/golden_test/page/statistics/localizations/usp_statistics_view_test.dart b/test/golden_test/page/statistics/localizations/usp_statistics_view_test.dart index 9c4cfb1c3..87f3c15fc 100644 --- a/test/golden_test/page/statistics/localizations/usp_statistics_view_test.dart +++ b/test/golden_test/page/statistics/localizations/usp_statistics_view_test.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:privacy_gui/page/statistics/views/usp_statistics_view.dart'; +import '../../../golden_framework/golden_interactions.dart'; import '../../../golden_framework/golden_runner.dart'; import '../../../golden_framework/golden_test_config.dart'; import '../../../golden_framework/mocks/mock_statistics.dart'; @@ -35,11 +34,7 @@ void main() { ), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); }, ), 'tab_system': Interaction( @@ -51,11 +46,7 @@ void main() { ), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(2)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 2); }, ), }, diff --git a/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart b/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart index 8102c890b..f7c39f7cd 100644 --- a/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart +++ b/test/golden_test/page/wifi_settings/localizations/usp_wifi_settings_view_test.dart @@ -1,7 +1,7 @@ -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacy_gui/page/wifi_settings/views/usp_wifi_settings_view.dart'; +import '../../../golden_framework/golden_interactions.dart'; import '../../../golden_framework/golden_runner.dart'; import '../../../golden_framework/golden_test_config.dart'; import '../../../golden_framework/mocks/mock_wifi_settings.dart'; @@ -43,11 +43,7 @@ void main() { ), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); }, ), 'tab_advanced_dfs_off': Interaction( @@ -58,11 +54,7 @@ void main() { ), ), steps: (tester) async { - await tester.tap(find.byType(Tab).at(1)); - await tester.pump(); - for (int i = 0; i < 10; i++) { - await tester.pump(const Duration(milliseconds: 50)); - } + await switchToTab(tester, 1); }, ), 'dialog_edit_name': Interaction(