Skip to content

Commit 166f1d7

Browse files
authored
Fix android semantics integration test flakiness (flutter#94875)
1 parent 77402ce commit 166f1d7

File tree

4 files changed

+21
-48
lines changed

4 files changed

+21
-48
lines changed

dev/integration_tests/android_semantics_testing/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
2828
import io.flutter.plugins.GeneratedPluginRegistrant;
2929

30-
import android.view.accessibility.AccessibilityEvent;
3130
import android.view.accessibility.AccessibilityManager;
3231
import android.view.accessibility.AccessibilityNodeProvider;
3332
import android.view.accessibility.AccessibilityNodeInfo;
@@ -67,25 +66,6 @@ public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
6766
result.success(convertSemantics(node, id));
6867
return;
6968
}
70-
if (methodCall.method.equals("sendSemanticsFocus")) {
71-
Map<String, Object> data = methodCall.arguments();
72-
@SuppressWarnings("unchecked")
73-
Integer id = (Integer) data.get("id");
74-
if (id == null) {
75-
result.error("No ID provided", "", null);
76-
return;
77-
}
78-
if (provider == null) {
79-
result.error("Semantics not enabled", "", null);
80-
return;
81-
}
82-
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
83-
event.setPackageName(flutterView.getContext().getPackageName());
84-
event.setSource(flutterView, id);
85-
flutterView.getParent().requestSendAccessibilityEvent(flutterView, event);
86-
result.success(null);
87-
return;
88-
}
8969
result.notImplemented();
9070
}
9171

dev/integration_tests/android_semantics_testing/lib/main.dart

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,6 @@ Future<String> dataHandler(String message) async {
3939
completeSemantics();
4040
return completer.future;
4141
}
42-
if (message.contains('sendSemanticsFocus')) {
43-
final Completer<String> completer = Completer<String>();
44-
final int id = int.tryParse(message.split('#')[1]) ?? 0;
45-
Future<void> completeSemantics([Object _]) async {
46-
final dynamic result = await kSemanticsChannel.invokeMethod<dynamic>('sendSemanticsFocus', <String, dynamic>{
47-
'id': id,
48-
});
49-
completer.complete(json.encode(result));
50-
}
51-
if (SchedulerBinding.instance.hasScheduledFrame)
52-
SchedulerBinding.instance.addPostFrameCallback(completeSemantics);
53-
else
54-
completeSemantics();
55-
return completer.future;
56-
}
5742
throw UnimplementedError();
5843
}
5944

dev/integration_tests/android_semantics_testing/lib/src/matcher.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Matcher hasAndroidSemantics({
2525
Rect rect,
2626
Size size,
2727
List<AndroidSemanticsAction> actions,
28+
List<AndroidSemanticsAction> ignoredActions,
2829
List<AndroidSemanticsNode> children,
2930
bool isChecked,
3031
bool isCheckable,
@@ -44,6 +45,7 @@ Matcher hasAndroidSemantics({
4445
size: size,
4546
id: id,
4647
actions: actions,
48+
ignoredActions: ignoredActions,
4749
isChecked: isChecked,
4850
isCheckable: isCheckable,
4951
isEditable: isEditable,
@@ -63,6 +65,7 @@ class _AndroidSemanticsMatcher extends Matcher {
6365
this.className,
6466
this.id,
6567
this.actions,
68+
this.ignoredActions,
6669
this.rect,
6770
this.size,
6871
this.isChecked,
@@ -81,6 +84,7 @@ class _AndroidSemanticsMatcher extends Matcher {
8184
final String contentDescription;
8285
final int id;
8386
final List<AndroidSemanticsAction> actions;
87+
final List<AndroidSemanticsAction> ignoredActions;
8488
final Rect rect;
8589
final Size size;
8690
final bool isChecked;
@@ -148,9 +152,13 @@ class _AndroidSemanticsMatcher extends Matcher {
148152
if (!unorderedEquals(actions).matches(itemActions, matchState)) {
149153
final List<String> actionsString = actions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
150154
final List<String> itemActionsString = itemActions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
151-
final Set<String> unexpected = itemActionsString.toSet().difference(actionsString.toSet());
152-
final Set<String> missing = actionsString.toSet().difference(itemActionsString.toSet());
153-
return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpected\nMissing: $missing', matchState);
155+
final Set<AndroidSemanticsAction> unexpected = itemActions.toSet().difference(actions.toSet());
156+
final Set<String> unexpectedInString = itemActionsString.toSet().difference(actionsString.toSet());
157+
final Set<String> missingInString = actionsString.toSet().difference(itemActionsString.toSet());
158+
if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions.contains)) {
159+
return true;
160+
}
161+
return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState);
154162
}
155163
}
156164
if (isChecked != null && isChecked != item.isChecked)

dev/integration_tests/android_semantics_testing/test_driver/main_test.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import 'package:path/path.dart' as path;
1111
import 'package:pub_semver/pub_semver.dart';
1212
import 'package:test/test.dart' hide isInstanceOf;
1313

14+
// The accessibility focus actions are added when a semantics node receives or
15+
// lose accessibility focus. This test ignores these actions since it is hard to
16+
// predict which node has the accessibility focus after a screen changes.
17+
const List<AndroidSemanticsAction> ignoredAccessibilityFocusActions = <AndroidSemanticsAction>[
18+
AndroidSemanticsAction.accessibilityFocus,
19+
AndroidSemanticsAction.clearAccessibilityFocus,
20+
];
21+
1422
String adbPath() {
1523
final String androidHome = io.Platform.environment['ANDROID_HOME'] ?? io.Platform.environment['ANDROID_SDK_ROOT'];
1624
if (androidHome == null) {
@@ -29,11 +37,6 @@ void main() {
2937
return AndroidSemanticsNode.deserialize(data);
3038
}
3139

32-
Future<void> sendSemanticsFocus(SerializableFinder finder) async {
33-
final int id = await driver.getSemanticsId(finder);
34-
await driver.requestData('sendSemanticsFocus#$id');
35-
}
36-
3740
// The version of TalkBack running on the device.
3841
Version talkbackVersion;
3942

@@ -153,10 +156,6 @@ void main() {
153156
matching: find.byType('Semantics'),
154157
firstMatchOnly: true,
155158
);
156-
// Make sure the focus is on the back button.
157-
await sendSemanticsFocus(find.byValueKey(backButtonKeyValue));
158-
await Future<void>.delayed(const Duration(milliseconds: 500));
159-
160159
expect(
161160
await getSemantics(normalTextField),
162161
hasAndroidSemantics(
@@ -166,9 +165,10 @@ void main() {
166165
isFocused: false,
167166
isPassword: false,
168167
actions: <AndroidSemanticsAction>[
169-
AndroidSemanticsAction.accessibilityFocus,
170168
AndroidSemanticsAction.click,
171169
],
170+
// We can't predict the a11y focus when the screen changes.
171+
ignoredActions: ignoredAccessibilityFocusActions
172172
),
173173
);
174174

0 commit comments

Comments
 (0)