From 166f1d76de879a1977dcce84aaa22edecc15918a Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Tue, 14 Dec 2021 17:34:10 -0800 Subject: [PATCH] Fix android semantics integration test flakiness (#94875) --- .../platforminteraction/MainActivity.java | 20 ------------------- .../android_semantics_testing/lib/main.dart | 15 -------------- .../lib/src/matcher.dart | 14 ++++++++++--- .../test_driver/main_test.dart | 20 +++++++++---------- 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/dev/integration_tests/android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java b/dev/integration_tests/android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java index 952bf30a46..e753f4eda6 100644 --- a/dev/integration_tests/android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java +++ b/dev/integration_tests/android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java @@ -27,7 +27,6 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugins.GeneratedPluginRegistrant; -import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeInfo; @@ -67,25 +66,6 @@ public class MainActivity extends FlutterActivity { result.success(convertSemantics(node, id)); return; } - if (methodCall.method.equals("sendSemanticsFocus")) { - Map data = methodCall.arguments(); - @SuppressWarnings("unchecked") - Integer id = (Integer) data.get("id"); - if (id == null) { - result.error("No ID provided", "", null); - return; - } - if (provider == null) { - result.error("Semantics not enabled", "", null); - return; - } - AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); - event.setPackageName(flutterView.getContext().getPackageName()); - event.setSource(flutterView, id); - flutterView.getParent().requestSendAccessibilityEvent(flutterView, event); - result.success(null); - return; - } result.notImplemented(); } diff --git a/dev/integration_tests/android_semantics_testing/lib/main.dart b/dev/integration_tests/android_semantics_testing/lib/main.dart index 47e2c55d05..e721e1ba52 100644 --- a/dev/integration_tests/android_semantics_testing/lib/main.dart +++ b/dev/integration_tests/android_semantics_testing/lib/main.dart @@ -39,21 +39,6 @@ Future dataHandler(String message) async { completeSemantics(); return completer.future; } - if (message.contains('sendSemanticsFocus')) { - final Completer completer = Completer(); - final int id = int.tryParse(message.split('#')[1]) ?? 0; - Future completeSemantics([Object _]) async { - final dynamic result = await kSemanticsChannel.invokeMethod('sendSemanticsFocus', { - 'id': id, - }); - completer.complete(json.encode(result)); - } - if (SchedulerBinding.instance.hasScheduledFrame) - SchedulerBinding.instance.addPostFrameCallback(completeSemantics); - else - completeSemantics(); - return completer.future; - } throw UnimplementedError(); } diff --git a/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart b/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart index d3f7f27555..626364b26b 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/matcher.dart @@ -25,6 +25,7 @@ Matcher hasAndroidSemantics({ Rect rect, Size size, List actions, + List ignoredActions, List children, bool isChecked, bool isCheckable, @@ -44,6 +45,7 @@ Matcher hasAndroidSemantics({ size: size, id: id, actions: actions, + ignoredActions: ignoredActions, isChecked: isChecked, isCheckable: isCheckable, isEditable: isEditable, @@ -63,6 +65,7 @@ class _AndroidSemanticsMatcher extends Matcher { this.className, this.id, this.actions, + this.ignoredActions, this.rect, this.size, this.isChecked, @@ -81,6 +84,7 @@ class _AndroidSemanticsMatcher extends Matcher { final String contentDescription; final int id; final List actions; + final List ignoredActions; final Rect rect; final Size size; final bool isChecked; @@ -148,9 +152,13 @@ class _AndroidSemanticsMatcher extends Matcher { if (!unorderedEquals(actions).matches(itemActions, matchState)) { final List actionsString = actions.map((AndroidSemanticsAction action) => action.toString()).toList()..sort(); final List itemActionsString = itemActions.map((AndroidSemanticsAction action) => action.toString()).toList()..sort(); - final Set unexpected = itemActionsString.toSet().difference(actionsString.toSet()); - final Set missing = actionsString.toSet().difference(itemActionsString.toSet()); - return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpected\nMissing: $missing', matchState); + final Set unexpected = itemActions.toSet().difference(actions.toSet()); + final Set unexpectedInString = itemActionsString.toSet().difference(actionsString.toSet()); + final Set missingInString = actionsString.toSet().difference(itemActionsString.toSet()); + if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions.contains)) { + return true; + } + return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState); } } if (isChecked != null && isChecked != item.isChecked) diff --git a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart index 4eac9c3556..4b29e23782 100644 --- a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart +++ b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart @@ -11,6 +11,14 @@ import 'package:path/path.dart' as path; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart' hide isInstanceOf; +// The accessibility focus actions are added when a semantics node receives or +// lose accessibility focus. This test ignores these actions since it is hard to +// predict which node has the accessibility focus after a screen changes. +const List ignoredAccessibilityFocusActions = [ + AndroidSemanticsAction.accessibilityFocus, + AndroidSemanticsAction.clearAccessibilityFocus, +]; + String adbPath() { final String androidHome = io.Platform.environment['ANDROID_HOME'] ?? io.Platform.environment['ANDROID_SDK_ROOT']; if (androidHome == null) { @@ -29,11 +37,6 @@ void main() { return AndroidSemanticsNode.deserialize(data); } - Future sendSemanticsFocus(SerializableFinder finder) async { - final int id = await driver.getSemanticsId(finder); - await driver.requestData('sendSemanticsFocus#$id'); - } - // The version of TalkBack running on the device. Version talkbackVersion; @@ -153,10 +156,6 @@ void main() { matching: find.byType('Semantics'), firstMatchOnly: true, ); - // Make sure the focus is on the back button. - await sendSemanticsFocus(find.byValueKey(backButtonKeyValue)); - await Future.delayed(const Duration(milliseconds: 500)); - expect( await getSemantics(normalTextField), hasAndroidSemantics( @@ -166,9 +165,10 @@ void main() { isFocused: false, isPassword: false, actions: [ - AndroidSemanticsAction.accessibilityFocus, AndroidSemanticsAction.click, ], + // We can't predict the a11y focus when the screen changes. + ignoredActions: ignoredAccessibilityFocusActions ), );